Skip to content

Commit

Permalink
Merge pull request #5 from JonasSchatz/bugfix-collection-v2
Browse files Browse the repository at this point in the history
Logger, bugfix in file synchronization
  • Loading branch information
JonasSchatz authored Dec 16, 2020
2 parents a280ae6 + 2cfd7ee commit 3c6ac53
Show file tree
Hide file tree
Showing 12 changed files with 183 additions and 38 deletions.
5 changes: 4 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ If you don't know what a Zettelkasten is, there are many resources on the web, b
## Recommended Extensions
To harness the full power of markdown documents in VS Code, [Markdown Preview Enhanced](https://marketplace.visualstudio.com/items?itemName=shd101wyy.markdown-preview-enhanced) is recommended.
For visualizing the relationships between nodes, [Markdown Links](https://marketplace.visualstudio.com/items?itemName=tchayen.markdown-links) is also recommended. It uses D3's Force Graph under the hood, so it is likely to not be a scaleable solution. Codekasten has its own graph visualization view, heavily based on Markdown Links. In future versions I plan to move this implementation to use [force-graph](https://github.com/vasturiano/force-graph) instead. However, this needs to wait until either [this issue](https://github.com/foambubble/foam/issues/378) or [this issue](https://github.com/microsoft/vscode/issues/112396) get solved.
For visualizing the relationships between nodes, [Markdown Links](https://marketplace.visualstudio.com/items?itemName=tchayen.markdown-links) is also recommended. It uses D3's Force Graph under the hood, so it is likely to not be a scaleable solution. Codekasten has its own graph visualization view, heavily based on Markdown Links. In future versions I plan to move this implementation to use [force-graph](https://github.com/vasturiano/force-graph) instead. However, this needs to wait until either [this issue](https://github.com/foambubble/foam/issues/378) or [this issue](https://github.com/microsoft/vscode/issues/112396) get solved.
For easy pasting of images, use [Paste Image](https://marketplace.visualstudio.com/items?itemName=mushan.vscode-paste-image). Note that this is not maintained anymore, but forks do exists.

## Features
Currently supported:
Expand All @@ -31,6 +32,8 @@ Short-term:
Mid-term:
- Make the extension more customizable, e.g. let the user provide glob-patterns for which files should be included in the graph visualization.
- Include a Template with the neccessary folder structure and some smart initial values for the settings into the Repo.
- Cleanup Scripts: Find Stubs and unlinked images. Maybe find unlinked notes and make it a challenge to connect them to the bulk of knowledge.
- Handle images: Right-click to change to HTML image tags with size.

Long-term:
- Implement or find a more robust Markdown Parser. Do own syntax highlighting (e.g. detected links), which might be cool and also help with debugging.
Expand Down
6 changes: 4 additions & 2 deletions src/core/Link.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,14 @@ export class MarkdownLink implements Link {
target: string;
description: string;
relativePath: string;
stringRepresentation: string;

constructor(source: string, target: string) {
this.source = source;
this.target = target;
this.relativePath = path.relative(path.dirname(this.source), this.target).replace(/\\/g, '/');
this.stringRepresentation = `[${this.description}](${this.relativePath})`;
};

getStringRepresentation(): string {
return `[${this.description}](${this.relativePath})`;
}
}
13 changes: 7 additions & 6 deletions src/core/NoteGraph.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { Edge, Graph } from 'graphlib';
import * as md5 from 'md5';
import { Uri } from 'vscode';
import { Logger } from '../services';

import { Event, Emitter } from './common/event';
import { Note } from './Note';
import { Parser } from './Parser';
import * as md5 from 'md5';
import { Event, Emitter } from './common/event';
import { notStrictEqual } from 'assert';


export class GraphNote{
path: string;
Expand Down Expand Up @@ -158,7 +158,9 @@ export class NoteGraph {
if(!edges){
return [];
} else {
return edges.map((edge) => this.graph.node(edge.w).path);
const forwardLinks: string[] = edges.map((edge) => this.graph.node(edge.w).path);
Logger.info(`Found ${forwardLinks.length} forward links for ${this.graph.node(sourceId).path}`);
return forwardLinks;
}
}

Expand All @@ -169,7 +171,6 @@ export class NoteGraph {

this.graph.setNode(newId, oldNode);


if(inEdges){
for(const inEdge of inEdges){
this.graph.setEdge(inEdge.v, newId);
Expand Down
10 changes: 5 additions & 5 deletions src/extension.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import * as vscode from 'vscode';

import { NoteGraph } from "./core";
import { features } from "./features";
import { Config, createConfigFromVsCode, NoteGraph } from "./core";
import { CodekastenParser } from "./vscode";
import { bootstrap, CodekastenLogger, Logger } from './services';
import { checkCodekastenSetup } from './vscode/Verification';
import { bootstrap } from './services/bootstrap';


export async function activate(context: vscode.ExtensionContext) {

const config: Config = createConfigFromVsCode();
export async function activate(context: vscode.ExtensionContext) {
Logger.setDefaultLogger(new CodekastenLogger);
const codekastenGraph: NoteGraph = await bootstrap();


Expand Down
5 changes: 3 additions & 2 deletions src/features/create-note.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { NoteGraph } from "../core";
import { letUserChooseTemplate, letUserChooseText } from "../vscode/Inputs";
import { createNote, openNoteInWorkspace } from "../vscode/NoteActions";
import { MarkdownLink } from "../core/Link";
import { Logger } from "../services";

const feature: Feature = {
activate: (context: vscode.ExtensionContext, graph: NoteGraph) => {
Expand Down Expand Up @@ -37,7 +38,7 @@ const feature: Feature = {

// InputBox: FileName
try{
var filename: string = await letUserChooseText('Enter the filename for the new note', '', convertToKebabCase(title));
var filename: string = await letUserChooseText('Enter the filename for the new note', convertToKebabCase(title));
} catch(err) {
return;
}
Expand All @@ -58,7 +59,7 @@ const feature: Feature = {
if (selection !== undefined && !selection.isEmpty) {
const markdownLink: MarkdownLink = new MarkdownLink(activeEditor.document.uri.fsPath, filePath);
markdownLink.description = title;
activeEditor.edit(editBuilder => editBuilder.replace(selection, markdownLink.stringRepresentation));
activeEditor.edit(editBuilder => editBuilder.replace(selection, markdownLink.getStringRepresentation()));
}

openNoteInWorkspace(filePath, vscode.ViewColumn.Active);
Expand Down
2 changes: 1 addition & 1 deletion src/features/search-note.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { NoteGraph } from "../core";
import { Note } from '../core/Note';
import { letUserSearchNoteByTitle, letUserChooseNoteAction } from '../vscode/Inputs';
import * as noteActions from "../vscode/NoteActions";

import { Logger } from "../services";

const feature: Feature = {
activate: (context: vscode.ExtensionContext, graph: NoteGraph) => {
Expand Down
16 changes: 13 additions & 3 deletions src/services/bootstrap.ts
Original file line number Diff line number Diff line change
@@ -1,31 +1,41 @@
import * as md5 from 'md5';
import * as vscode from 'vscode';

import { NoteGraph } from "../core";
import { Logger } from '../services';
import { CodekastenParser } from "../vscode";
import { FilesystemSyncher } from './filesystemSyncher';

export const bootstrap = async(): Promise<NoteGraph> => {
const watcher = vscode.workspace.createFileSystemWatcher("**/*.md");
const watcher: vscode.FileSystemWatcher = vscode.workspace.createFileSystemWatcher("**/*.md");
const parser: CodekastenParser = new CodekastenParser();
const codekastenGraph: NoteGraph = new NoteGraph();
await codekastenGraph.populateGraph(vscode.workspace.findFiles('**/*.md', '{.codekasten, ./index.md}'), parser);
Logger.info(`Loaded ${codekastenGraph.graph.nodeCount()} Notes and ${codekastenGraph.graph.edgeCount()} Links`);

const filesystemSyncher: FilesystemSyncher = new FilesystemSyncher(codekastenGraph);

watcher.onDidCreate(async uri => {
Logger.info(`Created a file: ${uri.fsPath}`);
codekastenGraph.setNote(await parser.parse(uri));
});

watcher.onDidChange(async uri => {
Logger.info(`Changed a file: ${uri.fsPath}`);
codekastenGraph.setNote(await parser.parse(uri));
});

watcher.onDidDelete(uri => {
Logger.info(`Deleted a file: ${uri.fsPath}`);
codekastenGraph.deleteNote(md5(uri.fsPath));
});

vscode.workspace.onWillRenameFiles(async event => {
await filesystemSyncher.onWillRename(event);
});

vscode.workspace.onDidRenameFiles(event => {
console.log('File Rename Event!');
filesystemSyncher.onDidRename(event);

});

return codekastenGraph;
Expand Down
39 changes: 27 additions & 12 deletions src/services/filesystemSyncher.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
import * as md5 from 'md5';
import * as vscode from 'vscode';

import { NoteGraph } from '../core';
import * as md5 from 'md5';
import { MarkdownLink } from '../core/Link';
import { escapeRegex, replaceTextInFile } from '../vscode/NoteActions';
import { replaceTextInFile } from '../vscode/NoteActions';

import { Logger } from './logger';

export class FilesystemSyncher{

Expand All @@ -12,35 +15,47 @@ export class FilesystemSyncher{
this.graph = graph;
}

onDidRename(event: vscode.FileRenameEvent) {
/**
* Handels the renaming of forward- and backlinks in the markdown files
* The changes in the NoteGraph will be made by the respective onDidChange events.
*/
async onWillRename(event: vscode.FileRenameEvent) {
for(const file of event.files){

const oldId = md5(file.oldUri.fsPath);
const newId = md5(file.newUri.fsPath);
Logger.info(`Renaming file from ${file.oldUri.fsPath} to ${file.newUri.fsPath}`);

this.graph.updateId(oldId, newId);

// Adjust forward links: They have a new source
const forwardLinkTargets: string[] = this.graph.getForwardLinksAsString(newId);
const forwardLinkTargets: string[] = this.graph.getForwardLinksAsString(oldId);
for(const forwardLinkTarget of forwardLinkTargets){
const oldRelativePath: string = new MarkdownLink(file.oldUri.fsPath, forwardLinkTarget).relativePath;
const newRelativePath: string = new MarkdownLink(file.newUri.fsPath, forwardLinkTarget).relativePath;
if(oldRelativePath !== newRelativePath){
replaceTextInFile(file.newUri.fsPath, oldRelativePath, newRelativePath);
replaceTextInFile(file.newUri.fsPath, oldRelativePath.replace(/\//g, '\\'), newRelativePath); // we might need to replace old links with backslashes
Logger.info(`Renaming file, need to adjust forward link from ${oldRelativePath} to ${newRelativePath}`);
if (!await replaceTextInFile(file.newUri.fsPath, oldRelativePath, newRelativePath)) {
if (!await replaceTextInFile(file.newUri.fsPath, oldRelativePath.replace(/\//g, '\\'), newRelativePath)) {
Logger.warn(`Could not find text to replace in ${file.newUri.fsPath}`);
}
}
}

}

// Adjust backlinks: They have a new target
const backLinkTargets: string[] = this.graph.getBacklinksAsString(newId);
const backLinkTargets: string[] = this.graph.getBacklinksAsString(oldId);
for(const backLinkTarget of backLinkTargets){
const oldRelativePath: string = new MarkdownLink(backLinkTarget, file.oldUri.fsPath).relativePath;
const newRelativePath: string = new MarkdownLink(backLinkTarget, file.newUri.fsPath).relativePath;
if(oldRelativePath !== newRelativePath){
replaceTextInFile(backLinkTarget, oldRelativePath, newRelativePath);
replaceTextInFile(backLinkTarget, oldRelativePath.replace(/\//g, '\\'), newRelativePath); // we might need to replace old links with backslashes
Logger.info(`Renaming file, need to adjust backlink from ${oldRelativePath} to ${newRelativePath}`);
if(!await replaceTextInFile(backLinkTarget, oldRelativePath, newRelativePath)) {
if(!await replaceTextInFile(backLinkTarget, oldRelativePath.replace(/\//g, '\\'), newRelativePath)){
Logger.warn(`Could not find text to replace in ${backLinkTarget}`);
}
}
}
}
Logger.info(`---`);
}
}
}
10 changes: 10 additions & 0 deletions src/services/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { CodekastenLogger, Logger } from './logger';
import { FilesystemSyncher } from './filesystemSyncher';
import { bootstrap} from './bootstrap';

export {
bootstrap,
CodekastenLogger,
FilesystemSyncher,
Logger
};
95 changes: 95 additions & 0 deletions src/services/logger.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import * as vscode from 'vscode';

export type LogLevel = 'debug' | 'info' | 'warn' | 'error';


abstract class BaseLogger {
constructor(private level: LogLevel = 'info') {}

private static severity = {
debug: 1,
info: 2,
warn: 3,
error: 4,
};

abstract log(lvl: LogLevel, msg?: any, ...extra: any[]): void;

doLog(msgLevel: LogLevel, message?: any, ...params: any[]): void {
if (BaseLogger.severity[msgLevel] >= BaseLogger.severity[this.level]) {
this.log(msgLevel, message, ...params);
}
}

debug(message?: any, ...params: any[]): void {
this.doLog('debug', message, ...params);
}
info(message?: any, ...params: any[]): void {
this.doLog('info', message, ...params);
}
warn(message?: any, ...params: any[]): void {
this.doLog('warn', message, ...params);
}
error(message?: any, ...params: any[]): void {
this.doLog('error', message, ...params);
}

getLevel(): LogLevel {
return this.level;
}
setLevel(level: LogLevel): void {
this.level = level;
}

}

export class ConsoleLogger extends BaseLogger {
log(level: LogLevel, msg?: string, ...params: any[]): void {
console[level](`[${level}] ${msg}`, ...params);
}
}



export class CodekastenLogger extends BaseLogger {
private channel = vscode.window.createOutputChannel("CodekastenLog");

log(lvl: LogLevel, msg?: any, ...extra: any[]): void {
if (msg) {
this.channel.appendLine(`[${lvl} - ${new Date().toLocaleTimeString()}] ${msg}`);
}
}

show() {
this.channel.show();
}
}


export class Logger {
static debug(message?: any, ...params: any[]): void {
Logger.defaultLogger.debug(message, ...params);
}
static info(message?: any, ...params: any[]): void {
Logger.defaultLogger.info(message, ...params);
}
static warn(message?: any, ...params: any[]): void {
Logger.defaultLogger.warn(message, ...params);
}
static error(message?: any, ...params: any[]): void {
Logger.defaultLogger.error(message, ...params);
}
static getLevel(): LogLevel {
return Logger.defaultLogger.getLevel();
}
static setLevel(level: LogLevel): void {
Logger.defaultLogger.setLevel(level);
}

private static defaultLogger: BaseLogger = new CodekastenLogger();

static setDefaultLogger(logger: BaseLogger) {
Logger.defaultLogger = logger;
}
}

3 changes: 1 addition & 2 deletions src/vscode/Inputs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,9 @@ export function letUserSearchNoteByTitle(graph: NoteGraph): Promise<Note> {
return noteQuickPickPromise;
}

export async function letUserChooseText(prompt: string, suggestion: string = '', value: string = ''): Promise<string> {
export async function letUserChooseText(prompt: string, value: string = ''): Promise<string> {
const inputBox = vscode.window.createInputBox();
inputBox.prompt = prompt;
inputBox.placeholder = suggestion;
inputBox.value = value;
inputBox.onDidHide(() => inputBox.dispose());

Expand Down
Loading

0 comments on commit 3c6ac53

Please sign in to comment.