From 95bc46b74a5f6b27b012a0ba75f8da21da6de23f Mon Sep 17 00:00:00 2001 From: Konrad Schatz Date: Thu, 2 Jan 2025 00:31:49 +0100 Subject: [PATCH 1/5] - fixed: error in tag-completion.ts (line 10): "This regular expression flag is only available when targeting 'es2022' or later." --- packages/foam-vscode/tsconfig.json | 2 +- tsconfig.base.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/foam-vscode/tsconfig.json b/packages/foam-vscode/tsconfig.json index a8b3fc88e..29916cc79 100644 --- a/packages/foam-vscode/tsconfig.json +++ b/packages/foam-vscode/tsconfig.json @@ -4,7 +4,7 @@ "moduleResolution": "node", "esModuleInterop": true, "outDir": "out", - "lib": ["ES2019", "es2020.string", "DOM"], + "lib": ["ES2022", "es2022.string", "DOM"], "sourceMap": true, "strict": false, "downlevelIteration": true diff --git a/tsconfig.base.json b/tsconfig.base.json index e83ebbb57..7dee6969b 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -5,7 +5,7 @@ "declarationMap": true, "baseUrl": ".", "module": "commonjs", - "target": "ES2019", + "target": "ES2022", "paths": { "foam-core": ["./packages/foam-core/src"], "foam-vscode": ["./packages/foam-vscode/src"] From a4acbad6ce80875ee309fbf1eef3d1413501f315 Mon Sep 17 00:00:00 2001 From: Konrad Schatz Date: Thu, 2 Jan 2025 00:37:42 +0100 Subject: [PATCH 2/5] - allow watching and accessing notes in external folders that are not part of VSCode workspace - allow defining templates in external workspace folders - added config. properties: foam.files.workspaceType, foam.files.externalWatchPaths, foam.files.externalTemplatesRoot --- packages/foam-vscode/package.json | 29 +++++++ packages/foam-vscode/src/core/model/foam.ts | 32 +++---- packages/foam-vscode/src/extension.ts | 86 ++++++++++++------- packages/foam-vscode/src/services/editor.ts | 67 +++++++++++---- .../foam-vscode/src/services/templates.ts | 44 ++++++++-- packages/foam-vscode/src/settings.ts | 16 ++++ packages/foam-vscode/src/utils/vsc-utils.ts | 28 +++++- 7 files changed, 229 insertions(+), 73 deletions(-) diff --git a/packages/foam-vscode/package.json b/packages/foam-vscode/package.json index be906a7af..3547a66bb 100644 --- a/packages/foam-vscode/package.json +++ b/packages/foam-vscode/package.json @@ -525,6 +525,35 @@ "Use the directory of the file in the current editor" ] }, + "foam.files.workspaceType": { + "type": "string", + "default": "internal", + "description": "Specifies which 'workspaces' of note folders to watch", + "enum": [ + "internal", + "external", + "combined" + ], + "enumDescriptions": [ + "Watch note folders within 'internal' workspace (exclusive VSCode workspace)", + "Watch note folders within 'external' workspace ('external' with respect to VSCode workspace; INFO: this may include folders from 'internal' workspace)", + "POTENTIALLY DEPRECATED: Watch note folders of both, 'internal' and 'external' workspace" + ] + }, + "foam.files.externalWatchPaths": { + "type": "array", + "items": { + "type": "string", + "default": "**/*" + }, + "default": [], + "description": "The array of root paths to be watched by the note file watcher" + }, + "foam.files.externalTemplatesRoot": { + "type": "string", + "default": "", + "description": "'External' (as to VSCode workspace) root path for note templates" + }, "foam.logging.level": { "type": "string", "default": "info", diff --git a/packages/foam-vscode/src/core/model/foam.ts b/packages/foam-vscode/src/core/model/foam.ts index 8e1566ac1..1a97b3c8a 100644 --- a/packages/foam-vscode/src/core/model/foam.ts +++ b/packages/foam-vscode/src/core/model/foam.ts @@ -22,7 +22,7 @@ export interface Foam extends IDisposable { export const bootstrap = async ( matcher: IMatcher, - watcher: IWatcher | undefined, + watchers: IWatcher[] | undefined, dataStore: IDataStore, parser: ResourceParser, initialProviders: ResourceProvider[], @@ -48,20 +48,22 @@ export const bootstrap = async ( ms => Logger.info(`Tags loaded in ${ms}ms`) ); - watcher?.onDidChange(async uri => { - if (matcher.isMatch(uri)) { - await workspace.fetchAndSet(uri); - } - }); - watcher?.onDidCreate(async uri => { - await matcher.refresh(); - if (matcher.isMatch(uri)) { - await workspace.fetchAndSet(uri); - } - }); - watcher?.onDidDelete(uri => { - workspace.delete(uri); - }); + for (const watcher of watchers) { + watcher?.onDidChange(async uri => { + if (matcher.isMatch(uri)) { + await workspace.fetchAndSet(uri); + } + }); + watcher?.onDidCreate(async uri => { + await matcher.refresh(); + if (matcher.isMatch(uri)) { + await workspace.fetchAndSet(uri); + } + }); + watcher?.onDidDelete(uri => { + workspace.delete(uri); + }); + } const foam: Foam = { workspace, diff --git a/packages/foam-vscode/src/extension.ts b/packages/foam-vscode/src/extension.ts index f27bdf604..001288431 100644 --- a/packages/foam-vscode/src/extension.ts +++ b/packages/foam-vscode/src/extension.ts @@ -1,6 +1,7 @@ /*global markdownit:readonly*/ import { workspace, ExtensionContext, window, commands } from 'vscode'; +import { externalGlobPattern } from './utils/vsc-utils'; import { MarkdownResourceProvider } from './core/services/markdown-provider'; import { bootstrap } from './core/model/foam'; import { Logger } from './core/utils/log'; @@ -11,6 +12,8 @@ import { getAttachmentsExtensions, getIgnoredFilesSetting, getNotesExtensions, + getExternalWatchPaths, + getWorkspaceType } from './settings'; import { AttachmentResourceProvider } from './core/services/attachment-provider'; import { VsCodeWatcher } from './services/watcher'; @@ -36,16 +39,31 @@ export async function activate(context: ExtensionContext) { const { matcher, dataStore, excludePatterns } = await createMatcherAndDataStore(excludes); - Logger.info('Loading from directories:'); - for (const folder of workspace.workspaceFolders) { - Logger.info('- ' + folder.uri.fsPath); - Logger.info(' Include: **/*'); - Logger.info(' Exclude: ' + excludePatterns.get(folder.name).join(',')); + let watchers: VsCodeWatcher[] = []; + + const workspaceType = getWorkspaceType(); + if(workspaceType == 'internal' || workspaceType == 'combined'){ + Logger.info('Loading from directories:'); + for (const folder of workspace.workspaceFolders) { + Logger.info('- ' + folder.uri.fsPath); + Logger.info(' Include: **/*'); + Logger.info(' Exclude: ' + excludePatterns.get(folder.name).join(',')); + } + + const watcher = new VsCodeWatcher( + workspace.createFileSystemWatcher('**/*') + ); + watchers.push(watcher); + } + if(workspaceType == 'external' || workspaceType == 'combined') { + for (const folder of getExternalWatchPaths()) { + const watcher = new VsCodeWatcher( + workspace.createFileSystemWatcher(externalGlobPattern(folder)) + ); + watchers.push(watcher); + } } - - const watcher = new VsCodeWatcher( - workspace.createFileSystemWatcher('**/*') - ); + const parserCache = new VsCodeBasedParserCache(context); const parser = createMarkdownParser([], parserCache); @@ -64,7 +82,7 @@ export async function activate(context: ExtensionContext) { const foamPromise = bootstrap( matcher, - watcher, + watchers, dataStore, parser, [markdownProvider, attachmentProvider], @@ -79,29 +97,33 @@ export async function activate(context: ExtensionContext) { const foam = await foamPromise; Logger.info(`Loaded ${foam.workspace.list().length} resources`); - context.subscriptions.push( - foam, - watcher, - markdownProvider, - 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 clearCacheRegCmdDisposable = commands.registerCommand('foam-vscode.clear-cache', () => + parserCache.clear() ); + const onDidChangeConfigDisposable = 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' + ); + } + }); + for (const watcher of watchers) { + context.subscriptions.push( + foam, + watcher, + markdownProvider, + attachmentProvider, + clearCacheRegCmdDisposable, + onDidChangeConfigDisposable + ); + } const feats = (await Promise.all(featuresPromises)).filter(r => r != null); diff --git a/packages/foam-vscode/src/services/editor.ts b/packages/foam-vscode/src/services/editor.ts index 7edf810b9..8aebbe833 100644 --- a/packages/foam-vscode/src/services/editor.ts +++ b/packages/foam-vscode/src/services/editor.ts @@ -14,6 +14,7 @@ import { WorkspaceEdit, MarkdownString, } from 'vscode'; +import { externalGlobPattern } from '../utils/vsc-utils'; import { getExcerpt, stripFrontMatter, stripImages } from '../core/utils/md'; import { isSome } from '../core/utils/core'; import { fromVsCodeUri, toVsCodeUri } from '../utils/vsc-utils'; @@ -25,6 +26,13 @@ import { IDataStore, IMatcher, } from '../core/services/datastore'; +import { + getExternalWatchPaths, + getExternalTemplatesRoot, + getWorkspaceType +} from '../settings'; +import { externalRelativePatternRootPath } from '../utils/vsc-utils'; + interface SelectionInfo { document: TextDocument; @@ -183,13 +191,27 @@ export function deleteFile(uri: URI) { * @param uri the uri to evaluate * @returns an absolute uri */ -export function asAbsoluteWorkspaceUri(uri: URI): URI { - if (workspace.workspaceFolders === undefined) { - throw new Error('An open folder or workspace is required'); +export function asAbsoluteWorkspaceUri(uri: URI): URI { + let folders: URI[] = []; + const workspaceType = getWorkspaceType(); + if(workspaceType == 'internal'){ + if (workspace.workspaceFolders === undefined) { + throw new Error('An open folder or workspace is required'); + } + folders = workspace.workspaceFolders.map(folder => + fromVsCodeUri(folder.uri) + ); + } else { + for (const folder of getExternalWatchPaths()) { + folders.push( + fromVsCodeUri( + Uri.file(externalRelativePatternRootPath(folder) + ) + ) + ); + } } - const folders = workspace.workspaceFolders.map(folder => - fromVsCodeUri(folder.uri) - ); + const res = asAbsoluteUri(uri, folders); return res; } @@ -218,17 +240,30 @@ export async function createMatcherAndDataStore(excludes: string[]): Promise<{ const listFiles = async () => { let files: Uri[] = []; - for (const folder of workspace.workspaceFolders) { - const uris = await workspace.findFiles( - new RelativePattern(folder.uri, '**/*'), - new RelativePattern( - folder.uri, - `{${excludePatterns.get(folder.name).join(',')}}` - ) - ); - files = [...files, ...uris]; - } + const workspaceType = getWorkspaceType(); + if(workspaceType == 'internal' || workspaceType == 'combined'){ + //--- collect files of internal VSCode workspace + for (const folder of workspace.workspaceFolders) { + const uris = await workspace.findFiles( + new RelativePattern(folder.uri, '**/*'), + new RelativePattern( + folder.uri, + `{${excludePatterns.get(folder.name).join(',')}}` + ) + ); + files = [...files, ...uris]; + } + } + if(workspaceType == 'external' || workspaceType == 'combined') { + //--- collect files of external foam workspace + for (const folder of getExternalWatchPaths()) { + const uris = await workspace.findFiles( + externalGlobPattern(folder) + ); + files = [...files, ...uris]; + } + } return files.map(fromVsCodeUri); }; diff --git a/packages/foam-vscode/src/services/templates.ts b/packages/foam-vscode/src/services/templates.ts index a590a1f1d..b4648ad62 100644 --- a/packages/foam-vscode/src/services/templates.ts +++ b/packages/foam-vscode/src/services/templates.ts @@ -6,8 +6,9 @@ import { commands, window, workspace, + Uri } from 'vscode'; -import { fromVsCodeUri, toVsCodeUri } from '../utils/vsc-utils'; +import { fromVsCodeUri, toVsCodeUri, externalGlobPattern } from '../utils/vsc-utils'; import { extractFoamTemplateFrontmatterMetadata } from '../utils/template-frontmatter-parser'; import { UserCancelledOperation } from './errors'; import { @@ -25,15 +26,28 @@ import { Resolver } from './variable-resolver'; import dateFormat from 'dateformat'; import { getFoamVsCodeConfig } from './config'; import { isNone } from '../core/utils'; +import { + getExternalTemplatesRoot, + getWorkspaceType +} from '../settings'; /** * The templates directory */ -export const getTemplatesDir = () => - fromVsCodeUri(workspace.workspaceFolders[0].uri).joinPath( - '.foam', - 'templates' - ); +export const getTemplatesDir = () => { + const workspaceType = getWorkspaceType(); + if(workspaceType == 'internal'){ + return fromVsCodeUri(workspace.workspaceFolders[0].uri).joinPath( + '.foam', + 'templates' + ); + } + const externalTemplatesRoot = getExternalTemplatesRoot() + if (externalTemplatesRoot == '') { + throw new Error(`Empty template root directory path in workspace mode "external"/"combined"`); + } + return fromVsCodeUri(Uri.file(getExternalTemplatesRoot())); +} /** * The URI of the default template @@ -77,9 +91,21 @@ export async function getTemplateMetadata( } export async function getTemplates(): Promise { - const templates = await workspace - .findFiles('.foam/templates/**.md', null) - .then(v => v.map(uri => fromVsCodeUri(uri))); + let templates: URI[] = []; + const workspaceType = getWorkspaceType(); + if(workspaceType == 'internal'){ + templates = await workspace + .findFiles('.foam/templates/**.md', null) + .then(v => v.map(uri => fromVsCodeUri(uri))); + return templates; + } + templates = await workspace + .findFiles( + externalGlobPattern( + getExternalTemplatesRoot()+'/**.md' + ), + null) + .then(v => v.map(uri => fromVsCodeUri(uri))); return templates; } diff --git a/packages/foam-vscode/src/settings.ts b/packages/foam-vscode/src/settings.ts index 2bfdddd71..54e18bef9 100644 --- a/packages/foam-vscode/src/settings.ts +++ b/packages/foam-vscode/src/settings.ts @@ -46,3 +46,19 @@ export function getIgnoredFilesSetting(): GlobPattern[] { ...Object.keys(workspace.getConfiguration().get('files.exclude', {})), ]; } + +/** Retrieve external (with respect to VSCode workspace) root paths for note file watching. */ +export function getExternalWatchPaths() { + return getFoamVsCodeConfig('files.externalWatchPaths', []); +} + +/** Retrieve external (with respect to VSCode workspace) root path for note templates. */ +export function getExternalTemplatesRoot() { + return getFoamVsCodeConfig('files.externalTemplatesRoot', ''); +} + +/** Retrieve external (with respect to VSCode workspace) root paths for file watching. */ +export function getWorkspaceType() { + return getFoamVsCodeConfig('files.workspaceType', 'internal'); +} + diff --git a/packages/foam-vscode/src/utils/vsc-utils.ts b/packages/foam-vscode/src/utils/vsc-utils.ts index fd184f6af..25c4212df 100644 --- a/packages/foam-vscode/src/utils/vsc-utils.ts +++ b/packages/foam-vscode/src/utils/vsc-utils.ts @@ -1,4 +1,4 @@ -import { Memento, Position, Range, Uri, commands } from 'vscode'; +import { Memento, Position, Range, RelativePattern, Uri, commands } from 'vscode'; import { Position as FoamPosition } from '../core/model/position'; import { Range as FoamRange } from '../core/model/range'; import { URI as FoamURI } from '../core/model/uri'; @@ -52,3 +52,29 @@ export class MapBasedMemento implements Memento { return Promise.resolve(); } } + +const externalRelativePatternExtract = (path: string): string[] => { + let rootPath: string = ""; + let relativeGlobPattern: string = ""; + if(!path.match(/^.*[\*\?\{\}\[\]\!].*$/)){ + rootPath = path; + relativeGlobPattern = "*.md"; + return [rootPath, relativeGlobPattern]; + } + const match = path.match(/^((?:[^\*\?\{\}\[\]\!]*\/)*)\s*(.*)$/); + if (!match) { + throw new Error(`Invalid glob pattern: ${path}`); + } + [, rootPath, relativeGlobPattern] = match; + return [rootPath, relativeGlobPattern]; +} + +export const externalGlobPattern = (path: string): RelativePattern => { + const [rootPath, relativeGlobPattern] = externalRelativePatternExtract(path); + return new RelativePattern(Uri.file(rootPath), relativeGlobPattern); +} + +export const externalRelativePatternRootPath = (path: string): string => { + const [rootPath] = externalRelativePatternExtract(path); + return rootPath; +} \ No newline at end of file From 91b22204d5c2bb6c3019fb2a2b0e1fc223d41df8 Mon Sep 17 00:00:00 2001 From: Konrad Schatz Date: Thu, 2 Jan 2025 01:34:58 +0100 Subject: [PATCH 3/5] - updated documentation --- docs/user/features/workspaces.md | 20 ++++++++++++++++++++ packages/foam-vscode/README.md | 4 ++++ packages/foam-vscode/package.json | 6 +++--- 3 files changed, 27 insertions(+), 3 deletions(-) create mode 100644 docs/user/features/workspaces.md diff --git a/docs/user/features/workspaces.md b/docs/user/features/workspaces.md new file mode 100644 index 000000000..81f5d2b7a --- /dev/null +++ b/docs/user/features/workspaces.md @@ -0,0 +1,20 @@ +# Workspaces + +It is possible to choose between two basic types of workspaces for the notes and templates: + + 1. **"internal" VSCode workspace:** Watch and access the note and template folders within "internal" workspace (exclusive VSCode workspace) + 2. **"external" workspace:** Watch and access the note and template folders within 'external' workspace ("external" with respect to VSCode workspace; INFO: this may include folders from the "internal" VSCode workspace) + +The switch may be done via the config. property `foam.files.workspaceType` with possible values `[ + "internal", + "external", + "combined" + ]`. + +This feature allows to decouple the essential foam workspace from the VSCode workspace and provides better flexibility and less workflow complexity, particularly when working with multiple VSCode workspaces and projects. + +The "external" and absolute note folder paths to be watched can be defined via the config. property +`foam.files.externalWatchPaths` with the path to be set within an array. +The external and absolute template root directory path may be define via th econfig. property `foam.files.externalTemplatesRoot` . + +**NOTE:** The "external" path definitions may be defined such that either only a respective root directory or, in addition, a glob pattern is provided. \ No newline at end of file diff --git a/packages/foam-vscode/README.md b/packages/foam-vscode/README.md index 504160929..01a755abb 100644 --- a/packages/foam-vscode/README.md +++ b/packages/foam-vscode/README.md @@ -128,6 +128,10 @@ With references you can also make your notes navigable both in GitHub UI as well - This becomes very powerful when combined with [note templates](https://foambubble.github.io/foam/user/features/note-templates) and the `Foam: Create New Note from Template` command - See your workspace as a connected graph with the `Foam: Show Graph` command +### Workspaces + +- Explore how to work with either the "internal" VSCode workspace or an "external" one: [workspaces](https://foambubble.github.io/foam/user/features/workspaces) . + ## Recipes People use Foam in different ways for different use cases, check out the [recipes](https://foambubble.github.io/foam/user/recipes/recipes) page for inspiration! diff --git a/packages/foam-vscode/package.json b/packages/foam-vscode/package.json index 3547a66bb..b3c9d0d76 100644 --- a/packages/foam-vscode/package.json +++ b/packages/foam-vscode/package.json @@ -528,15 +528,15 @@ "foam.files.workspaceType": { "type": "string", "default": "internal", - "description": "Specifies which 'workspaces' of note folders to watch", + "description": "Specifies the 'workspace' type", "enum": [ "internal", "external", "combined" ], "enumDescriptions": [ - "Watch note folders within 'internal' workspace (exclusive VSCode workspace)", - "Watch note folders within 'external' workspace ('external' with respect to VSCode workspace; INFO: this may include folders from 'internal' workspace)", + "Watch and access the note and template folders within 'internal' workspace (exclusive VSCode workspace)", + "Watch and access the note and template folders within 'external' workspace ('external' with respect to VSCode workspace; INFO: this may include folders from 'internal' workspace)", "POTENTIALLY DEPRECATED: Watch note folders of both, 'internal' and 'external' workspace" ] }, From 4a8785597dd62007c91956bf53f1c0be5950c5be Mon Sep 17 00:00:00 2001 From: Konrad Schatz Date: Thu, 2 Jan 2025 02:27:32 +0100 Subject: [PATCH 4/5] - updated readme --- readme.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/readme.md b/readme.md index 43f5bffec..e7e6408f9 100644 --- a/readme.md +++ b/readme.md @@ -132,6 +132,10 @@ With references you can also make your notes navigable both in GitHub UI as well - This becomes very powerful when combined with [note templates](https://foambubble.github.io/foam/user/features/note-templates) and the `Foam: Create New Note from Template` command - See your workspace as a connected graph with the `Foam: Show Graph` command +### Workspaces + +- Explore how to work with either the "internal" VSCode workspace or an "external" one: [workspaces](https://foambubble.github.io/foam/user/features/workspaces) . + ## Recipes People use Foam in different ways for different use cases, check out the [recipes](https://foambubble.github.io/foam/user/recipes/recipes) page for inspiration! From 1b09618bfd7ac99c9be45588abec89e7c893dfce Mon Sep 17 00:00:00 2001 From: Konrad Schatz Date: Fri, 3 Jan 2025 23:26:52 +0100 Subject: [PATCH 5/5] - fixed typos in doc. --- docs/user/features/workspaces.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/user/features/workspaces.md b/docs/user/features/workspaces.md index 81f5d2b7a..79dd3842d 100644 --- a/docs/user/features/workspaces.md +++ b/docs/user/features/workspaces.md @@ -3,7 +3,7 @@ It is possible to choose between two basic types of workspaces for the notes and templates: 1. **"internal" VSCode workspace:** Watch and access the note and template folders within "internal" workspace (exclusive VSCode workspace) - 2. **"external" workspace:** Watch and access the note and template folders within 'external' workspace ("external" with respect to VSCode workspace; INFO: this may include folders from the "internal" VSCode workspace) + 2. **"external" workspace:** Watch and access the note and template folders within "external" workspace ("external" with respect to VSCode workspace; INFO: this may include folders from the "internal" VSCode workspace) The switch may be done via the config. property `foam.files.workspaceType` with possible values `[ "internal", @@ -15,6 +15,6 @@ This feature allows to decouple the essential foam workspace from the VSCode wor The "external" and absolute note folder paths to be watched can be defined via the config. property `foam.files.externalWatchPaths` with the path to be set within an array. -The external and absolute template root directory path may be define via th econfig. property `foam.files.externalTemplatesRoot` . +The external and absolute template root directory path may be defined via the config. property `foam.files.externalTemplatesRoot` . -**NOTE:** The "external" path definitions may be defined such that either only a respective root directory or, in addition, a glob pattern is provided. \ No newline at end of file +**NOTE:** The "external" path definitions may be defined such that either only a respective root directory or, in addition, also a glob pattern is provided. \ No newline at end of file