Add new Adaptor layer on top of Harness Language Service
This commit is contained in:
parent
7a4a8107dd
commit
4c06838d60
1 changed files with 329 additions and 6 deletions
|
@ -75,7 +75,7 @@ module Harness.LanguageService {
|
|||
}
|
||||
|
||||
class ScriptSnapshotShim implements ts.ScriptSnapshotShim {
|
||||
constructor(public scriptSnapshot: ScriptSnapshot) {
|
||||
constructor(public scriptSnapshot: ts.IScriptSnapshot) {
|
||||
}
|
||||
|
||||
public getText(start: number, end: number): string {
|
||||
|
@ -109,6 +109,334 @@ module Harness.LanguageService {
|
|||
}
|
||||
}
|
||||
|
||||
export interface LanugageServiceAdaptor {
|
||||
getHost(): LanguageServiceAdaptorHost;
|
||||
getLanguageService(): ts.LanguageService;
|
||||
getClassifier(): ts.Classifier;
|
||||
getPreProcessedFileInfo(filename: string, fileContents: string): ts.PreProcessedFileInfo;
|
||||
}
|
||||
|
||||
class LanguageServiceHostBase {
|
||||
protected fileNameToScript: ts.Map<ScriptInfo> = {};
|
||||
|
||||
constructor(protected cancellationToken: ts.CancellationToken = CancellationToken.None,
|
||||
protected settings = ts.getDefaultCompilerOptions()) {
|
||||
|
||||
}
|
||||
|
||||
public getFilenames(): string[] {
|
||||
var fileNames: string[] = [];
|
||||
ts.forEachKey(this.fileNameToScript,(fileName) => { fileNames.push(fileName); });
|
||||
return fileNames;
|
||||
}
|
||||
|
||||
public getScriptInfo(fileName: string): ScriptInfo {
|
||||
return ts.lookUp(this.fileNameToScript, fileName);
|
||||
}
|
||||
|
||||
public addScript(fileName: string, content: string): void {
|
||||
this.fileNameToScript[fileName] = new ScriptInfo(fileName, content);
|
||||
}
|
||||
|
||||
public updateScript(fileName: string, content: string) {
|
||||
var script = this.getScriptInfo(fileName);
|
||||
if (script !== null) {
|
||||
script.updateContent(content);
|
||||
return;
|
||||
}
|
||||
|
||||
this.addScript(fileName, content);
|
||||
}
|
||||
|
||||
public editScript(fileName: string, minChar: number, limChar: number, newText: string) {
|
||||
var script = this.getScriptInfo(fileName);
|
||||
if (script !== null) {
|
||||
script.editContent(minChar, limChar, newText);
|
||||
return;
|
||||
}
|
||||
|
||||
throw new Error("No script with name '" + fileName + "'");
|
||||
}
|
||||
|
||||
/**
|
||||
* @param line 1 based index
|
||||
* @param col 1 based index
|
||||
*/
|
||||
public lineColToPosition(fileName: string, line: number, col: number): number {
|
||||
var script: ScriptInfo = this.fileNameToScript[fileName];
|
||||
assert.isNotNull(script);
|
||||
assert.isTrue(line >= 1);
|
||||
assert.isTrue(col >= 1);
|
||||
|
||||
return ts.getPositionFromLineAndCharacter(script.lineMap, line, col);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param line 0 based index
|
||||
* @param col 0 based index
|
||||
*/
|
||||
public positionToZeroBasedLineCol(fileName: string, position: number): ts.LineAndCharacter {
|
||||
var script: ScriptInfo = this.fileNameToScript[fileName];
|
||||
assert.isNotNull(script);
|
||||
|
||||
var result = ts.getLineAndCharacterOfPosition(script.lineMap, position);
|
||||
|
||||
assert.isTrue(result.line >= 1);
|
||||
assert.isTrue(result.character >= 1);
|
||||
return { line: result.line - 1, character: result.character - 1 };
|
||||
}
|
||||
}
|
||||
|
||||
export interface LanguageServiceAdaptorHost extends LanguageServiceHostBase {
|
||||
}
|
||||
|
||||
/// Native adabtor
|
||||
class NativeLanguageServiceHost extends LanguageServiceHostBase implements ts.LanguageServiceHost {
|
||||
getCompilationSettings(): ts.CompilerOptions { return this.settings; }
|
||||
getCancellationToken(): ts.CancellationToken { return this.cancellationToken; }
|
||||
getCurrentDirectory(): string { return ""; }
|
||||
getDefaultLibFilename(): string { return ""; }
|
||||
getScriptFileNames(): string[] { return this.getFilenames(); }
|
||||
getScriptSnapshot(filename: string): ts.IScriptSnapshot {
|
||||
var script = this.getScriptInfo(filename);
|
||||
return script ? new ScriptSnapshot(script) : undefined;
|
||||
}
|
||||
getScriptVersion(filename: string): string {
|
||||
var script = this.getScriptInfo(filename);
|
||||
return script ? script.version.toString() : undefined;
|
||||
}
|
||||
log(s: string): void { }
|
||||
trace(s: string): void { }
|
||||
error(s: string): void { }
|
||||
}
|
||||
|
||||
export class NativeLanugageServiceAdaptor implements LanugageServiceAdaptor {
|
||||
private host: NativeLanguageServiceHost;
|
||||
constructor(cancellationToken?: ts.CancellationToken, options?: ts.CompilerOptions) {
|
||||
this.host = new NativeLanguageServiceHost(cancellationToken, options);
|
||||
}
|
||||
getHost() { return this.host; }
|
||||
getLanguageService(): ts.LanguageService { return ts.createLanguageService(this.host); }
|
||||
getClassifier(): ts.Classifier { return ts.createClassifier(); }
|
||||
getPreProcessedFileInfo(filename: string, fileContents: string): ts.PreProcessedFileInfo { return ts.preProcessFile(fileContents); }
|
||||
}
|
||||
|
||||
/// Shim adabtor
|
||||
class ShimLanguageServiceHost extends LanguageServiceHostBase implements ts.LanguageServiceShimHost {
|
||||
private nativeHost: NativeLanguageServiceHost;
|
||||
constructor(cancellationToken?: ts.CancellationToken, options?: ts.CompilerOptions) {
|
||||
super(cancellationToken, options);
|
||||
this.nativeHost = new NativeLanguageServiceHost(cancellationToken, options);
|
||||
}
|
||||
|
||||
getFilenames(): string[] { return this.nativeHost.getFilenames(); }
|
||||
getScriptInfo(fileName: string): ScriptInfo { return this.nativeHost.getScriptInfo(fileName); }
|
||||
addScript(fileName: string, content: string): void { this.nativeHost.addScript(fileName, content); }
|
||||
updateScript(fileName: string, content: string): void { return this.nativeHost.updateScript(fileName, content); }
|
||||
editScript(fileName: string, minChar: number, limChar: number, newText: string): void { this.nativeHost.editScript(fileName, minChar, limChar, newText); }
|
||||
lineColToPosition(fileName: string, line: number, col: number): number { return this.nativeHost.lineColToPosition(fileName, line, col); }
|
||||
positionToZeroBasedLineCol(fileName: string, position: number): ts.LineAndCharacter { return this.nativeHost.positionToZeroBasedLineCol(fileName, position); }
|
||||
|
||||
getCompilationSettings(): string { return JSON.stringify(this.nativeHost.getCompilationSettings()); }
|
||||
getCancellationToken(): ts.CancellationToken { return this.nativeHost.getCancellationToken(); }
|
||||
getCurrentDirectory(): string { return this.nativeHost.getCurrentDirectory(); }
|
||||
getDefaultLibFilename(): string { return this.nativeHost.getDefaultLibFilename(); }
|
||||
getScriptFileNames(): string { return JSON.stringify(this.nativeHost.getScriptFileNames()); }
|
||||
getScriptSnapshot(filename: string): ts.ScriptSnapshotShim {
|
||||
var nativeScriptSnapshot = this.nativeHost.getScriptSnapshot(filename);
|
||||
return nativeScriptSnapshot && new ScriptSnapshotShim(nativeScriptSnapshot);
|
||||
}
|
||||
getScriptVersion(filename: string): string { return this.nativeHost.getScriptVersion(filename); }
|
||||
getLocalizedDiagnosticMessages(): string { return JSON.stringify({}); }
|
||||
log(s: string): void { this.nativeHost.log(s); }
|
||||
trace(s: string): void { this.nativeHost.trace(s); }
|
||||
error(s: string): void { this.nativeHost.error(s); }
|
||||
}
|
||||
|
||||
class ClassifierShimProxy implements ts.Classifier {
|
||||
constructor(private shim: ts.ClassifierShim) { }
|
||||
getClassificationsForLine(text: string, lexState: ts.EndOfLineState, classifyKeywordsInGenerics?: boolean): ts.ClassificationResult {
|
||||
var result = this.shim.getClassificationsForLine(text, lexState, classifyKeywordsInGenerics).split('\n');
|
||||
var entries: ts.ClassificationInfo[] = [];
|
||||
var i = 0;
|
||||
var position = 0;
|
||||
|
||||
for (; i < result.length - 1; i += 2) {
|
||||
var t = entries[i / 2] = {
|
||||
length: parseInt(result[i]),
|
||||
classification: parseInt(result[i + 1])
|
||||
};
|
||||
|
||||
assert.isTrue(t.length > 0, "Result length should be greater than 0, got :" + t.length);
|
||||
position += t.length;
|
||||
}
|
||||
var finalLexState = parseInt(result[result.length - 1]);
|
||||
|
||||
assert.equal(position, text.length, "Expected cumulative length of all entries to match the length of the source. expected: " + text.length + ", but got: " + position);
|
||||
|
||||
return {
|
||||
finalLexState,
|
||||
entries
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
function unwrappJSONCallResult(result: string): any {
|
||||
var parsedResult = JSON.parse(result);
|
||||
if (parsedResult.error) {
|
||||
throw new Error("Language Service Shim Error: " + JSON.stringify(parsedResult.error));
|
||||
}
|
||||
else if (parsedResult.canceled) {
|
||||
throw new ts.OperationCanceledException();
|
||||
}
|
||||
return parsedResult.result;
|
||||
}
|
||||
|
||||
class LanguageServiceShimProxy implements ts.LanguageService {
|
||||
constructor(private shim: ts.LanguageServiceShim) { }
|
||||
private unwrappJSONCallResult(result: string): any {
|
||||
var parsedResult = JSON.parse(result);
|
||||
if (parsedResult.error) {
|
||||
throw new Error("Language Service Shim Error: " + JSON.stringify(parsedResult.error));
|
||||
}
|
||||
return parsedResult.result;
|
||||
}
|
||||
cleanupSemanticCache(): void {
|
||||
this.shim.cleanupSemanticCache();
|
||||
}
|
||||
getSyntacticDiagnostics(fileName: string): ts.Diagnostic[] {
|
||||
return unwrappJSONCallResult(this.shim.getSyntacticDiagnostics(fileName));
|
||||
}
|
||||
getSemanticDiagnostics(fileName: string): ts.Diagnostic[] {
|
||||
return unwrappJSONCallResult(this.shim.getSemanticDiagnostics(fileName));
|
||||
}
|
||||
getCompilerOptionsDiagnostics(): ts.Diagnostic[] {
|
||||
return unwrappJSONCallResult(this.shim.getCompilerOptionsDiagnostics());
|
||||
}
|
||||
getSyntacticClassifications(fileName: string, span: ts.TextSpan): ts.ClassifiedSpan[] {
|
||||
return unwrappJSONCallResult(this.shim.getSyntacticClassifications(fileName, span.start, span.length));
|
||||
}
|
||||
getSemanticClassifications(fileName: string, span: ts.TextSpan): ts.ClassifiedSpan[] {
|
||||
return unwrappJSONCallResult(this.shim.getSemanticClassifications(fileName, span.start, span.length));
|
||||
}
|
||||
getCompletionsAtPosition(fileName: string, position: number): ts.CompletionInfo {
|
||||
return unwrappJSONCallResult(this.shim.getCompletionsAtPosition(fileName, position));
|
||||
}
|
||||
getCompletionEntryDetails(fileName: string, position: number, entryName: string): ts.CompletionEntryDetails {
|
||||
return unwrappJSONCallResult(this.shim.getCompletionEntryDetails(fileName, position, entryName));
|
||||
}
|
||||
getQuickInfoAtPosition(fileName: string, position: number): ts.QuickInfo {
|
||||
return unwrappJSONCallResult(this.shim.getQuickInfoAtPosition(fileName, position));
|
||||
}
|
||||
getNameOrDottedNameSpan(fileName: string, startPos: number, endPos: number): ts.TextSpan {
|
||||
return unwrappJSONCallResult(this.shim.getNameOrDottedNameSpan(fileName, startPos, endPos));
|
||||
}
|
||||
getBreakpointStatementAtPosition(fileName: string, position: number): ts.TextSpan {
|
||||
return unwrappJSONCallResult(this.shim.getBreakpointStatementAtPosition(fileName, position));
|
||||
}
|
||||
getSignatureHelpItems(fileName: string, position: number): ts.SignatureHelpItems {
|
||||
return unwrappJSONCallResult(this.shim.getSignatureHelpItems(fileName, position));
|
||||
}
|
||||
getRenameInfo(fileName: string, position: number): ts.RenameInfo {
|
||||
return unwrappJSONCallResult(this.shim.getRenameInfo(fileName, position));
|
||||
}
|
||||
findRenameLocations(fileName: string, position: number, findInStrings: boolean, findInComments: boolean): ts.RenameLocation[] {
|
||||
return unwrappJSONCallResult(this.shim.findRenameLocations(fileName, position, findInStrings, findInComments));
|
||||
}
|
||||
getDefinitionAtPosition(fileName: string, position: number): ts.DefinitionInfo[] {
|
||||
return unwrappJSONCallResult(this.shim.getDefinitionAtPosition(fileName, position));
|
||||
}
|
||||
getReferencesAtPosition(fileName: string, position: number): ts.ReferenceEntry[] {
|
||||
return unwrappJSONCallResult(this.shim.getReferencesAtPosition(fileName, position));
|
||||
}
|
||||
getOccurrencesAtPosition(fileName: string, position: number): ts.ReferenceEntry[] {
|
||||
return unwrappJSONCallResult(this.shim.getOccurrencesAtPosition(fileName, position));
|
||||
}
|
||||
getNavigateToItems(searchValue: string): ts.NavigateToItem[] {
|
||||
return unwrappJSONCallResult(this.shim.getNavigateToItems(searchValue));
|
||||
}
|
||||
getNavigationBarItems(fileName: string): ts.NavigationBarItem[] {
|
||||
return unwrappJSONCallResult(this.shim.getNavigationBarItems(fileName));
|
||||
}
|
||||
getOutliningSpans(fileName: string): ts.OutliningSpan[] {
|
||||
return unwrappJSONCallResult(this.shim.getOutliningSpans(fileName));
|
||||
}
|
||||
getTodoComments(fileName: string, descriptors: ts.TodoCommentDescriptor[]): ts.TodoComment[] {
|
||||
return unwrappJSONCallResult(this.shim.getTodoComments(fileName, JSON.stringify(descriptors)));
|
||||
}
|
||||
getBraceMatchingAtPosition(fileName: string, position: number): ts.TextSpan[] {
|
||||
return unwrappJSONCallResult(this.shim.getBraceMatchingAtPosition(fileName, position));
|
||||
}
|
||||
getIndentationAtPosition(fileName: string, position: number, options: ts.EditorOptions): number {
|
||||
return unwrappJSONCallResult(this.shim.getIndentationAtPosition(fileName, position, JSON.stringify(options)));
|
||||
}
|
||||
getFormattingEditsForRange(fileName: string, start: number, end: number, options: ts.FormatCodeOptions): ts.TextChange[] {
|
||||
return unwrappJSONCallResult(this.shim.getFormattingEditsForRange(fileName, start, end, JSON.stringify(options)));
|
||||
}
|
||||
getFormattingEditsForDocument(fileName: string, options: ts.FormatCodeOptions): ts.TextChange[] {
|
||||
return unwrappJSONCallResult(this.shim.getFormattingEditsForDocument(fileName, JSON.stringify(options)));
|
||||
}
|
||||
getFormattingEditsAfterKeystroke(fileName: string, position: number, key: string, options: ts.FormatCodeOptions): ts.TextChange[] {
|
||||
return unwrappJSONCallResult(this.shim.getFormattingEditsAfterKeystroke(fileName, position, key, JSON.stringify(options)));
|
||||
}
|
||||
getEmitOutput(fileName: string): ts.EmitOutput {
|
||||
return unwrappJSONCallResult(this.shim.getEmitOutput(fileName));
|
||||
}
|
||||
getProgram(): ts.Program {
|
||||
throw new Error("Program can not be marshalled accross the shim layer.");
|
||||
}
|
||||
getSourceFile(filename: string): ts.SourceFile {
|
||||
throw new Error("SourceFile can not be marshalled accross the shim layer.");
|
||||
}
|
||||
dispose(): void { this.shim.dispose({}); }
|
||||
}
|
||||
|
||||
export class ShimLanugageServiceAdaptor implements LanugageServiceAdaptor {
|
||||
private host: ShimLanguageServiceHost;
|
||||
private factory: ts.TypeScriptServicesFactory;
|
||||
constructor(cancellationToken?: ts.CancellationToken, options?: ts.CompilerOptions) {
|
||||
this.host = new ShimLanguageServiceHost(cancellationToken, options);
|
||||
this.factory = new TypeScript.Services.TypeScriptServicesFactory();
|
||||
}
|
||||
getHost() { return this.host; }
|
||||
getLanguageService(): ts.LanguageService { return new LanguageServiceShimProxy(this.factory.createLanguageServiceShim(this.host)); }
|
||||
getClassifier(): ts.Classifier { return new ClassifierShimProxy(this.factory.createClassifierShim(this.host)); }
|
||||
getPreProcessedFileInfo(filename: string, fileContents: string): ts.PreProcessedFileInfo {
|
||||
var shimResult: {
|
||||
referencedFiles: ts.IFileReference[];
|
||||
importedFiles: ts.IFileReference[];
|
||||
isLibFile: boolean;
|
||||
};
|
||||
|
||||
var coreServicesShim = this.factory.createCoreServicesShim(this.host);
|
||||
shimResult = unwrappJSONCallResult(coreServicesShim.getPreProcessedFileInfo(filename, ts.ScriptSnapshot.fromString(fileContents)));
|
||||
|
||||
var convertResult: ts.PreProcessedFileInfo = {
|
||||
referencedFiles: [],
|
||||
importedFiles: [],
|
||||
isLibFile: shimResult.isLibFile
|
||||
};
|
||||
|
||||
ts.forEach(shimResult.referencedFiles, refFile => {
|
||||
convertResult.referencedFiles.push({
|
||||
filename: refFile.path,
|
||||
pos: refFile.position,
|
||||
end: refFile.position + refFile.length
|
||||
});
|
||||
});
|
||||
|
||||
ts.forEach(shimResult.importedFiles, importedFile => {
|
||||
convertResult.importedFiles.push({
|
||||
filename: importedFile.path,
|
||||
pos: importedFile.position,
|
||||
end: importedFile.position + importedFile.length
|
||||
});
|
||||
});
|
||||
|
||||
return convertResult;
|
||||
}
|
||||
}
|
||||
|
||||
export class TypeScriptLS implements ts.LanguageServiceShimHost {
|
||||
private ls: ts.LanguageServiceShim = null;
|
||||
|
||||
|
@ -129,11 +457,6 @@ module Harness.LanguageService {
|
|||
return "TypeScriptLS";
|
||||
}
|
||||
|
||||
public addFile(fileName: string) {
|
||||
var code = Harness.IO.readFile(fileName);
|
||||
this.addScript(fileName, code);
|
||||
}
|
||||
|
||||
private getScriptInfo(fileName: string): ScriptInfo {
|
||||
return ts.lookUp(this.fileNameToScript, fileName);
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue