[html] offer JQuery intellisense

This commit is contained in:
Martin Aeschlimann 2016-11-23 16:33:17 +01:00
parent 1c4e72aa68
commit fee99086fb
7 changed files with 3337 additions and 69 deletions

View file

@ -0,0 +1,6 @@
// ATTENTION - THIS DIRECTORY CONTAINS THIRD PARTY OPEN SOURCE MATERIALS:
[{
"name": "definitelytyped",
"repositoryURL": "https://github.com/DefinitelyTyped/DefinitelyTyped",
"license": "MIT"
}]

3249
extensions/html/server/lib/jquery.d.ts vendored Normal file

File diff suppressed because it is too large Load diff

View file

@ -8,9 +8,11 @@ import { LanguageModelCache, getLanguageModelCache } from '../languageModelCache
import { TextDocument, Position } from 'vscode-languageserver-types';
import { getCSSLanguageService, Stylesheet } from 'vscode-css-languageservice';
import { LanguageMode } from './languageModes';
import { HTMLDocumentRegions } from './embeddedSupport';
export function getCSSMode(embeddedCSSDocuments: LanguageModelCache<TextDocument>): LanguageMode {
export function getCSSMode(documentRegions: LanguageModelCache<HTMLDocumentRegions>): LanguageMode {
let cssLanguageService = getCSSLanguageService();
let embeddedCSSDocuments = getLanguageModelCache<TextDocument>(10, 60, document => documentRegions.get(document).getEmbeddedDocument('css'));
let cssStylesheets = getLanguageModelCache<Stylesheet>(10, 60, document => cssLanguageService.parseStylesheet(document));
return {
@ -49,9 +51,11 @@ export function getCSSMode(embeddedCSSDocuments: LanguageModelCache<TextDocument
return cssLanguageService.findColorSymbols(embedded, cssStylesheets.get(embedded));
},
onDocumentRemoved(document: TextDocument) {
embeddedCSSDocuments.onDocumentRemoved(document);
cssStylesheets.onDocumentRemoved(document);
},
dispose() {
embeddedCSSDocuments.dispose();
cssStylesheets.dispose();
}
};

View file

@ -17,20 +17,78 @@ export interface HTMLDocumentRegions {
getLanguageRanges(range: Range): LanguageRange[];
getLanguageAtPosition(position: Position): string;
getLanguagesInDocument(): string[];
getImportedScripts(): string[];
}
interface EmbeddedRegion { languageId: string; start: number; end: number; attributeValue?: boolean; };
export function getDocumentRegions(languageService: LanguageService, document: TextDocument): HTMLDocumentRegions {
let regions = getEmbeddedRegions(languageService, document);
let regions: EmbeddedRegion[] = [];
let scanner = languageService.createScanner(document.getText());
let lastTagName: string;
let lastAttributeName: string;
let languageIdFromType: string;
let importedScripts = [];
let token = scanner.scan();
while (token !== TokenType.EOS) {
switch (token) {
case TokenType.StartTag:
lastTagName = scanner.getTokenText();
lastAttributeName = null;
languageIdFromType = 'javascript';
break;
case TokenType.Styles:
regions.push({ languageId: 'css', start: scanner.getTokenOffset(), end: scanner.getTokenEnd() });
break;
case TokenType.Script:
regions.push({ languageId: languageIdFromType, start: scanner.getTokenOffset(), end: scanner.getTokenEnd() });
break;
case TokenType.AttributeName:
lastAttributeName = scanner.getTokenText();
break;
case TokenType.AttributeValue:
if (lastAttributeName === 'src' && lastTagName.toLowerCase() === 'script') {
let value = scanner.getTokenText();
if (value[0] === '\'' || value[0] === '"') {
value = value.substr(1, value.length - 1);
}
importedScripts.push(value);
} else if (lastAttributeName === 'type' && lastTagName.toLowerCase() === 'script') {
if (/["'](text|application)\/(java|ecma)script["']/.test(scanner.getTokenText())) {
languageIdFromType = 'javascript';
} else {
languageIdFromType = void 0;
}
} else {
let attributelLanguageId = getAttributeLanguage(lastAttributeName);
if (attributelLanguageId) {
let start = scanner.getTokenOffset();
let end = scanner.getTokenEnd();
let firstChar = document.getText()[start];
if (firstChar === '\'' || firstChar === '"') {
start++;
end--;
}
regions.push({ languageId: attributelLanguageId, start, end, attributeValue: true });
}
}
lastAttributeName = null;
break;
}
token = scanner.scan();
}
return {
getLanguageRanges: (range: Range) => getLanguageRanges(document, regions, range),
getEmbeddedDocument: (languageId: string) => getEmbeddedDocument(document, regions, languageId),
getLanguageAtPosition: (position: Position) => getLanguageAtPosition(document, regions, position),
getLanguagesInDocument: () => getLanguagesInDocument(document, regions)
getLanguagesInDocument: () => getLanguagesInDocument(document, regions),
getImportedScripts: () => importedScripts
};
}
function getLanguageRanges(document: TextDocument, regions: EmbeddedRegion[], range: Range): LanguageRange[] {
let result: LanguageRange[] = [];
let currentPos = range ? range.start : Position.create(0, 0);
@ -164,58 +222,6 @@ function append(result: string, str: string, n: number): string {
return result;
}
function getEmbeddedRegions(languageService: LanguageService, document: TextDocument): EmbeddedRegion[] {
let regions: EmbeddedRegion[] = [];
let scanner = languageService.createScanner(document.getText());
let lastTagName: string;
let lastAttributeName: string;
let languageIdFromType: string;
let token = scanner.scan();
while (token !== TokenType.EOS) {
switch (token) {
case TokenType.StartTag:
lastTagName = scanner.getTokenText();
lastAttributeName = null;
languageIdFromType = 'javascript';
break;
case TokenType.Styles:
regions.push({ languageId: 'css', start: scanner.getTokenOffset(), end: scanner.getTokenEnd() });
break;
case TokenType.Script:
regions.push({ languageId: languageIdFromType, start: scanner.getTokenOffset(), end: scanner.getTokenEnd() });
break;
case TokenType.AttributeName:
lastAttributeName = scanner.getTokenText();
break;
case TokenType.AttributeValue:
if (lastAttributeName === 'type' && lastTagName.toLowerCase() === 'script') {
if (/["'](text|application)\/(java|ecma)script["']/.test(scanner.getTokenText())) {
languageIdFromType = 'javascript';
} else {
languageIdFromType = void 0;
}
} else {
let attributelLanguageId = getAttributeLanguage(lastAttributeName);
if (attributelLanguageId) {
let start = scanner.getTokenOffset();
let end = scanner.getTokenEnd();
let firstChar = document.getText()[start];
if (firstChar === '\'' || firstChar === '"') {
start++;
end--;
}
regions.push({ languageId: attributelLanguageId, start, end, attributeValue: true });
}
}
lastAttributeName = null;
break;
}
token = scanner.scan();
}
return regions;
}
function getAttributeLanguage(attributeName: string): string {
let match = attributeName.match(/^(style)|(on\w+)$/i);
if (!match) {

View file

@ -4,28 +4,33 @@
*--------------------------------------------------------------------------------------------*/
'use strict';
import { LanguageModelCache } from '../languageModelCache';
import { LanguageModelCache, getLanguageModelCache } from '../languageModelCache';
import { CompletionItem, Location, SignatureHelp, SignatureInformation, ParameterInformation, Definition, TextEdit, TextDocument, Diagnostic, DiagnosticSeverity, Range, CompletionItemKind, Hover, MarkedString, DocumentHighlight, DocumentHighlightKind, CompletionList, Position, FormattingOptions } from 'vscode-languageserver-types';
import { LanguageMode } from './languageModes';
import { getWordAtText } from '../utils/words';
import { HTMLDocumentRegions } from './embeddedSupport';
import * as ts from 'typescript';
import { join } from 'path';
const FILE_NAME = 'vscode://javascript/1'; // the same 'file' is used for all contents
const JQUERY_D_TS = join(__dirname, '../../lib/jquery.d.ts');
const JS_WORD_REGEX = /(-?\d*\.\d\w*)|([^\`\~\!\@\#\%\^\&\*\(\)\-\=\+\[\{\]\}\\\|\;\:\'\"\,\.\<\>\/\?\s]+)/g;
export function getJavascriptMode(jsDocuments: LanguageModelCache<TextDocument>): LanguageMode {
export function getJavascriptMode(documentRegions: LanguageModelCache<HTMLDocumentRegions>): LanguageMode {
let jsDocuments = getLanguageModelCache<TextDocument>(10, 60, document => documentRegions.get(document).getEmbeddedDocument('javascript'));
let compilerOptions = { allowNonTsExtensions: true, allowJs: true, target: ts.ScriptTarget.Latest };
let currentTextDocument: TextDocument;
let host = {
getCompilationSettings: () => compilerOptions,
getScriptFileNames: () => [FILE_NAME],
getScriptFileNames: () => [FILE_NAME, JQUERY_D_TS],
getScriptVersion: (fileName: string) => {
if (fileName === FILE_NAME) {
return String(currentTextDocument.version);
}
return '1'; // default lib is static
return '1'; // default lib an jquery.d.ts are static
},
getScriptSnapshot: (fileName: string) => {
let text = fileName === FILE_NAME ? currentTextDocument.getText() : ts.sys.readFile(fileName);
@ -35,7 +40,7 @@ export function getJavascriptMode(jsDocuments: LanguageModelCache<TextDocument>)
getChangeRange: () => void 0
};
},
getCurrentDirectory: () => ts.sys.getCurrentDirectory(),
getCurrentDirectory: () => '',
getDefaultLibFileName: (options) => ts.getDefaultLibFilePath(options)
};
let jsLanguageService = ts.createLanguageService(host);
@ -207,9 +212,11 @@ export function getJavascriptMode(jsDocuments: LanguageModelCache<TextDocument>)
return null;
},
onDocumentRemoved(document: TextDocument) {
jsDocuments.onDocumentRemoved(document);
},
dispose() {
jsLanguageService.dispose();
jsDocuments.dispose();
}
};
};

View file

@ -60,14 +60,10 @@ export function getLanguageModes(supportedLanguages: { [languageId: string]: boo
let modes = {};
modes['html'] = getHTMLMode(htmlLanguageService);
if (supportedLanguages['css']) {
let embeddedCSSDocuments = getLanguageModelCache<TextDocument>(10, 60, document => documentRegions.get(document).getEmbeddedDocument('css'));
modelCaches.push(embeddedCSSDocuments);
modes['css'] = getCSSMode(embeddedCSSDocuments);
modes['css'] = getCSSMode(documentRegions);
}
if (supportedLanguages['javascript']) {
let embeddedJSDocuments = getLanguageModelCache<TextDocument>(10, 60, document => documentRegions.get(document).getEmbeddedDocument('javascript'));
modelCaches.push(embeddedJSDocuments);
modes['javascript'] = getJavascriptMode(embeddedJSDocuments);
modes['javascript'] = getJavascriptMode(documentRegions);
}
return {
getModeAtPosition(document: TextDocument, position: Position): LanguageMode {

View file

@ -22,10 +22,9 @@ suite('HTML Javascript Support', () => {
let document = TextDocument.create('test://test/test.html', 'html', 0, value);
let documentRegions = embeddedSupport.getDocumentRegions(htmlLanguageService, document);
let documentRegions = getLanguageModelCache<embeddedSupport.HTMLDocumentRegions>(10, 60, document => embeddedSupport.getDocumentRegions(htmlLanguageService, document));
let embeddedJSDocuments = getLanguageModelCache<TextDocument>(10, 60, document => documentRegions.getEmbeddedDocument('javascript'));
var mode = getJavascriptMode(embeddedJSDocuments);
var mode = getJavascriptMode(documentRegions);
let position = document.positionAt(offset);
let list = mode.doComplete(document, position);
@ -40,5 +39,6 @@ suite('HTML Javascript Support', () => {
test('Completions', function (): any {
assertCompletions('<html><script>window.|</script></html>', ['location']);
assertCompletions('<html><script>$.|</script></html>', ['getJSON']);
});
});