Skip to content

Commit 1d528b6

Browse files
committed
feat(documentSymbol&scan): improve scan and closes #53
1 parent fe373fb commit 1d528b6

File tree

8 files changed

+217
-76
lines changed

8 files changed

+217
-76
lines changed

src/common/logger.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,5 +14,8 @@ export default function(name: string) {
1414
error(message: string) {
1515
connection.console.error(`${name}: ${message}`);
1616
},
17+
showErrorMessage(message: string) {
18+
connection.window.showErrorMessage(message)
19+
}
1720
};
1821
}

src/common/types.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { Subscription} from "rxjs";
2-
import { CompletionItem } from "vscode-languageserver";
2+
import { ClientCapabilities, CompletionItem } from "vscode-languageserver";
33

44
export interface IParserHandles {
55
[uri: string]: Subscription | undefined;
@@ -31,6 +31,7 @@ export interface IConfig {
3131
snippetSupport: boolean;
3232
suggest: ISuggest;
3333
indexes: IIndexes;
34+
capabilities: ClientCapabilities
3435
}
3536

3637
// builtin-doc

src/common/util.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -215,3 +215,11 @@ export const getRealPath = async (filePath: string): Promise<string> => {
215215
}
216216
return filePath;
217217
};
218+
219+
export const delay = async (ms: number) => {
220+
await new Promise<void>((res) => {
221+
setTimeout(() => {
222+
res()
223+
}, ms);
224+
})
225+
}

src/handles/documentSymbol.ts

Lines changed: 39 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
1-
import {DocumentSymbolParams, DocumentSymbol, SymbolKind, Range, Position} from "vscode-languageserver";
1+
import {DocumentSymbolParams, DocumentSymbol, SymbolKind, Range, Position, SymbolInformation} from "vscode-languageserver";
2+
import * as shvl from "shvl";
23

34
import {workspace} from "../server/workspaces";
4-
import {IFunction} from "../server/buffer";
5+
import {IFunction, IIdentifier} from "../server/buffer";
56
import {documents} from "../server/documents";
7+
import config from "../server/config";
68

