Skip to content

Commit b892c78

Browse files
Rename placeholder on note creation so it can update it if necessary (#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
1 parent e4f6259 commit b892c78

12 files changed

+244
-80
lines changed

packages/foam-vscode/src/core/model/foam.ts

+17-16
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { FoamGraph } from './graph';
55
import { ResourceParser } from './note';
66
import { ResourceProvider } from './provider';
77
import { FoamTags } from './tags';
8-
import { Logger } from '../utils/log';
8+
import { Logger, withTiming, withTimingAsync } from '../utils/log';
99

1010
export interface Services {
1111
dataStore: IDataStore;
@@ -28,24 +28,25 @@ export const bootstrap = async (
2828
initialProviders: ResourceProvider[],
2929
defaultExtension: string = '.md'
3030
) => {
31-
const tsStart = Date.now();
32-
33-
const workspace = await FoamWorkspace.fromProviders(
34-
initialProviders,
35-
dataStore,
36-
defaultExtension
31+
const workspace = await withTimingAsync(
32+
() =>
33+
FoamWorkspace.fromProviders(
34+
initialProviders,
35+
dataStore,
36+
defaultExtension
37+
),
38+
ms => Logger.info(`Workspace loaded in ${ms}ms`)
3739
);
3840

39-
const tsWsDone = Date.now();
40-
Logger.info(`Workspace loaded in ${tsWsDone - tsStart}ms`);
41-
42-
const graph = FoamGraph.fromWorkspace(workspace, true);
43-
const tsGraphDone = Date.now();
44-
Logger.info(`Graph loaded in ${tsGraphDone - tsWsDone}ms`);
41+
const graph = withTiming(
42+
() => FoamGraph.fromWorkspace(workspace, true),
43+
ms => Logger.info(`Graph loaded in ${ms}ms`)
44+
);
4545

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

5051
watcher?.onDidChange(async uri => {
5152
if (matcher.isMatch(uri)) {

packages/foam-vscode/src/core/model/graph.test.ts

+15
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,21 @@ describe('Graph', () => {
139139
).toEqual(['/path/another/page-c.md', '/somewhere/page-b.md']);
140140
});
141141

142+
it('should create inbound connections when targeting a section', () => {
143+
const noteA = createTestNote({
144+
uri: '/path/to/page-a.md',
145+
links: [{ slug: 'page-b#section 2' }],
146+
});
147+
const noteB = createTestNote({
148+
uri: '/somewhere/page-b.md',
149+
text: '## Section 1\n\n## Section 2',
150+
});
151+
const ws = createTestWorkspace().set(noteA).set(noteB);
152+
const graph = FoamGraph.fromWorkspace(ws);
153+
154+
expect(graph.getBacklinks(noteB.uri).length).toEqual(1);
155+
});
156+
142157
it('should support attachments', () => {
143158
const noteA = createTestNote({
144159
uri: '/path/to/page-a.md',

packages/foam-vscode/src/core/model/graph.ts

+26-25
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { ResourceLink } from './note';
33
import { URI } from './uri';
44
import { FoamWorkspace } from './workspace';
55
import { IDisposable } from '../common/lifecycle';
6-
import { Logger } from '../utils/log';
6+
import { Logger, withTiming } from '../utils/log';
77
import { Emitter } from '../common/event';
88

99
export type Connection = {
@@ -100,31 +100,32 @@ export class FoamGraph implements IDisposable {
100100
}
101101

102102
public update() {
103-
const start = Date.now();
104-
this.backlinks.clear();
105-
this.links.clear();
106-
this.placeholders.clear();
107-
108-
for (const resource of this.workspace.resources()) {
109-
for (const link of resource.links) {
110-
try {
111-
const targetUri = this.workspace.resolveLink(resource, link);
112-
this.connect(resource.uri, targetUri, link);
113-
} catch (e) {
114-
Logger.error(
115-
`Error while resolving link ${
116-
link.rawText
117-
} in ${resource.uri.toFsPath()}, skipping.`,
118-
link,
119-
e
120-
);
103+
withTiming(
104+
() => {
105+
this.backlinks.clear();
106+
this.links.clear();
107+
this.placeholders.clear();
108+
109+
for (const resource of this.workspace.resources()) {
110+
for (const link of resource.links) {
111+
try {
112+
const targetUri = this.workspace.resolveLink(resource, link);
113+
this.connect(resource.uri, targetUri, link);
114+
} catch (e) {
115+
Logger.error(
116+
`Error while resolving link ${
117+
link.rawText
118+
} in ${resource.uri.toFsPath()}, skipping.`,
119+
link,
120+
e
121+
);
122+
}
123+
}
124+
this.onDidUpdateEmitter.fire();
121125
}
122-
}
123-
}
124-
125-
const end = Date.now();
126-
Logger.debug(`Graph updated in ${end - start}ms`);
127-
this.onDidUpdateEmitter.fire();
126+
},
127+
ms => Logger.debug(`Graph updated in ${ms}ms`)
128+
);
128129
}
129130

130131
private connect(source: URI, target: URI, link: ResourceLink) {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import { Range } from './range';
2+
import { URI } from './uri';
3+
import { ResourceLink } from './note';
4+
5+
/**
6+
* Represents a location inside a resource, such as a line
7+
* inside a text file.
8+
*/
9+
export interface Location<T> {
10+
/**
11+
* The resource identifier of this location.
12+
*/
13+
uri: URI;
14+
/**
15+
* The document range of this locations.
16+
*/
17+
range: Range;
18+
/**
19+
* The data associated to this location.
20+
*/
21+
data: T;
22+
}
23+
24+
export abstract class Location<T> {
25+
static create<T>(uri: URI, range: Range, data: T): Location<T> {
26+
return { uri, range, data };
27+
}
28+
29+
static forObjectWithRange<T extends { range: Range }>(
30+
uri: URI,
31+
obj: T
32+
): Location<T> {
33+
return Location.create(uri, obj.range, obj);
34+
}
35+
}

packages/foam-vscode/src/core/utils/log.ts

+22
Original file line numberDiff line numberDiff line change
@@ -89,3 +89,25 @@ export class Logger {
8989
Logger.defaultLogger = logger;
9090
}
9191
}
92+
93+
export const withTiming = <T>(
94+
fn: () => T,
95+
onDidComplete: (elapsed: number) => void
96+
): T => {
97+
const tsStart = Date.now();
98+
const res = fn();
99+
const tsEnd = Date.now();
100+
onDidComplete(tsEnd - tsStart);
101+
return res;
102+
};
103+
104+
export const withTimingAsync = async <T>(
105+
fn: () => Promise<T>,
106+
onDidComplete: (elapsed: number) => void
107+
): Promise<T> => {
108+
const tsStart = Date.now();
109+
const res = await fn();
110+
const tsEnd = Date.now();
111+
onDidComplete(tsEnd - tsStart);
112+
return res;
113+
};

packages/foam-vscode/src/extension.ts

+6-3
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,9 @@ export async function activate(context: ExtensionContext) {
7373
);
7474

7575
// Load the features
76-
const resPromises = features.map(feature => feature(context, foamPromise));
76+
const featuresPromises = features.map(feature =>
77+
feature(context, foamPromise)
78+
);
7779

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

105-
const res = (await Promise.all(resPromises)).filter(r => r != null);
107+
const feats = (await Promise.all(featuresPromises)).filter(r => r != null);
106108

107109
return {
108110
extendMarkdownIt: (md: markdownit) => {
109-
return res.reduce((acc: markdownit, r: any) => {
111+
return feats.reduce((acc: markdownit, r: any) => {
110112
return r.extendMarkdownIt ? r.extendMarkdownIt(acc) : acc;
111113
}, md);
112114
},
115+
foam,
113116
};
114117
} catch (e) {
115118
Logger.error('An error occurred while bootstrapping Foam', e);

packages/foam-vscode/src/features/commands/create-note.spec.ts

+49-2
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,12 @@ import {
1010
showInEditor,
1111
} from '../../test/test-utils-vscode';
1212
import { fromVsCodeUri } from '../../utils/vsc-utils';
13-
import { CREATE_NOTE_COMMAND } from './create-note';
13+
import { CREATE_NOTE_COMMAND, createNote } from './create-note';
14+
import { Location } from '../../core/model/location';
15+
import { Range } from '../../core/model/range';
16+
import { ResourceLink } from '../../core/model/note';
17+
import { MarkdownResourceProvider } from '../../core/services/markdown-provider';
18+
import { createMarkdownParser } from '../../core/services/markdown-parser';
1419

1520
describe('create-note command', () => {
1621
afterEach(() => {
@@ -194,8 +199,14 @@ describe('factories', () => {
194199
describe('forPlaceholder', () => {
195200
it('adds the .md extension to notes created for placeholders', async () => {
196201
await closeEditors();
202+
const link: ResourceLink = {
203+
type: 'wikilink',
204+
rawText: '[[my-placeholder]]',
205+
range: Range.create(0, 0, 0, 0),
206+
isEmbed: false,
207+
};
197208
const command = CREATE_NOTE_COMMAND.forPlaceholder(
198-
'my-placeholder',
209+
Location.forObjectWithRange(URI.file(''), link),
199210
'.md'
200211
);
201212
await commands.executeCommand(command.name, command.params);
@@ -204,5 +215,41 @@ describe('factories', () => {
204215
expect(doc.uri.path).toMatch(/my-placeholder.md$/);
205216
expect(doc.getText()).toMatch(/^# my-placeholder/);
206217
});
218+
219+
it('replaces the original placeholder based on the new note identifier (#1327)', async () => {
220+
await closeEditors();
221+
const templateA = await createFile(
222+
`---
223+
foam_template:
224+
name: 'Example Template'
225+
description: 'An example for reproducing a bug'
226+
filepath: '$FOAM_SLUG-world.md'
227+
---`,
228+
['.foam', 'templates', 'template-a.md']
229+
);
230+
231+
const noteA = await createFile(`this is my [[hello]]`);
232+
233+
const parser = createMarkdownParser();
234+
const res = parser.parse(noteA.uri, noteA.content);
235+
236+
const command = CREATE_NOTE_COMMAND.forPlaceholder(
237+
Location.forObjectWithRange(noteA.uri, res.links[0]),
238+
'.md',
239+
{
240+
templatePath: templateA.uri.path,
241+
}
242+
);
243+
const results: Awaited<ReturnType<typeof createNote>> =
244+
await commands.executeCommand(command.name, command.params);
245+
expect(results.didCreateFile).toBeTruthy();
246+
expect(results.uri.path.endsWith('hello-world.md')).toBeTruthy();
247+
248+
const newNoteDoc = window.activeTextEditor.document;
249+
expect(newNoteDoc.uri.path).toMatch(/hello-world.md$/);
250+
251+
const { doc } = await showInEditor(noteA.uri);
252+
expect(doc.getText()).toEqual(`this is my [[hello-world]]`);
253+
});
207254
});
208255
});

0 commit comments

Comments
 (0)