diff --git a/src/completion/completer/subsuperscript.ts b/src/completion/completer/subsuperscript.ts new file mode 100644 index 000000000..f9f6b2fdc --- /dev/null +++ b/src/completion/completer/subsuperscript.ts @@ -0,0 +1,57 @@ +import * as vscode from 'vscode' +import type * as Ast from '@unified-latex/unified-latex-types' +import { lw } from '../../lw' +import type { CompletionArgs, CompletionItem, CompletionProvider, FileCache } from '../../types' + +import { argContentToStr } from '../../utils/parser' + +export const provider: CompletionProvider = { from } +export const subsuperscript = { + parse, +} + +function from(result: RegExpMatchArray, _: CompletionArgs) { + const isSub = result[0].startsWith('_') + let suggestions: CompletionItem[] = [] + lw.cache.getIncludedTeX().forEach(cachedFile => { + suggestions = [...suggestions, ...((isSub ? lw.cache.get(cachedFile)?.elements.subscripts : lw.cache.get(cachedFile)?.elements.superscripts) ?? [])] + }) + return suggestions +} + + +function parse(cache: FileCache) { + if (cache.ast !== undefined) { + const scripts = parseAst(cache.ast, cache.content.split('\n'), {sub: [], super: []}) + cache.elements.subscripts = scripts.sub + cache.elements.superscripts = scripts.super + } +} + +function parseAst(node: Ast.Node, lines: string[], scripts: {sub: CompletionItem[], super: CompletionItem[]}): {sub: CompletionItem[], super: CompletionItem[]} { + const entries = {sub: scripts.sub.map(entry => entry.label), super: scripts.super.map(entry => entry.label)} + if (node.type === 'macro' && ['^', '_'].includes(node.content)) { + const content = argContentToStr(node.args?.[0]?.content || []) + + if (content !== '' && node.position !== undefined && + !(node.content === '^' ? entries.super : entries.sub).includes(content)) { + + (node.content === '^' ? entries.super : entries.sub).push(content) + + ;(node.content === '^' ? scripts.super : scripts.sub).push({ + label: content, + kind: vscode.CompletionItemKind.Constant, + // One row before, four rows after + documentation: lines.slice(node.position.start.line - 2, node.position.end.line + 4).join('\n') + }) + } + } + + if ('content' in node && typeof node.content !== 'string') { + for (const subNode of node.content) { + parseAst(subNode, lines, scripts) + } + } + + return scripts +} diff --git a/src/completion/index.ts b/src/completion/index.ts index d2193f4f1..e74c1e55e 100644 --- a/src/completion/index.ts +++ b/src/completion/index.ts @@ -1,6 +1,7 @@ import { citation } from './completer/citation' import { environment } from './completer/environment' import { macro } from './completer/macro' +import { subsuperscript } from './completer/subsuperscript' import { reference } from './completer/reference' import { usepackage } from './completer/package' import { input } from './completer/input' @@ -13,6 +14,7 @@ export const completion = { citation, environment, macro, + subsuperscript, reference, usepackage, input, diff --git a/src/completion/latex.ts b/src/completion/latex.ts index 73061852e..5fcd23e62 100644 --- a/src/completion/latex.ts +++ b/src/completion/latex.ts @@ -4,6 +4,7 @@ import type { CompletionArgs, CompletionProvider, ReferenceDocType } from '../ty import { citation, provider as citationProvider } from './completer/citation' import { provider as environmentProvider } from './completer/environment' import { provider as macroProvider } from './completer/macro' +import { provider as subsuperProvider } from './completer/subsuperscript' import { provider as argumentProvider } from './completer/argument' import { provider as classProvider } from './completer/class' import { provider as referenceProvider } from './completer/reference' @@ -36,7 +37,7 @@ export class Provider implements vscode.CompletionItemProvider { provide(args: CompletionArgs): vscode.CompletionItem[] { // Note that the order of the following array affects the result. // 'command' must be at the last because it matches any commands. - for (const type of ['citation', 'reference', 'environment', 'package', 'documentclass', 'input', 'subimport', 'import', 'includeonly', 'glossary', 'argument', 'command']) { + for (const type of ['citation', 'reference', 'environment', 'package', 'documentclass', 'input', 'subimport', 'import', 'includeonly', 'glossary', 'argument', 'command', 'subsuper']) { const suggestions = this.completion(type, args) if (suggestions.length > 0) { if (type === 'citation') { @@ -149,6 +150,10 @@ export class Provider implements vscode.CompletionItemProvider { reg = /\\(gls(?:pl|text|first|fmt(?:text|short|long)|plural|firstplural|name|symbol|desc|disp|user(?:i|ii|iii|iv|v|vi))?|Acr(?:long|full|short)?(?:pl)?|ac[slf]?p?)(?:\[[^[\]]*\])?{([^}]*)$/i provider = glossaryProvider break + case 'subsuper': + reg = /(?:\^|_){([^}]*)$/ + provider = subsuperProvider + break default: // This shouldn't be possible, so mark as error case in log. logger.log(`Error - trying to complete unknown type ${type}`) diff --git a/src/core/cache.ts b/src/core/cache.ts index ef0ad1b70..aae4e520c 100644 --- a/src/core/cache.ts +++ b/src/core/cache.ts @@ -368,6 +368,7 @@ function updateElements(fileCache: FileCache) { lw.completion.glossary.parse(fileCache) lw.completion.environment.parse(fileCache) lw.completion.macro.parse(fileCache) + lw.completion.subsuperscript.parse(fileCache) lw.completion.input.parseGraphicsPath(fileCache) updateBibfiles(fileCache) const elapsed = performance.now() - start diff --git a/src/types.ts b/src/types.ts index bd2559c75..37babf0b7 100644 --- a/src/types.ts +++ b/src/types.ts @@ -22,7 +22,11 @@ export type FileCache = { /** command items */ command?: CmdEnvSuggestion[], /** \usepackage{}, a dictionary whose key is package name and value is the options */ - package?: {[packageName: string]: string[]} + package?: {[packageName: string]: string[]}, + /** _{} */ + subscripts?: CompletionItem[], + /** ^{} */ + superscripts?: CompletionItem[] }, /** The sub-files of the LaTeX file. They should be tex or plain files */ children: {