Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 70 additions & 0 deletions src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@ export const incLangId = "objectscript-macros";
export const cspLangId = "objectscript-csp";
export const outputLangId = "vscode-objectscript-output";

const dotPrefixRegex = /^(\s*(?:\.\s*)+)/;
const dotIndentLanguages = new Set<string>([macLangId, intLangId]);
const dotIndentSkipDocuments = new Set<string>();

import * as url from "url";
import path = require("path");
import {
Expand Down Expand Up @@ -1050,6 +1054,72 @@ export async function activate(context: vscode.ExtensionContext): Promise<any> {
}
}

context.subscriptions.push(
vscode.workspace.onDidChangeTextDocument(async (event) => {
if (!dotIndentLanguages.has(event.document.languageId)) {
return;
}

const docUriString = event.document.uri.toString();
if (dotIndentSkipDocuments.has(docUriString)) {
return;
}

const editor = vscode.window.visibleTextEditors.find((e) => e.document === event.document);
if (!editor) {
return;
}

for (const change of event.contentChanges) {
if (!change.text.includes("\n")) {
continue;
}

const newLineNumber = change.range.start.line + 1;
if (newLineNumber >= event.document.lineCount || newLineNumber <= 0) {
continue;
}

const previousLine = event.document.lineAt(newLineNumber - 1).text;
const prefixMatch = previousLine.match(dotPrefixRegex);
if (!prefixMatch) {
continue;
}

let insertText = prefixMatch[1];
if (!insertText.endsWith(" ")) {
insertText += " ";
}

const remainder = previousLine.slice(prefixMatch[1].length);
if (remainder.startsWith(";")) {
insertText += ";";
}

const newLine = event.document.lineAt(newLineNumber);
if (newLine.text.startsWith(insertText)) {
continue;
}

const indentMatch = newLine.text.match(/^\s*/);
const indentLength = indentMatch ? indentMatch[0].length : 0;
const replaceRange = new vscode.Range(newLine.range.start, new vscode.Position(newLineNumber, indentLength));

dotIndentSkipDocuments.add(docUriString);
try {
await editor.edit(
(editBuilder) => {
editBuilder.replace(replaceRange, insertText);
},
{ undoStopBefore: false, undoStopAfter: false }
);
} finally {
dotIndentSkipDocuments.delete(docUriString);
}
}
})
);

openedClasses = workspaceState.get("openedClasses") ?? [];

/** The stringified URIs of all `isfs` documents that are currently open in a UI tab */
Expand Down
47 changes: 47 additions & 0 deletions src/test/suite/extension.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,17 @@ async function waitForIndexedDocument(documentName: string, workspaceFolderName:
assert.fail(`Timed out waiting for '${documentName}' to be indexed in workspace folder '${workspaceFolderName}'.`);
}

async function waitForCondition(predicate: () => boolean, timeoutMs = 1000, message?: string): Promise<void> {
const start = Date.now();
while (Date.now() - start < timeoutMs) {
if (predicate()) {
return;
}
await new Promise((resolve) => setTimeout(resolve, 10));
}
assert.fail(message ?? "Timed out waiting for condition");
}

function getDefinitionTargets(definitions: (vscode.Location | vscode.DefinitionLink)[]): vscode.Uri[] {
return definitions
.map((definition) => ("targetUri" in definition ? definition.targetUri : definition.uri))
Expand All @@ -44,6 +55,42 @@ suite("Extension Test Suite", () => {
assert.ok("All good");
});

test("Dot-prefixed statements continue on newline", async () => {
const document = await vscode.workspace.openTextDocument({
language: "objectscript",
content: " . Do ##class(Test).Run()",
});
const editor = await vscode.window.showTextDocument(document);
try {
await editor.edit((editBuilder) => {
editBuilder.insert(document.lineAt(0).range.end, "\n");
});
await waitForCondition(() => document.lineCount > 1);
await waitForCondition(() => document.lineAt(1).text.length > 0);
assert.strictEqual(document.lineAt(1).text, " . ");
} finally {
await vscode.commands.executeCommand("workbench.action.closeActiveEditor");
}
});

test("Dot-prefixed semicolon comments continue on newline", async () => {
const document = await vscode.workspace.openTextDocument({
language: "objectscript",
content: " . ; Comment",
});
const editor = await vscode.window.showTextDocument(document);
try {
await editor.edit((editBuilder) => {
editBuilder.insert(document.lineAt(0).range.end, "\n");
});
await waitForCondition(() => document.lineCount > 1);
await waitForCondition(() => document.lineAt(1).text.length > 0);
assert.strictEqual(document.lineAt(1).text, " . ;");
} finally {
await vscode.commands.executeCommand("workbench.action.closeActiveEditor");
}
});

test("Go to Definition resolves to sibling workspace folder", async function () {
this.timeout(10000);
await waitForIndexedDocument("MultiRoot.Shared.cls", "shared");
Expand Down