diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 78870538..150f8eeb 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -20,7 +20,7 @@ jobs: uses: actions/setup-node@v4 with: node-version: '20' - - run: npm ci + - run: npm ci --foreground-scripts - run: npm run lint if: ${{ matrix.os == 'ubuntu-latest' }} - run: npm run build-web diff --git a/extension-browser.webpack.config.js b/extension-browser.webpack.config.js index 3a405db6..b26e21da 100644 --- a/extension-browser.webpack.config.js +++ b/extension-browser.webpack.config.js @@ -2,7 +2,7 @@ const path = require('path') const CopyWebpackPlugin = require('copy-webpack-plugin') -const { ProvidePlugin } = require('webpack') +const { ProvidePlugin, NormalModuleReplacementPlugin } = require('webpack') module.exports = { entry: { @@ -53,7 +53,7 @@ module.exports = { externals: { 'vscode': 'commonjs vscode', // ignored because it doesn't exist, 'applicationinsights-native-metrics': 'commonjs applicationinsights-native-metrics', // ignored because we don't ship native module - '@opentelemetry/tracing': 'commonjs @opentelemetry/tracing' // ignored because we don't ship this module + '@opentelemetry/tracing': 'commonjs @opentelemetry/tracing', // ignored because we don't ship this module }, performance: { hints: false @@ -69,6 +69,19 @@ module.exports = { // yes, really source maps devtool: 'nosources-source-map', plugins: [ + new NormalModuleReplacementPlugin(/antoraDocument$/, function ( + resource + ) { + // replaced because Antora cannot run in a web environment + resource.request = resource.request.replace( + /antoraDocument/, + `antoraDocumentBrowserShim` + ); + + if (resource.createData) { + resource.createData.request = resource.request; + } + }), new CopyWebpackPlugin({ patterns: [ { diff --git a/package-lock.json b/package-lock.json index 3e2a51cd..6406996f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,6 +13,7 @@ "@asciidoctor/core": "2.2.7", "@asciidoctor/docbook-converter": "2.0.0", "@orcid/bibtex-parse-js": "0.0.25", + "@vscode/ripgrep": "1.15.9", "asciidoctor-kroki": "0.18.1", "html-entities": "^2.4.0", "js-yaml": "^4.1.0", @@ -22,7 +23,6 @@ "util": "^0.12.5", "uuid": "8.3.2", "vscode-nls": "5.2.0", - "vscode-ripgrep": "^1.13.2", "vscode-uri": "^3.0.3", "worker-thread": "^1.1.0" }, @@ -31,7 +31,7 @@ "@fontsource/open-sans": "4.5.14", "@fontsource/roboto-mono": "4.5.10", "@highlightjs/cdn-assets": "~11.9.0", - "@playwright/test": "~1.44.0", + "@playwright/test": "^1.47.2", "@types/lodash.throttle": "~4.1", "@types/mocha": "~10.0", "@types/node": "~20.14", @@ -718,18 +718,18 @@ } }, "node_modules/@playwright/test": { - "version": "1.44.1", - "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.44.1.tgz", - "integrity": "sha512-1hZ4TNvD5z9VuhNJ/walIjvMVvYkZKf71axoF/uiAqpntQJXpG64dlXhoDXE3OczPuTuvjf/M5KWFg5VAVUS3Q==", + "version": "1.47.2", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.47.2.tgz", + "integrity": "sha512-jTXRsoSPONAs8Za9QEQdyjFn+0ZQFjCiIztAIF6bi1HqhBzG9Ma7g1WotyiGqFSBRZjIEqMdT8RUlbk1QVhzCQ==", "dev": true, "dependencies": { - "playwright": "1.44.1" + "playwright": "1.47.2" }, "bin": { "playwright": "cli.js" }, "engines": { - "node": ">=16" + "node": ">=18" } }, "node_modules/@sinonjs/commons": { @@ -1122,6 +1122,17 @@ "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==", "dev": true }, + "node_modules/@vscode/ripgrep": { + "version": "1.15.9", + "resolved": "https://registry.npmjs.org/@vscode/ripgrep/-/ripgrep-1.15.9.tgz", + "integrity": "sha512-4q2PXRvUvr3bF+LsfrifmUZgSPmCNcUZo6SbEAZgArIChchkezaxLoIeQMJe/z3CCKStvaVKpBXLxN3Z8lQjFQ==", + "hasInstallScript": true, + "dependencies": { + "https-proxy-agent": "^7.0.2", + "proxy-from-env": "^1.1.0", + "yauzl": "^2.9.2" + } + }, "node_modules/@vscode/test-electron": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/@vscode/test-electron/-/test-electron-2.4.0.tgz", @@ -1553,7 +1564,6 @@ "version": "7.1.1", "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.1.tgz", "integrity": "sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==", - "dev": true, "dependencies": { "debug": "^4.3.4" }, @@ -2476,7 +2486,6 @@ "version": "0.2.13", "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", - "dev": true, "engines": { "node": "*" } @@ -5254,7 +5263,6 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", "integrity": "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==", - "dev": true, "dependencies": { "pend": "~1.2.0" } @@ -6429,7 +6437,6 @@ "version": "7.0.4", "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.4.tgz", "integrity": "sha512-wlwpilI7YdjSkWaQ/7omYBMTliDcmCN8OLihO6I9B86g06lMyAoqgoDpV0XqoaPOKj+0DIdAvnsWfyAAhmimcg==", - "dev": true, "dependencies": { "agent-base": "^7.0.2", "debug": "4" @@ -10163,8 +10170,7 @@ "node_modules/pend": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", - "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==", - "dev": true + "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==" }, "node_modules/picocolors": { "version": "1.0.1", @@ -10430,33 +10436,33 @@ } }, "node_modules/playwright": { - "version": "1.44.1", - "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.44.1.tgz", - "integrity": "sha512-qr/0UJ5CFAtloI3avF95Y0L1xQo6r3LQArLIg/z/PoGJ6xa+EwzrwO5lpNr/09STxdHuUoP2mvuELJS+hLdtgg==", + "version": "1.47.2", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.47.2.tgz", + "integrity": "sha512-nx1cLMmQWqmA3UsnjaaokyoUpdVaaDhJhMoxX2qj3McpjnsqFHs516QAKYhqHAgOP+oCFTEOCOAaD1RgD/RQfA==", "dev": true, "dependencies": { - "playwright-core": "1.44.1" + "playwright-core": "1.47.2" }, "bin": { "playwright": "cli.js" }, "engines": { - "node": ">=16" + "node": ">=18" }, "optionalDependencies": { "fsevents": "2.3.2" } }, "node_modules/playwright-core": { - "version": "1.44.1", - "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.44.1.tgz", - "integrity": "sha512-wh0JWtYTrhv1+OSsLPgFzGzt67Y7BE/ZS3jEqgGBlp2ppp1ZDj8c+9IARNW4dwf1poq5MgHreEM2KV/GuR4cFA==", + "version": "1.47.2", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.47.2.tgz", + "integrity": "sha512-3JvMfF+9LJfe16l7AbSmU555PaTl2tPyQsVInqm3id16pdDfvZ8TTZ/pyzmkbDrZTQefyzU7AIHlZqQnxpqHVQ==", "dev": true, "bin": { "playwright-core": "cli.js" }, "engines": { - "node": ">=16" + "node": ">=18" } }, "node_modules/playwright/node_modules/fsevents": { @@ -13422,37 +13428,6 @@ "node": ">=0.10.0" } }, - "node_modules/vscode-ripgrep": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/vscode-ripgrep/-/vscode-ripgrep-1.13.2.tgz", - "integrity": "sha512-RlK9U87EokgHfiOjDQ38ipQQX936gWOcWPQaJpYf+kAkz1PQ1pK2n7nhiscdOmLu6XGjTs7pWFJ/ckonpN7twQ==", - "deprecated": "This package has been renamed to @vscode/ripgrep, please update to the new name", - "hasInstallScript": true, - "dependencies": { - "https-proxy-agent": "^4.0.0", - "proxy-from-env": "^1.1.0" - } - }, - "node_modules/vscode-ripgrep/node_modules/agent-base": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-5.1.1.tgz", - "integrity": "sha512-TMeqbNl2fMW0nMjTEPOwe3J/PRFP4vqeoNuQMG0HlMrtm5QxKqdvAkZ1pRBQ/ulIyDD5Yq0nJ7YbdD8ey0TO3g==", - "engines": { - "node": ">= 6.0.0" - } - }, - "node_modules/vscode-ripgrep/node_modules/https-proxy-agent": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-4.0.0.tgz", - "integrity": "sha512-zoDhWrkR3of1l9QAL8/scJZyLu8j/gBkcwcaQOZh7Gyh/+uJQzGVETdgT30akuwkpL8HTRfssqI3BZuV18teDg==", - "dependencies": { - "agent-base": "5", - "debug": "4" - }, - "engines": { - "node": ">= 6.0.0" - } - }, "node_modules/vscode-uri": { "version": "3.0.8", "resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-3.0.8.tgz", @@ -13943,7 +13918,6 @@ "version": "2.10.0", "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", "integrity": "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==", - "dev": true, "dependencies": { "buffer-crc32": "~0.2.3", "fd-slicer": "~1.1.0" diff --git a/package.json b/package.json index f05ae761..f724bcb5 100644 --- a/package.json +++ b/package.json @@ -637,7 +637,7 @@ "@fontsource/open-sans": "4.5.14", "@fontsource/roboto-mono": "4.5.10", "@highlightjs/cdn-assets": "~11.9.0", - "@playwright/test": "~1.44.0", + "@playwright/test": "^1.47.2", "@types/lodash.throttle": "~4.1", "@types/mocha": "~10.0", "@types/node": "~20.14", @@ -677,6 +677,7 @@ "@asciidoctor/core": "2.2.7", "@asciidoctor/docbook-converter": "2.0.0", "@orcid/bibtex-parse-js": "0.0.25", + "@vscode/ripgrep": "1.15.9", "asciidoctor-kroki": "0.18.1", "html-entities": "^2.4.0", "js-yaml": "^4.1.0", @@ -686,7 +687,6 @@ "util": "^0.12.5", "uuid": "8.3.2", "vscode-nls": "5.2.0", - "vscode-ripgrep": "^1.13.2", "vscode-uri": "^3.0.3", "worker-thread": "^1.1.0" }, diff --git a/src/asciidocEngine.ts b/src/asciidocEngine.ts index e9492842..d4fb402a 100644 --- a/src/asciidocEngine.ts +++ b/src/asciidocEngine.ts @@ -5,7 +5,8 @@ import { ExtensionContentSecurityPolicyArbiter } from './security' import { AsciidocPreviewConfigurationManager } from './features/previewConfig' import { SkinnyTextDocument } from './util/document' import { AsciidocContributionProvider } from './asciidocExtensions' -import { AntoraSupportManager, getAntoraDocumentContext, getAntoraConfig } from './features/antora/antoraSupport' +import { getAntoraDocumentContext, getAntoraConfig } from './features/antora/antoraDocument' +import { AntoraSupportManager } from './features/antora/antoraContext' import { WebviewResourceProvider } from './util/resources' import { AsciidoctorConfigProvider } from './features/asciidoctorConfig' import { AsciidocTextDocument } from './asciidocTextDocument' diff --git a/src/asciidocLoader.ts b/src/asciidocLoader.ts index d0dcefec..8b46442f 100644 --- a/src/asciidocLoader.ts +++ b/src/asciidocLoader.ts @@ -8,7 +8,7 @@ import { SkinnyTextDocument } from './util/document' import { AsciidoctorAttributesConfig } from './features/asciidoctorAttributesConfig' import { AsciidoctorDiagnosticProvider } from './features/asciidoctorDiagnostic' import { AsciidoctorIncludeItemsProvider, IncludeItems } from './features/asciidoctorIncludeItems' -import { getAntoraDocumentContext, getAntoraConfig } from './features/antora/antoraSupport' +import { getAntoraDocumentContext, getAntoraConfig } from './features/antora/antoraDocument' import { IncludeProcessor } from './features/antora/includeProcessor' import { resolveIncludeFile } from './features/antora/resolveIncludeFile' diff --git a/src/asciidoctorWebViewConverter.ts b/src/asciidoctorWebViewConverter.ts index 569cbd70..89a4d67b 100644 --- a/src/asciidoctorWebViewConverter.ts +++ b/src/asciidoctorWebViewConverter.ts @@ -7,7 +7,7 @@ import { Asciidoctor } from '@asciidoctor/core' import { SkinnyTextDocument } from './util/document' import * as nls from 'vscode-nls' import { AsciidocContributions } from './asciidocExtensions' -import { AntoraDocumentContext } from './features/antora/antoraSupport' +import { AntoraDocumentContext } from './features/antora/antoraContext' import { getWorkspaceFolder } from './util/workspace' const localize = nls.loadMessageBundle() diff --git a/src/extension.ts b/src/extension.ts index cc21855f..3335120b 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -15,7 +15,7 @@ import { AsciidocTargetPathAutoCompletionMonitor } from './util/includeAutoCompl import { AttributeReferenceProvider } from './features/attributeReferenceProvider' import { BuiltinDocumentAttributeProvider } from './features/builtinDocumentAttributeProvider' import AsciidocFoldingRangeProvider from './features/foldingProvider' -import { AntoraSupportManager } from './features/antora/antoraSupport' +import { AntoraSupportManager } from './features/antora/antoraContext' import { DropImageIntoEditorProvider } from './features/dropIntoEditor' import { AsciidoctorConfig } from './features/asciidoctorConfig' import { AsciidoctorExtensions } from './features/asciidoctorExtensions' diff --git a/src/features/antora/antoraCompletionProvider.ts b/src/features/antora/antoraCompletionProvider.ts index 4afcf235..b0713e01 100644 --- a/src/features/antora/antoraCompletionProvider.ts +++ b/src/features/antora/antoraCompletionProvider.ts @@ -1,5 +1,5 @@ import * as vscode from 'vscode' -import { getAttributes } from './antoraSupport' +import { getAttributes } from './antoraDocument' export default class AntoraCompletionProvider { async provideCompletionItems (textDocument: vscode.TextDocument, position: vscode.Position): Promise { diff --git a/src/features/antora/antoraContext.ts b/src/features/antora/antoraContext.ts new file mode 100644 index 00000000..0044e907 --- /dev/null +++ b/src/features/antora/antoraContext.ts @@ -0,0 +1,171 @@ +import vscode, { Memento, Uri } from 'vscode' +import ospath from 'path' +import AntoraCompletionProvider from './antoraCompletionProvider' +import { disposeAll } from '../../util/dispose' +import * as nls from 'vscode-nls' +import { antoraConfigFileExists, getAntoraConfig, getAttributes } from './antoraDocument' + +const localize = nls.loadMessageBundle() + +export interface AntoraResourceContext { + component: string; + version: string; + module: string; +} + +export class AntoraConfig { + public contentSourceRootPath: string + public contentSourceRootFsPath: string + + private static versionMap = new Map() + + constructor (public uri: vscode.Uri, public config: { [key: string]: any }) { + const path = uri.path + this.contentSourceRootPath = path.slice(0, path.lastIndexOf('/')) + this.contentSourceRootFsPath = ospath.dirname(uri.fsPath) + if (config.version === true || config.version === undefined) { + config.version = this.getVersionForPath(path) + } + } + + public getVersionForPath (path: string): string { + const version = AntoraConfig.versionMap.get(path) + if (version) return `V-${version}` + + const nextVersion = AntoraConfig.versionMap.size + 1 + AntoraConfig.versionMap.set(path, nextVersion) + return `V-${nextVersion}` + } +} + +export class AntoraDocumentContext { + private PERMITTED_FAMILIES = ['attachment', 'example', 'image', 'page', 'partial'] + + constructor (private antoraContext: AntoraContext, public resourceContext: AntoraResourceContext) { + } + + public resolveAntoraResourceIds (id: string, defaultFamily: string): string | undefined { + const resource = this.antoraContext.contentCatalog.resolveResource(id, this.resourceContext, defaultFamily, this.PERMITTED_FAMILIES) + if (resource) { + return resource.src?.abspath + } + return undefined + } + + public getComponents () { + return this.antoraContext.contentCatalog.getComponents() + } + + public getImages () { + return this.antoraContext.contentCatalog.findBy({ family: 'image' }) + } + + public getContentCatalog () { + return this.antoraContext.contentCatalog + } +} + +export class AntoraContext { + constructor (public contentCatalog) { + } + + public async getResource (textDocumentUri: Uri): Promise { + const antoraConfig = await getAntoraConfig(textDocumentUri) + if (antoraConfig === undefined) { + return undefined + } + const contentSourceRootPath = antoraConfig.contentSourceRootFsPath + const config = antoraConfig.config + if (config.name === undefined) { + return undefined + } + const page = this.contentCatalog.getByPath({ + component: config.name, + version: config.version, + // Vinyl will normalize path to system dependent path :( + path: ospath.relative(contentSourceRootPath, textDocumentUri.fsPath), + }) + if (page === undefined) { + return undefined + } + return page.src + } +} + +export class AntoraSupportManager implements vscode.Disposable { + // eslint-disable-next-line no-use-before-define + private static instance: AntoraSupportManager + private static workspaceState: Memento + private readonly _disposables: vscode.Disposable[] = [] + + private constructor () { + } + + public static getInstance (workspaceState: Memento) { + if (AntoraSupportManager.instance) { + AntoraSupportManager.workspaceState = workspaceState + return AntoraSupportManager.instance + } + AntoraSupportManager.instance = new AntoraSupportManager() + AntoraSupportManager.workspaceState = workspaceState + // look for Antora support setting in workspace state + const isEnableAntoraSupportSettingDefined = workspaceState.get('antoraSupportSetting') + if (isEnableAntoraSupportSettingDefined === true) { + AntoraSupportManager.instance.registerFeatures() + } else if (isEnableAntoraSupportSettingDefined === undefined) { + // choice has not been made + const onDidOpenAsciiDocFileAskAntoraSupport = vscode.workspace.onDidOpenTextDocument(async (textDocument) => { + if (await antoraConfigFileExists(textDocument.uri)) { + const yesAnswer = localize('antora.activateSupport.yes', 'Yes') + const noAnswer = localize('antora.activateSupport.no', 'No, thanks') + const answer = await vscode.window.showInformationMessage( + localize('antora.activateSupport.message', 'We detect that you are working with Antora. Do you want to activate Antora support?'), + yesAnswer, + noAnswer + ) + const enableAntoraSupport = answer === yesAnswer + await workspaceState.update('antoraSupportSetting', enableAntoraSupport) + if (enableAntoraSupport) { + AntoraSupportManager.instance.registerFeatures() + } + // do not ask again to avoid bothering users + onDidOpenAsciiDocFileAskAntoraSupport.dispose() + } + }) + AntoraSupportManager.instance._disposables.push(onDidOpenAsciiDocFileAskAntoraSupport) + } + } + + public async getAttributes (textDocumentUri: Uri): Promise<{ [key: string]: string }> { + const antoraEnabled = this.isEnabled() + if (antoraEnabled) { + return getAttributes(textDocumentUri) + } + return {} + } + + public isEnabled (): Boolean { + // look for Antora support setting in workspace state + const isEnableAntoraSupportSettingDefined = AntoraSupportManager.workspaceState.get('antoraSupportSetting') + if (isEnableAntoraSupportSettingDefined === true) { + return true + } + // choice has not been made or Antora is explicitly disabled + return false + } + + private registerFeatures (): void { + const attributesCompletionProvider = vscode.languages.registerCompletionItemProvider({ + language: 'asciidoc', + scheme: 'file', + }, + new AntoraCompletionProvider(), + '{' + ) + this._disposables.push(attributesCompletionProvider) + } + + public dispose (): void { + disposeAll(this._disposables) + } +} diff --git a/src/features/antora/antoraSupport.ts b/src/features/antora/antoraDocument.ts similarity index 52% rename from src/features/antora/antoraSupport.ts rename to src/features/antora/antoraDocument.ts index d17988d5..df20fb35 100644 --- a/src/features/antora/antoraSupport.ts +++ b/src/features/antora/antoraDocument.ts @@ -1,182 +1,15 @@ import vscode, { CancellationTokenSource, FileType, Memento, Uri } from 'vscode' import fs from 'fs' import yaml from 'js-yaml' -import ospath, { posix as posixpath } from 'path' -import AntoraCompletionProvider from './antoraCompletionProvider' -import { disposeAll } from '../../util/dispose' -import * as nls from 'vscode-nls' -import ContentCatalog from '@antora/content-classifier/content-catalog' +import { posix as posixpath } from 'path' import { getWorkspaceFolder } from '../../util/workspace' import { dir, exists } from '../../util/file' import * as contentClassifier from '@antora/content-classifier' -import { wrappedFindFiles } from '../../util/wrappedFindFiles' +import { findFiles } from '../../util/findFiles' +import { AntoraConfig, AntoraContext, AntoraDocumentContext, AntoraSupportManager } from './antoraContext' const classifyContent = contentClassifier.default || contentClassifier const MAX_DEPTH_SEARCH_ANTORA_CONFIG = 100 -const localize = nls.loadMessageBundle() - -export interface AntoraResourceContext { - component: string; - version: string; - module: string; -} - -export class AntoraConfig { - public contentSourceRootPath: string - public contentSourceRootFsPath: string - - private static versionMap = new Map() - - constructor (public uri: vscode.Uri, public config: { [key: string]: any }) { - const path = uri.path - this.contentSourceRootPath = path.slice(0, path.lastIndexOf('/')) - this.contentSourceRootFsPath = ospath.dirname(uri.fsPath) - if (config.version === true || config.version === undefined) { - config.version = this.getVersionForPath(path) - } - } - - public getVersionForPath (path: string): string { - const version = AntoraConfig.versionMap.get(path) - if (version) return `V-${version}` - - const nextVersion = AntoraConfig.versionMap.size + 1 - AntoraConfig.versionMap.set(path, nextVersion) - return `V-${nextVersion}` - } -} - -export class AntoraDocumentContext { - private PERMITTED_FAMILIES = ['attachment', 'example', 'image', 'page', 'partial'] - - constructor (private antoraContext: AntoraContext, public resourceContext: AntoraResourceContext) { - } - - public resolveAntoraResourceIds (id: string, defaultFamily: string): string | undefined { - const resource = this.antoraContext.contentCatalog.resolveResource(id, this.resourceContext, defaultFamily, this.PERMITTED_FAMILIES) - if (resource) { - return resource.src?.abspath - } - return undefined - } - - public getComponents () { - return this.antoraContext.contentCatalog.getComponents() - } - - public getImages () { - return this.antoraContext.contentCatalog.findBy({ family: 'image' }) - } - - public getContentCatalog () { - return this.antoraContext.contentCatalog - } -} - -export class AntoraContext { - constructor (public contentCatalog: ContentCatalog) { - } - - public async getResource (textDocumentUri: Uri): Promise { - const antoraConfig = await getAntoraConfig(textDocumentUri) - if (antoraConfig === undefined) { - return undefined - } - const contentSourceRootPath = antoraConfig.contentSourceRootFsPath - const config = antoraConfig.config - if (config.name === undefined) { - return undefined - } - const page = this.contentCatalog.getByPath({ - component: config.name, - version: config.version, - // Vinyl will normalize path to system dependent path :( - path: ospath.relative(contentSourceRootPath, textDocumentUri.fsPath), - }) - if (page === undefined) { - return undefined - } - return page.src - } -} - -export class AntoraSupportManager implements vscode.Disposable { - // eslint-disable-next-line no-use-before-define - private static instance: AntoraSupportManager - private static workspaceState: Memento - private readonly _disposables: vscode.Disposable[] = [] - - private constructor () { - } - - public static getInstance (workspaceState: Memento) { - if (AntoraSupportManager.instance) { - AntoraSupportManager.workspaceState = workspaceState - return AntoraSupportManager.instance - } - AntoraSupportManager.instance = new AntoraSupportManager() - AntoraSupportManager.workspaceState = workspaceState - // look for Antora support setting in workspace state - const isEnableAntoraSupportSettingDefined = workspaceState.get('antoraSupportSetting') - if (isEnableAntoraSupportSettingDefined === true) { - AntoraSupportManager.instance.registerFeatures() - } else if (isEnableAntoraSupportSettingDefined === undefined) { - // choice has not been made - const onDidOpenAsciiDocFileAskAntoraSupport = vscode.workspace.onDidOpenTextDocument(async (textDocument) => { - if (await antoraConfigFileExists(textDocument.uri)) { - const yesAnswer = localize('antora.activateSupport.yes', 'Yes') - const noAnswer = localize('antora.activateSupport.no', 'No, thanks') - const answer = await vscode.window.showInformationMessage( - localize('antora.activateSupport.message', 'We detect that you are working with Antora. Do you want to activate Antora support?'), - yesAnswer, - noAnswer - ) - const enableAntoraSupport = answer === yesAnswer - await workspaceState.update('antoraSupportSetting', enableAntoraSupport) - if (enableAntoraSupport) { - AntoraSupportManager.instance.registerFeatures() - } - // do not ask again to avoid bothering users - onDidOpenAsciiDocFileAskAntoraSupport.dispose() - } - }) - AntoraSupportManager.instance._disposables.push(onDidOpenAsciiDocFileAskAntoraSupport) - } - } - - public async getAttributes (textDocumentUri: Uri): Promise<{ [key: string]: string }> { - const antoraEnabled = this.isEnabled() - if (antoraEnabled) { - return getAttributes(textDocumentUri) - } - return {} - } - - public isEnabled (): Boolean { - // look for Antora support setting in workspace state - const isEnableAntoraSupportSettingDefined = AntoraSupportManager.workspaceState.get('antoraSupportSetting') - if (isEnableAntoraSupportSettingDefined === true) { - return true - } - // choice has not been made or Antora is explicitly disabled - return false - } - - private registerFeatures (): void { - const attributesCompletionProvider = vscode.languages.registerCompletionItemProvider({ - language: 'asciidoc', - scheme: 'file', - }, - new AntoraCompletionProvider(), - '{' - ) - this._disposables.push(attributesCompletionProvider) - } - - public dispose (): void { - disposeAll(this._disposables) - } -} export async function findAntoraConfigFile (textDocumentUri: Uri): Promise { const asciidocFilePath = posixpath.normalize(textDocumentUri.path) @@ -184,7 +17,7 @@ export async function findAntoraConfigFile (textDocumentUri: Uri): Promise { console.log('Cancellation requested, cause: ' + e) }) - const antoraConfigUris = await wrappedFindFiles('**/antora.yml') + const antoraConfigUris = await findFiles('**/antora.yml') // check for Antora configuration for (const antoraConfigUri of antoraConfigUris) { const antoraConfigParentDirPath = antoraConfigUri.path.slice(0, antoraConfigUri.path.lastIndexOf('/')) @@ -225,12 +58,12 @@ export async function antoraConfigFileExists (textDocumentUri: Uri): Promise { +async function getAntoraConfigs (): Promise { const cancellationToken = new CancellationTokenSource() cancellationToken.token.onCancellationRequested((e) => { console.log('Cancellation requested, cause: ' + e) }) - const antoraConfigUris = await wrappedFindFiles('**/antora.yml') + const antoraConfigUris = await findFiles('**/antora.yml') // check for Antora configuration const antoraConfigs = await Promise.all(antoraConfigUris.map(async (antoraConfigUri) => { let config = {} @@ -285,7 +118,7 @@ export async function getAntoraDocumentContext (textDocumentUri: Uri, workspaceS const workspaceFolder = getWorkspaceFolder(antoraConfig.uri) const workspaceRelative = posixpath.relative(workspaceFolder.uri.path, antoraConfig.contentSourceRootPath) const globPattern = 'modules/*/{attachments,examples,images,pages,partials,assets}/**' - const files = await Promise.all((await wrappedFindFiles(`${workspaceRelative ? `${workspaceRelative}/` : ''}${globPattern}`)).map(async (file) => { + const files = await Promise.all((await findFiles(`${workspaceRelative ? `${workspaceRelative}/` : ''}${globPattern}`)).map(async (file) => { const contentSourceRootPath = antoraConfig.contentSourceRootPath return { base: contentSourceRootPath, diff --git a/src/features/antora/antoraDocumentBrowserShim.ts b/src/features/antora/antoraDocumentBrowserShim.ts new file mode 100644 index 00000000..ab889d43 --- /dev/null +++ b/src/features/antora/antoraDocumentBrowserShim.ts @@ -0,0 +1,22 @@ +import { Memento, Uri } from 'vscode' +import { AntoraConfig, AntoraDocumentContext } from './antoraContext' + +export async function findAntoraConfigFile (_: Uri): Promise { + return undefined +} + +export async function antoraConfigFileExists (_: Uri): Promise { + return false +} + +export async function getAntoraConfig (textDocumentUri: Uri): Promise { + return new AntoraConfig(textDocumentUri, {}) +} + +export async function getAttributes (_: Uri): Promise<{ [key: string]: string }> { + return {} +} + +export async function getAntoraDocumentContext (_: Uri, __: Memento): Promise { + return undefined +} diff --git a/src/features/asciidoctorExtensions.ts b/src/features/asciidoctorExtensions.ts index 9f331306..b24bc46d 100644 --- a/src/features/asciidoctorExtensions.ts +++ b/src/features/asciidoctorExtensions.ts @@ -2,7 +2,7 @@ import vscode from 'vscode' import { AsciidoctorExtensionsSecurityPolicyArbiter } from '../security' import { Asciidoctor } from '@asciidoctor/core' import { mermaidJSProcessor } from './mermaid' -import { wrappedFindFiles } from '../util/wrappedFindFiles' +import { findFiles } from '../util/findFiles' export interface AsciidoctorExtensionsProvider { activate(registry: Asciidoctor.Extensions.Registry): Promise; @@ -39,7 +39,7 @@ export class AsciidoctorExtensions { } private async getExtensionFilesInWorkspace (): Promise { - return wrappedFindFiles('.asciidoctor/lib/**/*.js') + return findFiles('.asciidoctor/lib/**/*.js') } private isAsciidoctorExtensionsRegistrationEnabled (): boolean { diff --git a/src/features/workspaceSymbolProvider.ts b/src/features/workspaceSymbolProvider.ts index 032d8e05..fe0971fc 100644 --- a/src/features/workspaceSymbolProvider.ts +++ b/src/features/workspaceSymbolProvider.ts @@ -8,7 +8,7 @@ import { isAsciidocFile } from '../util/file' import { Lazy, lazy } from '../util/lazy' import AdocDocumentSymbolProvider from './documentSymbolProvider' import { SkinnyTextDocument } from '../util/document' -import { wrappedFindFiles } from '../util/wrappedFindFiles' +import { findFiles } from '../util/findFiles' export interface WorkspaceAsciidocDocumentProvider { getAllAsciidocDocuments(): Promise>; @@ -38,7 +38,7 @@ class VSCodeWorkspaceAsciidocDocumentProvider implements WorkspaceAsciidocDocume } async getAllAsciidocDocuments () { - const resources = await wrappedFindFiles('**/*.adoc') + const resources = await findFiles('**/*.adoc') const docs = await Promise.all(resources.map((doc) => this.getAsciidocDocument(doc))) return docs.filter((doc) => !!doc) as SkinnyTextDocument[] } diff --git a/src/providers/bibtex.provider.ts b/src/providers/bibtex.provider.ts index 443d8165..ce30fba2 100644 --- a/src/providers/bibtex.provider.ts +++ b/src/providers/bibtex.provider.ts @@ -1,7 +1,7 @@ import * as vscode from 'vscode' import { createContext, Context } from './createContext' import { readFileSync } from 'fs' -import { wrappedFindFiles } from '../util/wrappedFindFiles' +import { findFiles } from '../util/findFiles' const bibtexParse = require('@orcid/bibtex-parse-js') export const BibtexProvider = { @@ -32,7 +32,7 @@ function shouldProvide (context: Context): boolean { } async function getCitationKeys (): Promise { - const files = await wrappedFindFiles('*.bib') + const files = await findFiles('*.bib') const filesContent = files.map((file) => readFileSync(file.path).toString('utf-8') ) diff --git a/src/providers/xref.provider.ts b/src/providers/xref.provider.ts index ed7e73bb..06494f1d 100644 --- a/src/providers/xref.provider.ts +++ b/src/providers/xref.provider.ts @@ -1,7 +1,7 @@ import * as path from 'path' import * as vscode from 'vscode' import { createContext, Context } from './createContext' -import { wrappedFindFiles } from '../util/wrappedFindFiles' +import { findFiles } from '../util/findFiles' export const xrefProvider = { provideCompletionItems, @@ -84,7 +84,7 @@ async function provideCrossRef (context: Context): Promise { + if ('browser' in process && (process as any).browser === true) { + return vscode.workspace.findFiles(glob) + } + const searchedUris : Uri[] = [] + for (const workspaceFolder of getWorkspaceFolders()) { + const rootUri = workspaceFolder.uri + const paths = await ripgrep(glob, rootUri.fsPath) + searchedUris.push(...paths.map((path) => Uri.joinPath(rootUri, path))) + } + return searchedUris +} + async function ripgrep (glob: string, rootFolder: string): Promise { return new Promise((resolve, reject) => { const rg = spawn(rgPath, ['--hidden', '--follow', '--files', '-g', glob], { cwd: rootFolder }) @@ -35,32 +53,3 @@ async function ripgrep (glob: string, rootFolder: string): Promise { }) }) } - -async function internalWrappedFindFiles (glob: string): Promise { - // const uris = await vscode.workspace.findFiles(glob, undefined, 100, token) - const searchedUris : Uri[] = [] - - for (const workspaceFolder of getWorkspaceFolders()) { - const rootUri = workspaceFolder.uri - const paths = await ripgrep(glob, rootUri.path) - searchedUris.push(...paths.map((path) => Uri.joinPath(rootUri, path))) - } - return searchedUris -} - -const cache: Map = new Map() - -function isCacheValid (timestamp: number): boolean { - return (Date.now() - timestamp) < 5000 -} - -export async function wrappedFindFiles (glob: string): Promise { - const cacheEntry = cache.get(glob) - if (cacheEntry && isCacheValid(cacheEntry.timestamp)) { - return cacheEntry.uris - } - - const uris = await internalWrappedFindFiles(glob) - cache.set(glob, { timestamp: Date.now(), uris }) - return uris -}