Skip to content

Commit

Permalink
Support for multiple file extensions and default extension (#1235)
Browse files Browse the repository at this point in the history
* Added multiple extension support for markdown provider

* Added default extension support

* Injecting extensions params to FoamWorkspace and MarkdownProvider (to avoid dependencies on non-core code)

* Inject extensions to attachment provider
  • Loading branch information
riccardoferretti authored Jun 30, 2023
1 parent a00d18c commit a504054
Show file tree
Hide file tree
Showing 16 changed files with 211 additions and 53 deletions.
10 changes: 10 additions & 0 deletions packages/foam-vscode/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -395,6 +395,16 @@
"default": "pdf mp3 webm wav m4a mp4 avi mov rtf txt doc docx pages xls xlsx numbers ppt pptm pptx",
"description": "Space separated list of file extensions that will be considered attachments"
},
"foam.files.notesExtensions": {
"type": "string",
"default": "",
"description": "Space separated list of extra file extensions that will be considered text notes (e.g. 'mdx txt markdown')"
},
"foam.files.defaultNoteExtension": {
"type": "string",
"default": "md",
"description": "The default extension for new notes"
},
"foam.files.newNotePath": {
"type": "string",
"default": "root",
Expand Down
6 changes: 4 additions & 2 deletions packages/foam-vscode/src/core/model/foam.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,15 @@ export const bootstrap = async (
watcher: IWatcher | undefined,
dataStore: IDataStore,
parser: ResourceParser,
initialProviders: ResourceProvider[]
initialProviders: ResourceProvider[],
defaultExtension: string = '.md'
) => {
const tsStart = Date.now();

const workspace = await FoamWorkspace.fromProviders(
initialProviders,
dataStore
dataStore,
defaultExtension
);

const tsWsDone = Date.now();
Expand Down
4 changes: 4 additions & 0 deletions packages/foam-vscode/src/core/model/note.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,10 @@ export abstract class Resource {
return a.title.localeCompare(b.title);
}

public static sortByPath(a: Resource, b: Resource) {
return a.uri.path.localeCompare(b.uri.path);
}

public static isResource(thing: any): thing is Resource {
if (!thing) {
return false;
Expand Down
6 changes: 3 additions & 3 deletions packages/foam-vscode/src/core/model/workspace.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ describe('Identifier computation', () => {
const third = createTestNote({
uri: '/another/path/for/page-a.md',
});
const ws = new FoamWorkspace().set(first).set(second).set(third);
const ws = new FoamWorkspace('.md').set(first).set(second).set(third);

expect(ws.getIdentifier(first.uri)).toEqual('to/page-a');
expect(ws.getIdentifier(second.uri)).toEqual('way/for/page-a');
Expand All @@ -124,7 +124,7 @@ describe('Identifier computation', () => {
const third = createTestNote({
uri: '/another/path/for/page-a.md',
});
const ws = new FoamWorkspace().set(first).set(second).set(third);
const ws = new FoamWorkspace('.md').set(first).set(second).set(third);

expect(ws.getIdentifier(first.uri.withFragment('section name'))).toEqual(
'to/page-a#section name'
Expand Down Expand Up @@ -170,7 +170,7 @@ describe('Identifier computation', () => {
});

it('should ignore elements from the exclude list', () => {
const workspace = new FoamWorkspace();
const workspace = new FoamWorkspace('.md');
const noteA = createTestNote({ uri: '/path/to/note-a.md' });
const noteB = createTestNote({ uri: '/path/to/note-b.md' });
const noteC = createTestNote({ uri: '/path/to/note-c.md' });
Expand Down
25 changes: 16 additions & 9 deletions packages/foam-vscode/src/core/model/workspace.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,11 @@ export class FoamWorkspace implements IDisposable {
*/
private _resources: Map<string, Resource> = new Map();

/**
* @param defaultExtension: The default extension for notes in this workspace (e.g. `.md`)
*/
constructor(public defaultExtension: string = '.md') {}

registerProvider(provider: ResourceProvider) {
this.providers.push(provider);
}
Expand Down Expand Up @@ -67,14 +72,16 @@ export class FoamWorkspace implements IDisposable {
public listByIdentifier(identifier: string): Resource[] {
const needle = normalize('/' + identifier);
const mdNeedle =
getExtension(needle) !== '.md' ? needle + '.md' : undefined;
const resources = [];
getExtension(needle) !== this.defaultExtension
? needle + this.defaultExtension
: undefined;
const resources: Resource[] = [];
for (const key of this._resources.keys()) {
if ((mdNeedle && key.endsWith(mdNeedle)) || key.endsWith(needle)) {
if (key.endsWith(mdNeedle) || key.endsWith(needle)) {
resources.push(this._resources.get(normalize(key)));
}
}
return resources.sort((a, b) => a.uri.path.localeCompare(b.uri.path));
return resources.sort(Resource.sortByPath);
}

/**
Expand Down Expand Up @@ -105,7 +112,7 @@ export class FoamWorkspace implements IDisposable {
forResource.path,
amongst.map(uri => uri.path)
);
identifier = changeExtension(identifier, '.md', '');
identifier = changeExtension(identifier, this.defaultExtension, '');
if (forResource.fragment) {
identifier += `#${forResource.fragment}`;
}
Expand All @@ -121,7 +128,7 @@ export class FoamWorkspace implements IDisposable {
if (FoamWorkspace.isIdentifier(path)) {
resource = this.listByIdentifier(path)[0];
} else {
const candidates = [path, path + '.md'];
const candidates = [path, path + this.defaultExtension];
for (const candidate of candidates) {
const searchKey = isAbsolute(candidate)
? candidate
Expand All @@ -141,7 +148,6 @@ export class FoamWorkspace implements IDisposable {
}

public resolveLink(resource: Resource, link: ResourceLink): URI {
// TODO add tests
for (const provider of this.providers) {
if (provider.supports(resource.uri)) {
return provider.resolveLink(this, resource, link);
Expand Down Expand Up @@ -237,9 +243,10 @@ export class FoamWorkspace implements IDisposable {

static async fromProviders(
providers: ResourceProvider[],
dataStore: IDataStore
dataStore: IDataStore,
defaultExtension: string = '.md'
): Promise<FoamWorkspace> {
const workspace = new FoamWorkspace();
const workspace = new FoamWorkspace(defaultExtension);
await Promise.all(providers.map(p => workspace.registerProvider(p)));
const files = await dataStore.list();
await Promise.all(files.map(f => workspace.fetchAndSet(f)));
Expand Down
16 changes: 6 additions & 10 deletions packages/foam-vscode/src/core/services/attachment-provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,8 @@ import { URI } from '../model/uri';
import { FoamWorkspace } from '../model/workspace';
import { IDisposable } from '../common/lifecycle';
import { ResourceProvider } from '../model/provider';
import { getFoamVsCodeConfig } from '../../services/config';

const attachmentExtConfig = getFoamVsCodeConfig(
'files.attachmentExtensions',
''
)
.split(' ')
.map(ext => '.' + ext.trim());

const imageExtensions = ['.png', '.jpg', '.jpeg', '.gif', '.svg', '.webp'];
const attachmentExtensions = [...attachmentExtConfig, ...imageExtensions];

const asResource = (uri: URI): Resource => {
const type = imageExtensions.includes(uri.getExtension())
Expand All @@ -34,9 +25,14 @@ const asResource = (uri: URI): Resource => {

export class AttachmentResourceProvider implements ResourceProvider {
private disposables: IDisposable[] = [];
public readonly attachmentExtensions: string[];

constructor(attachmentExtensions: string[] = []) {
this.attachmentExtensions = [...imageExtensions, ...attachmentExtensions];
}

supports(uri: URI) {
return attachmentExtensions.includes(
return this.attachmentExtensions.includes(
uri.getExtension().toLocaleLowerCase()
);
}
Expand Down
10 changes: 7 additions & 3 deletions packages/foam-vscode/src/core/services/markdown-provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,12 @@ export class MarkdownResourceProvider implements ResourceProvider {

constructor(
private readonly dataStore: IDataStore,
private readonly parser: ResourceParser
private readonly parser: ResourceParser,
public readonly noteExtensions: string[] = ['.md']
) {}

supports(uri: URI) {
return uri.isMarkdown();
return this.noteExtensions.includes(uri.getExtension());
}

async readAsMarkdown(uri: URI): Promise<string | null> {
Expand Down Expand Up @@ -129,7 +130,10 @@ export function createMarkdownReferences(
}

let relativeUri = target.uri.relativeTo(resource.uri.getDirectory());
if (!includeExtension && relativeUri.path.endsWith('.md')) {
if (
!includeExtension &&
relativeUri.path.endsWith(workspace.defaultExtension)
) {
relativeUri = relativeUri.changeExtension('*', '');
}

Expand Down
49 changes: 41 additions & 8 deletions packages/foam-vscode/src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,17 @@ import { Logger } from './core/utils/log';

import { features } from './features';
import { VsCodeOutputLogger, exposeLogger } from './services/logging';
import { getIgnoredFilesSetting } from './settings';
import {
getAttachmentsExtensions,
getIgnoredFilesSetting,
getNotesExtensions,
} from './settings';
import { AttachmentResourceProvider } from './core/services/attachment-provider';
import { VsCodeWatcher } from './services/watcher';
import { createMarkdownParser } from './core/services/markdown-parser';
import VsCodeBasedParserCache from './services/cache';
import { createMatcherAndDataStore } from './services/editor';
import { getFoamVsCodeConfig } from './services/config';

export async function activate(context: ExtensionContext) {
const logger = new VsCodeOutputLogger();
Expand Down Expand Up @@ -45,13 +50,27 @@ export async function activate(context: ExtensionContext) {
const parserCache = new VsCodeBasedParserCache(context);
const parser = createMarkdownParser([], parserCache);

const markdownProvider = new MarkdownResourceProvider(dataStore, parser);
const attachmentProvider = new AttachmentResourceProvider();
const { notesExtensions, defaultExtension } = getNotesExtensions();

const foamPromise = bootstrap(matcher, watcher, dataStore, parser, [
markdownProvider,
attachmentProvider,
]);
const markdownProvider = new MarkdownResourceProvider(
dataStore,
parser,
notesExtensions
);

const attachmentExtConfig = getAttachmentsExtensions();
const attachmentProvider = new AttachmentResourceProvider(
attachmentExtConfig
);

const foamPromise = bootstrap(
matcher,
watcher,
dataStore,
parser,
[markdownProvider, attachmentProvider],
defaultExtension
);

// Load the features
const resPromises = features.map(feature => feature(context, foamPromise));
Expand All @@ -66,7 +85,21 @@ export async function activate(context: ExtensionContext) {
attachmentProvider,
commands.registerCommand('foam-vscode.clear-cache', () =>
parserCache.clear()
)
),
workspace.onDidChangeConfiguration(e => {
if (
[
'foam.files.ignore',
'foam.files.attachmentExtensions',
'foam.files.noteExtensions',
'foam.files.defaultNoteExtension',
].some(setting => e.affectsConfiguration(setting))
) {
window.showInformationMessage(
'Foam: Reload the window to use the updated settings'
);
}
})
);

const res = (await Promise.all(resPromises)).filter(r => r != null);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,10 @@ describe('factories', () => {
describe('forPlaceholder', () => {
it('adds the .md extension to notes created for placeholders', async () => {
await closeEditors();
const command = CREATE_NOTE_COMMAND.forPlaceholder('my-placeholder');
const command = CREATE_NOTE_COMMAND.forPlaceholder(
'my-placeholder',
'.md'
);
await commands.executeCommand(command.name, command.params);

const doc = window.activeTextEditor.document;
Expand Down
19 changes: 15 additions & 4 deletions packages/foam-vscode/src/features/commands/create-note.ts
Original file line number Diff line number Diff line change
Expand Up @@ -114,16 +114,27 @@ async function createNote(args: CreateNoteArgs) {
export const CREATE_NOTE_COMMAND = {
command: 'foam-vscode.create-note',

/**
* Creates a command descriptor to create a note from the given placeholder.
*
* @param placeholder the placeholder
* @param defaultExtension the default extension (e.g. '.md')
* @param extra extra command arguments
* @returns the command descriptor
*/
forPlaceholder: (
placeholder: string,
defaultExtension: string,
extra: Partial<CreateNoteArgs> = {}
): CommandDescriptor<CreateNoteArgs> => {
const title = placeholder.endsWith('.md')
? placeholder.replace(/\.md$/, '')
const endsWithDefaultExtension = new RegExp(defaultExtension + '$');

const title = placeholder.endsWith(defaultExtension)
? placeholder.replace(endsWithDefaultExtension, '')
: placeholder;
const notePath = placeholder.endsWith('.md')
const notePath = placeholder.endsWith(defaultExtension)
? placeholder
: placeholder + '.md';
: placeholder + defaultExtension;
return {
name: CREATE_NOTE_COMMAND.command,
params: {
Expand Down
12 changes: 8 additions & 4 deletions packages/foam-vscode/src/features/hover-provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -106,10 +106,14 @@ export class HoverProvider implements vscode.HoverProvider {
: this.workspace.get(targetUri).title;
}

const command = CREATE_NOTE_COMMAND.forPlaceholder(targetUri.path, {
askForTemplate: true,
onFileExists: 'open',
});
const command = CREATE_NOTE_COMMAND.forPlaceholder(
targetUri.path,
this.workspace.defaultExtension,
{
askForTemplate: true,
onFileExists: 'open',
}
);
const newNoteFromTemplate = new vscode.MarkdownString(
`[Create note from template for '${targetUri.getBasename()}'](${commandAsURI(
command
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ describe('Document navigation', () => {
expect(links.length).toEqual(1);
expect(links[0].target).toEqual(
commandAsURI(
CREATE_NOTE_COMMAND.forPlaceholder('a placeholder', {
CREATE_NOTE_COMMAND.forPlaceholder('a placeholder', '.md', {
onFileExists: 'open',
})
)
Expand Down
10 changes: 7 additions & 3 deletions packages/foam-vscode/src/features/navigation-provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -161,9 +161,13 @@ export class NavigationProvider
return targets
.filter(o => o.target.isPlaceholder()) // links to resources are managed by the definition provider
.map(o => {
const command = CREATE_NOTE_COMMAND.forPlaceholder(o.target.path, {
onFileExists: 'open',
});
const command = CREATE_NOTE_COMMAND.forPlaceholder(
o.target.path,
this.workspace.defaultExtension,
{
onFileExists: 'open',
}
);

const documentLink = new vscode.DocumentLink(
new vscode.Range(
Expand Down
Loading

0 comments on commit a504054

Please sign in to comment.