Skip to content

Commit

Permalink
first try at reference completions
Browse files Browse the repository at this point in the history
  • Loading branch information
oscarlevin committed Sep 20, 2024
1 parent 6e78a90 commit e98c5c9
Show file tree
Hide file tree
Showing 6 changed files with 211 additions and 175 deletions.
106 changes: 54 additions & 52 deletions src/completions.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import * as vscode from "vscode";
import * as fs from "fs";
import { _context } from "./extension";
import { labels } from "./extension";
// import { labels } from "./extension";
import { elementChildren } from "./constants";

function readJsonFile(relativePath: string): any {
Expand Down Expand Up @@ -225,51 +225,53 @@ async function elementCompletions(
return elementCompletionItems;
}

async function refCompletions(
document: vscode.TextDocument,
position: vscode.Position,
token: vscode.CancellationToken,
context: vscode.CompletionContext
) {
// const refs = readJsonFile("snippets/pretext-attributes.json");
const linePrefix = document
.lineAt(position.line)
.text.slice(0, position.character);
let completionItems: vscode.CompletionItem[] = [];
if (linePrefix.match(/<xref ref=\"$/)) {
for (let [reference, parent] of labels) {
const refCompletion = new vscode.CompletionItem(
reference,
vscode.CompletionItemKind.Reference
);
refCompletion.insertText = new vscode.SnippetString(reference);
refCompletion.documentation = "(a " + parent + ")";
refCompletion.detail = "(reference to " + parent + ")";
refCompletion.sortText = "0" + reference;
completionItems.push(refCompletion);
}
} else if (linePrefix.match(/<xi:include href="$/)) {
const files = await vscode.workspace.findFiles("**/source/**");
// const currentFile = vscode.workspace.asRelativePath(document.fileName);
// console.log(currentFile);
// get relative paths:
for (let file of files) {
let relativePath = vscode.workspace
.asRelativePath(file)
.replace("source/", "");
console.log(relativePath);
const refCompletion = new vscode.CompletionItem(
relativePath,
vscode.CompletionItemKind.Reference
);
refCompletion.insertText = new vscode.SnippetString(relativePath);
completionItems.push(refCompletion);
}
} else {
return undefined;
}
return completionItems;
}


// async function refCompletions(
// document: vscode.TextDocument,
// position: vscode.Position,
// token: vscode.CancellationToken,
// context: vscode.CompletionContext
// ) {
// // const refs = readJsonFile("snippets/pretext-attributes.json");
// const linePrefix = document
// .lineAt(position.line)
// .text.slice(0, position.character);
// let completionItems: vscode.CompletionItem[] = [];
// if (linePrefix.match(/<xref ref=\"$/)) {
// for (let [reference, parent] of labels) {
// const refCompletion = new vscode.CompletionItem(
// reference,
// vscode.CompletionItemKind.Reference
// );
// refCompletion.insertText = new vscode.SnippetString(reference);
// refCompletion.documentation = "(a " + parent + ")";
// refCompletion.detail = "(reference to " + parent + ")";
// refCompletion.sortText = "0" + reference;
// completionItems.push(refCompletion);
// }
// } else if (linePrefix.match(/<xi:include href="$/)) {
// const files = await vscode.workspace.findFiles("**/source/**");
// // const currentFile = vscode.workspace.asRelativePath(document.fileName);
// // console.log(currentFile);
// // get relative paths:
// for (let file of files) {
// let relativePath = vscode.workspace
// .asRelativePath(file)
// .replace("source/", "");
// console.log(relativePath);
// const refCompletion = new vscode.CompletionItem(
// relativePath,
// vscode.CompletionItemKind.Reference
// );
// refCompletion.insertText = new vscode.SnippetString(relativePath);
// completionItems.push(refCompletion);
// }
// } else {
// return undefined;
// }
// return completionItems;
// }

/**
* Activate completions for PreTeXt
Expand All @@ -283,11 +285,11 @@ export function activateCompletions(context: vscode.ExtensionContext) {
"@"
);

const refProvider = vscode.languages.registerCompletionItemProvider(
"pretext",
{ provideCompletionItems: refCompletions },
'"'
);
// const refProvider = vscode.languages.registerCompletionItemProvider(
// "pretext",
// { provideCompletionItems: refCompletions },
// '"'
// );

const elementProvider = vscode.languages.registerCompletionItemProvider(
"pretext",
Expand All @@ -303,7 +305,7 @@ export function activateCompletions(context: vscode.ExtensionContext) {
context.subscriptions.push(
elementProvider,
attributeProvider,
refProvider
// refProvider
// inlineProvider
);
console.log("Activated completion provider");
Expand Down
22 changes: 2 additions & 20 deletions src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,12 @@ import { formatPTX } from "./formatter";
import { ptxExec } from "./utils";
import * as utils from "./utils";
import { ptxCommandList } from "./constants";
// import { activateCompletions } from "./completions";

// Set up types:
import {
activate as lspActivate,
deactivate as lspDeactivate,
} from "./lsp-client/main";
type LabelArray = [string, string, string][];

// Set up vscode elements
export let _context: vscode.ExtensionContext;
Expand All @@ -26,7 +24,7 @@ let pretextTerminal: vscode.Terminal;
var lastTarget = "";
let pretextCommandList = ptxCommandList;

export let labels: LabelArray = [];


// The main function to run pretext commands:
async function runPretext(
Expand Down Expand Up @@ -303,23 +301,7 @@ export async function activate(context: vscode.ExtensionContext) {
console.log("Error setting up formatter");
}

///////////////// Completion Items //////////////////////
// labels = [];
// try {
// labels = await utils.getReferences();
// } catch {
// console.log("Error getting references");
// }

// try {
// activateCompletions(context);
// } catch {
// console.log("Error setting up completions");
// }

// vscode.workspace.onDidSaveTextDocument(async (document) => {
// labels = await utils.updateReferences(document, labels);
// });

///////////////// Commands //////////////////////

context.subscriptions.push(
Expand Down
14 changes: 11 additions & 3 deletions src/lsp-server/completions/get-completions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import {
import { ATTRIBUTES, ELEMENTS } from "./constants";
import { Position, Range, TextDocument } from "vscode-languageserver-textdocument";
import { elementChildren } from "../../constants";
import { references } from "../main";

const LINK_CONTENT_NODES = new Set(["xsl", "source", "publication"]);

Expand Down Expand Up @@ -83,9 +84,16 @@ export async function getCompletions(
})];
console.log("completionItems", completionItems);
} else if (completionType === "ref") {
// Form completions for references.
// return await refCompletions(params);
return null;
for (let [reference, parent] of references) {
const refCompletion: CompletionItem = {
label: reference,
kind: CompletionItemKind.Reference
};;
refCompletion.documentation = "(a " + parent + ")";
refCompletion.detail = "(reference to " + parent + ")";
refCompletion.sortText = "0" + reference;
completionItems.push(refCompletion);
}
} else {
// Completions for attributes and elements:
// First, stop completions if the previous character (or the one before it in case a trigger character is used) is not a space.
Expand Down
114 changes: 114 additions & 0 deletions src/lsp-server/completions/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ import {
} from "vscode-languageserver/node";
import { documents } from "../state";
import { TextDocument } from "vscode-languageserver-textdocument";
import path from "path";
import { URI, Utils } from "vscode-uri";

export async function readJsonFile(relativePath: string): Promise<any> {
try {
Expand Down Expand Up @@ -200,3 +202,115 @@ export async function getSnippetCompletionItems(
}
return completionItems;
}



function getMainFile() {
let dir = "./";
// Set default main file to main.ptx
let mainFile = path.join(dir, "source", "main.ptx");
// Check if project.ptx exists and if so, use that to find main file
let project = path.join(dir, "project.ptx");
if (fs.existsSync(project)) {
console.log("Found project.ptx");
const text = fs.readFileSync(project).toString();
// Determine whether v1 or v2:
let regexVersion = /<project\s(.*?)ptx-version="2"/;
if (regexVersion.test(text)) {
console.log("project.ptx is version 2");
let regexProject = /<project\s(.*?)source="(.*?)"/;
let regexTarget = /<target\s(.*?)source="(.*?)"/;
let projectSourceMatch = regexProject.exec(text);
let targetSourceMatch = regexTarget.exec(text);
if (projectSourceMatch) {
mainFile = path.join(dir, projectSourceMatch[2]);
if (targetSourceMatch) {
mainFile = path.join(mainFile, targetSourceMatch[2]);
}
} else if (targetSourceMatch) {
mainFile = path.join(dir, targetSourceMatch[2]);
}
} else {
console.log("project.ptx is legacy version");
let regexTarget = /<source>(.*?)<\/source>/;
let targetSourceMatch = regexTarget.exec(text);
if (targetSourceMatch) {
mainFile = path.join(dir, targetSourceMatch[1]);
}
}
}
console.log("Checking for main source file: ", mainFile);
if (fs.existsSync(mainFile)) {
console.log("Found main source file", mainFile);
return mainFile;
} else {
console.log("main source file not found");
return "";
}
}


// define a type for the array of labels:
type LabelArray = [string, string, string][];

/**
* Search through a project to find all xml:id's. Start with main.ptx and include any fine that is xi:included up to a depth of 5.
*/
export function getReferences(): LabelArray {
// Look through all files in project directory and collect all labels contained as xml:id attributes.
let baseFile = getMainFile();
let references: LabelArray = [];
let files = [baseFile];
let depth = 0;
// const uri = vscode.Uri.file(sourceDir);
// let files = await vscode.workspace.fs.readDirectory(uri);
while (depth < 5 && files.length > 0) {
let newFiles: string[] = [];
for (const file of files) {
if (fs.existsSync(file)) {
let text = fs.readFileSync(file).toString();
let regex = /<xi:include\s+href="([^"]+)"/g;
let matches = [...text.matchAll(regex)];
newFiles = newFiles.concat(
matches.map((match) => path.join(file, "..", match[1]))
);
regex = /<(\w*?)\s(.*?)xml:id="([^"]+?)"/g;
matches = [...text.matchAll(regex)];
const posixFile = file.replace(/\\/g, "/");
references = references.concat(
matches.map((match) => [match[3], match[1], posixFile])
);
}
}
files = newFiles;
depth++;
}
console.log("Finished collecting references, reached depth of ", depth);
return references;
}

