diff --git a/src/asciidocEngine.ts b/src/asciidocEngine.ts
index d4fb402a..fb6a8ad4 100644
--- a/src/asciidocEngine.ts
+++ b/src/asciidocEngine.ts
@@ -24,21 +24,12 @@ export type AsciidoctorBuiltInBackends = 'html5' | 'docbook5'
const previewConfigurationManager = new AsciidocPreviewConfigurationManager()
export class AsciidocEngine {
- private stylesdir: string
-
constructor (
readonly contributionProvider: AsciidocContributionProvider,
readonly asciidoctorConfigProvider: AsciidoctorConfigProvider,
readonly asciidoctorExtensionsProvider: AsciidoctorExtensionsProvider,
readonly asciidoctorDiagnosticProvider: AsciidoctorDiagnosticProvider
) {
- // Asciidoctor.js in the browser environment works with URIs however for desktop clients
- // the "stylesdir" attribute is expected to look like a file system path (especially on Windows)
- if ('browser' in process && (process as any).browser === true) {
- this.stylesdir = vscode.Uri.joinPath(contributionProvider.extensionUri, 'media').toString()
- } else {
- this.stylesdir = vscode.Uri.joinPath(contributionProvider.extensionUri, 'media').fsPath
- }
}
// Export
@@ -59,7 +50,7 @@ export class AsciidocEngine {
await this.asciidoctorConfigProvider.activate(registry, textDocumentUri)
asciidoctorProcessor.restoreBuiltInSyntaxHighlighter()
- const baseDir = AsciidocTextDocument.fromTextDocument(textDocument).getBaseDir()
+ const baseDir = AsciidocTextDocument.fromTextDocument(textDocument).baseDir
const options: { [key: string]: any } = {
attributes: {
'env-vscode': '',
@@ -92,10 +83,16 @@ export class AsciidocEngine {
context: vscode.ExtensionContext,
editor: WebviewResourceProvider,
line?: number
- ): Promise<{html: string, document?: Asciidoctor.Document}> {
+ ): Promise<{ html: string, document?: Asciidoctor.Document }> {
const textDocument = await vscode.workspace.openTextDocument(documentUri)
- const { html, document } = await this.convertFromTextDocument(textDocument, context, editor, line)
- return { html, document }
+ const {
+ html,
+ document,
+ } = await this.convertFromTextDocument(textDocument, context, editor, line)
+ return {
+ html,
+ document,
+ }
}
public async convertFromTextDocument (
@@ -112,7 +109,10 @@ export class AsciidocEngine {
// load the Asciidoc header only to get kroki-server-url attribute
const text = textDocument.getText()
const attributes = AsciidoctorAttributesConfig.getPreviewAttributes()
- const document = processor.load(text, { attributes, header_only: true })
+ const document = processor.load(text, {
+ attributes,
+ header_only: true,
+ })
const isRougeSourceHighlighterEnabled = document.isAttribute('source-highlighter', 'rouge')
if (isRougeSourceHighlighterEnabled) {
// Force the source highlighter to Highlight.js (since Rouge is not supported)
@@ -150,8 +150,7 @@ export class AsciidocEngine {
cursor,
antoraDocumentContext.getContentCatalog(),
antoraConfig
- )
- ))
+ )))
}
if (context && editor) {
highlightjsAdapter.register(asciidoctorProcessor.highlightjsBuiltInSyntaxHighlighter, context, editor)
@@ -160,12 +159,26 @@ export class AsciidocEngine {
}
const antoraSupport = AntoraSupportManager.getInstance(context.workspaceState)
const antoraAttributes = await antoraSupport.getAttributes(textDocumentUri)
- const baseDir = AsciidocTextDocument.fromTextDocument(textDocument).getBaseDir()
+ const asciidocTextDocument = AsciidocTextDocument.fromTextDocument(textDocument)
+ const baseDir = asciidocTextDocument.baseDir
+ const documentDirectory = asciidocTextDocument.dirName
+ const documentBasename = asciidocTextDocument.fileName
+ const documentExtensionName = asciidocTextDocument.extensionName
+ const documentFilePath = asciidocTextDocument.filePath
const templateDirs = this.getTemplateDirs()
const options: { [key: string]: any } = {
attributes: {
...attributes,
...antoraAttributes,
+ // The following attributes are "intrinsic attributes" but they are not set when the input is a string
+ // like we are doing, in that case it is expected that the attributes are set here for the API:
+ // https://docs.asciidoctor.org/asciidoc/latest/attributes/document-attributes-ref/#intrinsic-attributes
+ // this can be set since safe mode is 'UNSAFE'
+ ...(documentDirectory && { docdir: documentDirectory }),
+ ...(documentFilePath && { docfile: documentFilePath }),
+ ...(documentBasename && { docname: documentBasename }),
+ docfilesuffix: documentExtensionName,
+ filetype: asciidoctorWebViewConverter.outfilesuffix.substring(1), // remove the leading '.'
'!data-uri': '', // disable data-uri since Asciidoctor.js is unable to read files from a VS Code workspace.
},
backend: 'webview-html5',
diff --git a/src/asciidocLoader.ts b/src/asciidocLoader.ts
index 8b46442f..76de3284 100644
--- a/src/asciidocLoader.ts
+++ b/src/asciidocLoader.ts
@@ -29,7 +29,7 @@ export class AsciidocLoader {
memoryLogger,
registry,
} = await this.prepare(textDocument)
- const baseDir = AsciidocTextDocument.fromTextDocument(textDocument).getBaseDir()
+ const baseDir = AsciidocTextDocument.fromTextDocument(textDocument).baseDir
const attributes = AsciidoctorAttributesConfig.getPreviewAttributes()
const doc = this.processor.load(textDocument.getText(), this.getOptions(attributes, registry, baseDir))
this.asciidoctorDiagnosticProvider.reportErrors(memoryLogger, textDocument)
@@ -94,7 +94,7 @@ export class AsciidocIncludeItemsLoader extends AsciidocLoader {
registry,
} = await this.prepare(textDocument)
this.asciidoctorIncludeItemsProvider.activate(registry)
- const baseDir = AsciidocTextDocument.fromTextDocument(textDocument).getBaseDir()
+ const baseDir = AsciidocTextDocument.fromTextDocument(textDocument).baseDir
const attributes = AsciidoctorAttributesConfig.getPreviewAttributes()
this.asciidoctorIncludeItemsProvider.reset()
this.processor.load(textDocument.getText(), this.getOptions(attributes, registry, baseDir))
diff --git a/src/asciidocTextDocument.ts b/src/asciidocTextDocument.ts
index e5c967f7..118a879f 100644
--- a/src/asciidocTextDocument.ts
+++ b/src/asciidocTextDocument.ts
@@ -7,31 +7,77 @@ interface DocumentWithUri {
}
export class AsciidocTextDocument {
- private uri: Uri
+ public baseDir: string | undefined
+ public dir: string | undefined
+ public dirName: string | undefined
+ public extensionName: string
+ public fileName: string | undefined
+ public filePath: string | undefined
- private constructor () {
+ private constructor (private uri: Uri) {
+ this.baseDir = AsciidocTextDocument.getBaseDir(uri)
+ this.dirName = AsciidocTextDocument.getDirName(uri)
+ this.extensionName = AsciidocTextDocument.getExtensionName(uri)
+ this.fileName = AsciidocTextDocument.getFileName(uri)
+ this.filePath = AsciidocTextDocument.getFilePath(uri)
}
public static fromTextDocument (textDocument: DocumentWithUri): AsciidocTextDocument {
- const asciidocTextDocument = new AsciidocTextDocument()
- asciidocTextDocument.uri = textDocument.uri
- return asciidocTextDocument
+ return new AsciidocTextDocument(textDocument.uri)
}
/**
* Get the base directory.
* @private
*/
- public getBaseDir (): string | undefined {
+ private static getBaseDir (uri: Uri): string | undefined {
const useWorkspaceAsBaseDir = vscode.workspace.getConfiguration('asciidoc', null).get('useWorkspaceRootAsBaseDirectory')
if (useWorkspaceAsBaseDir) {
- const workspaceFolder = getWorkspaceFolder(this.uri)
+ const workspaceFolder = getWorkspaceFolder(uri)
if (workspaceFolder) {
return workspaceFolder.uri.fsPath
}
}
+ return AsciidocTextDocument.getDirName(uri)
+ }
+
+ private static getDirName (uri: Uri): string | undefined {
return 'browser' in process && (process as any).browser === true
? undefined
- : path.dirname(path.resolve(this.uri.fsPath))
+ : path.dirname(path.resolve(uri.fsPath))
+ }
+
+ /**
+ * Return the extension name of the file without the '.'.
+ * @param uri
+ * @private
+ */
+ private static getExtensionName (uri: Uri): string {
+ const textDocumentExt = path.extname(uri.path)
+ return textDocumentExt.startsWith('.') ? textDocumentExt.substring(1) : ''
+ }
+
+ /**
+ * Return the file name without the file extension.
+ * @param uri
+ * @private
+ */
+ public static getFileName (uri: Uri): string | undefined {
+ if ('browser' in process && (process as any).browser === true) {
+ return undefined
+ }
+ return path.parse(uri.fsPath).name
+ }
+
+ /**
+ * Return the filesystem path of the URI.
+ * @param uri
+ * @private
+ */
+ public static getFilePath (uri: Uri): string | undefined {
+ if ('browser' in process && (process as any).browser === true) {
+ return undefined
+ }
+ return uri.fsPath
}
}
diff --git a/src/commands/exportAsPDF.ts b/src/commands/exportAsPDF.ts
index 7c6e1b23..b51b8321 100644
--- a/src/commands/exportAsPDF.ts
+++ b/src/commands/exportAsPDF.ts
@@ -6,7 +6,6 @@ import { exec, spawn, SpawnOptions } from 'child_process'
import { uuidv4 } from 'uuid'
import { AsciidocEngine } from '../asciidocEngine'
import { Command } from '../commandManager'
-import { Logger } from '../logger'
import { Asciidoctor } from '@asciidoctor/core'
import { AsciidocTextDocument } from '../asciidocTextDocument'
import { getAsciidoctorConfigContent } from '../features/asciidoctorConfig'
@@ -16,7 +15,7 @@ export class ExportAsPDF implements Command {
public readonly id = 'asciidoc.exportAsPDF'
private readonly exportAsPdfStatusBarItem = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Right, 100)
- constructor (private readonly engine: AsciidocEngine, private readonly context: vscode.ExtensionContext, private readonly logger: Logger) {
+ constructor (private readonly engine: AsciidocEngine, private readonly context: vscode.ExtensionContext) {
}
public async execute () {
@@ -31,11 +30,9 @@ export class ExportAsPDF implements Command {
await vscode.window.showWarningMessage('Unable to get the workspace folder, aborting.')
return
}
- const workspacePath = workspaceFolder.uri.fsPath
- const docNameWithoutExtension = path.parse(doc.uri.fsPath).name
-
- const baseDirectory = AsciidocTextDocument.fromTextDocument(doc).getBaseDir()
- const pdfFilename = vscode.Uri.file(path.join(baseDirectory, docNameWithoutExtension + '.pdf'))
+ const asciidocTextDocument = AsciidocTextDocument.fromTextDocument(doc)
+ const baseDirectory = asciidocTextDocument.baseDir
+ const pdfFilename = vscode.Uri.file(path.join(baseDirectory, asciidocTextDocument.fileName + '.pdf'))
const asciidocPdfConfig = vscode.workspace.getConfiguration('asciidoc.pdf')
const pdfOutputUri = await vscode.window.showSaveDialog({ defaultUri: pdfFilename })
@@ -51,9 +48,9 @@ export class ExportAsPDF implements Command {
text = `${asciidoctorConfigContent}
${text}`
}
-
- const pdfEnfine = asciidocPdfConfig.get('engine')
- if (pdfEnfine === 'asciidoctor-pdf') {
+ const workspacePath = workspaceFolder.uri.fsPath
+ const pdfEngine = asciidocPdfConfig.get('engine')
+ if (pdfEngine === 'asciidoctor-pdf') {
const asciidoctorPdfCommand = await this.resolveAsciidoctorPdfCommand(asciidocPdfConfig, workspacePath)
if (asciidoctorPdfCommand === undefined) {
return
@@ -84,7 +81,7 @@ ${text}`
} finally {
this.exportAsPdfStatusBarItem.hide()
}
- } else if (pdfEnfine === 'wkhtmltopdf') {
+ } else if (pdfEngine === 'wkhtmltopdf') {
let wkhtmltopdfCommandPath = asciidocPdfConfig.get('wkhtmltopdfCommandPath', '')
if (wkhtmltopdfCommandPath === '') {
wkhtmltopdfCommandPath = `wkhtmltopdf${process.platform === 'win32' ? '.exe' : ''}`
diff --git a/src/extension.ts b/src/extension.ts
index 3335120b..23837252 100644
--- a/src/extension.ts
+++ b/src/extension.ts
@@ -95,7 +95,7 @@ export async function activate (context: vscode.ExtensionContext) {
commandManager.register(new commands.ShowPreviewSecuritySelectorCommand(previewSecuritySelector, previewManager))
commandManager.register(new commands.ShowAsciidoctorExtensionsTrustModeSelectorCommand(asciidoctorExtensionsTrustModeSelector))
commandManager.register(new commands.OpenDocumentLinkCommand(asciidocLoader))
- commandManager.register(new commands.ExportAsPDF(asciidocEngine, context, logger))
+ commandManager.register(new commands.ExportAsPDF(asciidocEngine, context))
commandManager.register(new commands.PasteImage(asciidocLoader))
commandManager.register(new commands.ToggleLockCommand(previewManager))
commandManager.register(new commands.ShowPreviewCommand(previewManager))
diff --git a/src/test/asciidoctorWebViewConverter.test.ts b/src/test/asciidoctorWebViewConverter.test.ts
index da6c07fb..158ef526 100644
--- a/src/test/asciidoctorWebViewConverter.test.ts
+++ b/src/test/asciidoctorWebViewConverter.test.ts
@@ -1,4 +1,5 @@
import vscode from 'vscode'
+import path from 'path'
import { AsciidoctorWebViewConverter } from '../asciidoctorWebViewConverter'
import { WebviewResourceProvider } from '../util/resources'
import { AsciidocPreviewConfigurationManager } from '../features/previewConfig'
@@ -36,6 +37,30 @@ function createAntoraDocumentContextStub (resourcePath: string | undefined) {
return antoraDocumentContextStub
}
+function createConverterOptions (converter: AsciidoctorWebViewConverter, fileName: string) {
+ // treat the file as the source file for conversion to handle xref correctly between documents
+ // review src/asciidocEngin.ts for more information
+ const intrinsicAttr = {
+ docdir: path.dirname(fileName),
+ docfile: fileName,
+ docfilesuffix: path.extname(fileName).substring(1),
+ docname: path.basename(fileName, path.extname(fileName)),
+ filetype: converter.outfilesuffix.substring(1),
+ }
+
+ return {
+ converter,
+ attributes: {
+ ...intrinsicAttr,
+
+ // required for navigation between source files in preview
+ // see: https://docs.asciidoctor.org/asciidoc/latest/macros/inter-document-xref/#navigating-between-source-files
+ relfilesuffix: '.adoc',
+ },
+ safe: 'unsafe', // needed so that we can actually perform includes, enabling xref tests
+ }
+}
+
async function testAsciidoctorWebViewConverter (
input: string,
antoraDocumentContext: AntoraDocumentContext | undefined,
@@ -55,12 +80,7 @@ async function testAsciidoctorWebViewConverter (
undefined
)
- const html = processor.convert(input, {
- converter: asciidoctorWebViewConverter,
- // required for navigation between source files in preview
- // see: https://docs.asciidoctor.org/asciidoc/latest/macros/inter-document-xref/#navigating-between-source-files
- attributes: { relfilesuffix: '.adoc' },
- })
+ const html = processor.convert(input, createConverterOptions(asciidoctorWebViewConverter, file.fileName))
assert.strictEqual(html, expected)
}
@@ -82,7 +102,10 @@ async function testAsciidoctorWebViewConverterStandalone (
antoraDocumentContext,
undefined
)
- const html = processor.convert(input, { converter: asciidoctorWebViewConverter, standalone: true })
+ const html = processor.convert(input, {
+ ...createConverterOptions(asciidoctorWebViewConverter, file.fileName),
+ standalone: true,
+ })
html.includes(expected)
}
@@ -102,6 +125,31 @@ link:help.adoc[]
createdFiles.push(await createDirectory('docs'))
await createFile('', 'docs', 'modules', 'ROOT', 'pages', 'dummy.adoc') // virtual file
createdFiles.push(asciidocFile)
+
+ // these help with testing xref cross documents
+ createdFiles.push(await createFile(`= Parent document
+
+Some text
+
+[#anchor]
+== Link to here
+
+Please scroll me into position
+
+include::docB.adoc[]`, 'docA.adoc'))
+ createdFiles.push(await createFile(`= Child document
+
+[#other_anchor]
+== Other link to here
+
+Other text
+
+I want to link to xref:docA.adoc#anchor[]`, 'docB.adoc'))
+ createdFiles.push(await createFile(`= Child document
+
+third text
+
+I want to link to xref:docB.adoc#other_anchor[]`, 'docC.adoc'))
})
suiteTeardown(async () => {
await removeFiles(createdFiles)
@@ -153,6 +201,42 @@ link:help.adoc[]
`,
},
// xref
+ {
+ title: 'Should resolve "xref:" macro from included document referencing the source document',
+ filePath: ['docA.adoc'],
+ input: `= Parent document
+
+Some text
+
+[#anchor]
+== Link to here
+
+Please scroll me into position
+
+include::docB.adoc[]`,
+ antoraDocumentContext: undefined, // Antora not enabled
+ expected: 'Link to here',
+ standalone: true,
+ },
+ {
+ title: 'Should resolve "xref:" macro from included document referencing a separate included document',
+ filePath: ['docA.adoc'],
+ input: `= Parent document
+
+Some text
+
+[#anchor]
+== Link to here
+
+Please scroll me into position
+
+include::docB.adoc[]
+
+include::docC.adoc[]`,
+ antoraDocumentContext: undefined, // Antora not enabled
+ expected: 'Other link to here',
+ standalone: true,
+ },
{
title: 'Should resolve "xref:" macro to document',
filePath: ['asciidoctorWebViewConverterTest.adoc'],