diff options
author | Aleksey Kladov <[email protected]> | 2018-07-30 19:58:49 +0100 |
---|---|---|
committer | Aleksey Kladov <[email protected]> | 2018-07-30 19:58:49 +0100 |
commit | ac0d8c48f7a277d4a43448fe7dd4279383bc53f0 (patch) | |
tree | 5fe6d1f761f15d1e2d63fc4e9be0c16e2f0b3d93 /code/common.ts | |
parent | 6fc66c4ee667da871ea1f0c8b48b5e9b7373a187 (diff) |
JS plugin
Diffstat (limited to 'code/common.ts')
-rw-r--r-- | code/common.ts | 278 |
1 files changed, 278 insertions, 0 deletions
diff --git a/code/common.ts b/code/common.ts new file mode 100644 index 000000000..dfd49833d --- /dev/null +++ b/code/common.ts | |||
@@ -0,0 +1,278 @@ | |||
1 | import * as vscode from 'vscode' | ||
2 | import { log } from 'util' | ||
3 | |||
4 | export function createPlugin( | ||
5 | backend, | ||
6 | fileExtension: string, | ||
7 | disposables: vscode.Disposable[], | ||
8 | doHighlighting: boolean = false, | ||
9 | diganosticCollection: vscode.DiagnosticCollection | null = null | ||
10 | ) { | ||
11 | let uris = { | ||
12 | syntaxTree: vscode.Uri.parse(`fall-${fileExtension}://syntaxtree`), | ||
13 | metrics: vscode.Uri.parse(`fall-${fileExtension}://metrics`) | ||
14 | } | ||
15 | |||
16 | function updateActiveEditor() { | ||
17 | let editor = vscode.window.activeTextEditor | ||
18 | if (editor == null) return | ||
19 | let file = currentFile() | ||
20 | if (file == null) return | ||
21 | if (doHighlighting) { | ||
22 | setHighlights(editor, file.highlight()) | ||
23 | } | ||
24 | if (diganosticCollection != null) { | ||
25 | diganosticCollection.clear() | ||
26 | diganosticCollection.set( | ||
27 | editor.document.uri, | ||
28 | file.diagnostics() | ||
29 | ) | ||
30 | } | ||
31 | } | ||
32 | |||
33 | |||
34 | function currentFile(): EditorFile | null { | ||
35 | let editor = vscode.window.activeTextEditor | ||
36 | if (editor == null) return | ||
37 | let doc = editor.document | ||
38 | return getFile(doc) | ||
39 | } | ||
40 | |||
41 | vscode.window.onDidChangeActiveTextEditor(updateActiveEditor) | ||
42 | let cmd = vscode.commands.registerCommand(`fall-${fileExtension}.applyContextAction`, (range, id) => { | ||
43 | let file = currentFile() | ||
44 | if (file == null) return | ||
45 | return file.applyContextAction(range, id) | ||
46 | }) | ||
47 | disposables.push(cmd) | ||
48 | |||
49 | return { | ||
50 | getFile: getFile, | ||
51 | showSyntaxTree: () => { | ||
52 | let file = currentFile() | ||
53 | if (file == null) return | ||
54 | return openDoc(uris.syntaxTree) | ||
55 | }, | ||
56 | metrics: () => { | ||
57 | let file = currentFile() | ||
58 | if (file == null) return | ||
59 | return openDoc(uris.metrics) | ||
60 | }, | ||
61 | extendSelection: () => { | ||
62 | let editor = vscode.window.activeTextEditor | ||
63 | let file = currentFile() | ||
64 | if (editor == null || file == null) return | ||
65 | editor.selections = editor.selections.map((s) => { | ||
66 | let range = file.extendSelection(s) | ||
67 | return new vscode.Selection(range.start, range.end) | ||
68 | }) | ||
69 | }, | ||
70 | documentSymbolsProvider: new DocumentSymbolProvider(getFile), | ||
71 | documentFormattingEditProvider: new DocumentFormattingEditProvider(getFile), | ||
72 | codeActionProvider: new CodeActionProvider(getFile, fileExtension) | ||
73 | } | ||
74 | } | ||
75 | |||
76 | |||
77 | export interface FileStructureNode { | ||
78 | name: string | ||
79 | range: [number, number] | ||
80 | children: [FileStructureNode] | ||
81 | } | ||
82 | |||
83 | export interface FallDiagnostic { | ||
84 | range: [number, number] | ||
85 | severity: string | ||
86 | message: string | ||
87 | } | ||
88 | |||
89 | export class EditorFile { | ||
90 | backend; | ||
91 | imp; | ||
92 | doc: vscode.TextDocument; | ||
93 | |||
94 | constructor(backend, imp, doc: vscode.TextDocument) { | ||
95 | this.backend = backend | ||
96 | this.imp = imp | ||
97 | this.doc = doc | ||
98 | } | ||
99 | |||
100 | metrics(): string { return this.call("metrics") } | ||
101 | syntaxTree(): string { return this.call("syntaxTree") } | ||
102 | extendSelection(range_: vscode.Range): vscode.Range | null { | ||
103 | let range = fromVsRange(this.doc, range_) | ||
104 | let exp = this.call("extendSelection", range) | ||
105 | if (exp == null) return null | ||
106 | return toVsRange(this.doc, exp) | ||
107 | } | ||
108 | |||
109 | structure(): Array<FileStructureNode> { return this.call("structure") } | ||
110 | reformat(): Array<vscode.TextEdit> { | ||
111 | let edits = this.call("reformat") | ||
112 | return toVsEdits(this.doc, edits) | ||
113 | } | ||
114 | |||
115 | highlight(): Array<[[number, number], string]> { return this.call("highlight") } | ||
116 | diagnostics(): Array<vscode.Diagnostic> { | ||
117 | return this.call("diagnostics").map((d) => { | ||
118 | let range = toVsRange(this.doc, d.range) | ||
119 | let severity = d.severity == "Error" | ||
120 | ? vscode.DiagnosticSeverity.Error | ||
121 | : vscode.DiagnosticSeverity.Warning | ||
122 | |||
123 | return new vscode.Diagnostic(range, d.message, severity) | ||
124 | }) | ||
125 | } | ||
126 | |||
127 | contextActions(range_: vscode.Range): Array<string> { | ||
128 | let range = fromVsRange(this.doc, range_) | ||
129 | let result = this.call("contextActions", range) | ||
130 | return result | ||
131 | } | ||
132 | |||
133 | applyContextAction(range_: vscode.Range, id: string) { | ||
134 | let range = fromVsRange(this.doc, range_) | ||
135 | let edits = this.call("applyContextAction", range, id) | ||
136 | let editor = vscode.window.activeTextEditor | ||
137 | return editor.edit((builder) => { | ||
138 | for (let op of edits) { | ||
139 | builder.replace(toVsRange(this.doc, op.delete), op.insert) | ||
140 | } | ||
141 | }) | ||
142 | } | ||
143 | |||
144 | call(method: string, ...args) { | ||
145 | let result = this.backend[method](this.imp, ...args) | ||
146 | return result | ||
147 | } | ||
148 | } | ||
149 | |||
150 | function documentToFile(backend, fileExtension: string, disposables: vscode.Disposable[], onChange) { | ||
151 | let docs = {} | ||
152 | function update(doc: vscode.TextDocument, file) { | ||
153 | let key = doc.uri.toString() | ||
154 | if (file == null) { | ||
155 | delete docs[key] | ||
156 | } else { | ||
157 | docs[key] = file | ||
158 | } | ||
159 | onChange(doc) | ||
160 | } | ||
161 | function get(doc: vscode.TextDocument) { | ||
162 | return docs[doc.uri.toString()] | ||
163 | } | ||
164 | |||
165 | function isKnownDoc(doc: vscode.TextDocument) { | ||
166 | return doc.fileName.endsWith(`.${fileExtension}`) | ||
167 | } | ||
168 | |||
169 | vscode.workspace.onDidChangeTextDocument((event: vscode.TextDocumentChangeEvent) => { | ||
170 | let doc = event.document | ||
171 | if (!isKnownDoc(event.document)) return | ||
172 | let tree = get(doc) | ||
173 | if (event.contentChanges.length == 1 && tree) { | ||
174 | let edits = event.contentChanges.map((change) => { | ||
175 | let start = doc.offsetAt(change.range.start) | ||
176 | return { | ||
177 | "delete": [start, start + change.rangeLength], | ||
178 | "insert": change.text | ||
179 | } | ||
180 | }) | ||
181 | update(doc, backend.edit(tree, edits)) | ||
182 | return | ||
183 | } | ||
184 | update(doc, null) | ||
185 | }, null, disposables) | ||
186 | |||
187 | vscode.workspace.onDidOpenTextDocument((doc: vscode.TextDocument) => { | ||
188 | if (!isKnownDoc(doc)) return | ||
189 | update(doc, backend.parse(doc.getText())) | ||
190 | }, null, disposables) | ||
191 | |||
192 | vscode.workspace.onDidCloseTextDocument((doc: vscode.TextDocument) => { | ||
193 | update(doc, null) | ||
194 | }, null, disposables) | ||
195 | |||
196 | return (doc: vscode.TextDocument) => { | ||
197 | if (!isKnownDoc(doc)) return null | ||
198 | |||
199 | if (!get(doc)) { | ||
200 | update(doc, backend.parse(doc.getText())) | ||
201 | } | ||
202 | let imp = get(doc) | ||
203 | return new EditorFile(backend, imp, doc) | ||
204 | } | ||
205 | } | ||
206 | |||
207 | export class DocumentSymbolProvider implements vscode.DocumentSymbolProvider { | ||
208 | getFile: (doc: vscode.TextDocument) => EditorFile | null; | ||
209 | constructor(getFile) { | ||
210 | this.getFile = getFile | ||
211 | } | ||
212 | |||
213 | provideDocumentSymbols(document: vscode.TextDocument, token: vscode.CancellationToken) { | ||
214 | let file = this.getFile(document) | ||
215 | if (file == null) return null | ||
216 | return file.structure().map((node) => { | ||
217 | return new vscode.SymbolInformation( | ||
218 | node.name, | ||
219 | vscode.SymbolKind.Function, | ||
220 | toVsRange(document, node.range), | ||
221 | null, | ||
222 | null | ||
223 | ) | ||
224 | }) | ||
225 | } | ||
226 | } | ||
227 | |||
228 | export class DocumentFormattingEditProvider implements vscode.DocumentFormattingEditProvider { | ||
229 | getFile: (doc: vscode.TextDocument) => EditorFile | null; | ||
230 | constructor(getFile) { this.getFile = getFile } | ||
231 | |||
232 | provideDocumentFormattingEdits( | ||
233 | document: vscode.TextDocument, | ||
234 | options: vscode.FormattingOptions, | ||
235 | token: vscode.CancellationToken | ||
236 | ): vscode.TextEdit[] { | ||
237 | let file = this.getFile(document) | ||
238 | if (file == null) return [] | ||
239 | return file.reformat() | ||
240 | } | ||
241 | } | ||
242 | |||
243 | export class CodeActionProvider implements vscode.CodeActionProvider { | ||
244 | fileExtension: string | ||
245 | getFile: (doc: vscode.TextDocument) => EditorFile | null; | ||
246 | constructor(getFile, fileExtension) { | ||
247 | this.getFile = getFile | ||
248 | this.fileExtension = fileExtension | ||
249 | } | ||
250 | |||
251 | provideCodeActions( | ||
252 | document: vscode.TextDocument, | ||
253 | range: vscode.Range, | ||
254 | context: vscode.CodeActionContext, | ||
255 | token: vscode.CancellationToken | ||
256 | ): vscode.Command[] { | ||
257 | let file = this.getFile(document) | ||
258 | if (file == null) return | ||
259 | let actions = file.contextActions(range) | ||
260 | return actions.map((id) => { | ||
261 | return { | ||
262 | title: id, | ||
263 | command: `fall-${this.fileExtension}.applyContextAction`, | ||
264 | arguments: [range, id] | ||
265 | } | ||
266 | }) | ||
267 | } | ||
268 | } | ||
269 | |||
270 | |||
271 | export function toVsEdits(doc: vscode.TextDocument, edits): Array<vscode.TextEdit> { | ||
272 | return edits.map((op) => vscode.TextEdit.replace(toVsRange(doc, op.delete), op.insert)) | ||
273 | } | ||
274 | |||
275 | async function openDoc(uri: vscode.Uri) { | ||
276 | let document = await vscode.workspace.openTextDocument(uri) | ||
277 | vscode.window.showTextDocument(document, vscode.ViewColumn.Two, true) | ||
278 | } | ||