295 lines
13 KiB
JavaScript
295 lines
13 KiB
JavaScript
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
return new (P || (P = Promise))(function (resolve, reject) {
|
|
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
});
|
|
};
|
|
import ist from "ist";
|
|
import { LSPClient, LSPPlugin, renameSymbol, formatDocument, serverCompletion, hoverTooltips, showSignatureHelp, nextSignature } from "@codemirror/lsp-client";
|
|
import { EditorView } from "@codemirror/view";
|
|
import { javascript } from "@codemirror/lang-javascript";
|
|
import { syntaxHighlighting } from "@codemirror/language";
|
|
import { startCompletion, currentCompletions, acceptCompletion, autocompletion, moveCompletionSelection } from "@codemirror/autocomplete";
|
|
import { classHighlighter } from "@lezer/highlight";
|
|
import { DummyServer } from "./server.js";
|
|
function setup(conf = {}) {
|
|
let server = new DummyServer(conf.server);
|
|
let client = new LSPClient(conf.client);
|
|
client.connect(server);
|
|
return { server, client };
|
|
}
|
|
const URI = "file:///home/holly/src/test.js";
|
|
function ed(client, conf, uri = URI) {
|
|
return new EditorView(Object.assign(Object.assign({}, conf), { extensions: [conf.extensions || [], LSPPlugin.create(client, uri, "javascript")] }));
|
|
}
|
|
function sync(cm) {
|
|
LSPPlugin.get(cm).client.sync();
|
|
}
|
|
function wait(ms = 2) {
|
|
return new Promise(resolve => setTimeout(resolve, ms));
|
|
}
|
|
function place(cm) {
|
|
let ws = document.querySelector("#workspace");
|
|
while (ws.firstChild)
|
|
ws.firstChild.remove();
|
|
ws.appendChild(cm.dom);
|
|
setTimeout(() => cm.destroy(), 1000);
|
|
return cm;
|
|
}
|
|
describe("LSPClient", () => {
|
|
it("can connect to a server", () => {
|
|
let { client } = setup();
|
|
return client.initializing;
|
|
});
|
|
it("can open a file", () => __awaiter(void 0, void 0, void 0, function* () {
|
|
let { client, server } = setup();
|
|
ed(client, { doc: "stitchwort" });
|
|
yield wait();
|
|
ist(server.getFile(URI).text, "stitchwort");
|
|
}));
|
|
it("can update a file", () => __awaiter(void 0, void 0, void 0, function* () {
|
|
let { client, server } = setup();
|
|
let cm = ed(client, { doc: "goldenrod" });
|
|
cm.dispatch({ changes: { from: 1, insert: "-" } });
|
|
sync(cm);
|
|
yield wait();
|
|
ist(server.getFile(URI).text, cm.state.sliceDoc());
|
|
}));
|
|
it("can update a file with multiple changes", () => __awaiter(void 0, void 0, void 0, function* () {
|
|
let { client, server } = setup();
|
|
let cm = ed(client, { doc: "hawkweed\n".repeat(1000) });
|
|
yield wait();
|
|
cm.dispatch({ changes: [{ from: 0, insert: "<" }, { from: cm.state.doc.length, insert: ">" }] });
|
|
sync(cm);
|
|
yield wait();
|
|
ist(server.getFile(URI).text, cm.state.sliceDoc());
|
|
}));
|
|
it("can close a file", () => __awaiter(void 0, void 0, void 0, function* () {
|
|
let { client, server } = setup();
|
|
let cm = ed(client, { doc: "cowleek" });
|
|
yield wait();
|
|
cm.destroy();
|
|
yield wait();
|
|
ist(!server.getFile(URI));
|
|
}));
|
|
it("can open multiple files", () => __awaiter(void 0, void 0, void 0, function* () {
|
|
let { client, server } = setup();
|
|
let cm1 = ed(client, { doc: "elder" }), cm2 = ed(client, { doc: "alfalfa" }, "file:///x");
|
|
cm1.dispatch({ changes: { from: 5, insert: "?" } });
|
|
cm2.dispatch({ changes: { from: 7, insert: "!" } });
|
|
sync(cm1);
|
|
yield wait();
|
|
ist(server.getFile(URI).text, "elder?");
|
|
ist(server.getFile("file:///x").text, "alfalfa!");
|
|
}));
|
|
it("can provide mapping", () => __awaiter(void 0, void 0, void 0, function* () {
|
|
let { client } = setup();
|
|
let cm = ed(client, { doc: "1\n2\n3" });
|
|
yield client.withMapping((mapping) => __awaiter(void 0, void 0, void 0, function* () {
|
|
let req = client.request("textDocument/formatting", {
|
|
textDocument: { uri: URI },
|
|
options: { tabSize: 2, insertSpaces: true }
|
|
});
|
|
cm.dispatch({ changes: { from: 1, to: 3 } });
|
|
let response = yield req;
|
|
ist(response[0].range.start.line, 2);
|
|
ist(mapping.mapPos(URI, 5), 3);
|
|
}));
|
|
}));
|
|
it("can provide mapping across syncs", () => __awaiter(void 0, void 0, void 0, function* () {
|
|
let { client } = setup({ server: { delay: { "textDocument/formatting": 10 } } });
|
|
let cm = ed(client, { doc: "1\n2\n3" });
|
|
yield client.withMapping((mapping) => __awaiter(void 0, void 0, void 0, function* () {
|
|
let req = client.request("textDocument/formatting", {
|
|
textDocument: { uri: URI },
|
|
options: { tabSize: 2, insertSpaces: true }
|
|
});
|
|
cm.dispatch({ changes: { from: 1, to: 3 } });
|
|
sync(cm);
|
|
cm.dispatch({ changes: { from: 0, insert: "#" } });
|
|
let response = yield req;
|
|
ist(response[0].range.start.line, 2);
|
|
ist(mapping.mapPos(URI, 5), 4);
|
|
}));
|
|
}));
|
|
it("reports invalid methods", () => __awaiter(void 0, void 0, void 0, function* () {
|
|
let { client } = setup();
|
|
try {
|
|
yield client.request("none/such", {});
|
|
ist(false);
|
|
}
|
|
catch (e) {
|
|
ist(e.code, -32601);
|
|
}
|
|
}));
|
|
it("can receive custom notifications", () => __awaiter(void 0, void 0, void 0, function* () {
|
|
let received = null;
|
|
let { client } = setup({ client: { notificationHandlers: {
|
|
"custom/notification": (client, params) => received = params
|
|
} } });
|
|
client.request("custom/sendNotification", { method: "custom/notification", params: { verify: true } });
|
|
yield wait();
|
|
ist(received.verify);
|
|
}));
|
|
it("can report unknown notifications", () => __awaiter(void 0, void 0, void 0, function* () {
|
|
let received = null;
|
|
let { client } = setup({ client: { unhandledNotification: (client, method, params) => received = params } });
|
|
client.request("custom/sendNotification", { method: "custom/notification", params: { verify: true } });
|
|
yield wait();
|
|
ist(received.verify);
|
|
}));
|
|
it("can display messages in the editor", () => __awaiter(void 0, void 0, void 0, function* () {
|
|
let { client } = setup();
|
|
let cm = ed(client, {});
|
|
client.request("custom/sendNotification", { method: "window/showMessage", params: { type: 2 /* Warning */, message: "WARNING" } });
|
|
yield wait();
|
|
let dialog = cm.dom.querySelector(".cm-lsp-message-warning");
|
|
ist(dialog);
|
|
ist(dialog.innerHTML.indexOf("WARNING"), "-1", ">");
|
|
}));
|
|
it("routes exceptions from Transport.send to the request promise", () => __awaiter(void 0, void 0, void 0, function* () {
|
|
let broken = false;
|
|
let { client } = setup({ server: { brokenPipe: () => broken } });
|
|
yield client.initializing;
|
|
broken = true;
|
|
let req = client.request("test", {});
|
|
try {
|
|
yield req;
|
|
ist(false);
|
|
}
|
|
catch (e) {
|
|
ist(e.message, "Broken Pipe");
|
|
}
|
|
}));
|
|
describe("LSPPlugin", () => {
|
|
it("can render doc strings", () => {
|
|
let { client } = setup({ client: { sanitizeHTML: s => s.replace(/x/g, "y") } });
|
|
let cm = ed(client, {});
|
|
ist(LSPPlugin.get(cm).docToHTML({ kind: "markdown", value: "# xx" }), "<h1>yy</h1>\n");
|
|
});
|
|
it("can render doc strings with highlighting", () => {
|
|
let { client } = setup();
|
|
let cm = ed(client, { extensions: [
|
|
javascript(),
|
|
syntaxHighlighting(classHighlighter)
|
|
] });
|
|
ist(LSPPlugin.get(cm).docToHTML({ kind: "markdown", value: "```javascript\nreturn\n```" }), '<pre><code class="language-javascript"><span class="tok-keyword">return</span>\n</code></pre>\n');
|
|
});
|
|
it("can convert to LSP positions", () => {
|
|
let { client } = setup();
|
|
let cm = ed(client, { doc: "one\ntwo\nthree" });
|
|
let pos = LSPPlugin.get(cm).toPosition(6);
|
|
ist(pos.line, 1);
|
|
ist(pos.character, 2);
|
|
});
|
|
it("can convert from positions", () => {
|
|
let { client } = setup();
|
|
let cm = ed(client, { doc: "one\ntwo\nthree" });
|
|
ist(LSPPlugin.get(cm).fromPosition({ line: 0, character: 3 }), 3);
|
|
ist(LSPPlugin.get(cm).fromPosition({ line: 2, character: 1 }), 9);
|
|
});
|
|
it("can display errors", () => {
|
|
let { client } = setup();
|
|
let cm = ed(client, {});
|
|
LSPPlugin.get(cm).reportError("E", "Oh no");
|
|
ist(cm.dom.querySelector(".cm-lsp-message-error").innerHTML.indexOf("Oh no"), -1, ">");
|
|
});
|
|
});
|
|
describe("renameSymbol", () => {
|
|
it("can run a rename", () => __awaiter(void 0, void 0, void 0, function* () {
|
|
let { client } = setup();
|
|
let cm = place(ed(client, { doc: "let foo = 1; console.log(foo)", selection: { anchor: 4 } }));
|
|
let cm2 = ed(client, { doc: "foo?" }, "file:///2");
|
|
yield wait();
|
|
ist(renameSymbol(cm), true);
|
|
let form = cm.dom.querySelector(".cm-panel form");
|
|
form.querySelector("input").value = "bar";
|
|
form.requestSubmit();
|
|
yield wait();
|
|
ist(cm.state.sliceDoc(), "let bar = 1; console.log(bar)");
|
|
ist(cm2.state.sliceDoc(), "bar?");
|
|
cm.destroy();
|
|
}));
|
|
it("can handle changes during the request", () => __awaiter(void 0, void 0, void 0, function* () {
|
|
let { client } = setup({ server: { delay: { "textDocument/rename": 5 } } });
|
|
let cm = place(ed(client, { doc: "let foo = 1; console.log(foo)", selection: { anchor: 4 } }));
|
|
yield wait();
|
|
ist(renameSymbol(cm), true);
|
|
let form = cm.dom.querySelector(".cm-panel form");
|
|
form.querySelector("input").value = "bar";
|
|
form.requestSubmit();
|
|
yield wait();
|
|
cm.dispatch({ changes: { from: 0, insert: " " } });
|
|
yield wait(10);
|
|
ist(cm.state.sliceDoc(), " let bar = 1; console.log(bar)");
|
|
cm.destroy();
|
|
}));
|
|
});
|
|
describe("formatDocument", () => {
|
|
it("can make format requests", () => __awaiter(void 0, void 0, void 0, function* () {
|
|
let { client } = setup();
|
|
let cm = ed(client, { doc: "hawthorn" });
|
|
formatDocument(cm);
|
|
yield wait();
|
|
ist(cm.state.sliceDoc(), "hawthorn\n// formatted!");
|
|
}));
|
|
});
|
|
describe("completion", () => {
|
|
it("can get completions from the server", () => __awaiter(void 0, void 0, void 0, function* () {
|
|
let { client } = setup();
|
|
let cm = ed(client, { doc: "..o", selection: { anchor: 3 }, extensions: [
|
|
serverCompletion(),
|
|
autocompletion({ interactionDelay: 0, activateOnTypingDelay: 10 })
|
|
] });
|
|
startCompletion(cm);
|
|
yield wait(60);
|
|
let cs = currentCompletions(cm.state);
|
|
ist(cs.length, 2);
|
|
ist(cs[0].label, "one!");
|
|
ist(cs[1].label, "ookay");
|
|
acceptCompletion(cm);
|
|
ist(cm.state.sliceDoc(), "..one!");
|
|
cm.dispatch({ changes: { from: 6, insert: "\no" }, userEvent: "input.type", selection: { anchor: 8 } });
|
|
yield wait(20);
|
|
ist(currentCompletions(cm.state).length, 2);
|
|
moveCompletionSelection(true)(cm);
|
|
yield wait();
|
|
ist(cm.dom.querySelector(".cm-completionInfo"));
|
|
acceptCompletion(cm);
|
|
ist(cm.state.sliceDoc(), "..one!\nookay");
|
|
}));
|
|
});
|
|
describe("hoverTooltips", () => {
|
|
it("can retrieve hover info", () => __awaiter(void 0, void 0, void 0, function* () {
|
|
let { client } = setup();
|
|
let cm = place(ed(client, { doc: "speedwell", extensions: [
|
|
hoverTooltips({ hoverTime: 10 }),
|
|
javascript(),
|
|
syntaxHighlighting(classHighlighter)
|
|
] }));
|
|
let pos = cm.coordsAtPos(1), x = pos.left, y = pos.top + 5;
|
|
cm.contentDOM.firstChild.dispatchEvent(new MouseEvent("mousemove", {
|
|
screenX: x, screenY: y,
|
|
clientX: x, clientY: y,
|
|
bubbles: true
|
|
}));
|
|
yield wait(15);
|
|
ist(cm.dom.querySelector(".cm-tooltip .tok-string"));
|
|
cm.destroy();
|
|
}));
|
|
});
|
|
describe("signatureHelp", () => {
|
|
it("can display a signature", () => __awaiter(void 0, void 0, void 0, function* () {
|
|
let { client } = setup();
|
|
let cm = ed(client, { doc: "bugloss" });
|
|
showSignatureHelp(cm);
|
|
yield wait();
|
|
ist(cm.dom.querySelector(".cm-lsp-active-parameter").innerHTML, "b");
|
|
nextSignature(cm);
|
|
ist(cm.dom.querySelector(".cm-lsp-active-parameter").innerHTML, "y");
|
|
}));
|
|
});
|
|
});
|