diff --git a/package.json b/package.json index 6f2d9017..d51df963 100644 --- a/package.json +++ b/package.json @@ -1,216 +1,247 @@ -{ - "name": "aurelia", - "version": "0.3.4", - "description": "A VS Code extension for Aurelia.", - "icon": "images/logo.png", - "keywords": [ - "aurelia", - "vscode", - "extension" - ], - "homepage": "http://aurelia.io", - "bugs": { - "url": "https://github.com/aurelia/vscode-extension/issues" - }, - "galleryBanner": { - "color": "#5c2d91", - "theme": "dark" - }, - "license": "MIT", - "author": "AureliaEffect", - "publisher": "AureliaEffect", - "preview": true, - "repository": { - "type": "git", - "url": "https://github.com/aurelia/vscode-extension" - }, - "engines": { - "vscode": "^1.18.1" - }, - "categories": [ - "Languages", - "Themes", - "Snippets" - ], - "activationEvents": [ - "onLanguage:html", - "onCommand:extension.auNew", - "onCommand:extension.auGenerate", - "onCommand:extension.auTest", - "onCommand:extension.auBuild", - "onCommand:extension.auRun", - "onCommand:extension.auRunWatch", - "onCommand:extension.auOpenRelated" - ], - "main": "./dist/src/client/main", - "contributes": { - "languages": [ - { - "id": "html", - "order": 1, - "extensions": [ - ".html", - ".htm", - ".shtml", - ".xhtml", - ".mdoc", - ".jsp", - ".asp", - ".aspx", - ".jshtm", - ".volt", - ".ejs" - ], - "aliases": [ - "HTML", - "htm", - "html", - "xhtml" - ], - "mimetypes": [ - "text/html", - "text/x-jshtm", - "text/template", - "text/ng-template", - "application/xhtml+xml" - ], - "configuration": "./language-configuration.json" - } - ], - "grammars": [ - { - "language": "html", - "scopeName": "au.html", - "path": "./syntaxes/html.json" - } - ], - "commands": [ - { - "command": "extension.auNew", - "title": "au new" - }, - { - "command": "extension.auGenerate", - "title": "au generate" - }, - { - "command": "extension.auBuild", - "title": "au build" - }, - { - "command": "extension.auTest", - "title": "au test" - }, - { - "command": "extension.auRun", - "title": "au run" - }, - { - "command": "extension.auRunWatch", - "title": "au run --watch" - }, - { - "command": "extension.auOpenRelated", - "title": "Open Related Aurelia File" - } - ], - "themes": [ - { - "label": "Aurelia Dark+", - "uiTheme": "vs-dark", - "path": "./themes/dark_plus.json" - }, - { - "label": "Aurelia Dark", - "uiTheme": "vs-dark", - "path": "./themes/dark_vs.json" - }, - { - "label": "Aurelia Light+", - "uiTheme": "vs", - "path": "./themes/light_plus.json" - }, - { - "label": "Aurelia Light", - "uiTheme": "vs", - "path": "./themes/light_vs.json" - }, - { - "label": "Aurelia Solarized (dark)", - "uiTheme": "vs-dark", - "path": "./themes/solarized_dark.json" - }, - { - "label": "Aurelia Solarized (light)", - "uiTheme": "vs", - "path": "./themes/solarized_light.json" - } - ], - "configuration": { - "id": "aurelia", - "order": 20, - "type": "object", - "title": "Aurelia", - "properties": { - "aurelia.autocomplete.quotes": { - "type": "string", - "enum": [ - "single", - "double" - ], - "default": "double", - "description": "Quotes to use when performing an autocomplete action" - } - } - } - }, - "scripts": { - "vscode:prepublish": "node ./node_modules/typescript/bin/tsc -p ./", - "build": "node ./node_modules/typescript/bin/tsc -p ./", - "watch": "node ./node_modules/typescript/bin/tsc -watch -p ./", - "postinstall": "node ./node_modules/vscode/bin/install", - "lint": "node ./node_modules/tslint/lib/tslint-cli.js -c tslint.json --project tsconfig.json", - "changelog": "node ./node_modules/conventional-changelog-cli/cli.js -p angular -i CHANGELOG.md -s", - "test": "mocha ./dist/test --recursive", - "vscode-typings": "node ./node_modules/vscode/bin/install" - }, - "commitplease": { - "style": "angular", - "types": [ - "feat", - "fix", - "docs", - "style", - "refactor", - "perf", - "test", - "chore" - ], - "scope": "\\S+.*" - }, - "devDependencies": { - "@types/chai": "^3.5.2", - "@types/mocha": "^2.2.39", - "@types/node": "^7.0.18", - "chai": "^3.5.0", - "commitplease": "^2.7.10", - "conventional-changelog-cli": "^1.3.1", - "mocha": "^3.3.0", - "mocha-junit-reporter": "^1.13.0", - "tslint": "^5.2.0", - "typescript": "^2.3.2", - "vscode": "^1.1.10", - "vscode-textmate": "^3.1.4" - }, - "dependencies": { - "aurelia-cli": "^0.29.0", - "aurelia-dependency-injection": "^1.3.0", - "aurelia-templating-binding": "^1.4.0", - "parse5": "^3.0.1", - "reflect-metadata": "^0.1.9", - "vscode-languageclient": "^3.5.0", - "vscode-languageserver": "^3.5.0", - "vscode-languageserver-types": "^3.5.0", - "vscode-uri": "^1.0.0" - } -} +{ + "name": "aurelia", + "version": "0.3.4", + "description": "A VS Code extension for Aurelia.", + "icon": "images/logo.png", + "keywords": [ + "aurelia", + "vscode", + "extension" + ], + "homepage": "http://aurelia.io", + "bugs": { + "url": "https://github.com/aurelia/vscode-extension/issues" + }, + "galleryBanner": { + "color": "#5c2d91", + "theme": "dark" + }, + "license": "MIT", + "author": "AureliaEffect", + "publisher": "AureliaEffect", + "preview": true, + "repository": { + "type": "git", + "url": "https://github.com/aurelia/vscode-extension" + }, + "engines": { + "vscode": "^1.18.1" + }, + "categories": [ + "Languages", + "Themes", + "Snippets" + ], + "activationEvents": [ + "onLanguage:html", + "onCommand:extension.auNew", + "onCommand:extension.auGenerate", + "onCommand:extension.auTest", + "onCommand:extension.auBuild", + "onCommand:extension.auRun", + "onCommand:extension.auRunWatch", + "onCommand:extension.auOpenRelated" + ], + "main": "./dist/src/client/main", + "contributes": { + "languages": [ + { + "id": "html", + "order": 1, + "extensions": [ + ".html", + ".htm", + ".shtml", + ".xhtml", + ".mdoc", + ".jsp", + ".asp", + ".aspx", + ".jshtm", + ".volt", + ".ejs" + ], + "aliases": [ + "HTML", + "htm", + "html", + "xhtml" + ], + "mimetypes": [ + "text/html", + "text/x-jshtm", + "text/template", + "text/ng-template", + "application/xhtml+xml" + ], + "configuration": "./language-configuration.json" + } + ], + "grammars": [ + { + "language": "html", + "scopeName": "au.html", + "path": "./syntaxes/html.json" + } + ], + "commands": [ + { + "command": "extension.auNew", + "title": "au new" + }, + { + "command": "extension.auGenerate", + "title": "au generate" + }, + { + "command": "extension.auBuild", + "title": "au build" + }, + { + "command": "extension.auTest", + "title": "au test" + }, + { + "command": "extension.auRun", + "title": "au run" + }, + { + "command": "extension.auRunWatch", + "title": "au run --watch" + }, + { + "command": "extension.auOpenRelated", + "title": "Open Related Aurelia File" + } + ], + "themes": [ + { + "label": "Aurelia Dark+", + "uiTheme": "vs-dark", + "path": "./themes/dark_plus.json" + }, + { + "label": "Aurelia Dark", + "uiTheme": "vs-dark", + "path": "./themes/dark_vs.json" + }, + { + "label": "Aurelia Light+", + "uiTheme": "vs", + "path": "./themes/light_plus.json" + }, + { + "label": "Aurelia Light", + "uiTheme": "vs", + "path": "./themes/light_vs.json" + }, + { + "label": "Aurelia Solarized (dark)", + "uiTheme": "vs-dark", + "path": "./themes/solarized_dark.json" + }, + { + "label": "Aurelia Solarized (light)", + "uiTheme": "vs", + "path": "./themes/solarized_light.json" + } + ], + "configuration": { + "id": "aurelia", + "order": 20, + "type": "object", + "title": "Aurelia", + "properties": { + "aurelia.autocomplete.quotes": { + "type": "string", + "enum": [ + "single", + "double" + ], + "default": "double", + "description": "Quotes to use when performing an autocomplete action" + }, + "aurelia.autocomplete.bindings.data" : { + "type": "array", + "description": "Auto complete options to provide on data bindings", + "default": [ + { + "name": "bind", + "documentation": "automatically chooses the binding mode. Uses two-way binding for form controls and to-view/one-way binding for almost everything else" + }, + { + "name": "to-view", + "documentation": "flows data one direction: from the view-model to the view" + }, + { + "name": "one-way", + "documentation": "[deprecated, use to-view instead] flows data one direction: from the view-model to the view", + "label": ".one-way='' [deprecated]" + }, + { + "name": "from-view", + "documentation": "flows data one direction: from the view to the view-model" + }, + { + "name": "two-way", + "documentation": "flows data both ways: from view-model to view and from view to view-model" + }, + { + "name": "one-time", + "documentation": "flows data one direction: from the view-model to the view, once" + } + ] + } + } + } + }, + "scripts": { + "vscode:prepublish": "node ./node_modules/typescript/bin/tsc -p ./", + "build": "node ./node_modules/typescript/bin/tsc -p ./", + "watch": "node ./node_modules/typescript/bin/tsc -watch -p ./", + "postinstall": "node ./node_modules/vscode/bin/install", + "lint": "node ./node_modules/tslint/lib/tslint-cli.js -c tslint.json --project tsconfig.json", + "changelog": "node ./node_modules/conventional-changelog-cli/cli.js -p angular -i CHANGELOG.md -s", + "test": "mocha ./dist/test --recursive", + "vscode-typings": "node ./node_modules/vscode/bin/install" + }, + "commitplease": { + "style": "angular", + "types": [ + "feat", + "fix", + "docs", + "style", + "refactor", + "perf", + "test", + "chore" + ], + "scope": "\\S+.*" + }, + "devDependencies": { + "@types/chai": "^3.5.2", + "@types/mocha": "^2.2.39", + "@types/node": "^7.0.18", + "chai": "^3.5.0", + "commitplease": "^2.7.10", + "conventional-changelog-cli": "^1.3.1", + "mocha": "^3.3.0", + "mocha-junit-reporter": "^1.13.0", + "tslint": "^5.2.0", + "typescript": "^2.3.2", + "vscode": "^1.1.10", + "vscode-textmate": "^3.1.4" + }, + "dependencies": { + "aurelia-cli": "^0.29.0", + "aurelia-dependency-injection": "^1.3.0", + "aurelia-templating-binding": "^1.4.0", + "parse5": "^3.0.1", + "reflect-metadata": "^0.1.9", + "vscode-languageclient": "^3.5.0", + "vscode-languageserver": "^3.5.0", + "vscode-languageserver-types": "^3.5.0", + "vscode-uri": "^1.0.0" + } +} diff --git a/src/server/AureliaSettings.ts b/src/server/AureliaSettings.ts index 518cb249..4d8bb20b 100644 --- a/src/server/AureliaSettings.ts +++ b/src/server/AureliaSettings.ts @@ -1,3 +1,7 @@ -export default class AureliaSettings { - public quote: string = '"'; -} +export default class AureliaSettings { + public quote: string = '"'; + + public bindings = { + data : [] + } +} diff --git a/src/server/Completions/BindingCompletionFactory.ts b/src/server/Completions/BindingCompletionFactory.ts index 29cc6268..7294390d 100644 --- a/src/server/Completions/BindingCompletionFactory.ts +++ b/src/server/Completions/BindingCompletionFactory.ts @@ -1,101 +1,79 @@ -import { - CompletionItem, - CompletionItemKind, - InsertTextFormat, MarkedString } from 'vscode-languageserver-types'; -import { autoinject } from 'aurelia-dependency-injection'; -import ElementLibrary from './Library/_elementLibrary'; -import { TagDefinition, AttributeDefinition } from './../DocumentParser'; -import BaseAttributeCompletionFactory from './BaseAttributeCompletionFactory'; -import { GlobalAttributes } from './Library/_elementStructure'; -import AureliaSettings from './../AureliaSettings'; - -@autoinject() -export default class BindingCompletionFactory extends BaseAttributeCompletionFactory { - - constructor(library: ElementLibrary, private settings: AureliaSettings) { super(library); } - - public create(tagDef: TagDefinition, attributeDef: AttributeDefinition, nextChar: string): Array { - - let snippetPrefix = nextChar === '=' ? '' : `=${this.settings.quote}$0${this.settings.quote}`; - let result: Array = []; - - let element = this.getElement(tagDef.name); - if (!element.events.get(attributeDef.name) && !GlobalAttributes.events.get(attributeDef.name)) { - this.setAttributes(element.attributes, attributeDef.name, snippetPrefix, result); - } - - this.setEvents(element.events, attributeDef.name, snippetPrefix, result); - - return result; - } - - private setEvents(events, name, snippetPrefix, result) { - let event = events.get(name); - if (!event) { - event = GlobalAttributes.events.get(name); - } - - if (event) { - if (event.bubbles) { - for(let binding of ['delegate', 'capture']) { - result.push({ - documentation: binding, - insertText: binding + snippetPrefix, - insertTextFormat: InsertTextFormat.Snippet, - kind: CompletionItemKind.Property, - label: `.${binding}=${this.settings.quote}${this.settings.quote}` - }); - } - } - - for (let binding of ['trigger', 'call']) { - result.push({ - documentation: binding, - insertText: binding + snippetPrefix, - insertTextFormat: InsertTextFormat.Snippet, - kind: CompletionItemKind.Property, - label: `.${binding}=${this.settings.quote}${this.settings.quote}` - }); - } - } - } - - private setAttributes(attributes, name, snippetPrefix, result) { - let attribute = attributes.get(name); - if (!attribute) { - attribute = GlobalAttributes.attributes.get(name); - } - - let bindings = this.getDefaultDataBindings(); - for(let binding of bindings) { - result.push({ - documentation: binding.documentation, - insertText: `${binding.name}${snippetPrefix}`, - insertTextFormat: InsertTextFormat.Snippet, - kind: CompletionItemKind.Property, - label: `.${binding.name}=${this.settings.quote}${this.settings.quote}` - }); - } - } - - private getDefaultDataBindings() { - return [ - { - name: 'bind', - documentation: 'automatically chooses the binding mode. Uses two-way binding for form controls and one-way binding for almost everything else' - }, - { - name: 'one-way', - documentation: 'flows data one direction: from the view-model to the view' - }, - { - name: 'two-way', - documentation: 'flows data both ways: from view-model to view and from view to view-model' - }, - { - name: 'one-time', - documentation: 'flows data one direction: from the view-model to the view, only one' - }, - ]; - } -} +import { + CompletionItem, + CompletionItemKind, + InsertTextFormat, MarkedString } from 'vscode-languageserver-types'; +import { autoinject } from 'aurelia-dependency-injection'; +import ElementLibrary from './Library/_elementLibrary'; +import { TagDefinition, AttributeDefinition } from './../DocumentParser'; +import BaseAttributeCompletionFactory from './BaseAttributeCompletionFactory'; +import { GlobalAttributes } from './Library/_elementStructure'; +import AureliaSettings from './../AureliaSettings'; + +@autoinject() +export default class BindingCompletionFactory extends BaseAttributeCompletionFactory { + + constructor(library: ElementLibrary, private settings: AureliaSettings) { super(library); } + + public create(tagDef: TagDefinition, attributeDef: AttributeDefinition, nextChar: string): Array { + + let snippetPrefix = nextChar === '=' ? '' : `=${this.settings.quote}$0${this.settings.quote}`; + let result: Array = []; + + let element = this.getElement(tagDef.name); + if (!element.events.get(attributeDef.name) && !GlobalAttributes.events.get(attributeDef.name)) { + this.setAttributes(element.attributes, attributeDef.name, snippetPrefix, result); + } + + this.setEvents(element.events, attributeDef.name, snippetPrefix, result); + + return result; + } + + private setEvents(events, name, snippetPrefix, result) { + let event = events.get(name); + if (!event) { + event = GlobalAttributes.events.get(name); + } + + if (event) { + if (event.bubbles) { + for(let binding of ['delegate', 'capture']) { + result.push({ + documentation: binding, + insertText: binding + snippetPrefix, + insertTextFormat: InsertTextFormat.Snippet, + kind: CompletionItemKind.Property, + label: `.${binding}=${this.settings.quote}${this.settings.quote}` + }); + } + } + + for (let binding of ['trigger', 'call']) { + result.push({ + documentation: binding, + insertText: binding + snippetPrefix, + insertTextFormat: InsertTextFormat.Snippet, + kind: CompletionItemKind.Property, + label: `.${binding}=${this.settings.quote}${this.settings.quote}` + }); + } + } + } + + private setAttributes(attributes, name, snippetPrefix, result) { + let attribute = attributes.get(name); + if (!attribute) { + attribute = GlobalAttributes.attributes.get(name); + } + + for(let binding of this.settings.bindings.data) { + result.push({ + documentation: binding.documentation, + insertText: `${binding.name}${snippetPrefix}`, + insertTextFormat: InsertTextFormat.Snippet, + kind: CompletionItemKind.Property, + label: binding.label ? (binding.label as string).replace(/'/g, this.settings.quote) : `.${binding.name}=${this.settings.quote}${this.settings.quote}` + }); + } + } +} diff --git a/src/server/main.ts b/src/server/main.ts index 289331e7..8aef0254 100644 --- a/src/server/main.ts +++ b/src/server/main.ts @@ -1,70 +1,71 @@ -import 'reflect-metadata'; -import { createConnection, - IConnection, - TextDocuments, - InitializeParams, - InitializeResult, - CompletionList, Hover } from 'vscode-languageserver'; -import { MarkedString } from 'vscode-languageserver-types'; -import { HTMLDocument, getLanguageService } from './aurelia-languageservice/aureliaLanguageService'; -import { getLanguageModelCache } from './languageModelCache'; -import { Container } from 'aurelia-dependency-injection'; -import CompletionItemFactory from './CompletionItemFactory'; -import ElementLibrary from './Completions/Library/_elementLibrary'; -import AureliaSettings from './AureliaSettings'; - -// Bind console.log & error to the Aurelia output -let connection: IConnection = createConnection(); -console.log = connection.console.log.bind(connection.console); -console.error = connection.console.error.bind(connection.console); - -// Cache documents -let documents: TextDocuments = new TextDocuments(); -documents.listen(connection); -let htmlDocuments = getLanguageModelCache(10, 60, document => getLanguageService().parseHTMLDocument(document)); -documents.onDidClose(e => htmlDocuments.onDocumentRemoved(e.document)); -connection.onShutdown(() => htmlDocuments.dispose()); - -// Setup Aurelia dependency injection -let globalContainer = new Container(); -let completionItemFactory = globalContainer.get(CompletionItemFactory); - -// Register characters to lisen for -connection.onInitialize((params: InitializeParams): InitializeResult => { - - // TODO: find better way to init this - let dummy = globalContainer.get(ElementLibrary); - - return { - capabilities: { - completionProvider: { resolveProvider: false, triggerCharacters: ['<', ' ', '.', '[', '"', '\''] }, - textDocumentSync: documents.syncKind, - }, - }; -}); - -// Register and get changes to Aurelia settings -connection.onDidChangeConfiguration(change => { - let settings = globalContainer.get(AureliaSettings); - settings.quote = change.settings.aurelia.autocomplete.quotes === 'single' ? '\'' : '"'; -}); - -// Setup Validation -let languageService = getLanguageService(); -documents.onDidChangeContent(async change => { - let htmlDocument = htmlDocuments.get(change.document); - const diagnostics = await languageService.doValidation(change.document, htmlDocument); - connection.sendDiagnostics({ uri: change.document.uri, diagnostics }); -}); - -// Lisen for completion requests -connection.onCompletion(textDocumentPosition => { - let document = documents.get(textDocumentPosition.textDocument.uri); - let text = document.getText(); - let offset = document.offsetAt(textDocumentPosition.position); - let triggerCharacter = text.substring(offset - 1, offset); - let position = textDocumentPosition.position; - return completionItemFactory.create(triggerCharacter, position, text, offset, textDocumentPosition.textDocument.uri); -}); - -connection.listen(); +import 'reflect-metadata'; +import { createConnection, + IConnection, + TextDocuments, + InitializeParams, + InitializeResult, + CompletionList, Hover } from 'vscode-languageserver'; +import { MarkedString } from 'vscode-languageserver-types'; +import { HTMLDocument, getLanguageService } from './aurelia-languageservice/aureliaLanguageService'; +import { getLanguageModelCache } from './languageModelCache'; +import { Container } from 'aurelia-dependency-injection'; +import CompletionItemFactory from './CompletionItemFactory'; +import ElementLibrary from './Completions/Library/_elementLibrary'; +import AureliaSettings from './AureliaSettings'; + +// Bind console.log & error to the Aurelia output +let connection: IConnection = createConnection(); +console.log = connection.console.log.bind(connection.console); +console.error = connection.console.error.bind(connection.console); + +// Cache documents +let documents: TextDocuments = new TextDocuments(); +documents.listen(connection); +let htmlDocuments = getLanguageModelCache(10, 60, document => getLanguageService().parseHTMLDocument(document)); +documents.onDidClose(e => htmlDocuments.onDocumentRemoved(e.document)); +connection.onShutdown(() => htmlDocuments.dispose()); + +// Setup Aurelia dependency injection +let globalContainer = new Container(); +let completionItemFactory = globalContainer.get(CompletionItemFactory); + +// Register characters to lisen for +connection.onInitialize((params: InitializeParams): InitializeResult => { + + // TODO: find better way to init this + let dummy = globalContainer.get(ElementLibrary); + + return { + capabilities: { + completionProvider: { resolveProvider: false, triggerCharacters: ['<', ' ', '.', '[', '"', '\''] }, + textDocumentSync: documents.syncKind, + }, + }; +}); + +// Register and get changes to Aurelia settings +connection.onDidChangeConfiguration(change => { + let settings = globalContainer.get(AureliaSettings); + settings.quote = change.settings.aurelia.autocomplete.quotes === 'single' ? '\'' : '"'; + settings.bindings.data = change.settings.aurelia.autocomplete.bindings.data; +}); + +// Setup Validation +let languageService = getLanguageService(); +documents.onDidChangeContent(async change => { + let htmlDocument = htmlDocuments.get(change.document); + const diagnostics = await languageService.doValidation(change.document, htmlDocument); + connection.sendDiagnostics({ uri: change.document.uri, diagnostics }); +}); + +// Lisen for completion requests +connection.onCompletion(textDocumentPosition => { + let document = documents.get(textDocumentPosition.textDocument.uri); + let text = document.getText(); + let offset = document.offsetAt(textDocumentPosition.position); + let triggerCharacter = text.substring(offset - 1, offset); + let position = textDocumentPosition.position; + return completionItemFactory.create(triggerCharacter, position, text, offset, textDocumentPosition.textDocument.uri); +}); + +connection.listen();