Skip to content

Commit 4c06838

Browse files
committed
Add new Adaptor layer on top of Harness Language Service
1 parent 7a4a810 commit 4c06838

1 file changed

Lines changed: 329 additions & 6 deletions

File tree

src/harness/harnessLanguageService.ts

Lines changed: 329 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ module Harness.LanguageService {
7575
}
7676

7777
class ScriptSnapshotShim implements ts.ScriptSnapshotShim {
78-
constructor(public scriptSnapshot: ScriptSnapshot) {
78+
constructor(public scriptSnapshot: ts.IScriptSnapshot) {
7979
}
8080

8181
public getText(start: number, end: number): string {
@@ -109,6 +109,334 @@ module Harness.LanguageService {
109109
}
110110
}
111111

112+
export interface LanugageServiceAdaptor {
113+
getHost(): LanguageServiceAdaptorHost;
114+
getLanguageService(): ts.LanguageService;
115+
getClassifier(): ts.Classifier;
116+
getPreProcessedFileInfo(filename: string, fileContents: string): ts.PreProcessedFileInfo;
117+
}
118+
119+
class LanguageServiceHostBase {
120+
protected fileNameToScript: ts.Map<ScriptInfo> = {};
121+
122+
constructor(protected cancellationToken: ts.CancellationToken = CancellationToken.None,
123+
protected settings = ts.getDefaultCompilerOptions()) {
124+
125+
}
126+
127+
public getFilenames(): string[] {
128+
var fileNames: string[] = [];
129+
ts.forEachKey(this.fileNameToScript,(fileName) => { fileNames.push(fileName); });
130+
return fileNames;
131+
}
132+
133+
public getScriptInfo(fileName: string): ScriptInfo {
134+
return ts.lookUp(this.fileNameToScript, fileName);
135+
}
136+
137+
public addScript(fileName: string, content: string): void {
138+
this.fileNameToScript[fileName] = new ScriptInfo(fileName, content);
139+
}
140+
141+
public updateScript(fileName: string, content: string) {
142+
var script = this.getScriptInfo(fileName);
143+
if (script !== null) {
144+
script.updateContent(content);
145+
return;
146+
}
147+
148+
this.addScript(fileName, content);
149+
}
150+
151+
public editScript(fileName: string, minChar: number, limChar: number, newText: string) {
152+
var script = this.getScriptInfo(fileName);
153+
if (script !== null) {
154+
script.editContent(minChar, limChar, newText);
155+
return;
156+
}
157+
158+
throw new Error("No script with name '" + fileName + "'");
159+
}
160+
161+
/**
162+
* @param line 1 based index
163+
* @param col 1 based index
164+
*/
165+
public lineColToPosition(fileName: string, line: number, col: number): number {
166+
var script: ScriptInfo = this.fileNameToScript[fileName];
167+
assert.isNotNull(script);
168+
assert.isTrue(line >= 1);
169+
assert.isTrue(col >= 1);
170+
171+
return ts.getPositionFromLineAndCharacter(script.lineMap, line, col);
172+
}
173+
174+
/**
175+
* @param line 0 based index
176+
* @param col 0 based index
177+
*/
178+
public positionToZeroBasedLineCol(fileName: string, position: number): ts.LineAndCharacter {
179+
var script: ScriptInfo = this.fileNameToScript[fileName];
180+
assert.isNotNull(script);
181+
182+
var result = ts.getLineAndCharacterOfPosition(script.lineMap, position);
183+
184+
assert.isTrue(result.line >= 1);
185+
assert.isTrue(result.character >= 1);
186+
return { line: result.line - 1, character: result.character - 1 };
187+
}
188+
}
189+
190+
export interface LanguageServiceAdaptorHost extends LanguageServiceHostBase {
191+
}
192+
193+
/// Native adabtor
194+
class NativeLanguageServiceHost extends LanguageServiceHostBase implements ts.LanguageServiceHost {
195+
getCompilationSettings(): ts.CompilerOptions { return this.settings; }
196+
getCancellationToken(): ts.CancellationToken { return this.cancellationToken; }
197+
getCurrentDirectory(): string { return ""; }
198+
getDefaultLibFilename(): string { return ""; }
199+
getScriptFileNames(): string[] { return this.getFilenames(); }
200+
getScriptSnapshot(filename: string): ts.IScriptSnapshot {
201+
var script = this.getScriptInfo(filename);
202+
return script ? new ScriptSnapshot(script) : undefined;
203+
}
204+
getScriptVersion(filename: string): string {
205+
var script = this.getScriptInfo(filename);
206+
return script ? script.version.toString() : undefined;
207+
}
208+
log(s: string): void { }
209+
trace(s: string): void { }
210+
error(s: string): void { }
211+
}
212+
213+
export class NativeLanugageServiceAdaptor implements LanugageServiceAdaptor {
214+
private host: NativeLanguageServiceHost;
215+
constructor(cancellationToken?: ts.CancellationToken, options?: ts.CompilerOptions) {
216+
this.host = new NativeLanguageServiceHost(cancellationToken, options);
217+
}
218+
getHost() { return this.host; }
219+
getLanguageService(): ts.LanguageService { return ts.createLanguageService(this.host); }
220+
getClassifier(): ts.Classifier { return ts.createClassifier(); }
221+
getPreProcessedFileInfo(filename: string, fileContents: string): ts.PreProcessedFileInfo { return ts.preProcessFile(fileContents); }
222+
}
223+
224+
/// Shim adabtor
225+
class ShimLanguageServiceHost extends LanguageServiceHostBase implements ts.LanguageServiceShimHost {
226+
private nativeHost: NativeLanguageServiceHost;
227+
constructor(cancellationToken?: ts.CancellationToken, options?: ts.CompilerOptions) {
228+
super(cancellationToken, options);
229+
this.nativeHost = new NativeLanguageServiceHost(cancellationToken, options);
230+
}
231+
232+
getFilenames(): string[] { return this.nativeHost.getFilenames(); }
233+
getScriptInfo(fileName: string): ScriptInfo { return this.nativeHost.getScriptInfo(fileName); }
234+
addScript(fileName: string, content: string): void { this.nativeHost.addScript(fileName, content); }
235+
updateScript(fileName: string, content: string): void { return this.nativeHost.updateScript(fileName, content); }
236+
editScript(fileName: string, minChar: number, limChar: number, newText: string): void { this.nativeHost.editScript(fileName, minChar, limChar, newText); }
237+
lineColToPosition(fileName: string, line: number, col: number): number { return this.nativeHost.lineColToPosition(fileName, line, col); }
238+
positionToZeroBasedLineCol(fileName: string, position: number): ts.LineAndCharacter { return this.nativeHost.positionToZeroBasedLineCol(fileName, position); }
239+
240+
getCompilationSettings(): string { return JSON.stringify(this.nativeHost.getCompilationSettings()); }
241+
getCancellationToken(): ts.CancellationToken { return this.nativeHost.getCancellationToken(); }
242+
getCurrentDirectory(): string { return this.nativeHost.getCurrentDirectory(); }
243+
getDefaultLibFilename(): string { return this.nativeHost.getDefaultLibFilename(); }
244+
getScriptFileNames(): string { return JSON.stringify(this.nativeHost.getScriptFileNames()); }
245+
getScriptSnapshot(filename: string): ts.ScriptSnapshotShim {
246+
var nativeScriptSnapshot = this.nativeHost.getScriptSnapshot(filename);
247+
return nativeScriptSnapshot && new ScriptSnapshotShim(nativeScriptSnapshot);
248+
}
249+
getScriptVersion(filename: string): string { return this.nativeHost.getScriptVersion(filename); }
250+
getLocalizedDiagnosticMessages(): string { return JSON.stringify({}); }
251+
log(s: string): void { this.nativeHost.log(s); }
252+
trace(s: string): void { this.nativeHost.trace(s); }
253+
error(s: string): void { this.nativeHost.error(s); }
254+
}
255+
256+
class ClassifierShimProxy implements ts.Classifier {
257+
constructor(private shim: ts.ClassifierShim) { }
258+
getClassificationsForLine(text: string, lexState: ts.EndOfLineState, classifyKeywordsInGenerics?: boolean): ts.ClassificationResult {
259+
var result = this.shim.getClassificationsForLine(text, lexState, classifyKeywordsInGenerics).split('\n');
260+
var entries: ts.ClassificationInfo[] = [];
261+
var i = 0;
262+
var position = 0;
263+
264+
for (; i < result.length - 1; i += 2) {
265+
var t = entries[i / 2] = {
266+
length: parseInt(result[i]),
267+
classification: parseInt(result[i + 1])
268+
};
269+
270+
assert.isTrue(t.length > 0, "Result length should be greater than 0, got :" + t.length);
271+
position += t.length;
272+
}
273+
var finalLexState = parseInt(result[result.length - 1]);
274+
275+
assert.equal(position, text.length, "Expected cumulative length of all entries to match the length of the source. expected: " + text.length + ", but got: " + position);
276+
277+
return {
278+
finalLexState,
279+
entries
280+
};
281+
}
282+
}
283+
284+
function unwrappJSONCallResult(result: string): any {
285+
var parsedResult = JSON.parse(result);
286+
if (parsedResult.error) {
287+
throw new Error("Language Service Shim Error: " + JSON.stringify(parsedResult.error));
288+
}
289+
else if (parsedResult.canceled) {
290+
throw new ts.OperationCanceledException();
291+
}
292+
return parsedResult.result;
293+
}
294+
295+
class LanguageServiceShimProxy implements ts.LanguageService {
296+
constructor(private shim: ts.LanguageServiceShim) { }
297+
private unwrappJSONCallResult(result: string): any {
298+
var parsedResult = JSON.parse(result);
299+
if (parsedResult.error) {
300+
throw new Error("Language Service Shim Error: " + JSON.stringify(parsedResult.error));
301+
}
302+
return parsedResult.result;
303+
}
304+
cleanupSemanticCache(): void {
305+
this.shim.cleanupSemanticCache();
306+
}
307+
getSyntacticDiagnostics(fileName: string): ts.Diagnostic[] {
308+
return unwrappJSONCallResult(this.shim.getSyntacticDiagnostics(fileName));
309+
}
310+
getSemanticDiagnostics(fileName: string): ts.Diagnostic[] {
311+
return unwrappJSONCallResult(this.shim.getSemanticDiagnostics(fileName));
312+
}
313+
getCompilerOptionsDiagnostics(): ts.Diagnostic[] {
314+
return unwrappJSONCallResult(this.shim.getCompilerOptionsDiagnostics());
315+
}
316+
getSyntacticClassifications(fileName: string, span: ts.TextSpan): ts.ClassifiedSpan[] {
317+
return unwrappJSONCallResult(this.shim.getSyntacticClassifications(fileName, span.start, span.length));
318+
}
319+
getSemanticClassifications(fileName: string, span: ts.TextSpan): ts.ClassifiedSpan[] {
320+
return unwrappJSONCallResult(this.shim.getSemanticClassifications(fileName, span.start, span.length));
321+
}
322+
getCompletionsAtPosition(fileName: string, position: number): ts.CompletionInfo {
323+
return unwrappJSONCallResult(this.shim.getCompletionsAtPosition(fileName, position));
324+
}
325+
getCompletionEntryDetails(fileName: string, position: number, entryName: string): ts.CompletionEntryDetails {
326+
return unwrappJSONCallResult(this.shim.getCompletionEntryDetails(fileName, position, entryName));
327+
}
328+
getQuickInfoAtPosition(fileName: string, position: number): ts.QuickInfo {
329+
return unwrappJSONCallResult(this.shim.getQuickInfoAtPosition(fileName, position));
330+
}
331+
getNameOrDottedNameSpan(fileName: string, startPos: number, endPos: number): ts.TextSpan {
332+
return unwrappJSONCallResult(this.shim.getNameOrDottedNameSpan(fileName, startPos, endPos));
333+
}
334+
getBreakpointStatementAtPosition(fileName: string, position: number): ts.TextSpan {
335+
return unwrappJSONCallResult(this.shim.getBreakpointStatementAtPosition(fileName, position));
336+
}
337+
getSignatureHelpItems(fileName: string, position: number): ts.SignatureHelpItems {
338+
return unwrappJSONCallResult(this.shim.getSignatureHelpItems(fileName, position));
339+
}
340+
getRenameInfo(fileName: string, position: number): ts.RenameInfo {
341+
return unwrappJSONCallResult(this.shim.getRenameInfo(fileName, position));
342+
}
343+
findRenameLocations(fileName: string, position: number, findInStrings: boolean, findInComments: boolean): ts.RenameLocation[] {
344+
return unwrappJSONCallResult(this.shim.findRenameLocations(fileName, position, findInStrings, findInComments));
345+
}
346+
getDefinitionAtPosition(fileName: string, position: number): ts.DefinitionInfo[] {
347+
return unwrappJSONCallResult(this.shim.getDefinitionAtPosition(fileName, position));
348+
}
349+
getReferencesAtPosition(fileName: string, position: number): ts.ReferenceEntry[] {
350+
return unwrappJSONCallResult(this.shim.getReferencesAtPosition(fileName, position));
351+
}
352+
getOccurrencesAtPosition(fileName: string, position: number): ts.ReferenceEntry[] {
353+
return unwrappJSONCallResult(this.shim.getOccurrencesAtPosition(fileName, position));
354+
}
355+
getNavigateToItems(searchValue: string): ts.NavigateToItem[] {
356+
return unwrappJSONCallResult(this.shim.getNavigateToItems(searchValue));
357+
}
358+
getNavigationBarItems(fileName: string): ts.NavigationBarItem[] {
359+
return unwrappJSONCallResult(this.shim.getNavigationBarItems(fileName));
360+
}
361+
getOutliningSpans(fileName: string): ts.OutliningSpan[] {
362+
return unwrappJSONCallResult(this.shim.getOutliningSpans(fileName));
363+
}
364+
getTodoComments(fileName: string, descriptors: ts.TodoCommentDescriptor[]): ts.TodoComment[] {
365+
return unwrappJSONCallResult(this.shim.getTodoComments(fileName, JSON.stringify(descriptors)));
366+
}
367+
getBraceMatchingAtPosition(fileName: string, position: number): ts.TextSpan[] {
368+
return unwrappJSONCallResult(this.shim.getBraceMatchingAtPosition(fileName, position));
369+
}
370+
getIndentationAtPosition(fileName: string, position: number, options: ts.EditorOptions): number {
371+
return unwrappJSONCallResult(this.shim.getIndentationAtPosition(fileName, position, JSON.stringify(options)));
372+
}
373+
getFormattingEditsForRange(fileName: string, start: number, end: number, options: ts.FormatCodeOptions): ts.TextChange[] {
374+
return unwrappJSONCallResult(this.shim.getFormattingEditsForRange(fileName, start, end, JSON.stringify(options)));
375+
}
376+
getFormattingEditsForDocument(fileName: string, options: ts.FormatCodeOptions): ts.TextChange[] {
377+
return unwrappJSONCallResult(this.shim.getFormattingEditsForDocument(fileName, JSON.stringify(options)));
378+
}
379+
getFormattingEditsAfterKeystroke(fileName: string, position: number, key: string, options: ts.FormatCodeOptions): ts.TextChange[] {
380+
return unwrappJSONCallResult(this.shim.getFormattingEditsAfterKeystroke(fileName, position, key, JSON.stringify(options)));
381+
}
382+
getEmitOutput(fileName: string): ts.EmitOutput {
383+
return unwrappJSONCallResult(this.shim.getEmitOutput(fileName));
384+
}
385+
getProgram(): ts.Program {
386+
throw new Error("Program can not be marshalled accross the shim layer.");
387+
}
388+
getSourceFile(filename: string): ts.SourceFile {
389+
throw new Error("SourceFile can not be marshalled accross the shim layer.");
390+
}
391+
dispose(): void { this.shim.dispose({}); }
392+
}
393+
394+
export class ShimLanugageServiceAdaptor implements LanugageServiceAdaptor {
395+
private host: ShimLanguageServiceHost;
396+
private factory: ts.TypeScriptServicesFactory;
397+
constructor(cancellationToken?: ts.CancellationToken, options?: ts.CompilerOptions) {
398+
this.host = new ShimLanguageServiceHost(cancellationToken, options);
399+
this.factory = new TypeScript.Services.TypeScriptServicesFactory();
400+
}
401+
getHost() { return this.host; }
402+
getLanguageService(): ts.LanguageService { return new LanguageServiceShimProxy(this.factory.createLanguageServiceShim(this.host)); }
403+
getClassifier(): ts.Classifier { return new ClassifierShimProxy(this.factory.createClassifierShim(this.host)); }
404+
getPreProcessedFileInfo(filename: string, fileContents: string): ts.PreProcessedFileInfo {
405+
var shimResult: {
406+
referencedFiles: ts.IFileReference[];
407+
importedFiles: ts.IFileReference[];
408+
isLibFile: boolean;
409+
};
410+
411+
var coreServicesShim = this.factory.createCoreServicesShim(this.host);
412+
shimResult = unwrappJSONCallResult(coreServicesShim.getPreProcessedFileInfo(filename, ts.ScriptSnapshot.fromString(fileContents)));
413+
414+
var convertResult: ts.PreProcessedFileInfo = {
415+
referencedFiles: [],
416+
importedFiles: [],
417+
isLibFile: shimResult.isLibFile
418+
};
419+
420+
ts.forEach(shimResult.referencedFiles, refFile => {
421+
convertResult.referencedFiles.push({
422+
filename: refFile.path,
423+
pos: refFile.position,
424+
end: refFile.position + refFile.length
425+
});
426+
});
427+
428+
ts.forEach(shimResult.importedFiles, importedFile => {
429+
convertResult.importedFiles.push({
430+
filename: importedFile.path,
431+
pos: importedFile.position,
432+
end: importedFile.position + importedFile.length
433+
});
434+
});
435+
436+
return convertResult;
437+
}
438+
}
439+
112440
export class TypeScriptLS implements ts.LanguageServiceShimHost {
113441
private ls: ts.LanguageServiceShim = null;
114442

@@ -129,11 +457,6 @@ module Harness.LanguageService {
129457
return "TypeScriptLS";
130458
}
131459

132-
public addFile(fileName: string) {
133-
var code = Harness.IO.readFile(fileName);
134-
this.addScript(fileName, code);
135-
}
136-
137460
private getScriptInfo(fileName: string): ScriptInfo {
138461
return ts.lookUp(this.fileNameToScript, fileName);
139462
}

0 commit comments

Comments
 (0)