Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rewrite graph to use a map data structure #78

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .yarnrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
--ignore-engines true
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -81,16 +81,18 @@
"compile": "webpack --mode production",
"lint": "eslint src --ext ts",
"watch": "tsc -watch -p ./",
"pretest": "yarn run compile && yarn run lint",
"pretest": "yarn run test-compile && yarn run lint",
"test": "node ./out/test/runTest.js"
},
"devDependencies": {
"@types/chai": "^4.2.14",
"@types/glob": "^7.1.1",
"@types/mocha": "^7.0.2",
"@types/node": "^13.11.0",
"@types/vscode": "^1.45.0",
"@typescript-eslint/eslint-plugin": "^2.33.0",
"@typescript-eslint/parser": "^2.33.0",
"chai": "^4.2.0",
"eslint": "^6.8.0",
"glob": "^7.1.6",
"mocha": "^7.1.2",
Expand Down
47 changes: 12 additions & 35 deletions src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import { TextDecoder } from "util";
import * as path from "path";
import { parseFile, parseDirectory, learnFileId } from "./parsing";
import {
filterNonExistingEdges,
getColumnSetting,
getConfiguration,
getFileTypesSetting,
Expand Down Expand Up @@ -32,37 +31,33 @@ const watch = (
const sendGraph = () => {
panel.webview.postMessage({
type: "refresh",
payload: graph,
payload: graph.toD3Graph(),
});
};

// Watch file changes in case user adds a link.
watcher.onDidChange(async (event) => {
await parseFile(graph, event.path);
filterNonExistingEdges(graph);
graph.fixEdges();
sendGraph();
});

// Watch file creation in case user adds a new file.
watcher.onDidCreate(async (event) => {
await parseFile(graph, event.path);
filterNonExistingEdges(graph);
graph.fixEdges();
sendGraph();
});

// Watch file deletion and remove the matching node from the graph.
watcher.onDidDelete(async (event) => {
const filePath = path.normalize(event.path);
const index = graph.nodes.findIndex((node) => node.path === filePath);
if (index === -1) {
const node = graph.getNodeByPath(filePath);
if (!node) {
return;
}

graph.nodes.splice(index, 1);
graph.edges = graph.edges.filter(
(edge) => edge.source !== filePath && edge.target !== filePath
);

filterNonExistingEdges(graph);
graph.removeNode(node.id);
sendGraph();
});

Expand All @@ -80,25 +75,10 @@ const watch = (
for (const file of event.files) {
const previous = path.normalize(file.oldUri.path);
const next = path.normalize(file.newUri.path);

for (const edge of graph.edges) {
if (edge.source === previous) {
edge.source = next;
}

if (edge.target === previous) {
edge.target = next;
}
}

for (const node of graph.nodes) {
if (node.path === previous) {
node.path = next;
}
}

sendGraph();
graph.updateNodePath(previous, next);
}

sendGraph();
});

panel.webview.onDidReceiveMessage(
Expand Down Expand Up @@ -146,14 +126,11 @@ export function activate(context: vscode.ExtensionContext) {
return;
}

const graph: Graph = {
nodes: [],
edges: [],
};
const graph: Graph = new Graph();

await parseDirectory(graph, learnFileId);
await parseDirectory(graph, parseFile);
filterNonExistingEdges(graph);
graph.fixEdges();

panel.webview.html = await getWebviewContent(context, panel);

Expand Down
26 changes: 12 additions & 14 deletions src/parsing.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import * as unified from "unified";
import * as markdown from "remark-parse";
import * as wikiLinkPlugin from "remark-wiki-link";
import * as frontmatter from "remark-frontmatter";
import { MarkdownNode, Graph } from "./types";
import { MarkdownNode, Graph, Node } from "./types";
import { TextDecoder } from "util";
import {
findTitle,
Expand Down Expand Up @@ -41,27 +41,25 @@ export const parseFile = async (graph: Graph, filePath: string) => {

let title: string | null = findTitle(ast);

const index = graph.nodes.findIndex((node) => node.path === filePath);

const node = graph.getNodeByPath(filePath);
if (!title) {
if (index !== -1) {
graph.nodes.splice(index, 1);
if (node) {
graph.removeNode(node.id);
}

return;
}

if (index !== -1) {
graph.nodes[index].label = title;
const nodeId = node ? node.id : id(filePath);
if (node) {
node.label = title;
// Remove edges based on an old version of this file.
graph.clearNodeLinks(nodeId);
} else {
graph.nodes.push({ id: id(filePath), path: filePath, label: title });
graph.addNode(new Node(nodeId, filePath, title));
}

// Remove edges based on an old version of this file.
graph.edges = graph.edges.filter((edge) => edge.source !== id(filePath));

// Returns a list of decoded links (by default markdown only supports encoded URI)
const links = findLinks(ast).map(uri => decodeURI(uri));
const links = findLinks(ast).map((uri) => decodeURI(uri));
const parentDirectory = filePath.split(path.sep).slice(0, -1).join(path.sep);

for (const link of links) {
Expand All @@ -70,7 +68,7 @@ export const parseFile = async (graph: Graph, filePath: string) => {
target = path.normalize(`${parentDirectory}/${link}`);
}

graph.edges.push({ source: id(filePath), target: id(target) });
graph.addLink(nodeId, id(target));
}
};

Expand Down
6 changes: 3 additions & 3 deletions src/test/suite/extension.test.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import * as assert from 'assert';
import { assert } from 'chai';

// You can import and use all API from the 'vscode' module
// as well as import your extension to test it
import * as vscode from 'vscode';
// import * as myExtension from '../../extension';

suite('Extension Test Suite', () => {
describe('Extension Test Suite', () => {
vscode.window.showInformationMessage('Start all tests.');

test('Sample test', () => {
context('Sample test', function() {
assert.equal(-1, [1, 2, 3].indexOf(5));
assert.equal(-1, [1, 2, 3].indexOf(0));
});
Expand Down
133 changes: 133 additions & 0 deletions src/test/suite/graph.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
import { assert } from "chai";
import { Graph, Node } from "../../types";

describe("Graph", function () {
context("given an empty graph", function () {
let graph: Graph;

beforeEach(() => {
graph = new Graph();
});

context("when a node with extra links and backlinks is added", function () {
const firstNode = new Node("firstNodeId", "/tmp/nodeOne.md", "Node One");
firstNode.addLink("bogusLink");
firstNode.addBacklink("bogusBacklink");
let retrievedNode: Node | undefined;
beforeEach(() => {
graph.addNode(firstNode);
retrievedNode = graph.getNode(firstNode.id);
});

it("appears in the graph", function () {
assert.exists(retrievedNode);
});

it("does not remove the broken links", function () {
assert.equal(retrievedNode?.links.size, firstNode.links.size);
});

it("does not remove the broken backlinks", function () {
assert.equal(retrievedNode?.backlinks.size, firstNode.backlinks.size);
});

context("when fixEdges is called on the graph", function () {
let retrievedNode: Node | undefined;
beforeEach(() => {
graph.fixEdges();
retrievedNode = graph.getNode(firstNode.id);
});

it("removes the broken links", function () {
assert.equal(retrievedNode?.links.size, 0);
});

it("removes the broken backlinks", function () {
assert.equal(retrievedNode?.backlinks.size, 0);
});
});

context("when a second node is added", function () {
const secondNode = new Node(
"secondNodeId",
"/tmp/nodeTwo.md",
"Node Two"
);
beforeEach(() => graph.addNode(secondNode));

context(
"when a link is added from the first node to the second node",
function () {
beforeEach(() => graph.addLink(firstNode.id, secondNode.id));

it("adds the link to the first node", function () {
const retrievedNode = graph.getNode(firstNode.id);
assert.exists(retrievedNode);
assert.include(
[...retrievedNode?.links.values()!],
secondNode.id
);
});

it("does not add the backlink to the second node", function () {
const retrievedNode = graph.getNode(secondNode.id);
assert.exists(retrievedNode);
assert.notInclude(
[...retrievedNode?.backlinks.values()!],
secondNode.id
);
});

context("when fixEdges is called on the graph", function () {
let retrievedFirstNode: Node;
let retrievedSecondNode: Node;
beforeEach(() => {
graph.fixEdges();
retrievedFirstNode = graph.getNode(firstNode.id)!;
retrievedSecondNode = graph.getNode(secondNode.id)!;
});

it("does not remove the valid link", function () {
assert.include(
[...retrievedFirstNode!.links.values()],
secondNode.id
);
});

it("adds a backlink to the second node", function () {
assert.include(
[...retrievedSecondNode.backlinks.values()],
firstNode.id
);
});
});

context(
"when clearNodeLinks is called for the first node",
function () {
let retrievedFirstNode: Node;
let retrievedSecondNode: Node;
beforeEach(() => {
graph.clearNodeLinks(firstNode.id);
retrievedFirstNode = graph.getNode(firstNode.id)!;
retrievedSecondNode = graph.getNode(secondNode.id)!;
});

it("removes all links from the first node", function () {
assert.equal(retrievedFirstNode!.links.size, 0);
});

it("removes the backlink from the first node", function () {
assert.notInclude(
[...retrievedSecondNode!.backlinks.values()],
firstNode.id
);
});
}
);
}
);
});
});
});
});
2 changes: 1 addition & 1 deletion src/test/suite/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import * as glob from 'glob';
export function run(): Promise<void> {
// Create the mocha test
const mocha = new Mocha({
ui: 'tdd',
ui: 'bdd',
color: true
});

Expand Down
Loading