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 `