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" }), "
return\n\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");
}));
});
});