[mod] update ebnf
This commit is contained in:
parent
3d94ef0d60
commit
7f55855473
12 changed files with 2491 additions and 235 deletions
57
test/server.js
Normal file
57
test/server.js
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
export class DummyServer {
|
||||
constructor(config = {}) {
|
||||
this.config = config;
|
||||
this.initialized = false;
|
||||
this.subscribers = [];
|
||||
this.files = [];
|
||||
}
|
||||
subscribe(listener) {
|
||||
this.subscribers.push(listener);
|
||||
}
|
||||
unsubscribe(listener) {
|
||||
this.subscribers = this.subscribers.filter(l => l != listener);
|
||||
}
|
||||
send(message) {
|
||||
var _a, _b;
|
||||
if ((_b = (_a = this.config).brokenPipe) === null || _b === void 0 ? void 0 : _b.call(_a))
|
||||
throw new Error("Broken Pipe");
|
||||
const msg = JSON.parse(message);
|
||||
if ("id" in msg) {
|
||||
this.handleRequest(msg.method, msg.params).then(result => {
|
||||
this.broadcast({ jsonrpc: "2.0", id: msg.id, result });
|
||||
}, e => {
|
||||
let error = e instanceof ServerError ? { code: e.code, message: e.message }
|
||||
: { code: -32603 /* InternalError */, message: String(e) };
|
||||
this.broadcast({ jsonrpc: "2.0", id: msg.id, error });
|
||||
});
|
||||
}
|
||||
else {
|
||||
this.handleNotification(msg.method, msg.params);
|
||||
}
|
||||
}
|
||||
broadcast(message) {
|
||||
for (let sub of this.subscribers)
|
||||
sub(JSON.stringify(message));
|
||||
}
|
||||
handleRequest(method, params) {
|
||||
return new Promise(resolve => {
|
||||
var _a;
|
||||
if (!this.initialized && method != "initialize")
|
||||
throw new ServerError(-32002 /* ServerNotInitialized */, "Not initialized");
|
||||
let handler = requestHandlers[method];
|
||||
if (!handler)
|
||||
throw new ServerError(-32601 /* MethodNotFound */, "Method not found");
|
||||
let result = handler(params, this), delay = (_a = this.config.delay) === null || _a === void 0 ? void 0 : _a[method];
|
||||
if (delay)
|
||||
setTimeout(() => resolve(result), delay);
|
||||
else
|
||||
queueMicrotask(() => resolve(result));
|
||||
});
|
||||
}
|
||||
handleNotification(method, params) {
|
||||
let handler = notificationHandlers[method];
|
||||
if (handler)
|
||||
handler(params, this);
|
||||
}
|
||||
getFile(uri) { return this.files.find(f => f.uri == uri); }
|
||||
}
|
||||
295
test/webtest-client.js
Normal file
295
test/webtest-client.js
Normal file
|
|
@ -0,0 +1,295 @@
|
|||
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");
|
||||
}));
|
||||
});
|
||||
});
|
||||
317
test/webtest-client.ts
Normal file
317
test/webtest-client.ts
Normal file
|
|
@ -0,0 +1,317 @@
|
|||
import type * as lsp from "vscode-languageserver-protocol"
|
||||
import ist from "ist"
|
||||
import {LSPClientConfig, LSPClient, LSPPlugin, renameSymbol,
|
||||
formatDocument, serverCompletion, hoverTooltips,
|
||||
showSignatureHelp, nextSignature} from "@codemirror/lsp-client"
|
||||
import {EditorView, EditorViewConfig} 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: {client?: LSPClientConfig, server?: ConstructorParameters<typeof DummyServer>[0]} = {}) {
|
||||
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: LSPClient, conf: EditorViewConfig, uri = URI) {
|
||||
return new EditorView({...conf, extensions: [conf.extensions || [], LSPPlugin.create(client, uri, "javascript")]})
|
||||
}
|
||||
|
||||
function sync(cm: EditorView) {
|
||||
LSPPlugin.get(cm)!.client.sync()
|
||||
}
|
||||
|
||||
function wait(ms: number = 2) {
|
||||
return new Promise(resolve => setTimeout(resolve, ms))
|
||||
}
|
||||
|
||||
function place(cm: EditorView) {
|
||||
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", async () => {
|
||||
let {client, server} = setup()
|
||||
ed(client, {doc: "stitchwort"})
|
||||
await wait()
|
||||
ist(server.getFile(URI)!.text, "stitchwort")
|
||||
})
|
||||
|
||||
it("can update a file", async () => {
|
||||
let {client, server} = setup()
|
||||
let cm = ed(client, {doc: "goldenrod"})
|
||||
cm.dispatch({changes: {from: 1, insert: "-"}})
|
||||
sync(cm)
|
||||
await wait()
|
||||
ist(server.getFile(URI)!.text, cm.state.sliceDoc())
|
||||
})
|
||||
|
||||
it("can update a file with multiple changes", async () => {
|
||||
let {client, server} = setup()
|
||||
let cm = ed(client, {doc: "hawkweed\n".repeat(1000)})
|
||||
await wait()
|
||||
cm.dispatch({changes: [{from: 0, insert: "<"}, {from: cm.state.doc.length, insert: ">"}]})
|
||||
sync(cm)
|
||||
await wait()
|
||||
ist(server.getFile(URI)!.text, cm.state.sliceDoc())
|
||||
})
|
||||
|
||||
it("can close a file", async () => {
|
||||
let {client, server} = setup()
|
||||
let cm = ed(client, {doc: "cowleek"})
|
||||
await wait()
|
||||
cm.destroy()
|
||||
await wait()
|
||||
ist(!server.getFile(URI))
|
||||
})
|
||||
|
||||
it("can open multiple files", async () => {
|
||||
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)
|
||||
await wait()
|
||||
ist(server.getFile(URI)!.text, "elder?")
|
||||
ist(server.getFile("file:///x")!.text, "alfalfa!")
|
||||
})
|
||||
|
||||
it("can provide mapping", async () => {
|
||||
let {client} = setup()
|
||||
let cm = ed(client, {doc: "1\n2\n3"})
|
||||
await client.withMapping(async mapping => {
|
||||
let req = client.request<lsp.DocumentFormattingParams, lsp.TextEdit[] | null>("textDocument/formatting", {
|
||||
textDocument: {uri: URI},
|
||||
options: {tabSize: 2, insertSpaces: true}
|
||||
})
|
||||
cm.dispatch({changes: {from: 1, to: 3}})
|
||||
let response = await req
|
||||
ist(response![0].range.start.line, 2)
|
||||
ist(mapping.mapPos(URI, 5), 3)
|
||||
})
|
||||
})
|
||||
|
||||
it("can provide mapping across syncs", async () => {
|
||||
let {client} = setup({server: {delay: {"textDocument/formatting": 10}}})
|
||||
let cm = ed(client, {doc: "1\n2\n3"})
|
||||
await client.withMapping(async mapping => {
|
||||
let req = client.request<lsp.DocumentFormattingParams, lsp.TextEdit[] | null>("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 = await req
|
||||
ist(response![0].range.start.line, 2)
|
||||
ist(mapping.mapPos(URI, 5), 4)
|
||||
})
|
||||
})
|
||||
|
||||
it("reports invalid methods", async () => {
|
||||
let {client} = setup()
|
||||
try {
|
||||
await client.request("none/such", {})
|
||||
ist(false)
|
||||
} catch (e: any) {
|
||||
ist(e.code, -32601)
|
||||
}
|
||||
})
|
||||
|
||||
it("can receive custom notifications", async () => {
|
||||
let received: any = null
|
||||
let {client} = setup({client: {notificationHandlers: {
|
||||
"custom/notification": (client, params) => received = params
|
||||
}}})
|
||||
client.request("custom/sendNotification", {method: "custom/notification", params: {verify: true}})
|
||||
await wait()
|
||||
ist(received.verify)
|
||||
})
|
||||
|
||||
it("can report unknown notifications", async () => {
|
||||
let received: any = null
|
||||
let {client} = setup({client: {unhandledNotification: (client, method, params) => received = params}})
|
||||
client.request("custom/sendNotification", {method: "custom/notification", params: {verify: true}})
|
||||
await wait()
|
||||
ist(received.verify)
|
||||
})
|
||||
|
||||
it("can display messages in the editor", async () => {
|
||||
let {client} = setup()
|
||||
let cm = ed(client, {})
|
||||
client.request("custom/sendNotification", {method: "window/showMessage", params: {type: 2 /* Warning */, message: "WARNING"}})
|
||||
await 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", async () => {
|
||||
let broken = false
|
||||
let {client} = setup({server: {brokenPipe: () => broken}})
|
||||
await client.initializing
|
||||
broken = true
|
||||
let req = client.request("test", {})
|
||||
try {
|
||||
await req
|
||||
ist(false)
|
||||
} catch (e: any) {
|
||||
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", async () => {
|
||||
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")
|
||||
await wait()
|
||||
ist(renameSymbol(cm), true)
|
||||
let form = cm.dom.querySelector(".cm-panel form") as HTMLFormElement
|
||||
form.querySelector("input")!.value = "bar"
|
||||
form.requestSubmit()
|
||||
await 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", async () => {
|
||||
let {client} = setup({server: {delay: {"textDocument/rename": 5}}})
|
||||
let cm = place(ed(client, {doc: "let foo = 1; console.log(foo)", selection: {anchor: 4}}))
|
||||
await wait()
|
||||
ist(renameSymbol(cm), true)
|
||||
let form = cm.dom.querySelector(".cm-panel form") as HTMLFormElement
|
||||
form.querySelector("input")!.value = "bar"
|
||||
form.requestSubmit()
|
||||
await wait()
|
||||
cm.dispatch({changes: {from: 0, insert: " "}})
|
||||
await wait(10)
|
||||
ist(cm.state.sliceDoc(), " let bar = 1; console.log(bar)")
|
||||
cm.destroy()
|
||||
})
|
||||
})
|
||||
|
||||
describe("formatDocument", () => {
|
||||
it("can make format requests", async () => {
|
||||
let {client} = setup()
|
||||
let cm = ed(client, {doc: "hawthorn"})
|
||||
formatDocument(cm)
|
||||
await wait()
|
||||
ist(cm.state.sliceDoc(), "hawthorn\n// formatted!")
|
||||
})
|
||||
})
|
||||
|
||||
describe("completion", () => {
|
||||
it("can get completions from the server", async () => {
|
||||
let {client} = setup()
|
||||
let cm = ed(client, {doc: "..o", selection: {anchor: 3}, extensions: [
|
||||
serverCompletion(),
|
||||
autocompletion({interactionDelay: 0, activateOnTypingDelay: 10})
|
||||
]})
|
||||
startCompletion(cm)
|
||||
await 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}})
|
||||
await wait(20)
|
||||
ist(currentCompletions(cm.state).length, 2)
|
||||
moveCompletionSelection(true)(cm)
|
||||
await wait()
|
||||
ist(cm.dom.querySelector(".cm-completionInfo"))
|
||||
acceptCompletion(cm)
|
||||
ist(cm.state.sliceDoc(), "..one!\nookay")
|
||||
})
|
||||
})
|
||||
|
||||
describe("hoverTooltips", () => {
|
||||
it("can retrieve hover info", async () => {
|
||||
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
|
||||
}))
|
||||
await wait(15)
|
||||
ist(cm.dom.querySelector(".cm-tooltip .tok-string"))
|
||||
cm.destroy()
|
||||
})
|
||||
})
|
||||
|
||||
describe("signatureHelp", () => {
|
||||
it("can display a signature", async () => {
|
||||
let {client} = setup()
|
||||
let cm = ed(client, {doc: "bugloss"})
|
||||
showSignatureHelp(cm)
|
||||
await wait()
|
||||
ist(cm.dom.querySelector(".cm-lsp-active-parameter")!.innerHTML, "b")
|
||||
nextSignature(cm)
|
||||
ist(cm.dom.querySelector(".cm-lsp-active-parameter")!.innerHTML, "y")
|
||||
})
|
||||
})
|
||||
})
|
||||
Loading…
Add table
Add a link
Reference in a new issue