From a6ad733f5b908a6d88bda6dec8412f6b2a50499f Mon Sep 17 00:00:00 2001 From: purocean Date: Thu, 24 Mar 2022 13:41:22 +0800 Subject: [PATCH] feat: add reference link completion --- .../plugins/editor-path-completion.ts | 81 ++++++++++++++++--- 1 file changed, 72 insertions(+), 9 deletions(-) diff --git a/src/renderer/plugins/editor-path-completion.ts b/src/renderer/plugins/editor-path-completion.ts index 6ac7cecb..8e5f1f2f 100644 --- a/src/renderer/plugins/editor-path-completion.ts +++ b/src/renderer/plugins/editor-path-completion.ts @@ -4,6 +4,7 @@ import type Markdown from 'markdown-it' import type * as Monaco from 'monaco-editor' import type { Ctx, Plugin } from '@fe/context' import type { Components } from '@fe/types' +import type Token from 'markdown-it/lib/token' enum CompletionContextKind { Link, // [...](|) @@ -59,7 +60,7 @@ interface CompletionContext { } class CompletionProvider implements Monaco.languages.CompletionItemProvider { - triggerCharacters = ['/', '#'] + triggerCharacters = ['/', '#', '['] private readonly monaco: typeof Monaco private readonly ctx: Ctx @@ -80,8 +81,11 @@ class CompletionProvider implements Monaco.languages.CompletionItemProvider { switch (context.kind) { case CompletionContextKind.ReferenceLink: { - // TODO - return { suggestions: [] } + return { + suggestions: Array.from( + this.provideReferenceSuggestions(this.ctx.view.getRenderEnv()?.source || model.getValue(), position, context) + ) + } } case CompletionContextKind.LinkDefinition: @@ -99,7 +103,9 @@ class CompletionProvider implements Monaco.languages.CompletionItemProvider { position.column ) - for await (const item of this.provideHeaderSuggestions(this.ctx.store.state.currentContent, context, position, insertRange)) { + const tokens = this.ctx.view.getRenderEnv()?.tokens || [] + + for await (const item of this.provideHeaderSuggestions(tokens, context, position, insertRange)) { items.push(item) } } @@ -145,12 +151,16 @@ class CompletionProvider implements Monaco.languages.CompletionItemProvider { /// [...](...| private readonly linkStartPattern = /\[([^\]]*?)\]\(\s*([^\s()]*)$/; - /// [...][...| - private readonly referenceLinkStartPattern = /\[([^\]]*?)\]\[\s*([^\s()]*)$/; + /// [...| + private readonly referenceLinkStartPattern = /\[\s*([^\s[\]]*)$/; /// [id]: | private readonly definitionPattern = /^\s*\[[\w-]+\]:\s*([^\s]*)$/m; + private readonly defPattern = /^([\t ]*\[(?!\^)((?:\\\]|[^\]])+)\]:\s*)([^<]\S*|<[^>]+>)/gm; + + private readonly angleBracketLinkRe = /^<(.*)>$/; + private getPathCompletionContext (model: Monaco.editor.IModel, position: Monaco.Position): CompletionContext | undefined { const line = model.getLineContent(position.lineNumber) @@ -195,7 +205,7 @@ class CompletionProvider implements Monaco.languages.CompletionItemProvider { const referenceLinkPrefixMatch = linePrefixText.match(this.referenceLinkStartPattern) if (referenceLinkPrefixMatch) { - const prefix = referenceLinkPrefixMatch[2] + const prefix = referenceLinkPrefixMatch[1] const suffix = lineSuffixText.match(/^[^\]\s]*/) return { kind: CompletionContextKind.ReferenceLink, @@ -298,8 +308,8 @@ class CompletionProvider implements Monaco.languages.CompletionItemProvider { return result } - private async * provideHeaderSuggestions (mdContent: string, context: CompletionContext, position: Monaco.Position, insertionRange: Monaco.Range): AsyncIterable { - const tokens = this.markdown.parse(mdContent, []) + private async * provideHeaderSuggestions (tokensOrContent: string | Token[], context: CompletionContext, position: Monaco.Position, insertionRange: Monaco.Range): AsyncIterable { + const tokens = typeof tokensOrContent === 'string' ? this.markdown.parse(tokensOrContent, []) : tokensOrContent const endPos = position.delta(0, context.linkSuffix.length) const replacementRange = new this.monaco.Range( insertionRange.startLineNumber, @@ -328,6 +338,59 @@ class CompletionProvider implements Monaco.languages.CompletionItemProvider { } } } + + private * provideReferenceSuggestions (text: string, position: Monaco.Position, context: CompletionContext): Iterable { + const insertionRange = new this.monaco.Range( + context.linkTextStartPosition.lineNumber, + context.linkTextStartPosition.column, + position.lineNumber, + position.column, + ) + + const replacementPos = position.delta(0, context.linkSuffix.length) + + const replacementRange = new this.monaco.Range( + insertionRange.startLineNumber, + insertionRange.startColumn, + replacementPos.lineNumber, + replacementPos.column, + ) + + const definitions = this.getDefinitions(text) + let i = 0 + for (const def of definitions) { + i++ + yield { + kind: this.monaco.languages.CompletionItemKind.Reference, + label: def[0], + insertText: def[0], + range: { + insert: insertionRange, + replace: replacementRange, + }, + sortText: i.toString().padStart(7) + } + } + } + + private getDefinitions (text: string) { + const out = new Map() + for (const match of text.matchAll(this.defPattern)) { + const reference = match[2] + const link = match[3].trim() + + if (this.angleBracketLinkRe.test(link)) { + out.set(reference, { + link: link.substring(1, link.length - 1), + }) + } else { + out.set(reference, { + link: link, + }) + } + } + return out + } } export default {