diff --git a/src/extension.ts b/src/extension.ts index 1be2f5b..ff522d9 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -637,31 +637,31 @@ export async function activate(context: vscode.ExtensionContext) { // vscode.commands.registerCommand("pretext-tools.test", async () => { // console.log("Running Experiment"); // utils.experiment(context); - // const editor = vscode.window.activeTextEditor; - // const document = editor?.document; - // const position = editor?.selection.active; - // if (document) { - // let labels = await utils.getReferences(); - // console.log("Found labels: ", labels); - // } - // if (position) { - // const textUntilPosition = document?.getText( - // new vscode.Range(new vscode.Position(0, 0), position) - // ); - // const openedTags = ( - // textUntilPosition?.match(/<(\w)+(?![^>]*\/>)/g) || [] - // ).map((tag) => tag.slice(1)); - // const closedTags = (textUntilPosition?.match(/<\/\w+/g) || []).map( - // (tag) => tag.slice(2) - // ); - // const unclosedTags = openedTags.filter( - // (tag) => - // openedTags.filter(x => x === tag).length > closedTags.filter(x => x === tag).length - // ); - // const currentTag = unclosedTags[unclosedTags.length - 1]; - // console.log("Current XML Element: ", currentTag); - // } - //}) + // const editor = vscode.window.activeTextEditor; + // const document = editor?.document; + // const position = editor?.selection.active; + // if (document) { + // let labels = await utils.getReferences(); + // console.log("Found labels: ", labels); + // } + // if (position) { + // const textUntilPosition = document?.getText( + // new vscode.Range(new vscode.Position(0, 0), position) + // ); + // const openedTags = ( + // textUntilPosition?.match(/<(\w)+(?![^>]*\/>)/g) || [] + // ).map((tag) => tag.slice(1)); + // const closedTags = (textUntilPosition?.match(/<\/\w+/g) || []).map( + // (tag) => tag.slice(2) + // ); + // const unclosedTags = openedTags.filter( + // (tag) => + // openedTags.filter(x => x === tag).length > closedTags.filter(x => x === tag).length + // ); + // const currentTag = unclosedTags[unclosedTags.length - 1]; + // console.log("Current XML Element: ", currentTag); + // } + //}) //); } diff --git a/src/lsp-client/main.ts b/src/lsp-client/main.ts index d4c5002..d467b4d 100644 --- a/src/lsp-client/main.ts +++ b/src/lsp-client/main.ts @@ -8,67 +8,67 @@ import * as vscode from "vscode"; import { workspace, ExtensionContext } from "vscode"; import { - LanguageClient, - LanguageClientOptions, - ServerOptions, - TransportKind, + LanguageClient, + LanguageClientOptions, + ServerOptions, + TransportKind, } from "vscode-languageclient/node"; let client: LanguageClient; export function activate(context: ExtensionContext) { - // The server is implemented in node - const serverModule = context.asAbsolutePath( - path.join("out", "lsp-server.js") - ); - // The debug options for the server - // --inspect=6009: runs the server in Node's Inspector mode so VS Code can attach to the server for debugging - const debugOptions = { execArgv: ["--nolazy", "--inspect=6009"] }; + // The server is implemented in node + const serverModule = context.asAbsolutePath( + path.join("out", "lsp-server.js") + ); + // The debug options for the server + // --inspect=6009: runs the server in Node's Inspector mode so VS Code can attach to the server for debugging + const debugOptions = { execArgv: ["--nolazy", "--inspect=6009"] }; - // If the extension is launched in debug mode then the debug server options are used - // Otherwise the run options are used - const serverOptions: ServerOptions = { - run: { module: serverModule, transport: TransportKind.ipc }, - debug: { - module: serverModule, - transport: TransportKind.ipc, - options: debugOptions, - }, - }; + // If the extension is launched in debug mode then the debug server options are used + // Otherwise the run options are used + const serverOptions: ServerOptions = { + run: { module: serverModule, transport: TransportKind.ipc }, + debug: { + module: serverModule, + transport: TransportKind.ipc, + options: debugOptions, + }, + }; - // Options to control the language client - const clientOptions: LanguageClientOptions = { - // Register the server for plain text documents - documentSelector: [{ scheme: "file", language: "pretext" }], - synchronize: { - // Notify the server about file changes to '.clientrc files contained in the workspace - //fileEvents: workspace.createFileSystemWatcher('**/.clientrc') - }, - markdown: { isTrusted: true }, - }; + // Options to control the language client + const clientOptions: LanguageClientOptions = { + // Register the server for plain text documents + documentSelector: [{ scheme: "file", language: "pretext" }], + synchronize: { + // Notify the server about file changes to '.clientrc files contained in the workspace + //fileEvents: workspace.createFileSystemWatcher('**/.clientrc') + }, + markdown: { isTrusted: true }, + }; - // Create the language client and start the client. - client = new LanguageClient( - "pretextLanguageServer", - "PreTeXt Language Server", - serverOptions, - clientOptions - ); + // Create the language client and start the client. + client = new LanguageClient( + "pretextLanguageServer", + "PreTeXt Language Server", + serverOptions, + clientOptions + ); - //context.subscriptions.push( - // vscode.commands.registerCommand("myExtension.sayHello", () => { - // vscode.window.showWarningMessage("Hello!"); - // }) - //); + //context.subscriptions.push( + // vscode.commands.registerCommand("myExtension.sayHello", () => { + // vscode.window.showWarningMessage("Hello!"); + // }) + //); - // Start the client. This will also launch the server - client.start(); - console.log("PreTeXt LSP Launched"); + // Start the client. This will also launch the server + client.start(); + console.log("PreTeXt LSP Launched"); } export function deactivate(): Thenable | undefined { - if (!client) { - return undefined; - } - return client.stop(); + if (!client) { + return undefined; + } + return client.stop(); } diff --git a/src/lsp-server/completions/constants.ts b/src/lsp-server/completions/constants.ts index 14dbe18..27bb854 100644 --- a/src/lsp-server/completions/constants.ts +++ b/src/lsp-server/completions/constants.ts @@ -1,5 +1,5 @@ /* eslint-disable @typescript-eslint/naming-convention */ -// The constants ATTRIBUTES and ELEMENTS contain snippets for completions. Any element/attribute that +// The constants ATTRIBUTES and ELEMENTS contain snippets for completions. Any element/attribute that import { CompletionItems } from "../../types"; @@ -88,7 +88,8 @@ export const ATTRIBUTES: CompletionItems = { }, text: { label: "@text", - insertText: 'text="${1|type-local,type-global,type-hybrid,local,global,hybrid,phrase-global,phrase-hybrid,title|}"$0', + insertText: + 'text="${1|type-local,type-global,type-hybrid,local,global,hybrid,phrase-global,phrase-hybrid,title|}"$0', documentation: "text attribute, to specify style of cross-reference text.", }, top: { @@ -119,7 +120,8 @@ export const ATTRIBUTES: CompletionItems = { "xi-namespace": { label: "@xmlns:xi", insertText: 'xmlns:xi="http://www.w3.org/2001/XInclude"', - documentation: "xinclude namespace: add to top division of xinclude-ed file.", + documentation: + "xinclude namespace: add to top division of xinclude-ed file.", }, "xml:id": { label: "@xml:id", @@ -145,7 +147,8 @@ export const ATTRIBUTES: CompletionItems = { label: "@compression", }, format: { - insertText: 'format="${1|html,pdf,latex,epub,kindle,slides,braille,custom|}"$0', + insertText: + 'format="${1|html,pdf,latex,epub,kindle,slides,braille,custom|}"$0', documentation: "format, for this particular build target.", label: "@format", }, @@ -186,7 +189,8 @@ export const ATTRIBUTES: CompletionItems = { }, "ptx-version": { insertText: 'ptx-version="2"$0', - documentation: "ptx-version attribute, for project tag. Must have value 2.", + documentation: + "ptx-version attribute, for project tag. Must have value 2.", label: "@ptx-version", }, publication: { @@ -230,7 +234,8 @@ export const ELEMENTS: CompletionItems = { }, algorithm: { label: "", - insertText: '\n\t\n\t\t

\n\t\t\t$2\n\t\t

\n\t
\n
', + insertText: + '\n\t\n\t\t

\n\t\t\t$2\n\t\t

\n\t
\n
', documentation: "algorithm (theorem-like).", }, answer: { @@ -245,16 +250,19 @@ export const ELEMENTS: CompletionItems = { }, assemblage: { label: "", - insertText: '\n\t$2\n\t

\n\t\t$0\n\t

\n
', + insertText: + '\n\t$2\n\t

\n\t\t$0\n\t

\n
', }, assumption: { label: "", - insertText: '\n\t\n\t\t

\n\t\t\t$0\n\t\t

\n\t
\n
', + insertText: + '\n\t\n\t\t

\n\t\t\t$0\n\t\t

\n\t
\n
', documentation: "assumption (axiom-like)", }, axiom: { label: "", - insertText: '\n\t\n\t\t

\n\t\t\t$0\n\t\t

\n\t
\n
', + insertText: + '\n\t\n\t\t

\n\t\t\t$0\n\t\t

\n\t
\n
', documentation: "axiom (axiom-like)", }, biographical: { @@ -274,7 +282,8 @@ export const ELEMENTS: CompletionItems = { }, blocks: { label: "", - insertText: "\n\t\n\t\t

\n\t\t\t$1\n\t\t

\n\t
\n
", + insertText: + "\n\t\n\t\t

\n\t\t\t$1\n\t\t

\n\t
\n
", documentation: "Parsons problem blocks", }, caption: { @@ -294,22 +303,26 @@ export const ELEMENTS: CompletionItems = { }, chapter: { label: "", - insertText: '\n\t$1\n\t$0\n', + insertText: + '\n\t$1\n\t$0\n', documentation: "chapter (division)", }, choice: { label: "", - insertText: "\n\t\n\t\t

\n\t\t\t$0\n\t\t

\n\t
\n
", + insertText: + "\n\t\n\t\t

\n\t\t\t$0\n\t\t

\n\t
\n
", documentation: "Choice for multiple choice question.", }, choices: { label: "", - insertText: "\n\t\n\t\t\n\t\t\t

\n\t\t\t\t$0\n\t\t\t

\n\t\t
\n\t
\n\n
", + insertText: + "\n\t\n\t\t\n\t\t\t

\n\t\t\t\t$0\n\t\t\t

\n\t\t
\n\t
\n\n
", documentation: "Choices for interactive multiple choice questions.", }, claim: { label: "", - insertText: '\n\t\n\t\t

\n\t\t\t$2\n\t\t

\n\t
\n
', + insertText: + '\n\t\n\t\t

\n\t\t\t$2\n\t\t

\n\t
\n
', documentation: "claim (theorem-like).", }, cline: { @@ -329,12 +342,14 @@ export const ELEMENTS: CompletionItems = { }, conjecture: { label: "", - insertText: '\n\t\n\t\t

\n\t\t\t$0\n\t\t

\n\t
\n
', + insertText: + '\n\t\n\t\t

\n\t\t\t$0\n\t\t

\n\t
\n
', documentation: "conjecture (axiom-like)", }, console: { label: "", - insertText: "\n$1\n\n\n$2\n$0", + insertText: + "\n$1\n\n\n$2\n$0", documentation: "console (input/output)", }, convention: { @@ -344,7 +359,8 @@ export const ELEMENTS: CompletionItems = { }, corollary: { label: "", - insertText: '\n\t\n\t\t

\n\t\t\t$2\n\t\t

\n\t
\n
', + insertText: + '\n\t\n\t\t

\n\t\t\t$2\n\t\t

\n\t
\n
', documentation: "corollary (theorem-like).", }, creator: { @@ -354,7 +370,8 @@ export const ELEMENTS: CompletionItems = { }, definition: { label: "", - insertText: '\n\t\n\t\t

\n\t\t\t$0\n\t\t

\n\t
\n
', + insertText: + '\n\t\n\t\t

\n\t\t\t$0\n\t\t

\n\t
\n
', documentation: "definition", }, documentation: { @@ -364,7 +381,8 @@ export const ELEMENTS: CompletionItems = { }, dl: { label: "
", - insertText: "
\n\t
  • \n\t\t$1\n\t\t

    \n\t\t\t$0\n\t\t

    \n\t
  • \n
    ", + insertText: + "
    \n\t
  • \n\t\t$1\n\t\t

    \n\t\t\t$0\n\t\t

    \n\t
  • \n
    ", documentation: "definition list", }, example: { @@ -386,7 +404,8 @@ export const ELEMENTS: CompletionItems = { }, exercisegroup: { label: "", - insertText: "\n\n\t\n\t\t

    \n\t\t\t$0\n\t\t

    \n\t
    \n\n\t\n\t\t\n\t\t\t

    \n\t\t\t\t\n\t\t\t

    \n\t\t
    \n\t
    \n\n
    ", + insertText: + "\n\n\t\n\t\t

    \n\t\t\t$0\n\t\t

    \n\t
    \n\n\t\n\t\t\n\t\t\t

    \n\t\t\t\t\n\t\t\t

    \n\t\t
    \n\t
    \n\n
    ", documentation: "exercise group", }, exercises: { @@ -401,7 +420,8 @@ export const ELEMENTS: CompletionItems = { }, fact: { label: "", - insertText: '\n\t\n\t\t

    \n\t\t\t$2\n\t\t

    \n\t
    \n
    ', + insertText: + '\n\t\n\t\t

    \n\t\t\t$2\n\t\t

    \n\t
    \n
    ', documentation: "fact (theorem-like).", }, feedback: { @@ -421,7 +441,8 @@ export const ELEMENTS: CompletionItems = { }, heuristic: { label: "", - insertText: '\n\t\n\t\t

    \n\t\t\t$0\n\t\t

    \n\t
    \n
    ', + insertText: + '\n\t\n\t\t

    \n\t\t\t$0\n\t\t

    \n\t
    \n
    ', documentation: "heuristic (axiom-like)", }, hint: { @@ -436,12 +457,14 @@ export const ELEMENTS: CompletionItems = { }, hypothesis: { label: "", - insertText: '\n\t\n\t\t

    \n\t\t\t$0\n\t\t

    \n\t
    \n
    ', + insertText: + '\n\t\n\t\t

    \n\t\t\t$0\n\t\t

    \n\t
    \n
    ', documentation: "hypothesis (axiom-like)", }, identity: { label: "", - insertText: '\n\t\n\t\t

    \n\t\t\t$0\n\t\t

    \n\t
    \n
    ', + insertText: + '\n\t\n\t\t

    \n\t\t\t$0\n\t\t

    \n\t
    \n
    ', documentation: "identity (theorem-like).", }, idx: { @@ -451,12 +474,14 @@ export const ELEMENTS: CompletionItems = { }, "image (external)": { label: '', - insertText: '\n\t${0:(for accessibility)}\n', + insertText: + '\n\t${0:(for accessibility)}\n', documentation: "image (external)", }, "image (not external)": { label: "", - insertText: "\n\t$2\n\t$0\n", + insertText: + "\n\t$2\n\t$0\n", documentation: "image (not external)", sortText: "0", }, @@ -482,12 +507,14 @@ export const ELEMENTS: CompletionItems = { }, "latex-image": { label: "", - insertText: "\n\t\\\\begin{tikzpicture}\n\t\t$0\n\t\\\\end{tikzpicture}\n", + insertText: + "\n\t\\\\begin{tikzpicture}\n\t\t$0\n\t\\\\end{tikzpicture}\n", documentation: "latex-image", }, lemma: { label: "", - insertText: '\n\t\n\t\t

    \n\t\t\t$0\n\t\t

    \n\t
    \n
    ', + insertText: + '\n\t\n\t\t

    \n\t\t\t$0\n\t\t

    \n\t
    \n
    ', documentation: "lemma (theorem-like).", }, li: { @@ -496,7 +523,7 @@ export const ELEMENTS: CompletionItems = { documentation: "list item (w/ p)", sortText: "0", }, - "li-dl": { + "li-dl": { label: "
  • ", insertText: "
  • \n\t$1\n\t

    \n\t\t$0\n\t

    \n
  • ", documentation: "list item (w title and p)", @@ -508,12 +535,14 @@ export const ELEMENTS: CompletionItems = { }, match: { label: "", - insertText: '\n\t$2\n\t$3\n\n$0', + insertText: + '\n\t$2\n\t$3\n\n$0', documentation: "match pair for interactive Matching exercise", }, matches: { label: "", - insertText: '\n\t\n\t\t$2\n\t\t$3\n\t\n\t$0\n', + insertText: + '\n\t\n\t\t$2\n\t\t$3\n\t\n\t$0\n', documentation: "Matches for interactive exercise", }, md: { @@ -542,7 +571,8 @@ export const ELEMENTS: CompletionItems = { }, notation: { label: "", - insertText: "\n\t${1:(in math mode)}\n $2\n$0", + insertText: + "\n\t${1:(in math mode)}\n $2\n$0", }, note: { label: "", @@ -587,12 +617,14 @@ export const ELEMENTS: CompletionItems = { }, part: { label: "", - insertText: '\n\t$1\n\t$0\n', + insertText: + '\n\t$1\n\t$0\n', documentation: "part (division)", }, principle: { label: "", - insertText: '\n\t\n\t\t

    \n\t\t\t$0\n\t\t

    \n\t
    \n
    ', + insertText: + '\n\t\n\t\t

    \n\t\t\t$0\n\t\t

    \n\t
    \n
    ', documentation: "principle (axiom-like)", }, problem: { @@ -602,7 +634,8 @@ export const ELEMENTS: CompletionItems = { }, program: { label: "", - insertText: '\n\t\n$0\n\t\n', + insertText: + '\n\t\n$0\n\t\n', documentation: "program (program-like)", }, project: { @@ -617,7 +650,8 @@ export const ELEMENTS: CompletionItems = { }, proposition: { label: "", - insertText: '\n\t\n\t\t

    \n\t\t\t$0\n\t\t

    \n\t
    \n
    ', + insertText: + '\n\t\n\t\t

    \n\t\t\t$0\n\t\t

    \n\t
    \n
    ', documentation: "proposition (theorem-like).", }, question: { @@ -627,7 +661,8 @@ export const ELEMENTS: CompletionItems = { }, "reading-questions": { label: "", - insertText: '\n\t$0\n', + insertText: + '\n\t$0\n', documentation: "reading-questions (division)", }, references: { @@ -651,7 +686,8 @@ export const ELEMENTS: CompletionItems = { }, sage: { label: "", - insertText: "\n\t\n\t\t$1\n\t\n\t\n\t\t$2\n\t\n$0", + insertText: + "\n\t\n\t\t$1\n\t\n\t\n\t\t$2\n\t\n$0", documentation: "sage math cell", }, sageplot: { @@ -661,13 +697,15 @@ export const ELEMENTS: CompletionItems = { }, sbsgroup: { label: "", - insertText: '\n\t\n\t\t$0\n\t\n', + insertText: + '\n\t\n\t\t$0\n\t\n', documentation: "sidebyside-group: for a grid layout with multiple rows of side-by-sides.", }, section: { label: "
    ", - insertText: '
    \n\t$1\n\t$0\n
    ', + insertText: + '
    \n\t$1\n\t$0\n
    ', documentation: "section (division)", }, see: { @@ -707,38 +745,45 @@ export const ELEMENTS: CompletionItems = { }, subexercises: { label: "", - insertText: "\n\t$1\n\n\t\n\t\t\n\t\t\t

    \n\t\t\t\t$2\n\t\t\t

    \n\t\t
    \n\t
    \n\t$0\n
    ", + insertText: + "\n\t$1\n\n\t\n\t\t\n\t\t\t

    \n\t\t\t\t$2\n\t\t\t

    \n\t\t
    \n\t
    \n\t$0\n
    ", documentation: "subexercises (to group exercises)", }, subsection: { label: "", - insertText: '\n\t$1\n\t$0\n', + insertText: + '\n\t$1\n\t$0\n', documentation: "subsection (division)", }, subsubsection: { label: "", - insertText: '\n\t$1\n\t$0\n', + insertText: + '\n\t$1\n\t$0\n', documentation: "subsubsection (division)", }, table: { label: "", - insertText: "
    \n\t$1\n\t\n\t\t\n\t\t\t$2$0\n\t\t\n\t\n
    ", + insertText: + "\n\t$1\n\t\n\t\t\n\t\t\t$2$0\n\t\t\n\t\n
    ", documentation: "table", }, tabular: { label: "", - insertText: "\n\t\n\t\t$1$0\n\t\n", + insertText: + "\n\t\n\t\t$1$0\n\t\n", documentation: "tabular", }, task: { label: "", - insertText: "\n\t\n\t\t

    \n\t\t\t$0\n\t\t

    \n\t
    \n
    ", + insertText: + "\n\t\n\t\t

    \n\t\t\t$0\n\t\t

    \n\t
    \n
    ", documentation: "task (for parts of an exercise/project).", sortText: "0", }, "task-workspace": { label: '', - insertText: '\n\t\n\t\t

    \n\t\t\t$0\n\t\t

    \n\t
    \n
    ', + insertText: + '\n\t\n\t\t

    \n\t\t\t$0\n\t\t

    \n\t
    \n
    ', documentation: "task (for parts of an exercise in worksheet)", }, technology: { @@ -748,7 +793,8 @@ export const ELEMENTS: CompletionItems = { }, theorem: { label: "", - insertText: '\n\t\n\t\t

    \n\t\t\t$0\n\t\t

    \n\t
    \n
    \n', + insertText: + '\n\t\n\t\t

    \n\t\t\t$0\n\t\t

    \n\t
    \n
    \n', documentation: "theorem (theorem-like).", }, title: { @@ -778,7 +824,8 @@ export const ELEMENTS: CompletionItems = { }, webwork: { label: "", - insertText: "\n\t\n\t\t$0\n\t\n\n\t\n\t\t

    \n\t\t\t\n\t\t

    \n\t
    \n
    ", + insertText: + "\n\t\n\t\t$0\n\t\n\n\t\n\t\t

    \n\t\t\t\n\t\t

    \n\t
    \n
    ", documentation: "WeBWorK (with pg code)", }, "webwork-server": { @@ -800,177 +847,175 @@ export const ELEMENTS: CompletionItems = { "&": { label: "&", insertText: "&", - documentation: "ampersand character" + documentation: "ampersand character", }, "<": { label: "<", insertText: "<", - documentation: "less than character" + documentation: "less than character", }, abbr: { label: "", insertText: "${1:$TM_SELECTED_TEXT}$0", - documentation: "abbreviations" + documentation: "abbreviations", }, acro: { label: "", insertText: "${1:$TM_SELECTED_TEXT}$0", - documentation: "acronym" + documentation: "acronym", }, alert: { label: "", insertText: "${1:$TM_SELECTED_TEXT}$0", - documentation: "alert text" + documentation: "alert text", }, articletitle: { label: "", insertText: "$1$0", - documentation: "article title" + documentation: "article title", }, attr: { label: "", insertText: "$1$0", - documentation: "attribute" + documentation: "attribute", }, c: { label: "", insertText: "${1:$TM_SELECTED_TEXT}$0", - documentation: "code" + documentation: "code", }, delete: { label: "", insertText: "${1:$TM_SELECTED_TEXT}$0", - documentation: "delete (strike-through) text" + documentation: "delete (strike-through) text", }, em: { label: "", insertText: "${1:$TM_SELECTED_TEXT}$0", documentation: "emphasis text", - sortText: "0" + sortText: "0", }, email: { label: "", insertText: "$1$0", - documentation: "email" + documentation: "email", }, fillin: { label: "", - insertText: "$0", - documentation: "fillin" + insertText: '$0', + documentation: "fillin", }, fn: { label: "", insertText: "$0", - documentation: "footnote" + documentation: "footnote", }, foreign: { label: "", insertText: "${1:$TM_SELECTED_TEXT}$0", - documentation: "foreign" + documentation: "foreign", }, init: { label: "", insertText: "${1:$TM_SELECTED_TEXT}$0", - documentation: "initialism" + documentation: "initialism", }, insert: { label: "", insertText: "${1:$TM_SELECTED_TEXT}$0", - documentation: "insert text" + documentation: "insert text", }, latex: { label: "", insertText: "$0", - documentation: "latex (fancy letters)" + documentation: "latex (fancy letters)", }, m: { label: "", insertText: "${1:$TM_SELECTED_TEXT}$0", documentation: "inline math", - sortText: "0" + sortText: "0", }, pretext: { label: "", insertText: "$0", - documentation: "pretext (fancy letters)" + documentation: "pretext (fancy letters)", }, pubtitle: { label: "", insertText: "$1$0", - documentation: "publication title" + documentation: "publication title", }, q: { label: "", insertText: "${1:$TM_SELECTED_TEXT}$0", - documentation: "quote (double)" + documentation: "quote (double)", }, sq: { label: "", insertText: "${1:$TM_SELECTED_TEXT}$0", - documentation: "single quote" + documentation: "single quote", }, stale: { label: "", insertText: "${1:$TM_SELECTED_TEXT}$0", - documentation: "stale text" + documentation: "stale text", }, tag: { label: "", insertText: "${1:$TM_SELECTED_TEXT}$0", - documentation: "tag" + documentation: "tag", }, tage: { label: "", insertText: "${1:$TM_SELECTED_TEXT}$0", - documentation: "empty tag" + documentation: "empty tag", }, taxon: { label: "", insertText: "${1:$TM_SELECTED_TEXT}$0", - documentation: "taxonomy" + documentation: "taxonomy", }, term: { label: "", insertText: "${1:$TM_SELECTED_TEXT}$0", documentation: "term (for defined terms)", - sortText: "0" + sortText: "0", }, tex: { label: "", insertText: "$0", - documentation: "tex (fancy letters)" + documentation: "tex (fancy letters)", }, url: { label: "", - insertText: "$2$0", - documentation: "url" + insertText: '$2$0', + documentation: "url", }, "url-empty": { label: "", - insertText: "$0", - documentation: "url (empty)" + insertText: '$0', + documentation: "url (empty)", }, "webwork-inline": { label: "", insertText: "$0", - documentation: "webwork (fancy letters)" + documentation: "webwork (fancy letters)", }, xref: { label: "", - insertText: "$0", + insertText: '$0', documentation: "reference", command: suggestCommand, - } + }, }; // the EXTRA_SNIPPETS object is used to add additional snippets from the above lists beyond what the // schema suggests. This is useful for variants of snippets. Each key is the name of an element // that can contain the values for that key. export const EXTRA_SNIPPETS = { - "worksheet": { - elements: [ - "exercise-workspace", - ], - attributes: [], - } -}; \ No newline at end of file + worksheet: { + elements: ["exercise-workspace"], + attributes: [], + }, +}; diff --git a/src/lsp-server/completions/utils.ts b/src/lsp-server/completions/utils.ts index aecc6fe..63f7a4f 100644 --- a/src/lsp-server/completions/utils.ts +++ b/src/lsp-server/completions/utils.ts @@ -67,46 +67,48 @@ export function lineToPosition(position: Position): Range { }; } - /** Finds the current node of the curser by comparing all tags above the curser to the open tags above the curser. */ export function getCurrentTag( - document: TextDocument, - position: Position - ): string | undefined { - const textUntilPosition = document?.getText({start: { line: 0, character: 0 }, end: position}); - // Get all open and close tags from the text until the current position. - const allTags = ( - textUntilPosition?.match(/<(\w)+(?![^>]*\/>)|<\/\w+/g) || [] - ).map((tag) => tag.slice(1)); - const openedTags = ( - textUntilPosition?.match(/<(\w)+(?![^>]*\/>)/g) || [] - ).map((tag) => tag.slice(1)); - const closedTags = (textUntilPosition?.match(/<\/\w+/g) || []).map((tag) => - tag.slice(2) - ); - - // Now walk through list of all tags, creating a stack of open tags and removing closed tags from the stack. - let openTagStack: string[] = []; - for (let tag of allTags) { - if (tag.startsWith("/")) { - const lastOpenTag = openTagStack.pop(); - if (lastOpenTag !== tag.slice(1)) { - console.error( - `Error: Found closing tag ${tag} without matching opening tag.` - ); - } - } else { - openTagStack.push(tag); + document: TextDocument, + position: Position +): string | undefined { + const textUntilPosition = document?.getText({ + start: { line: 0, character: 0 }, + end: position, + }); + // Get all open and close tags from the text until the current position. + const allTags = ( + textUntilPosition?.match(/<(\w)+(?![^>]*\/>)|<\/\w+/g) || [] + ).map((tag) => tag.slice(1)); + const openedTags = ( + textUntilPosition?.match(/<(\w)+(?![^>]*\/>)/g) || [] + ).map((tag) => tag.slice(1)); + const closedTags = (textUntilPosition?.match(/<\/\w+/g) || []).map((tag) => + tag.slice(2) + ); + + // Now walk through list of all tags, creating a stack of open tags and removing closed tags from the stack. + let openTagStack: string[] = []; + for (let tag of allTags) { + if (tag.startsWith("/")) { + const lastOpenTag = openTagStack.pop(); + if (lastOpenTag !== tag.slice(1)) { + console.error( + `Error: Found closing tag ${tag} without matching opening tag.` + ); } + } else { + openTagStack.push(tag); } - - const currentTag = openTagStack.pop(); - console.log("Current XML Element: ", currentTag); - return currentTag; } + const currentTag = openTagStack.pop(); + console.log("Current XML Element: ", currentTag); + return currentTag; +} + /** * Reads a snippet file and returns a list of completion items. * @param snippets - the snippets to read @@ -193,7 +195,10 @@ export async function getSnippetCompletionItems( } // Also add a closing tag for the current node, since we have turned off this feature from redhat.vscode-xml. if (node && trigger === "<") { - const closeTagCompletion: CompletionItem = {label:`/${node}>`, kind:kind}; + const closeTagCompletion: CompletionItem = { + label: `/${node}>`, + kind: kind, + }; closeTagCompletion.insertText = `/${node}>`; closeTagCompletion.documentation = `Close the ${node} tag`; closeTagCompletion.detail = `Close the ${node} tag`; @@ -202,8 +207,6 @@ export async function getSnippetCompletionItems( return completionItems; } - - function getMainFile() { const pwd = "./"; // Default source directory is ./source @@ -253,7 +256,6 @@ function getMainFile() { } } - // define a type for the array of labels: type LabelArray = [string, string, string][]; @@ -306,7 +308,7 @@ export function updateReferences( const rootDir = fs.realpathSync("."); console.log("Root directory: ", rootDir); const currentFile = document.uri; - console.log("Current file: ", currentFile); + console.log("Current file: ", currentFile); const posixFile = path.posix.relative(rootDir, currentFile); console.log("Current file: ", posixFile); // Remove all (old) labels from the current file: @@ -317,4 +319,4 @@ export function updateReferences( ); console.log("Done updating labels"); return references; -} \ No newline at end of file +} diff --git a/src/lsp-server/projectPtx/get-hover.ts b/src/lsp-server/projectPtx/get-hover.ts index 1fb9419..9ecb0c2 100644 --- a/src/lsp-server/projectPtx/get-hover.ts +++ b/src/lsp-server/projectPtx/get-hover.ts @@ -6,46 +6,46 @@ import { documents, getDocumentInfo } from "../state"; * Return all the linkable items in a project.ptx file. */ export async function getProjectPtxHoverInfo( - params: HoverParams + params: HoverParams ): Promise { - const uri = params.textDocument.uri; - const info = getDocumentInfo(uri); - const doc = documents.get(uri); - if (!info || !doc) { - console.warn("Requested project symbols for uninitialized file", uri); - return null; - } - const ast = await info.ast; - if (!ast) { - // There could be no AST because the document was malformed, so this isn't neccessarily an error. - return null; - } - const offset = doc.offsetAt(params.position); - const containingElm = elementAtOffset(offset, ast); - if (!containingElm) { - return null; - } - - // We know what element we're contained in, but not whether we are hovering over the body - // of the element or the actual tag hame. - const slicedText = doc - .getText() - .slice( - containingElm.position?.start.offset || 0, - containingElm.position?.end.offset || 0 - ); - // The first thing in our sliced text should be the opening tag, so we just search for the tag name. - const tagOffsetStart = slicedText.indexOf(containingElm.name); - const tagOffsetEnd = tagOffsetStart + containingElm.name.length; - const relOffset = offset - (containingElm.position?.start.offset || 0); - if (relOffset >= tagOffsetStart && relOffset < tagOffsetEnd) { - // Only return a hover hint if we are hovering over the tag name. - return { - contents: { - kind: MarkupKind.Markdown, - value: `Hovering over ${containingElm.name}`, - }, - }; - } + const uri = params.textDocument.uri; + const info = getDocumentInfo(uri); + const doc = documents.get(uri); + if (!info || !doc) { + console.warn("Requested project symbols for uninitialized file", uri); + return null; + } + const ast = await info.ast; + if (!ast) { + // There could be no AST because the document was malformed, so this isn't neccessarily an error. return null; + } + const offset = doc.offsetAt(params.position); + const containingElm = elementAtOffset(offset, ast); + if (!containingElm) { + return null; + } + + // We know what element we're contained in, but not whether we are hovering over the body + // of the element or the actual tag hame. + const slicedText = doc + .getText() + .slice( + containingElm.position?.start.offset || 0, + containingElm.position?.end.offset || 0 + ); + // The first thing in our sliced text should be the opening tag, so we just search for the tag name. + const tagOffsetStart = slicedText.indexOf(containingElm.name); + const tagOffsetEnd = tagOffsetStart + containingElm.name.length; + const relOffset = offset - (containingElm.position?.start.offset || 0); + if (relOffset >= tagOffsetStart && relOffset < tagOffsetEnd) { + // Only return a hover hint if we are hovering over the tag name. + return { + contents: { + kind: MarkupKind.Markdown, + value: `Hovering over ${containingElm.name}`, + }, + }; + } + return null; } diff --git a/src/lsp-server/projectPtx/get-links.ts b/src/lsp-server/projectPtx/get-links.ts index d223efd..57ea0d0 100644 --- a/src/lsp-server/projectPtx/get-links.ts +++ b/src/lsp-server/projectPtx/get-links.ts @@ -1,14 +1,14 @@ import { visit, EXIT } from "unist-util-visit"; import { - DocumentLink, - DocumentSymbol, - SymbolKind, + DocumentLink, + DocumentSymbol, + SymbolKind, } from "vscode-languageserver/node"; import { toString } from "xast-util-to-string"; import { - isElement, - positionOfSubstring, - unifiedPositionToLspPosition, + isElement, + positionOfSubstring, + unifiedPositionToLspPosition, } from "../../parse/utils"; import { getDocumentInfo } from "../state"; import { Element } from "xast"; @@ -18,56 +18,56 @@ type Position = Element["position"]; const LINK_CONTENT_NODES = new Set(["xsl", "source", "publication"]); function findLinkPosition(node: Element): Position { - const textNode = node.children[0]; - if ( - node.children.length !== 1 || - !textNode || - textNode.type !== "text" || - !textNode.position - ) { - return node.position; - } - const text = textNode.value; - const len = text.length; - const start = len - text.trimStart().length; - const end = text.trimEnd().length; + const textNode = node.children[0]; + if ( + node.children.length !== 1 || + !textNode || + textNode.type !== "text" || + !textNode.position + ) { + return node.position; + } + const text = textNode.value; + const len = text.length; + const start = len - text.trimStart().length; + const end = text.trimEnd().length; - const origPos = textNode.position; - const newPos = positionOfSubstring(start, end, origPos, text); + const origPos = textNode.position; + const newPos = positionOfSubstring(start, end, origPos, text); - return newPos; + return newPos; } /** * Return all the linkable items in a project.ptx file. */ export async function getProjectPtxLinks(uri: string): Promise { - const info = getDocumentInfo(uri); - if (!info) { - console.warn("Requested project symbols for uninitialized file", uri); - return []; - } - const ast = await info.ast; - if (!ast) { - // There could be no AST because the document was malformed, so this isn't neccessarily an error. - return []; - } - const links: Element[] = []; + const info = getDocumentInfo(uri); + if (!info) { + console.warn("Requested project symbols for uninitialized file", uri); + return []; + } + const ast = await info.ast; + if (!ast) { + // There could be no AST because the document was malformed, so this isn't neccessarily an error. + return []; + } + const links: Element[] = []; - visit(ast, (node, index, parent) => { - if (!isElement(node)) { - return; - } + visit(ast, (node, index, parent) => { + if (!isElement(node)) { + return; + } - if (LINK_CONTENT_NODES.has(node.name)) { - links.push(node); - } - }); + if (LINK_CONTENT_NODES.has(node.name)) { + links.push(node); + } + }); - // We convert into DocumentSymbol type. - return links.map((n) => ({ - range: unifiedPositionToLspPosition(findLinkPosition(n)), - target: "" + new URL(toString(n), uri), - tooltip: `${n.name} file at ${toString(n)}`, - })); + // We convert into DocumentSymbol type. + return links.map((n) => ({ + range: unifiedPositionToLspPosition(findLinkPosition(n)), + target: "" + new URL(toString(n), uri), + tooltip: `${n.name} file at ${toString(n)}`, + })); } diff --git a/src/lsp-server/projectPtx/get-symbols.ts b/src/lsp-server/projectPtx/get-symbols.ts index cd27671..7df826d 100644 --- a/src/lsp-server/projectPtx/get-symbols.ts +++ b/src/lsp-server/projectPtx/get-symbols.ts @@ -8,58 +8,56 @@ import { Element } from "xast"; type Position = Element["position"] & {}; export async function getProjectPtxSymbols( - uri: string + uri: string ): Promise { - const info = getDocumentInfo(uri); - if (!info) { - console.warn("Requested project symbols for uninitialized file", uri); - return []; + const info = getDocumentInfo(uri); + if (!info) { + console.warn("Requested project symbols for uninitialized file", uri); + return []; + } + const ast = await info.ast; + if (!ast) { + // There could be no AST because the document was malformed, so this isn't neccessarily an error. + return []; + } + const targets: { name: string; format: string; position: Position }[] = []; + + visit(ast, (node, index, parent) => { + if (!isElement(node)) { + return; } - const ast = await info.ast; - if (!ast) { - // There could be no AST because the document was malformed, so this isn't neccessarily an error. - return []; + if (node.name !== "targets") { + return; + } + // We've found the section. Collect all the data we need from it. + for (const t of node.children) { + if (!isElement(t) || t.name !== "target" || !t.attributes) { + continue; + } + const name = t.attributes["name"] || ""; + const formatNode = t.children.find( + (c) => isElement(c) && c.name === "format" + ); + const format = formatNode ? toString(formatNode) : ""; + const position: Position = t.position + ? t.position + : { + start: { line: 1, column: 1 }, + end: { line: 1, column: 1 }, + }; + targets.push({ format, position, name }); } - const targets: { name: string; format: string; position: Position }[] = []; - - visit(ast, (node, index, parent) => { - if (!isElement(node)) { - return; - } - if (node.name !== "targets") { - return; - } - // We've found the section. Collect all the data we need from it. - for (const t of node.children) { - if (!isElement(t) || t.name !== "target" || !t.attributes) { - continue; - } - const name = t.attributes["name"] || ""; - const formatNode = t.children.find( - (c) => isElement(c) && c.name === "format" - ); - const format = formatNode - ? toString(formatNode) - : ""; - const position: Position = t.position - ? t.position - : { - start: { line: 1, column: 1 }, - end: { line: 1, column: 1 }, - }; - targets.push({ format, position, name }); - } - return EXIT; - }); + return EXIT; + }); - console.log("targets", targets); - // We convert into DocumentSymbol type. - return targets.map((t) => ({ - kind: SymbolKind.Module, - name: t.name, - detail: `(${t.format})`, - range: unifiedPositionToLspPosition(t.position), - selectionRange: unifiedPositionToLspPosition(t.position), - })); + console.log("targets", targets); + // We convert into DocumentSymbol type. + return targets.map((t) => ({ + kind: SymbolKind.Module, + name: t.name, + detail: `(${t.format})`, + range: unifiedPositionToLspPosition(t.position), + selectionRange: unifiedPositionToLspPosition(t.position), + })); } diff --git a/src/lsp-server/projectPtx/is-project-ptx.ts b/src/lsp-server/projectPtx/is-project-ptx.ts index 4a45360..0331909 100644 --- a/src/lsp-server/projectPtx/is-project-ptx.ts +++ b/src/lsp-server/projectPtx/is-project-ptx.ts @@ -4,12 +4,12 @@ import { TextDocument } from "vscode-languageserver-textdocument"; * Determine whether a file is a `project.ptx` configuration file or not. */ export function isProjectPtx(textDocument: TextDocument | string): boolean { - let uri = textDocument; - if (typeof uri !== "string") { - uri = uri.uri; - } - if (uri.endsWith("project.ptx")) { - return true; - } - return false; + let uri = textDocument; + if (typeof uri !== "string") { + uri = uri.uri; + } + if (uri.endsWith("project.ptx")) { + return true; + } + return false; } diff --git a/src/parse/utils.ts b/src/parse/utils.ts index b16b20a..c3ae645 100644 --- a/src/parse/utils.ts +++ b/src/parse/utils.ts @@ -5,36 +5,36 @@ import { Element, Node } from "xast"; type Position = Element["position"] & {}; export function isElement(node: any): node is Element { - if (node && typeof node === "object" && node.type === "element") { - return true; - } + if (node && typeof node === "object" && node.type === "element") { + return true; + } - return false; + return false; } export function unifiedPositionToLspPosition(uPos: Element["position"]): Range { - if (!uPos) { - return { - start: { - line: 0, - character: 0, - }, - end: { - line: 0, - character: 0, - }, - }; - } - + if (!uPos) { return { - start: { - line: uPos.start.line - 1, - character: uPos.start.column - 1, - }, - end: { - line: uPos.end.line - 1, - character: uPos.end.column - 1, - }, + start: { + line: 0, + character: 0, + }, + end: { + line: 0, + character: 0, + }, }; + } + + return { + start: { + line: uPos.start.line - 1, + character: uPos.start.column - 1, + }, + end: { + line: uPos.end.line - 1, + character: uPos.end.column - 1, + }, + }; } /** @@ -43,92 +43,91 @@ export function unifiedPositionToLspPosition(uPos: Element["position"]): Range { * after `end` characters (relative to `origSource`). */ export function positionOfSubstring( - start: number, - end: number, - origPosition: Position, - origSource: string + start: number, + end: number, + origPosition: Position, + origSource: string ): Position { - let startLineOffset = 0; - let startColOffset = 0; + let startLineOffset = 0; + let startColOffset = 0; - let startBound = Math.min(start, origSource.length); - for (let i = 0; i < startBound; i++) { - if (origSource.charAt(i) === "\n") { - startLineOffset += 1; - startColOffset = 0; - } else { - startColOffset += 1; - } + let startBound = Math.min(start, origSource.length); + for (let i = 0; i < startBound; i++) { + if (origSource.charAt(i) === "\n") { + startLineOffset += 1; + startColOffset = 0; + } else { + startColOffset += 1; } + } - let endLineOffset = startLineOffset; - let endColOffset = startColOffset; - let endBound = Math.min(end, origSource.length); - for (let i = startBound; i < endBound; i++) { - if (origSource.charAt(i) === "\n") { - endLineOffset += 1; - endColOffset = 0; - } else { - endColOffset += 1; - } + let endLineOffset = startLineOffset; + let endColOffset = startColOffset; + let endBound = Math.min(end, origSource.length); + for (let i = startBound; i < endBound; i++) { + if (origSource.charAt(i) === "\n") { + endLineOffset += 1; + endColOffset = 0; + } else { + endColOffset += 1; } + } - return { - start: { - line: origPosition.start.line + startLineOffset, - column: - startLineOffset > 0 - ? startColOffset + 1 - : origPosition.start.column + startColOffset, - }, - end: { - line: origPosition.start.line + endLineOffset, - column: - startLineOffset > 0 - ? endColOffset + 1 - : origPosition.start.column + endColOffset, - }, - }; + return { + start: { + line: origPosition.start.line + startLineOffset, + column: + startLineOffset > 0 + ? startColOffset + 1 + : origPosition.start.column + startColOffset, + }, + end: { + line: origPosition.start.line + endLineOffset, + column: + startLineOffset > 0 + ? endColOffset + 1 + : origPosition.start.column + endColOffset, + }, + }; } /** * Grabs the nearest element to the cursor position, if there is one. */ export function elementAtOffset(offset: number, ast: Node): Element | null { - let ret: Element | null = null; - visit(ast, (node) => { - if (!isElement(node)) { - return; - } - const low = node.position?.start.offset || 0; - const hi = node.position?.end.offset || 0; - if (!ret && offset >= low && offset <= hi) { - ret = node; - return; - } - if ( - ret && - nodeHasSmallerRange(ret, node) && - offset >= low && - offset <= hi - ) { - ret = node; - return; - } - }); - return ret; + let ret: Element | null = null; + visit(ast, (node) => { + if (!isElement(node)) { + return; + } + const low = node.position?.start.offset || 0; + const hi = node.position?.end.offset || 0; + if (!ret && offset >= low && offset <= hi) { + ret = node; + return; + } + if ( + ret && + nodeHasSmallerRange(ret, node) && + offset >= low && + offset <= hi + ) { + ret = node; + return; + } + }); + return ret; } /** * Determine if `inner` is nested in `outer`. */ function nodeHasSmallerRange(outer: Node, inner: Node): boolean { - if (!outer.position || !inner.position) { - return false; - } - return ( - (outer.position.start.offset || 0) < - (inner.position.start.offset || 0) || - (outer.position.end.offset || 0) > (inner.position.end.offset || 0) - ); + if (!outer.position || !inner.position) { + return false; + } + return ( + (outer.position.start.offset || 0) < (inner.position.start.offset || 0) || + (outer.position.end.offset || 0) > (inner.position.end.offset || 0) + ); } diff --git a/src/parse/validate-xml.ts b/src/parse/validate-xml.ts index 193fc47..fafa902 100644 --- a/src/parse/validate-xml.ts +++ b/src/parse/validate-xml.ts @@ -7,25 +7,25 @@ import { Diagnostic, DiagnosticSeverity } from "vscode-languageserver/node"; * is empty, the parse was successful. */ export async function validateXml(source: string): Promise { - const ret: Diagnostic[] = []; - const parser = new SaxesParser({ xmlns: true, position: true }); - try { - parser.write(source).close(); - } catch (err: unknown) { - const e = err as Error; - const diagnostic: Diagnostic = { - severity: DiagnosticSeverity.Error, - range: { - start: { line: parser.line - 1, character: 0 }, - end: { line: parser.line - 1, character: parser.columnIndex }, - // start: textDocument.positionAt(m.index), - // end: textDocument.positionAt(m.index + m[0].length), - }, - message: e.message, - //source: "ex", - }; - ret.push(diagnostic); - } + const ret: Diagnostic[] = []; + const parser = new SaxesParser({ xmlns: true, position: true }); + try { + parser.write(source).close(); + } catch (err: unknown) { + const e = err as Error; + const diagnostic: Diagnostic = { + severity: DiagnosticSeverity.Error, + range: { + start: { line: parser.line - 1, character: 0 }, + end: { line: parser.line - 1, character: parser.columnIndex }, + // start: textDocument.positionAt(m.index), + // end: textDocument.positionAt(m.index + m[0].length), + }, + message: e.message, + //source: "ex", + }; + ret.push(diagnostic); + } - return ret; + return ret; } diff --git a/src/test/suite/extension.test.ts b/src/test/suite/extension.test.ts index 9bca13a..5060953 100644 --- a/src/test/suite/extension.test.ts +++ b/src/test/suite/extension.test.ts @@ -1,15 +1,15 @@ -import * as assert from 'assert'; +import * as assert from "assert"; // You can import and use all API from the 'vscode' module // as well as import your extension to test it -import * as vscode from 'vscode'; +import * as vscode from "vscode"; // import * as myExtension from '../../extension'; -suite('Extension Test Suite', () => { - vscode.window.showInformationMessage('Start all tests.'); +suite("Extension Test Suite", () => { + vscode.window.showInformationMessage("Start all tests."); - test('Sample test', () => { - assert.strictEqual([1, 2, 3].indexOf(5), -1); - assert.strictEqual([1, 2, 3].indexOf(0), -1); - }); + test("Sample test", () => { + assert.strictEqual([1, 2, 3].indexOf(5), -1); + assert.strictEqual([1, 2, 3].indexOf(0), -1); + }); }); diff --git a/src/types.ts b/src/types.ts index b460b31..3c7667f 100644 --- a/src/types.ts +++ b/src/types.ts @@ -25,7 +25,7 @@ type Snippets = { }; type CompletionItems = { - [key:string]: CompletionItem; + [key: string]: CompletionItem; }; export { CompletionItems, SpellCheckScope, Snippet, Snippets };