diff --git a/Cargo.toml b/Cargo.toml index a35636649..96e219e6f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,6 +10,8 @@ members = [ "packages/router", "packages/ssr", "packages/webview", + # Not a rust package, :(. One day, maybe + "packages/vscode-ext", # "packages/macro", # TODO @Jon, share the validation code # "packages/web", diff --git a/examples/common.rs b/examples/common.rs index 73a3bdfb3..d5097da24 100644 --- a/examples/common.rs +++ b/examples/common.rs @@ -46,7 +46,5 @@ pub mod logger { .chain(std::io::stdout()) .apply() .unwrap(); - - debug!("finished setting up logging! yay!"); } } diff --git a/examples/doc_generator.rs b/examples/doc_generator.rs index e6b7846f4..2d8c599a9 100644 --- a/examples/doc_generator.rs +++ b/examples/doc_generator.rs @@ -17,7 +17,6 @@ fn main() { - diff --git a/examples/hello_web.rs b/examples/hello_web.rs index bdce8b29f..1f4c05399 100644 --- a/examples/hello_web.rs +++ b/examples/hello_web.rs @@ -1,9 +1,6 @@ -fn main() { - println!("Hello world!"); - println!("Hello world!"); - println!("Hello world!"); - println!("Hello world!"); - println!("Hello world!"); - println!("Hello world!"); - println!("Hello world!"); -} +//! Example: Hello-web +//! ----------------- +//! +//! This example showcases Dioxus-websys to build interactive single-page-applications, much like React. + +fn main() {} diff --git a/examples/router.rs b/examples/router.rs index 4d101d64f..7f09b872a 100644 --- a/examples/router.rs +++ b/examples/router.rs @@ -1,9 +1,11 @@ //! Dioxus Router +//! ------------- +//! +//! This exmaple showcases the Dioxus Router hook. This hook makes it possible to using the browser's navigation API to +//! display different content based on the Page's URL. The hook provides a configuration object that calls various +//! specified callbacks whenever the page URL changes. Using this hook should feel like building a "URL directory", similar +//! to how Tide handles paths. use dioxus::prelude::*; -fn main() { - // let mut app = App::new(); - - // app.at(c) -} +fn main() {} diff --git a/examples/tide_ssr.rs b/examples/tide_ssr.rs index 974786ee0..68cf9444f 100644 --- a/examples/tide_ssr.rs +++ b/examples/tide_ssr.rs @@ -1,3 +1,6 @@ +//! Example: Tide Server-Side-Rendering +//! ----------------------------------- +//! //! This demo shows how to use the to_string utility on VNodes to convert them into valid HTML. //! You can use the html! macro to craft webpages generated by the server on-the-fly. //! @@ -5,6 +8,7 @@ //! thousands of simultaneous clients on minimal hardware. use dioxus::prelude::*; +use rand::Rng; use tide::{Request, Response}; #[async_std::main] @@ -18,35 +22,38 @@ async fn main() -> Result<(), std::io::Error> { // Launch the server let addr = "127.0.0.1:8080"; log::info!("App is ready at {}", addr); - log::info!("Navigate to an fibonacci number, like so http://localhost:8080/fib/21"); + log::info!("Navigate to a fibonacci number: http://{}/fib/21", addr); app.listen(addr).await?; Ok(()) } +fn fib(n: usize) -> usize { + if n == 0 || n == 1 { + n + } else { + fib(n - 1) + fib(n - 2) + } +} + /// Calculate the fibonacci number for a given request input async fn fibsum(req: Request<()>) -> tide::Result { - use std::time::Instant; let n: usize = req.param("n")?.parse().unwrap_or(0); - fn fib(n: usize) -> usize { - if n == 0 || n == 1 { - n - } else { - fib(n - 1) + fib(n - 2) - } - } - // Start a stopwatch // Compute the nth number in the fibonacci sequence // Stop the stopwatch - let start = Instant::now(); + let start = std::time::Instant::now(); let fib_n = fib(n); let duration = start.elapsed().as_nanos(); - let mut rng = rand::thread_rng(); - use rand::Rng; - let other_fib_to_try = rng.gen_range(1..42); + // Generate another random number to try + let other_fib_to_try = rand::thread_rng().gen_range(1..42); + + let g = html! { +
+
+ }; let nodes = html! { diff --git a/examples/webview.rs b/examples/webview.rs index b9a633f12..138d07cd9 100644 --- a/examples/webview.rs +++ b/examples/webview.rs @@ -1,4 +1,5 @@ //! Example: Webview Renderer +//! ------------------------- //! //! This example shows how to use the dioxus_webview crate to build a basic desktop application. //! diff --git a/packages/vscode-ext/.gitignore b/packages/vscode-ext/.gitignore new file mode 100644 index 000000000..998358030 --- /dev/null +++ b/packages/vscode-ext/.gitignore @@ -0,0 +1,4 @@ +out +node_modules +client/server +.vscode-test diff --git a/packages/vscode-ext/.vscode/launch.json b/packages/vscode-ext/.vscode/launch.json new file mode 100644 index 000000000..99b82799a --- /dev/null +++ b/packages/vscode-ext/.vscode/launch.json @@ -0,0 +1,44 @@ +// A launch configuration that compiles the extension and then opens it inside a new window +{ + "version": "0.2.0", + "configurations": [ + { + "type": "extensionHost", + "request": "launch", + "name": "Launch Client", + "runtimeExecutable": "${execPath}", + "args": ["--extensionDevelopmentPath=${workspaceRoot}"], + "outFiles": ["${workspaceRoot}/client/out/**/*.js"], + "preLaunchTask": { + "type": "npm", + "script": "watch" + } + }, + { + "type": "node", + "request": "attach", + "name": "Attach to Server", + "port": 6009, + "restart": true, + "outFiles": ["${workspaceRoot}/server/out/**/*.js"] + }, + { + "name": "Language Server E2E Test", + "type": "extensionHost", + "request": "launch", + "runtimeExecutable": "${execPath}", + "args": [ + "--extensionDevelopmentPath=${workspaceRoot}", + "--extensionTestsPath=${workspaceRoot}/client/out/test", + "${workspaceRoot}/client/testFixture" + ], + "outFiles": ["${workspaceRoot}/client/out/test/**/*.js"] + } + ], + "compounds": [ + { + "name": "Client + Server", + "configurations": ["Launch Client", "Attach to Server"] + } + ] +} diff --git a/packages/vscode-ext/.vscode/tasks.json b/packages/vscode-ext/.vscode/tasks.json new file mode 100644 index 000000000..5efd80488 --- /dev/null +++ b/packages/vscode-ext/.vscode/tasks.json @@ -0,0 +1,33 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "type": "npm", + "script": "compile", + "group": "build", + "presentation": { + "panel": "dedicated", + "reveal": "never" + }, + "problemMatcher": [ + "$tsc" + ] + }, + { + "type": "npm", + "script": "watch", + "isBackground": true, + "group": { + "kind": "build", + "isDefault": true + }, + "presentation": { + "panel": "dedicated", + "reveal": "never" + }, + "problemMatcher": [ + "$tsc-watch" + ] + } + ] +} \ No newline at end of file diff --git a/packages/vscode-ext/.vscodeignore b/packages/vscode-ext/.vscodeignore new file mode 100644 index 000000000..2ca7fc371 --- /dev/null +++ b/packages/vscode-ext/.vscodeignore @@ -0,0 +1,14 @@ +.vscode/** +**/*.ts +**/*.map +.gitignore +**/tsconfig.json +**/tsconfig.base.json +contributing.md +.travis.yml +client/node_modules/** +!client/node_modules/vscode-jsonrpc/** +!client/node_modules/vscode-languageclient/** +!client/node_modules/vscode-languageserver-protocol/** +!client/node_modules/vscode-languageserver-types/** +!client/node_modules/semver/** diff --git a/packages/vscode-ext/Cargo.toml b/packages/vscode-ext/Cargo.toml new file mode 100644 index 000000000..bf805a2a8 --- /dev/null +++ b/packages/vscode-ext/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "syntax-gen" +version = "0.1.0" +authors = ["Jonathan Kelley "] +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] + + +[[bin]] +path = "./rust/main.rs" +name = "main" diff --git a/packages/vscode-ext/README.md b/packages/vscode-ext/README.md new file mode 100644 index 000000000..1d1f5e457 --- /dev/null +++ b/packages/vscode-ext/README.md @@ -0,0 +1,17 @@ +# VSCode support for Dioxus html macro + +This macro provides syntax highlighting for the html! macro used in Dioxus projects. Users should feel at home writing html and css alongside the custom attributes used by Dioxus. + + +## How it works +This extension works by: +- Creating a custom HTML ruleset for dioxus html! nodes +- Request forwarding content + + +## Resources +Request forwarding is performed intelligently by the extension. +Requests within the html! tag are forwarded to the html language service. It's simple and doesn't + +https://code.visualstudio.com/api/language-extensions/embedded-languages#language-services + diff --git a/packages/vscode-ext/client/package-lock.json b/packages/vscode-ext/client/package-lock.json new file mode 100644 index 000000000..0694916f1 --- /dev/null +++ b/packages/vscode-ext/client/package-lock.json @@ -0,0 +1,76 @@ +{ + "name": "lsp-sample-client", + "version": "0.0.1", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "@types/vscode": { + "version": "1.43.0", + "resolved": "https://registry.npmjs.org/@types/vscode/-/vscode-1.43.0.tgz", + "integrity": "sha512-kIaR9qzd80rJOxePKpCB/mdy00mz8Apt2QA5Y6rdrKFn13QNFNeP3Hzmsf37Bwh/3cS7QjtAeGSK7wSqAU0sYQ==", + "dev": true + }, + "vscode-html-languageservice": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/vscode-html-languageservice/-/vscode-html-languageservice-3.0.3.tgz", + "integrity": "sha512-U+upM3gHp3HaF3wXAnUduA6IDKcz6frWS/dTAju3cZVIyZwOLBBFElQVlLH0ycHyMzqUFrjvdv+kEyPAEWfQ/g==", + "requires": { + "vscode-languageserver-types": "^3.15.0-next.2", + "vscode-nls": "^4.1.1", + "vscode-uri": "^2.0.3" + }, + "dependencies": { + "vscode-languageserver-types": { + "version": "3.15.1", + "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.15.1.tgz", + "integrity": "sha512-+a9MPUQrNGRrGU630OGbYVQ+11iOIovjCkqxajPa9w57Sd5ruK8WQNsslzpa0x/QJqC8kRc2DUxWjIFwoNm4ZQ==" + } + } + }, + "vscode-jsonrpc": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-5.0.1.tgz", + "integrity": "sha512-JvONPptw3GAQGXlVV2utDcHx0BiY34FupW/kI6mZ5x06ER5DdPG/tXWMVHjTNULF5uKPOUUD0SaXg5QaubJL0A==" + }, + "vscode-languageclient": { + "version": "6.1.3", + "resolved": "https://registry.npmjs.org/vscode-languageclient/-/vscode-languageclient-6.1.3.tgz", + "integrity": "sha512-YciJxk08iU5LmWu7j5dUt9/1OLjokKET6rME3cI4BRpiF6HZlusm2ZwPt0MYJ0lV5y43sZsQHhyon2xBg4ZJVA==", + "requires": { + "semver": "^6.3.0", + "vscode-languageserver-protocol": "^3.15.3" + }, + "dependencies": { + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" + } + } + }, + "vscode-languageserver-protocol": { + "version": "3.15.3", + "resolved": "https://registry.npmjs.org/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.15.3.tgz", + "integrity": "sha512-zrMuwHOAQRhjDSnflWdJG+O2ztMWss8GqUUB8dXLR/FPenwkiBNkMIJJYfSN6sgskvsF0rHAoBowNQfbyZnnvw==", + "requires": { + "vscode-jsonrpc": "^5.0.1", + "vscode-languageserver-types": "3.15.1" + } + }, + "vscode-languageserver-types": { + "version": "3.15.1", + "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.15.1.tgz", + "integrity": "sha512-+a9MPUQrNGRrGU630OGbYVQ+11iOIovjCkqxajPa9w57Sd5ruK8WQNsslzpa0x/QJqC8kRc2DUxWjIFwoNm4ZQ==" + }, + "vscode-nls": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/vscode-nls/-/vscode-nls-4.1.2.tgz", + "integrity": "sha512-7bOHxPsfyuCqmP+hZXscLhiHwe7CSuFE4hyhbs22xPIhQ4jv99FcR4eBzfYYVLP356HNFpdvz63FFb/xw6T4Iw==" + }, + "vscode-uri": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-2.1.1.tgz", + "integrity": "sha512-eY9jmGoEnVf8VE8xr5znSah7Qt1P/xsCdErz+g8HYZtJ7bZqKH5E3d+6oVNm1AC/c6IHUDokbmVXKOi4qPAC9A==" + } + } +} diff --git a/packages/vscode-ext/client/package.json b/packages/vscode-ext/client/package.json new file mode 100644 index 000000000..fb82ea44d --- /dev/null +++ b/packages/vscode-ext/client/package.json @@ -0,0 +1,22 @@ +{ + "name": "lsp-sample-client", + "description": "VSCode part of a language server", + "author": "Microsoft Corporation", + "license": "MIT", + "version": "0.0.1", + "publisher": "vscode", + "repository": { + "type": "git", + "url": "https://github.com/Microsoft/vscode-extension-samples" + }, + "engines": { + "vscode": "^1.43.0" + }, + "dependencies": { + "vscode-html-languageservice": "^3.0.3", + "vscode-languageclient": "^6.1.3" + }, + "devDependencies": { + "@types/vscode": "^1.43.0" + } +} diff --git a/packages/vscode-ext/client/src/embeddedSupport.ts b/packages/vscode-ext/client/src/embeddedSupport.ts new file mode 100644 index 000000000..6e0631795 --- /dev/null +++ b/packages/vscode-ext/client/src/embeddedSupport.ts @@ -0,0 +1,415 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { TextDocument, Position, Range } from 'vscode-languageclient'; +import { LanguageService, TokenType } from 'vscode-html-languageservice'; + +export interface LanguageRange extends Range { + languageId: string | undefined; + attributeValue?: boolean; +} + +export interface HTMLDocumentRegions { + getEmbeddedDocument(languageId: string, ignoreAttributeValues?: boolean): TextDocument; + getLanguageRanges(range: Range): LanguageRange[]; + getLanguageAtPosition(position: Position): string | undefined; + getLanguagesInDocument(): string[]; + getImportedScripts(): string[]; +} + +export const CSS_STYLE_RULE = '__'; + +interface EmbeddedRegion { + languageId: string | undefined; + start: number; + end: number; + attributeValue?: boolean; +} + +export function isInsideStyleRegion( + languageService: LanguageService, + documentText: string, + offset: number +) { + let scanner = languageService.createScanner(documentText); + + let token = scanner.scan(); + while (token !== TokenType.EOS) { + switch (token) { + case TokenType.Styles: + if (offset >= scanner.getTokenOffset() && offset <= scanner.getTokenEnd()) { + return true; + } + } + token = scanner.scan(); + } + + return false; +} + +export function getCSSVirtualContent( + languageService: LanguageService, + documentText: string +): string { + let regions: EmbeddedRegion[] = []; + let scanner = languageService.createScanner(documentText); + let lastTagName: string = ''; + let lastAttributeName: string | null = null; + let languageIdFromType: string | undefined = undefined; + let importedScripts: 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 === '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 ( + /["'](module|(text|application)\/(java|ecma)script|text\/babel)["']/.test( + scanner.getTokenText() + ) + ) { + languageIdFromType = 'javascript'; + } else if (/["']text\/typescript["']/.test(scanner.getTokenText())) { + languageIdFromType = 'typescript'; + } else { + languageIdFromType = undefined; + } + } else { + let attributeLanguageId = getAttributeLanguage(lastAttributeName!); + if (attributeLanguageId) { + let start = scanner.getTokenOffset(); + let end = scanner.getTokenEnd(); + let firstChar = documentText[start]; + if (firstChar === "'" || firstChar === '"') { + start++; + end--; + } + regions.push({ + languageId: attributeLanguageId, + start, + end, + attributeValue: true + }); + } + } + lastAttributeName = null; + break; + } + token = scanner.scan(); + } + + let content = documentText + .split('\n') + .map(line => { + return ' '.repeat(line.length); + }).join('\n'); + + regions.forEach(r => { + if (r.languageId === 'css') { + content = content.slice(0, r.start) + documentText.slice(r.start, r.end) + content.slice(r.end); + } + }); + + return content; +} + + +export function getDocumentRegions( + languageService: LanguageService, + document: TextDocument +): HTMLDocumentRegions { + let regions: EmbeddedRegion[] = []; + let scanner = languageService.createScanner(document.getText()); + let lastTagName: string = ''; + let lastAttributeName: string | null = null; + let languageIdFromType: string | undefined = undefined; + let importedScripts: 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 === '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 ( + /["'](module|(text|application)\/(java|ecma)script|text\/babel)["']/.test( + scanner.getTokenText() + ) + ) { + languageIdFromType = 'javascript'; + } else if (/["']text\/typescript["']/.test(scanner.getTokenText())) { + languageIdFromType = 'typescript'; + } else { + languageIdFromType = undefined; + } + } else { + let attributeLanguageId = getAttributeLanguage(lastAttributeName!); + if (attributeLanguageId) { + let start = scanner.getTokenOffset(); + let end = scanner.getTokenEnd(); + let firstChar = document.getText()[start]; + if (firstChar === "'" || firstChar === '"') { + start++; + end--; + } + regions.push({ + languageId: attributeLanguageId, + start, + end, + attributeValue: true + }); + } + } + lastAttributeName = null; + break; + } + token = scanner.scan(); + } + return { + getLanguageRanges: (range: Range) => getLanguageRanges(document, regions, range), + getEmbeddedDocument: (languageId: string, ignoreAttributeValues: boolean) => + getEmbeddedDocument(document, regions, languageId, ignoreAttributeValues), + getLanguageAtPosition: (position: Position) => + getLanguageAtPosition(document, regions, position), + 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); + let currentOffset = range ? document.offsetAt(range.start) : 0; + let endOffset = range ? document.offsetAt(range.end) : document.getText().length; + for (let region of regions) { + if (region.end > currentOffset && region.start < endOffset) { + let start = Math.max(region.start, currentOffset); + let startPos = document.positionAt(start); + if (currentOffset < region.start) { + result.push({ + start: currentPos, + end: startPos, + languageId: 'html' + }); + } + let end = Math.min(region.end, endOffset); + let endPos = document.positionAt(end); + if (end > region.start) { + result.push({ + start: startPos, + end: endPos, + languageId: region.languageId, + attributeValue: region.attributeValue + }); + } + currentOffset = end; + currentPos = endPos; + } + } + if (currentOffset < endOffset) { + let endPos = range ? range.end : document.positionAt(endOffset); + result.push({ + start: currentPos, + end: endPos, + languageId: 'html' + }); + } + return result; +} + +function getLanguagesInDocument( + _document: TextDocument, + regions: EmbeddedRegion[] +): string[] { + let result = []; + for (let region of regions) { + if (region.languageId && result.indexOf(region.languageId) === -1) { + result.push(region.languageId); + if (result.length === 3) { + return result; + } + } + } + result.push('html'); + return result; +} + +function getLanguageAtPosition( + document: TextDocument, + regions: EmbeddedRegion[], + position: Position +): string | undefined { + let offset = document.offsetAt(position); + for (let region of regions) { + if (region.start <= offset) { + if (offset <= region.end) { + return region.languageId; + } + } else { + break; + } + } + return 'html'; +} + +function getEmbeddedDocument( + document: TextDocument, + contents: EmbeddedRegion[], + languageId: string, + ignoreAttributeValues: boolean +): TextDocument { + let currentPos = 0; + let oldContent = document.getText(); + let result = ''; + let lastSuffix = ''; + for (let c of contents) { + if (c.languageId === languageId && (!ignoreAttributeValues || !c.attributeValue)) { + result = substituteWithWhitespace( + result, + currentPos, + c.start, + oldContent, + lastSuffix, + getPrefix(c) + ); + result += oldContent.substring(c.start, c.end); + currentPos = c.end; + lastSuffix = getSuffix(c); + } + } + result = substituteWithWhitespace( + result, + currentPos, + oldContent.length, + oldContent, + lastSuffix, + '' + ); + return TextDocument.create(document.uri, languageId, document.version, result); +} + +function getPrefix(c: EmbeddedRegion) { + if (c.attributeValue) { + switch (c.languageId) { + case 'css': + return CSS_STYLE_RULE + '{'; + } + } + return ''; +} +function getSuffix(c: EmbeddedRegion) { + if (c.attributeValue) { + switch (c.languageId) { + case 'css': + return '}'; + case 'javascript': + return ';'; + } + } + return ''; +} + +function substituteWithWhitespace( + result: string, + start: number, + end: number, + oldContent: string, + before: string, + after: string +) { + let accumulatedWS = 0; + result += before; + for (let i = start + before.length; i < end; i++) { + let ch = oldContent[i]; + if (ch === '\n' || ch === '\r') { + // only write new lines, skip the whitespace + accumulatedWS = 0; + result += ch; + } else { + accumulatedWS++; + } + } + result = append(result, ' ', accumulatedWS - after.length); + result += after; + return result; +} + +function append(result: string, str: string, n: number): string { + while (n > 0) { + if (n & 1) { + result += str; + } + n >>= 1; + str += str; + } + return result; +} + +function getAttributeLanguage(attributeName: string): string | null { + let match = attributeName.match(/^(style)$|^(on\w+)$/i); + if (!match) { + return null; + } + return match[1] ? 'css' : 'javascript'; +} diff --git a/packages/vscode-ext/client/src/extension.ts b/packages/vscode-ext/client/src/extension.ts new file mode 100644 index 000000000..0c8598018 --- /dev/null +++ b/packages/vscode-ext/client/src/extension.ts @@ -0,0 +1,87 @@ +/* -------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + * ------------------------------------------------------------------------------------------ */ + +import * as path from 'path'; +import { commands, CompletionList, ExtensionContext, Uri, workspace } from 'vscode'; +import { getLanguageService } from 'vscode-html-languageservice'; +import { LanguageClient, LanguageClientOptions, ServerOptions, TransportKind } from 'vscode-languageclient'; +import { getCSSVirtualContent, isInsideStyleRegion } from './embeddedSupport'; + +let client: LanguageClient; + +const htmlLanguageService = getLanguageService(); + +export function activate(context: ExtensionContext) { + // The server is implemented in node + let serverModule = context.asAbsolutePath(path.join('server', 'out', 'server.js')); + // The debug options for the server + // --inspect=6009: runs the server in Node's Inspector mode so VS Code can attach to the server for debugging + let debugOptions = { execArgv: ['--nolazy', '--inspect=6009'] }; + + // If the extension is launched in debug mode then the debug server options are used + // Otherwise the run options are used + let serverOptions: ServerOptions = { + run: { module: serverModule, transport: TransportKind.ipc }, + debug: { + module: serverModule, + transport: TransportKind.ipc, + options: debugOptions + } + }; + + const virtualDocumentContents = new Map(); + + workspace.registerTextDocumentContentProvider('embedded-content', { + provideTextDocumentContent: uri => { + const originalUri = uri.path.slice(1).slice(0, -4); + const decodedUri = decodeURIComponent(originalUri); + return virtualDocumentContents.get(decodedUri); + } + }); + + let clientOptions: LanguageClientOptions = { + documentSelector: [{ scheme: 'file', language: 'html1' }], + middleware: { + provideCompletionItem: async (document, position, context, token, next) => { + // If not in `