export function updateReferences(
document: TextDocument,
references: LabelArray = []
) {
console.log("Updating references");
// Look through the specified file collect all labels contained as xml:id attributes.
// This can then be used to update the current list of references every time a file is saved.
let fileContents = document.getText();
let regex = /<(\w*?)\s(.*?)xml:id="([^"]+)"/g;
let matches = [...fileContents.matchAll(regex)];
const rootDir = fs.realpathSync(".");
console.log("Root directory: ", rootDir);
const currentFile = document.uri;
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:
references = references.filter((label) => label[2] !== posixFile);
// Add all (new) labels from the current file:
references = references.concat(
matches.map((match) => [match[3], match[1], posixFile])
);
console.log("Done updating labels");
return references;
}
29 changes: 28 additions & 1 deletion src/lsp-server/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,25 @@ import {
getCompletionDetails,
} from "./completions/get-completions";
import { formatDocument } from "./formatter";
import { getReferences, updateReferences } from "./completions/utils";

let hasConfigurationCapability = false;
let hasWorkspaceFolderCapability = false;
export let hasDiagnosticRelatedInformationCapability = false;

connection.onInitialize((params: InitializeParams) => {

type LabelArray = [string, string, string][];
export let references: LabelArray = [];





// vscode.workspace.onDidSaveTextDocument(async (document) => {
// labels = await utils.updateReferences(document, labels);
// });

connection.onInitialize( (params: InitializeParams) => {
const capabilities = params.capabilities;

// Does the client support the `workspace/configuration` request?
Expand Down Expand Up @@ -92,6 +105,14 @@ connection.onInitialized(() => {
connection.console.log("Workspace folder change event received.");
});
}

///////////////// Completion Items //////////////////////
references = [];
try {
references = getReferences();
} catch {
console.log("Error getting references");
}
});

connection.onDidChangeConfiguration((change) => {
Expand Down Expand Up @@ -128,6 +149,12 @@ documents.onDidChangeContent(async (change) => {
}
});

documents.onDidSave((e) => {
// update references for the saved document
// TODO: this should just update references instead of finding all of them again.
references = getReferences();
});

connection.onDidChangeWatchedFiles((_change) => {
// Monitored files have change in VSCode
connection.console.log("We received a file change event");
Expand Down
Loading

0 comments on commit e98c5c9

Please sign in to comment.