Skip to content

Commit

Permalink
Rename placeholder on note creation so it can update it if necessary (f…
Browse files Browse the repository at this point in the history
…oambubble#1344)

* Introduced Location
* Passing a reference to the source link to the create-note command

Also
* Added withTiming fn for performance logging
* Added extra test to check incoming wikilink with sections
* Tweaked creation of vscode URI to also support raw objects
  • Loading branch information
riccardoferretti committed Mar 17, 2024
1 parent e4f6259 commit b892c78
Show file tree
Hide file tree
Showing 12 changed files with 244 additions and 80 deletions.
33 changes: 17 additions & 16 deletions packages/foam-vscode/src/core/model/foam.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { FoamGraph } from './graph';
import { ResourceParser } from './note';
import { ResourceProvider } from './provider';
import { FoamTags } from './tags';
import { Logger } from '../utils/log';
import { Logger, withTiming, withTimingAsync } from '../utils/log';

export interface Services {
dataStore: IDataStore;
Expand All @@ -28,24 +28,25 @@ export const bootstrap = async (
initialProviders: ResourceProvider[],
defaultExtension: string = '.md'
) => {
const tsStart = Date.now();

const workspace = await FoamWorkspace.fromProviders(
initialProviders,
dataStore,
defaultExtension
const workspace = await withTimingAsync(
() =>
FoamWorkspace.fromProviders(
initialProviders,
dataStore,
defaultExtension
),
ms => Logger.info(`Workspace loaded in ${ms}ms`)
);

const tsWsDone = Date.now();
Logger.info(`Workspace loaded in ${tsWsDone - tsStart}ms`);

const graph = FoamGraph.fromWorkspace(workspace, true);
const tsGraphDone = Date.now();
Logger.info(`Graph loaded in ${tsGraphDone - tsWsDone}ms`);
const graph = withTiming(
() => FoamGraph.fromWorkspace(workspace, true),
ms => Logger.info(`Graph loaded in ${ms}ms`)
);

const tags = FoamTags.fromWorkspace(workspace, true);
const tsTagsEnd = Date.now();
Logger.info(`Tags loaded in ${tsTagsEnd - tsGraphDone}ms`);
const tags = withTiming(
() => FoamTags.fromWorkspace(workspace, true),
ms => Logger.info(`Tags loaded in ${ms}ms`)
);

watcher?.onDidChange(async uri => {
if (matcher.isMatch(uri)) {
Expand Down
15 changes: 15 additions & 0 deletions packages/foam-vscode/src/core/model/graph.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,21 @@ describe('Graph', () => {
).toEqual(['/path/another/page-c.md', '/somewhere/page-b.md']);
});

it('should create inbound connections when targeting a section', () => {
const noteA = createTestNote({
uri: '/path/to/page-a.md',
links: [{ slug: 'page-b#section 2' }],
});
const noteB = createTestNote({
uri: '/somewhere/page-b.md',
text: '## Section 1\n\n## Section 2',
});
const ws = createTestWorkspace().set(noteA).set(noteB);
const graph = FoamGraph.fromWorkspace(ws);

expect(graph.getBacklinks(noteB.uri).length).toEqual(1);
});

it('should support attachments', () => {
const noteA = createTestNote({
uri: '/path/to/page-a.md',
Expand Down
51 changes: 26 additions & 25 deletions packages/foam-vscode/src/core/model/graph.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { ResourceLink } from './note';
import { URI } from './uri';
import { FoamWorkspace } from './workspace';
import { IDisposable } from '../common/lifecycle';
import { Logger } from '../utils/log';
import { Logger, withTiming } from '../utils/log';
import { Emitter } from '../common/event';

export type Connection = {
Expand Down Expand Up @@ -100,31 +100,32 @@ export class FoamGraph implements IDisposable {
}

public update() {
const start = Date.now();
this.backlinks.clear();
this.links.clear();
this.placeholders.clear();

for (const resource of this.workspace.resources()) {
for (const link of resource.links) {
try {
const targetUri = this.workspace.resolveLink(resource, link);
this.connect(resource.uri, targetUri, link);
} catch (e) {
Logger.error(
`Error while resolving link ${
link.rawText
} in ${resource.uri.toFsPath()}, skipping.`,
link,
e
);
withTiming(
() => {
this.backlinks.clear();
this.links.clear();
this.placeholders.clear();

for (const resource of this.workspace.resources()) {
for (const link of resource.links) {
try {
const targetUri = this.workspace.resolveLink(resource, link);
this.connect(resource.uri, targetUri, link);
} catch (e) {
Logger.error(
`Error while resolving link ${
link.rawText
} in ${resource.uri.toFsPath()}, skipping.`,
link,
e
);
}
}
this.onDidUpdateEmitter.fire();
}
}
}

const end = Date.now();
Logger.debug(`Graph updated in ${end - start}ms`);
this.onDidUpdateEmitter.fire();
},
ms => Logger.debug(`Graph updated in ${ms}ms`)
);
}

private connect(source: URI, target: URI, link: ResourceLink) {
Expand Down
35 changes: 35 additions & 0 deletions packages/foam-vscode/src/core/model/location.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { Range } from './range';
import { URI } from './uri';
import { ResourceLink } from './note';

/**
* Represents a location inside a resource, such as a line
* inside a text file.
*/
export interface Location<T> {
/**
* The resource identifier of this location.
*/
uri: URI;
/**
* The document range of this locations.
*/
range: Range;
/**
* The data associated to this location.
*/
data: T;
}

export abstract class Location<T> {
static create<T>(uri: URI, range: Range, data: T): Location<T> {
return { uri, range, data };
}

static forObjectWithRange<T extends { range: Range }>(
uri: URI,
obj: T
): Location<T> {
return Location.create(uri, obj.range, obj);
}
}
22 changes: 22 additions & 0 deletions packages/foam-vscode/src/core/utils/log.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,3 +89,25 @@ export class Logger {
Logger.defaultLogger = logger;
}
}

export const withTiming = <T>(
fn: () => T,
onDidComplete: (elapsed: number) => void
): T => {
const tsStart = Date.now();
const res = fn();
const tsEnd = Date.now();
onDidComplete(tsEnd - tsStart);
return res;
};

export const withTimingAsync = async <T>(
fn: () => Promise<T>,
onDidComplete: (elapsed: number) => void
): Promise<T> => {
const tsStart = Date.now();
const res = await fn();
const tsEnd = Date.now();
onDidComplete(tsEnd - tsStart);
return res;
};
9 changes: 6 additions & 3 deletions packages/foam-vscode/src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,9 @@ export async function activate(context: ExtensionContext) {
);

// Load the features
const resPromises = features.map(feature => feature(context, foamPromise));
const featuresPromises = features.map(feature =>
feature(context, foamPromise)
);

const foam = await foamPromise;
Logger.info(`Loaded ${foam.workspace.list().length} resources`);
Expand Down Expand Up @@ -102,14 +104,15 @@ export async function activate(context: ExtensionContext) {
})
);

const res = (await Promise.all(resPromises)).filter(r => r != null);
const feats = (await Promise.all(featuresPromises)).filter(r => r != null);

return {
extendMarkdownIt: (md: markdownit) => {
return res.reduce((acc: markdownit, r: any) => {
return feats.reduce((acc: markdownit, r: any) => {
return r.extendMarkdownIt ? r.extendMarkdownIt(acc) : acc;
}, md);
},
foam,
};
} catch (e) {
Logger.error('An error occurred while bootstrapping Foam', e);
Expand Down
51 changes: 49 additions & 2 deletions packages/foam-vscode/src/features/commands/create-note.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,12 @@ import {
showInEditor,
} from '../../test/test-utils-vscode';
import { fromVsCodeUri } from '../../utils/vsc-utils';
import { CREATE_NOTE_COMMAND } from './create-note';
import { CREATE_NOTE_COMMAND, createNote } from './create-note';
import { Location } from '../../core/model/location';
import { Range } from '../../core/model/range';
import { ResourceLink } from '../../core/model/note';
import { MarkdownResourceProvider } from '../../core/services/markdown-provider';
import { createMarkdownParser } from '../../core/services/markdown-parser';

describe('create-note command', () => {
afterEach(() => {
Expand Down Expand Up @@ -194,8 +199,14 @@ describe('factories', () => {
describe('forPlaceholder', () => {
it('adds the .md extension to notes created for placeholders', async () => {
await closeEditors();
const link: ResourceLink = {
type: 'wikilink',
rawText: '[[my-placeholder]]',
range: Range.create(0, 0, 0, 0),
isEmbed: false,
};
const command = CREATE_NOTE_COMMAND.forPlaceholder(
'my-placeholder',
Location.forObjectWithRange(URI.file(''), link),
'.md'
);
await commands.executeCommand(command.name, command.params);
Expand All @@ -204,5 +215,41 @@ describe('factories', () => {
expect(doc.uri.path).toMatch(/my-placeholder.md$/);
expect(doc.getText()).toMatch(/^# my-placeholder/);
});

it('replaces the original placeholder based on the new note identifier (#1327)', async () => {
await closeEditors();
const templateA = await createFile(
`---
foam_template:
name: 'Example Template'
description: 'An example for reproducing a bug'
filepath: '$FOAM_SLUG-world.md'
---`,
['.foam', 'templates', 'template-a.md']
);

const noteA = await createFile(`this is my [[hello]]`);

const parser = createMarkdownParser();
const res = parser.parse(noteA.uri, noteA.content);

const command = CREATE_NOTE_COMMAND.forPlaceholder(
Location.forObjectWithRange(noteA.uri, res.links[0]),
'.md',
{
templatePath: templateA.uri.path,
}
);
const results: Awaited<ReturnType<typeof createNote>> =
await commands.executeCommand(command.name, command.params);
expect(results.didCreateFile).toBeTruthy();
expect(results.uri.path.endsWith('hello-world.md')).toBeTruthy();

const newNoteDoc = window.activeTextEditor.document;
expect(newNoteDoc.uri.path).toMatch(/hello-world.md$/);

const { doc } = await showInEditor(noteA.uri);
expect(doc.getText()).toEqual(`this is my [[hello-world]]`);
});
});
});
Loading

0 comments on commit b892c78

Please sign in to comment.