/
analysis.js
126 lines (112 loc) · 3.5 KB
/
analysis.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
const bootstrapServer = require('tern/lib/bootstrap');
const path = require('path');
const util = require('util');
const URI = require('vscode-uri').default;
const {connection, documents, root} = require('./index');
const tern = bootstrapServer({
projectDir: root,
});
tern.asyncRequest = util.promisify(tern.request);
async function ternRequest(event, type, options = {}) {
return await tern.asyncRequest({
query: Object.assign({
type,
file: nameFromUri(event.textDocument.uri),
end: ternPosition(event.position),
lineCharPositions: true,
}, options),
});
}
// lsp <-> tern conversions
const nameFromUri = uri => path.relative(tern.options.projectDir, URI.parse(uri).fsPath);
const uriFromName = name => URI.file(path.resolve(tern.options.projectDir, name)).toString();
const ternPosition = ({line, character}) => ({line, ch: character});
const lspPosition = ({line, ch}) => ({line, character: ch});
function lspRange(start, end) {
return {
range: {
start: lspPosition(start),
end: lspPosition(end),
},
};
}
function lspLocation(file, start, end) {
return {
uri: uriFromName(file),
...lspRange(start, end),
};
}
const onUpdate = async event => {
const document = event.document;
await tern.asyncRequest({
files: [{
type: 'full',
name: nameFromUri(document.uri),
text: document.getText(),
}],
});
};
documents.onDidOpen(onUpdate);
documents.onDidChangeContent(onUpdate);
documents.onDidClose(async event => {
const document = event.document;
await tern.asyncRequest({
files: [{
type: 'delete',
name: nameFromUri(document.uri),
}],
});
});
connection.onCompletion(async event => {
const {completions} = await ternRequest(event, 'completions', {
types: true,
docs: true,
caseInsensitive: true,
});
return completions.map(completion => ({
label: completion.name,
detail: completion.type,
}));
});
connection.onDefinition(async event => {
const {file, start, end} = await ternRequest(event, 'definition');
if (file === undefined)
return null;
return lspLocation(file, start, end);
});
connection.onHover(async event => {
const info = await ternRequest(event, 'type');
const lines = [];
const name = info.exprName || info.name;
let description = `${name}: ${info.type}`;
if (info.type.startsWith('fn(') && name !== undefined)
description = name + info.type.slice(2);
lines.push(description);
if (info.doc !== undefined)
lines.push(info.doc);
if (info.url !== undefined)
lines.push(info.url);
return {contents: lines.join('\n')};
});
connection.onReferences(async event => {
const {refs} = await ternRequest(event, 'refs');
return refs.map(({file, start, end}) => lspLocation(file, start, end));
});
connection.onRenameRequest(async event => {
const {changes} = await ternRequest(event, 'rename', {
newName: event.newName,
});
const changesByFile = changes.reduce((acc, change) => {
const { file, start, end, text } = change;
const completeName = uriFromName(file);
if (!acc[completeName]) {
acc[completeName] = [];
}
acc[completeName].push({
newText: text,
...lspRange(start, end),
});
return acc;
}, {});
return { changes: changesByFile };
});