7-
export const documentSymbolProvider = (params: DocumentSymbolParams): DocumentSymbol[] => {
9+
export const documentSymbolProvider = (params: DocumentSymbolParams): DocumentSymbol[] | SymbolInformation[] => {
810
const documentSymbols: DocumentSymbol[] = []
911
const { textDocument } = params
1012
const buffer = workspace.getBufferByUri(textDocument.uri)
@@ -22,6 +24,40 @@ export const documentSymbolProvider = (params: DocumentSymbolParams): DocumentSy
2224
let variables = Object.values(globalVariables).concat(Object.values(localVariables)).reduce((pre, cur) => {
2325
return pre.concat(cur)
2426
}, [])
27+
28+
// hierarchicalDocumentSymbolSupport: false
29+
if (!config.capabilities || !shvl.get(config.capabilities, 'textDocument.documentSymbol.hierarchicalDocumentSymbolSupport')) {
30+
return ([] as (IFunction | IIdentifier)[]).concat(functions,variables).sort((a, b) => {
31+
if (a.startLine === b.startLine) {
32+
return a.startCol - b.startCol
33+
}
34+
return a.startLine - b.startLine
35+
}).map<SymbolInformation>(item => {
36+
const vimRange = (item as IFunction).range
37+
const line = vimRange
38+
? document.getText(Range.create( Position.create(vimRange.endLine - 1, 0), Position.create(vimRange.endLine, 0)))
39+
: ''
40+
const range = vimRange
41+
? Range.create(
42+
Position.create(vimRange.startLine - 1, vimRange.startCol - 1),
43+
Position.create(vimRange.endLine - 1, vimRange.endCol - 1 + line.slice(vimRange.endCol - 1).split(' ')[0].length)
44+
)
45+
:
46+
Range.create(
47+
Position.create(item.startLine - 1, item.startCol - 1),
48+
Position.create(item.startLine, item.startCol - 1 + item.name.length)
49+
)
50+
return {
51+
name: item.name,
52+
kind: vimRange ? SymbolKind.Function : SymbolKind.Variable,
53+
location: {
54+
uri: textDocument.uri,
55+
range,
56+
}
57+
}
58+
})
59+
}
60+
2561
const sortFunctions: IFunction[] = []
2662
functions.forEach(func => {
2763
if (sortFunctions.length === 0) {

src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ connection.onInitialize((param: InitializeParams) => {
6767
projectRootPatterns,
6868
...(indexes || {}),
6969
},
70+
capabilities: param.capabilities
7071
};
7172

7273
// init config

src/server/config.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,4 +74,8 @@ export default {
7474

7575
return defaults;
7676
},
77+
78+
get capabilities () {
79+
return conf && conf.capabilities
80+
}
7781
};

src/server/parser.ts

Lines changed: 133 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,16 @@
11
import childProcess, {ChildProcess} from "child_process";
22
import { join } from "path";
3-
import { from, Subject, timer } from "rxjs";
3+
import { from, of, Subject, timer } from "rxjs";
44
import { waitMap } from "rxjs-operators/lib/waitMap";
5-
import { filter, map, switchMap } from "rxjs/operators";
5+
import { catchError, filter, map, switchMap, timeout } from "rxjs/operators";
66
import { TextDocument } from "vscode-languageserver-textdocument";
77
import { URI } from "vscode-uri";
88

99
import logger from "../common/logger";
1010
import { IParserHandles} from "../common/types";
11-
import { handleParse } from "../common/util";
11+
import {delay} from "../common/util";
1212
import { handleDiagnostic } from "../handles/diagnostic";
13+
import {INode} from "../lib/vimparser";
1314
import config from "./config";
1415
import { workspace } from "./workspaces";
1516

@@ -18,30 +19,36 @@ const log = logger("parser");
1819
const parserHandles: IParserHandles = {};
1920

2021
const indexes: Record<string, boolean> = {};
22+
const indexesFsPaths: Record<string, boolean> = {};
2123

2224
const origin$: Subject<TextDocument> = new Subject<TextDocument>();
2325

26+
const parserCallbacks: Record<string, (param: any) => void> = {}
27+
const queueFsPaths: string[] = []
28+
2429
let scanProcess: ChildProcess;
2530
let isScanRuntimepath: boolean = false;
31+
let isParsing = false
2632

27-
function send(params: any) {
33+
function send(params: any): boolean {
2834
if (!scanProcess) {
2935
log.log('scan process do not exists')
30-
return
36+
return false
3137
}
3238
if ((scanProcess as any).signalCode) {
3339
log.log(`scan process signal code: ${(scanProcess as any).signalCode}`)
34-
return
40+
return false
3541
}
3642
if (scanProcess.killed) {
3743
log.log('scan process was killed')
38-
return
44+
return false
3945
}
4046
scanProcess.send(params, (err) => {
4147
if (err) {
4248
log.warn(`Send error: ${err.stack || err.message || err.name}`)
4349
}
4450
})
51+
return true
4552
}
4653

4754
function startIndex() {
@@ -54,16 +61,23 @@ function startIndex() {
5461
);
5562

5663
scanProcess.on("message", (mess) => {
57-
const { data, msglog } = mess;
58-
if (data) {
59-
if (!workspace.isExistsBuffer(data.uri)) {
60-
workspace.updateBuffer(data.uri, data.node);
61-
}
62-
}
64+
const { msglog, id, res, error, fsPaths } = mess;
6365

6466
if (msglog) {
6567
log.info(`child_log: ${msglog}`);
6668
}
69+
70+
if (fsPaths) {
71+
parserFiles(fsPaths)
72+
}
73+
74+
if (id && parserCallbacks[id]) {
75+
parserCallbacks[id]({
76+
res,
77+
error
78+
})
79+
delete parserCallbacks[id]
80+
}
6781
});
6882

6983
scanProcess.on("error", (err: Error) => {
@@ -108,16 +122,47 @@ export function next(
108122
);
109123
}),
110124
waitMap((td: TextDocument) => {
111-
return from(handleParse(td));
125+
const id = `${Date.now()}-${Math.random()}`
126+
return from(new Promise<{res: any, error: any, isTimeout?: boolean}>((res) => {
127+
parserCallbacks[id] = res
128+
send({
129+
id,
130+
uri,
131+
text: td.getText()
132+
})
133+
})).pipe(
134+
timeout(10000),
135+
catchError(() => {
136+
if (parserCallbacks[id]) {
137+
delete parserCallbacks[id]
138+
}
139+
scanProcess.kill()
140+
scanProcess = undefined
141+
return of({
142+
res: '',
143+
error: `Timeout: 10000ms`,
144+
isTimeout: true,
145+
})
146+
})
147+
)
112148
}, true),
113149
).subscribe(
114-
(res) => {
115-
if (config.diagnostic.enable) {
116-
// handle diagnostic
117-
handleDiagnostic(textDoc, res[1]);
150+
(data) => {
151+
const { res, error, isTimeout } = data
152+
if (res) {
153+
if (config.diagnostic.enable) {
154+
// handle diagnostic
155+
handleDiagnostic(textDoc, res[1]);
156+
}
157+
// handle node
158+
workspace.updateBuffer(uri, res[0]);
159+
}
160+
if (error) {
161+
log.error(`Parse ${uri} error: ${error}`)
162+
}
163+
if (isTimeout) {
164+
log.showErrorMessage(`Parse ${uri} error: ${error}`)
118165
}
119-
// handle node
120-
workspace.updateBuffer(uri, res[0]);
121166
// scan project
122167
if (!indexes[uri]) {
123168
indexes[uri] = true;
@@ -126,7 +171,7 @@ export function next(
126171
})
127172
if (!isScanRuntimepath) {
128173
isScanRuntimepath = true;
129-
scan([config.vimruntime].concat(config.runtimepath));
174+
scanRuntimePaths([config.vimruntime].concat(config.runtimepath));
130175
}
131176
}
132177
},
@@ -149,7 +194,7 @@ export function unsubscribe(textDoc: TextDocument) {
149194
}
150195

151196
// scan directory
152-
export function scan(paths: string | string[]) {
197+
export function scanRuntimePaths(paths: string | string[]) {
153198
if (!scanProcess) {
154199
startIndex();
155200
}
@@ -164,9 +209,72 @@ export function scan(paths: string | string[]) {
164209
if (!p || p === "/") {
165210
continue;
166211
}
167-
send({
168-
uri: URI.file(join(p, "f")).toString(),
169-
})
212+
const uri = URI.file(join(p, "f")).toString()
213+
if (!indexes[uri]) {
214+
indexes[uri] = true;
215+
send({
216+
uri
217+
})
218+
}
170219
}
171220
}
172221
}
222+
223+
export async function parserFiles (paths: string[]) {
224+
queueFsPaths.push(...paths)
225+
if (isParsing) {
226+
return
227+
}
228+
isParsing = true
229+
while (queueFsPaths.length) {
230+
await Promise.all(
231+
Array(config.indexes.count).fill('').map(async () => {
232+
const fsPath = queueFsPaths.shift()
233+
if (!fsPath || indexesFsPaths[fsPath]) {
234+
return
235+
}
236+
indexesFsPaths[fsPath] = true
237+
const id = `${Date.now()}-${Math.random()}`
238+
const data = await new Promise<{res?: [INode | null, string], error: any, timeout?: boolean}>(res => {
239+
parserCallbacks[id] = res
240+
const isSend = send({
241+
id,
242+
fsPath
243+
})
244+
if (isSend) {
245+
setTimeout(() => {
246+
delete parserCallbacks[id]
247+
res({
248+
error: 'Timeout 10000ms',
249+
timeout: true
250+
})
251+
}, 10000);
252+
} else {
253+
queueFsPaths.unshift(fsPath)
254+
delete parserCallbacks[id]
255+
res({
256+
error: `Cancel parser since scan process does not exists`
257+
})
258+
}
259+
})
260+
if (data.res && data.res[0]) {
261+
const uri = URI.file(fsPath).toString()
262+
if (!workspace.isExistsBuffer(uri)) {
263+
workspace.updateBuffer(uri, data.res[0]);
264+
}
265+
}
266+
if (data.error) {
267+
log.error(`Parse ${fsPath} error: ${data.error}`)
268+
}
269+
if (data.timeout) {
270+
scanProcess.kill()
271+
scanProcess = undefined
272+
startIndex()
273+
log.showErrorMessage(`Parse ${fsPath} error: ${data.error}`)
274+
}
275+
})
276+
)
277+
await delay(config.indexes.gap)
278+
}
279+
isParsing = false
280+
}

0 commit comments

Comments
 (0)