Feat: Clean up repo a bit
This commit is contained in:
1 parent
c28697e1fe
commit
a99147c85b
55 files changed
+76
-6609
No files matched your search
+2
-2
@@ -5,10 +5,10 @@ members = [
|
|||||||
"packages/core",
|
"packages/core",
|
||||||
"packages/web",
|
"packages/web",
|
||||||
"packages/dioxus",
|
"packages/dioxus",
|
||||||
|
"packages/recoil",
|
||||||
|
"packages/docsite",
|
||||||
] # "packages/webview",
|
] # "packages/webview",
|
||||||
|
|
||||||
# "packages/docsite",
|
|
||||||
# "packages/ssr",
|
# "packages/ssr",
|
||||||
# "packages/cli",
|
# "packages/cli",
|
||||||
# "packages/webview",
|
# "packages/webview",
|
||||||
|
|||||||
+5
-5
@@ -77,17 +77,16 @@ Welcome to the first iteration of the Dioxus Virtual DOM! This release brings su
|
|||||||
- [x] keys on components
|
- [x] keys on components
|
||||||
- [x] Allow paths for components
|
- [x] Allow paths for components
|
||||||
- [x] todo mvc
|
- [x] todo mvc
|
||||||
- [ ] Make events lazy (use traits + Box<dyn>) - not sure what this means anymore
|
|
||||||
- [ ] Attributes on elements should implement format_args instead of string fmt
|
|
||||||
- [ ] Beef up the dioxus CLI tool to report build progress
|
|
||||||
- [x] Tweak macro parsing for better errors
|
- [x] Tweak macro parsing for better errors
|
||||||
|
- [x] dirty tagging, compression
|
||||||
|
- [x] code health
|
||||||
|
- [ ] Make events lazy (use traits + Box<dyn>) - not sure what this means anymore
|
||||||
|
- [ ] Beef up the dioxus CLI tool to report build progress
|
||||||
- [ ] Extract arena logic out for better safety guarantees
|
- [ ] Extract arena logic out for better safety guarantees
|
||||||
- [ ] Extract BumpFrame logic out for better safety guarantees
|
- [ ] Extract BumpFrame logic out for better safety guarantees
|
||||||
- [ ] make SSR follow HTML spec
|
- [ ] make SSR follow HTML spec
|
||||||
- [x] dirty tagging, compression
|
|
||||||
- [ ] fix keys on elements
|
- [ ] fix keys on elements
|
||||||
- [ ] MIRI tests
|
- [ ] MIRI tests
|
||||||
- [ ] code health
|
|
||||||
- [ ] all synthetic events filled out
|
- [ ] all synthetic events filled out
|
||||||
- [ ] double check event targets and stuff
|
- [ ] double check event targets and stuff
|
||||||
- [ ] Documentation overhaul
|
- [ ] Documentation overhaul
|
||||||
@@ -95,6 +94,7 @@ Welcome to the first iteration of the Dioxus Virtual DOM! This release brings su
|
|||||||
- [ ] controlled components
|
- [ ] controlled components
|
||||||
|
|
||||||
lower priority features
|
lower priority features
|
||||||
|
- [ ] Attributes on elements should implement format_args instead of string fmt
|
||||||
- [ ] fragments
|
- [ ] fragments
|
||||||
- [ ] node refs (postpone for future release?)
|
- [ ] node refs (postpone for future release?)
|
||||||
- [ ] styling built-in (future release?)
|
- [ ] styling built-in (future release?)
|
||||||
|
|||||||
@@ -1,9 +0,0 @@
|
|||||||
[package]
|
|
||||||
name = "dioxus3d"
|
|
||||||
version = "0.0.0"
|
|
||||||
authors = ["Jonathan Kelley <jkelleyrtp@gmail.com>"]
|
|
||||||
edition = "2018"
|
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
# Dioxus3d: declarative framework for using the `Canvas`
|
|
||||||
|
|
||||||
Declarative wrapper over wgpu for creating interactive gpu-enabled visualizations in the browser with Dioxus. This crate's analog is ThreeJS and react-three-fiber. Here, we expose a set of hooks for using the `Canvas` imperatively, and then provide a set of components that interact with this canvas context. From there, declaring scenes is as easy as:
|
|
||||||
|
|
||||||
```rust
|
|
||||||
use dioxus3d::{Canvas};
|
|
||||||
use dioxus::prelude::*;
|
|
||||||
|
|
||||||
static HelloWorld = |ctx, props| {
|
|
||||||
ctx.render(rsx! {
|
|
||||||
Canvas {
|
|
||||||
Text {
|
|
||||||
"Hello world"
|
|
||||||
rel_pos: (0,0,1)
|
|
||||||
size: (1,1,1)
|
|
||||||
}
|
|
||||||
Cube {
|
|
||||||
size: (1,1,1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
};
|
|
||||||
```
|
|
||||||
|
|
||||||
// dioxus bevy: wrap a bevy instance with reactive controls.
|
|
||||||
@@ -14,7 +14,7 @@ description = "Core functionality for Dioxus - a concurrent renderer-agnostic Vi
|
|||||||
dioxus-core-macro = { path = "../core-macro", version = "0.1.1" }
|
dioxus-core-macro = { path = "../core-macro", version = "0.1.1" }
|
||||||
|
|
||||||
# Backs scopes and graphs between parent and children
|
# Backs scopes and graphs between parent and children
|
||||||
generational-arena = { version = "0.2.8", features = ["serde"] }
|
generational-arena = { version = "0.2.8" }
|
||||||
|
|
||||||
# Bumpalo backs the VNode creation
|
# Bumpalo backs the VNode creation
|
||||||
bumpalo = { version = "3.6.0", features = ["collections", "boxed"] }
|
bumpalo = { version = "3.6.0", features = ["collections", "boxed"] }
|
||||||
@@ -36,3 +36,4 @@ serde = { version = "1", features = ["derive"], optional = true }
|
|||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = []
|
default = []
|
||||||
|
serialize = ["serde", "generational-arena/serde"]
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
use std::{cell::UnsafeCell, collections::HashMap};
|
||||||
|
|
||||||
|
use generational_arena::Arena;
|
||||||
|
|
||||||
|
use crate::innerlude::*;
|
||||||
|
|
||||||
|
pub struct ScopeArena {
|
||||||
|
pub(crate) arena: UnsafeCell<Arena<Scope>>,
|
||||||
|
locks: HashMap<ScopeIdx, MutStatus>,
|
||||||
|
}
|
||||||
|
|
||||||
|
enum MutStatus {
|
||||||
|
Immut,
|
||||||
|
Mut,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ScopeArena {
|
||||||
|
pub fn new(arena: Arena<Scope>) -> Self {
|
||||||
|
Self {
|
||||||
|
arena: UnsafeCell::new(arena),
|
||||||
|
locks: Default::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get(&self, idx: ScopeIdx) -> Result<&Scope> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_mut(&self, idx: ScopeIdx) -> Result<&Scope> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn inner(&self) -> &Arena<Scope> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn inner_mut(&mut self) -> &mut Arena<Scope> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with<T>(&mut self, f: impl FnOnce(&mut Arena<Scope>) -> T) -> T {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe fn inner_unchecked<'s>() -> &'s mut Arena<Scope> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -120,4 +120,4 @@ impl<'a> crate::virtual_dom::Context<'a> {
|
|||||||
///
|
///
|
||||||
/// - Dioxus reducer is built on the safe API and provides a useful but slightly limited API.
|
/// - Dioxus reducer is built on the safe API and provides a useful but slightly limited API.
|
||||||
/// - Dioxus Dataflow is built on the unsafe API and provides an even snazzier API than Dioxus Reducer.
|
/// - Dioxus Dataflow is built on the unsafe API and provides an even snazzier API than Dioxus Reducer.
|
||||||
fn blah() {}
|
fn _blah() {}
|
||||||
@@ -1,3 +1,7 @@
|
|||||||
|
//! Internal error handling for Dioxus
|
||||||
|
//!
|
||||||
|
//!
|
||||||
|
|
||||||
use thiserror::Error as ThisError;
|
use thiserror::Error as ThisError;
|
||||||
pub type Result<T, E = Error> = std::result::Result<T, E>;
|
pub type Result<T, E = Error> = std::result::Result<T, E>;
|
||||||
|
|
||||||
|
|||||||
@@ -65,6 +65,7 @@
|
|||||||
//! - dioxus-liveview (SSR + StringRenderer)
|
//! - dioxus-liveview (SSR + StringRenderer)
|
||||||
//!
|
//!
|
||||||
|
|
||||||
|
pub mod arena;
|
||||||
pub mod component; // Logic for extending FC
|
pub mod component; // Logic for extending FC
|
||||||
pub mod context; // Logic for providing hook + context functionality to user components
|
pub mod context; // Logic for providing hook + context functionality to user components
|
||||||
pub mod debug_renderer;
|
pub mod debug_renderer;
|
||||||
|
|||||||
@@ -19,16 +19,11 @@
|
|||||||
//!
|
//!
|
||||||
//! # Known Issues
|
//! # Known Issues
|
||||||
//! ----
|
//! ----
|
||||||
//! - stack machine approach does not work when 3rd party extensions inject elements (breaking our view of the dom)
|
//! - stack machine approach does not work when 3rd party extensions inject elements (breaking our view of the dom) - solvable by the renderer
|
||||||
|
|
||||||
use std::cell::Ref;
|
|
||||||
|
|
||||||
use crate::innerlude::ScopeIdx;
|
use crate::innerlude::ScopeIdx;
|
||||||
|
|
||||||
pub type EditList<'src> = Vec<Edit<'src>>;
|
pub type EditList<'src> = Vec<Edit<'src>>;
|
||||||
// pub struct EditList<'src> {
|
|
||||||
// edits: Vec<Edit<'src>>,
|
|
||||||
// }
|
|
||||||
|
|
||||||
/// The `Edit` represents a single modifcation of the renderer tree.
|
/// The `Edit` represents a single modifcation of the renderer tree.
|
||||||
/// todo @jon, go through and make certain fields static. tag names should be known at compile time
|
/// todo @jon, go through and make certain fields static. tag names should be known at compile time
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
Generate the liveview site powering diouxslabs.com.
|
Generate the liveview site powering diouxslabs.com.
|
||||||
|
|
||||||
We use the dioxus SSR crate and the use_router hook to generate a
|
We use the dioxus SSR crate and the use_router hook to generate all the routes
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,105 +1,11 @@
|
|||||||
use dioxus_ssr::{
|
use dioxus_ssr::{
|
||||||
prelude::{builder::IntoDomTree, dioxus::events::on::MouseEvent, *},
|
prelude::*,
|
||||||
|
prelude::{builder::IntoDomTree, dioxus::events::on::MouseEvent},
|
||||||
TextRenderer,
|
TextRenderer,
|
||||||
};
|
};
|
||||||
mod components {
|
|
||||||
mod app;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
TextRenderer::new(App);
|
TextRenderer::new(App);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq)]
|
fn App(ctx: Context, props: &()) -> DomTree {}
|
||||||
enum Route {
|
|
||||||
Homepage,
|
|
||||||
Examples,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Props)]
|
|
||||||
struct AppProps {
|
|
||||||
route: Route,
|
|
||||||
}
|
|
||||||
|
|
||||||
static App: FC<AppProps> = |ctx, props| {
|
|
||||||
let body = match props.route {
|
|
||||||
Route::Homepage => ctx.render(rsx! {
|
|
||||||
div {
|
|
||||||
"Some Content"
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
|
|
||||||
Route::Examples => ctx.render(rsx! {
|
|
||||||
div {
|
|
||||||
"Other Content"
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
|
|
||||||
ctx.render(rsx!(
|
|
||||||
div {
|
|
||||||
Header {}
|
|
||||||
{body}
|
|
||||||
ul {
|
|
||||||
{(0..10).map(|f| rsx!{
|
|
||||||
li {
|
|
||||||
"this is list item {f}"
|
|
||||||
}
|
|
||||||
})}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
))
|
|
||||||
};
|
|
||||||
|
|
||||||
static Header: FC<()> = |ctx, _| {
|
|
||||||
ctx.render(rsx! {
|
|
||||||
div {
|
|
||||||
|
|
||||||
}
|
|
||||||
})
|
|
||||||
};
|
|
||||||
|
|
||||||
mod example {
|
|
||||||
use super::*;
|
|
||||||
static ExampleUsage: FC<()> = |ctx, props| {
|
|
||||||
// direct rsx!
|
|
||||||
let body = rsx! {
|
|
||||||
div {}
|
|
||||||
};
|
|
||||||
|
|
||||||
// rendered rsx!
|
|
||||||
let top = ctx.render(rsx! {
|
|
||||||
div {
|
|
||||||
"ack!"
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// listy rsx
|
|
||||||
let list2 = (0..10).map(|f| {
|
|
||||||
rsx! {
|
|
||||||
div {}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// rendered list rsx
|
|
||||||
let list = (0..10).map(|f| {
|
|
||||||
ctx.render(rsx! {
|
|
||||||
div {}
|
|
||||||
})
|
|
||||||
});
|
|
||||||
|
|
||||||
ctx.render(rsx!(
|
|
||||||
div {
|
|
||||||
Header {}
|
|
||||||
{body}
|
|
||||||
{top}
|
|
||||||
{list}
|
|
||||||
{list2}
|
|
||||||
// inline rsx
|
|
||||||
{rsx!{
|
|
||||||
div { "hello" }
|
|
||||||
}}
|
|
||||||
}
|
|
||||||
))
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
#[test]
|
|
||||||
fn it_works() {
|
|
||||||
assert_eq!(2 + 2, 4);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
[package]
|
|
||||||
name = "dioxus-redux"
|
|
||||||
version = "0.0.0"
|
|
||||||
authors = ["Jonathan Kelley <jkelleyrtp@gmail.com>"]
|
|
||||||
edition = "2018"
|
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
@@ -1,72 +0,0 @@
|
|||||||
pub struct Store<T> {
|
|
||||||
user_data: T,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Select just a small bit of the
|
|
||||||
fn use_selector() {}
|
|
||||||
|
|
||||||
/*
|
|
||||||
|
|
||||||
// borrow a closure so we can copy the reference
|
|
||||||
let dispatch = use_dispatch::<UserData>(ctx);
|
|
||||||
|
|
||||||
|
|
||||||
dispatch(|| UserData::Logout)
|
|
||||||
|
|
||||||
dispatch()
|
|
||||||
|
|
||||||
*/
|
|
||||||
fn use_dispatch() {}
|
|
||||||
|
|
||||||
mod tests {
|
|
||||||
|
|
||||||
struct UserData {}
|
|
||||||
|
|
||||||
// static SelectLoggedIn: FC<T> = |_| {};
|
|
||||||
|
|
||||||
/*
|
|
||||||
|
|
||||||
// Merge the various stores into a single context
|
|
||||||
// This auto generates the appropriate selectors, reducing the need to wrap the app in excess providers
|
|
||||||
let all = combine_stores(vec![store1, store2]);
|
|
||||||
|
|
||||||
<Redux store={all}>
|
|
||||||
|
|
||||||
</Redux>
|
|
||||||
*/
|
|
||||||
}
|
|
||||||
|
|
||||||
struct Context {
|
|
||||||
data: String,
|
|
||||||
logged_in: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
// "static" selectors automatically get memoized
|
|
||||||
static SelectUserName: Selector<&str> = |root: Context| root.data.as_str();
|
|
||||||
static SelectUserName: Selector<bool> = |root: Context| root.data.logged_in;
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
/*
|
|
||||||
use_context is very unsafe! It essentially exposes your data in an unsafecell where &mut T and &T can exist at the same time. It's up to *you* the library implmenetor to make this safe.
|
|
||||||
|
|
||||||
We provide a redux-style system and a recoil-style system that are both saf
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
// connect itsy bits of state together
|
|
||||||
let context = use_create_context(
|
|
||||||
ctx,
|
|
||||||
ContextBuilder::new()
|
|
||||||
.with_root(|| Context {})
|
|
||||||
.with_root(|| Context {})
|
|
||||||
.with_root(|| Context {})
|
|
||||||
.with_root(|| Context {})
|
|
||||||
.with_root(|| Context {})
|
|
||||||
.with_root(|| Context {})
|
|
||||||
.with_root(|| Context {})
|
|
||||||
.with_root(|| Context {})
|
|
||||||
.build(),
|
|
||||||
);
|
|
||||||
|
|
||||||
let g: HashMap<TypeId, Box<dyn Any>> = HashMap::new();
|
|
||||||
}
|
|
||||||
@@ -1,58 +0,0 @@
|
|||||||
# virtual-dom-rs Changelog
|
|
||||||
|
|
||||||
Types of changes:
|
|
||||||
|
|
||||||
- `[added]` for new features.
|
|
||||||
- `[changed]` for changes in existing functionality.
|
|
||||||
- `[deprecated]` for once-stable features removed in upcoming releases.
|
|
||||||
- `[removed]` for deprecated features removed in this release.
|
|
||||||
- `[fixed]` for any bug fixes.
|
|
||||||
- `[security]` to invite users to upgrade in case of vulnerabilities.
|
|
||||||
|
|
||||||
## Not Yet Published
|
|
||||||
|
|
||||||
_Here we list notable things that have been merged into the master branch but have not been released yet._
|
|
||||||
|
|
||||||
- [added] SVG support [#104](https://github.com/chinedufn/percy/pull/104)
|
|
||||||
- ...
|
|
||||||
|
|
||||||
## 0.6.9 - May 23, 2019
|
|
||||||
|
|
||||||
- [added] `on_create_elem` [Docs](https://chinedufn.github.io/percy/html-macro/real-elements-and-nodes/on-create-elem/index.html)
|
|
||||||
- [added] `inner_html` [Docs](https://chinedufn.github.io/percy/html-macro/setting-inner-html/index.html)
|
|
||||||
|
|
||||||
## 0.6.7 - Mar 16, 2019
|
|
||||||
|
|
||||||
- [fixed] Spacing between elements is done by inserting space before and after existing text nodes instead of creating new ones.
|
|
||||||
|
|
||||||
## 0.6.6 - Mar 6, 2019
|
|
||||||
|
|
||||||
- [fixed] Proper spacing in between text nodes and elements in most use cases [PR](https://github.com/chinedufn/percy/pull/97)
|
|
||||||
- Still need to address [#98](https://github.com/chinedufn/percy/issues/98) and then we should have all cases handled.
|
|
||||||
|
|
||||||
## 0.6.5 - Mar 4, 2019
|
|
||||||
|
|
||||||
- [added] Start supporting braced text in the `html!` macro [#96](https://github.com/chinedufn/percy/pull/96)
|
|
||||||
- [removed] Removed the `text!` macro
|
|
||||||
|
|
||||||
```rust
|
|
||||||
let hello = "hello world";
|
|
||||||
html! { {hello} }
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
## 0.6.4 - Feb 24, 2019
|
|
||||||
|
|
||||||
- [fixed] Using the `html!` macro to create an event now uses the fully qualified path to `std::rc::Rc`
|
|
||||||
- [added] Started adding key support. If a VirtualNode's key attribute changes it will lead to a `Replace` patch.
|
|
||||||
|
|
||||||
```rust
|
|
||||||
// example
|
|
||||||
html! { <div key="5"></div> }`;
|
|
||||||
````
|
|
||||||
|
|
||||||
## 0.6.1 - Feb 22, 2019
|
|
||||||
|
|
||||||
- [fixed] Fix DomUpdater not storing closures for nodes that were created during `Patch::AppendChildren`
|
|
||||||
and `Patch::Replace`
|
|
||||||
- [Issue](https://github.com/chinedufn/percy/issues/70)
|
|
||||||
@@ -1,47 +0,0 @@
|
|||||||
[package]
|
|
||||||
name = "virtual-dom-rs"
|
|
||||||
version = "0.6.14"
|
|
||||||
authors = ["Chinedu Francis Nwafili <frankie.nwafili@gmail.com>"]
|
|
||||||
description = "A standalone Virtual DOM creation, diffing and patching implementation"
|
|
||||||
keywords = ["virtual", "dom", "wasm", "assembly", "webassembly"]
|
|
||||||
license = "MIT/Apache-2.0"
|
|
||||||
repository = "https://github.com/chinedufn/percy"
|
|
||||||
documentation = "https://chinedufn.github.io/percy/api/virtual_dom_rs/"
|
|
||||||
edition = "2018"
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
js-sys = "0.3"
|
|
||||||
wasm-bindgen = "0.2.33"
|
|
||||||
virtual-node = { path = "../virtual-node", version = "0.2.7" }
|
|
||||||
html-macro = { path = "../html-macro", version = "0.1.8"}
|
|
||||||
|
|
||||||
[dependencies.web-sys]
|
|
||||||
version = "0.3"
|
|
||||||
features = [
|
|
||||||
"Comment",
|
|
||||||
"Document",
|
|
||||||
"Element",
|
|
||||||
"HtmlElement",
|
|
||||||
"EventTarget",
|
|
||||||
"HtmlCollection",
|
|
||||||
"Node",
|
|
||||||
"NodeList",
|
|
||||||
"Text",
|
|
||||||
"CharacterData",
|
|
||||||
"Window",
|
|
||||||
]
|
|
||||||
|
|
||||||
[dev-dependencies]
|
|
||||||
wasm-bindgen-test = "0.2.33"
|
|
||||||
console_error_panic_hook = "0.1.5"
|
|
||||||
|
|
||||||
[dev-dependencies.web-sys]
|
|
||||||
version = "0.3"
|
|
||||||
features = [
|
|
||||||
"DomTokenList",
|
|
||||||
"HtmlInputElement",
|
|
||||||
"Event",
|
|
||||||
"MouseEvent",
|
|
||||||
"InputEvent",
|
|
||||||
"console",
|
|
||||||
]
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
# virtual-dom-rs
|
|
||||||
|
|
||||||
> A standalone Virtual DOM creation, diffing and patching implementation
|
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
//! Kept in it's own file to more easily import into the Percy book.
|
|
||||||
|
|
||||||
use crate::diff::diff;
|
|
||||||
use crate::patch::Patch;
|
|
||||||
use virtual_node::VirtualNode;
|
|
||||||
|
|
||||||
/// Test that we generate the right Vec<Patch> for some start and end virtual dom.
|
|
||||||
pub struct DiffTestCase<'a> {
|
|
||||||
// ex: "Patching root level nodes works"
|
|
||||||
pub description: &'static str,
|
|
||||||
// ex: html! { <div> </div> }
|
|
||||||
pub old: VirtualNode,
|
|
||||||
// ex: html! { <strong> </strong> }
|
|
||||||
pub new: VirtualNode,
|
|
||||||
// ex: vec![Patch::Replace(0, &html! { <strong></strong> })],
|
|
||||||
pub expected: Vec<Patch<'a>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> DiffTestCase<'a> {
|
|
||||||
pub fn test(&self) {
|
|
||||||
// ex: vec![Patch::Replace(0, &html! { <strong></strong> })],
|
|
||||||
let patches = diff(&self.old, &self.new);
|
|
||||||
|
|
||||||
assert_eq!(patches, self.expected, "{}", self.description);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,351 +0,0 @@
|
|||||||
use crate::Patch;
|
|
||||||
use crate::VirtualNode;
|
|
||||||
use std::cmp::min;
|
|
||||||
use std::collections::HashMap;
|
|
||||||
use std::mem;
|
|
||||||
|
|
||||||
/// Given two VirtualNode's generate Patch's that would turn the old virtual node's
|
|
||||||
/// real DOM node equivalent into the new VirtualNode's real DOM node equivalent.
|
|
||||||
pub fn diff<'a>(old: &'a VirtualNode, new: &'a VirtualNode) -> Vec<Patch<'a>> {
|
|
||||||
diff_recursive(&old, &new, &mut 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn diff_recursive<'a, 'b>(
|
|
||||||
old: &'a VirtualNode,
|
|
||||||
new: &'a VirtualNode,
|
|
||||||
cur_node_idx: &'b mut usize,
|
|
||||||
) -> Vec<Patch<'a>> {
|
|
||||||
let mut patches = vec![];
|
|
||||||
let mut replace = false;
|
|
||||||
|
|
||||||
// Different enum variants, replace!
|
|
||||||
if mem::discriminant(old) != mem::discriminant(new) {
|
|
||||||
replace = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if let (VirtualNode::Element(old_element), VirtualNode::Element(new_element)) = (old, new) {
|
|
||||||
// Replace if there are different element tags
|
|
||||||
if old_element.tag != new_element.tag {
|
|
||||||
replace = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Replace if two elements have different keys
|
|
||||||
// TODO: More robust key support. This is just an early stopgap to allow you to force replace
|
|
||||||
// an element... say if it's event changed. Just change the key name for now.
|
|
||||||
// In the future we want keys to be used to create a Patch::ReOrder to re-order siblings
|
|
||||||
if old_element.attrs.get("key").is_some()
|
|
||||||
&& old_element.attrs.get("key") != new_element.attrs.get("key")
|
|
||||||
{
|
|
||||||
replace = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle replacing of a node
|
|
||||||
if replace {
|
|
||||||
patches.push(Patch::Replace(*cur_node_idx, &new));
|
|
||||||
if let VirtualNode::Element(old_element_node) = old {
|
|
||||||
for child in old_element_node.children.iter() {
|
|
||||||
increment_node_idx_for_children(child, cur_node_idx);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return patches;
|
|
||||||
}
|
|
||||||
|
|
||||||
// The following comparison can only contain identical variants, other
|
|
||||||
// cases have already been handled above by comparing variant
|
|
||||||
// discriminants.
|
|
||||||
match (old, new) {
|
|
||||||
// We're comparing two text nodes
|
|
||||||
(VirtualNode::Text(old_text), VirtualNode::Text(new_text)) => {
|
|
||||||
if old_text != new_text {
|
|
||||||
patches.push(Patch::ChangeText(*cur_node_idx, &new_text));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// We're comparing two element nodes
|
|
||||||
(VirtualNode::Element(old_element), VirtualNode::Element(new_element)) => {
|
|
||||||
let mut add_attributes: HashMap<&str, &str> = HashMap::new();
|
|
||||||
let mut remove_attributes: Vec<&str> = vec![];
|
|
||||||
|
|
||||||
// TODO: -> split out into func
|
|
||||||
for (new_attr_name, new_attr_val) in new_element.attrs.iter() {
|
|
||||||
match old_element.attrs.get(new_attr_name) {
|
|
||||||
Some(ref old_attr_val) => {
|
|
||||||
if old_attr_val != &new_attr_val {
|
|
||||||
add_attributes.insert(new_attr_name, new_attr_val);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
None => {
|
|
||||||
add_attributes.insert(new_attr_name, new_attr_val);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: -> split out into func
|
|
||||||
for (old_attr_name, old_attr_val) in old_element.attrs.iter() {
|
|
||||||
if add_attributes.get(&old_attr_name[..]).is_some() {
|
|
||||||
continue;
|
|
||||||
};
|
|
||||||
|
|
||||||
match new_element.attrs.get(old_attr_name) {
|
|
||||||
Some(ref new_attr_val) => {
|
|
||||||
if new_attr_val != &old_attr_val {
|
|
||||||
remove_attributes.push(old_attr_name);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
None => {
|
|
||||||
remove_attributes.push(old_attr_name);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
if add_attributes.len() > 0 {
|
|
||||||
patches.push(Patch::AddAttributes(*cur_node_idx, add_attributes));
|
|
||||||
}
|
|
||||||
if remove_attributes.len() > 0 {
|
|
||||||
patches.push(Patch::RemoveAttributes(*cur_node_idx, remove_attributes));
|
|
||||||
}
|
|
||||||
|
|
||||||
let old_child_count = old_element.children.len();
|
|
||||||
let new_child_count = new_element.children.len();
|
|
||||||
|
|
||||||
if new_child_count > old_child_count {
|
|
||||||
let append_patch: Vec<&'a VirtualNode> =
|
|
||||||
new_element.children[old_child_count..].iter().collect();
|
|
||||||
patches.push(Patch::AppendChildren(*cur_node_idx, append_patch))
|
|
||||||
}
|
|
||||||
|
|
||||||
if new_child_count < old_child_count {
|
|
||||||
patches.push(Patch::TruncateChildren(*cur_node_idx, new_child_count))
|
|
||||||
}
|
|
||||||
|
|
||||||
let min_count = min(old_child_count, new_child_count);
|
|
||||||
for index in 0..min_count {
|
|
||||||
*cur_node_idx = *cur_node_idx + 1;
|
|
||||||
let old_child = &old_element.children[index];
|
|
||||||
let new_child = &new_element.children[index];
|
|
||||||
patches.append(&mut diff_recursive(&old_child, &new_child, cur_node_idx))
|
|
||||||
}
|
|
||||||
if new_child_count < old_child_count {
|
|
||||||
for child in old_element.children[min_count..].iter() {
|
|
||||||
increment_node_idx_for_children(child, cur_node_idx);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
(VirtualNode::Text(_), VirtualNode::Element(_))
|
|
||||||
| (VirtualNode::Element(_), VirtualNode::Text(_)) => {
|
|
||||||
unreachable!("Unequal variant discriminants should already have been handled");
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// new_root.create_element()
|
|
||||||
patches
|
|
||||||
}
|
|
||||||
|
|
||||||
fn increment_node_idx_for_children<'a, 'b>(old: &'a VirtualNode, cur_node_idx: &'b mut usize) {
|
|
||||||
*cur_node_idx += 1;
|
|
||||||
if let VirtualNode::Element(element_node) = old {
|
|
||||||
for child in element_node.children.iter() {
|
|
||||||
increment_node_idx_for_children(&child, cur_node_idx);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod diff_test_case;
|
|
||||||
#[cfg(test)]
|
|
||||||
use self::diff_test_case::*;
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use super::*;
|
|
||||||
use crate::{html, VText, VirtualNode};
|
|
||||||
use std::collections::HashMap;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn replace_node() {
|
|
||||||
DiffTestCase {
|
|
||||||
description: "Replace the root if the tag changed",
|
|
||||||
old: html! { <div> </div> },
|
|
||||||
new: html! { <span> </span> },
|
|
||||||
expected: vec![Patch::Replace(0, &html! { <span></span> })],
|
|
||||||
}
|
|
||||||
.test();
|
|
||||||
DiffTestCase {
|
|
||||||
description: "Replace a child node",
|
|
||||||
old: html! { <div> <b></b> </div> },
|
|
||||||
new: html! { <div> <strong></strong> </div> },
|
|
||||||
expected: vec![Patch::Replace(1, &html! { <strong></strong> })],
|
|
||||||
}
|
|
||||||
.test();
|
|
||||||
DiffTestCase {
|
|
||||||
description: "Replace node with a child",
|
|
||||||
old: html! { <div> <b>1</b> <b></b> </div> },
|
|
||||||
new: html! { <div> <i>1</i> <i></i> </div>},
|
|
||||||
expected: vec![
|
|
||||||
Patch::Replace(1, &html! { <i>1</i> }),
|
|
||||||
Patch::Replace(3, &html! { <i></i> }),
|
|
||||||
], //required to check correct index
|
|
||||||
}
|
|
||||||
.test();
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn add_children() {
|
|
||||||
DiffTestCase {
|
|
||||||
description: "Added a new node to the root node",
|
|
||||||
old: html! { <div> <b></b> </div> },
|
|
||||||
new: html! { <div> <b></b> <span></span> </div> },
|
|
||||||
expected: vec![Patch::AppendChildren(0, vec![&html! { <span></span> }])],
|
|
||||||
}
|
|
||||||
.test();
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn remove_nodes() {
|
|
||||||
DiffTestCase {
|
|
||||||
description: "Remove all child nodes at and after child sibling index 1",
|
|
||||||
old: html! { <div> <b></b> <span></span> </div> },
|
|
||||||
new: html! { <div> </div> },
|
|
||||||
expected: vec![Patch::TruncateChildren(0, 0)],
|
|
||||||
}
|
|
||||||
.test();
|
|
||||||
DiffTestCase {
|
|
||||||
description: "Remove a child and a grandchild node",
|
|
||||||
old: html! {
|
|
||||||
<div>
|
|
||||||
<span>
|
|
||||||
<b></b>
|
|
||||||
// This `i` tag will get removed
|
|
||||||
<i></i>
|
|
||||||
</span>
|
|
||||||
// This `strong` tag will get removed
|
|
||||||
<strong></strong>
|
|
||||||
</div> },
|
|
||||||
new: html! {
|
|
||||||
<div>
|
|
||||||
<span>
|
|
||||||
<b></b>
|
|
||||||
</span>
|
|
||||||
</div> },
|
|
||||||
expected: vec![Patch::TruncateChildren(0, 1), Patch::TruncateChildren(1, 1)],
|
|
||||||
}
|
|
||||||
.test();
|
|
||||||
DiffTestCase {
|
|
||||||
description: "Removing child and change next node after parent",
|
|
||||||
old: html! { <div> <b> <i></i> <i></i> </b> <b></b> </div> },
|
|
||||||
new: html! { <div> <b> <i></i> </b> <i></i> </div>},
|
|
||||||
expected: vec![
|
|
||||||
Patch::TruncateChildren(1, 1),
|
|
||||||
Patch::Replace(4, &html! { <i></i> }),
|
|
||||||
], //required to check correct index
|
|
||||||
}
|
|
||||||
.test();
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn add_attributes() {
|
|
||||||
let mut attributes = HashMap::new();
|
|
||||||
attributes.insert("id", "hello");
|
|
||||||
|
|
||||||
DiffTestCase {
|
|
||||||
old: html! { <div> </div> },
|
|
||||||
new: html! { <div id="hello"> </div> },
|
|
||||||
expected: vec![Patch::AddAttributes(0, attributes.clone())],
|
|
||||||
description: "Add attributes",
|
|
||||||
}
|
|
||||||
.test();
|
|
||||||
|
|
||||||
DiffTestCase {
|
|
||||||
old: html! { <div id="foobar"> </div> },
|
|
||||||
new: html! { <div id="hello"> </div> },
|
|
||||||
expected: vec![Patch::AddAttributes(0, attributes)],
|
|
||||||
description: "Change attribute",
|
|
||||||
}
|
|
||||||
.test();
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn remove_attributes() {
|
|
||||||
DiffTestCase {
|
|
||||||
old: html! { <div id="hey-there"></div> },
|
|
||||||
new: html! { <div> </div> },
|
|
||||||
expected: vec![Patch::RemoveAttributes(0, vec!["id"])],
|
|
||||||
description: "Add attributes",
|
|
||||||
}
|
|
||||||
.test();
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn change_attribute() {
|
|
||||||
let mut attributes = HashMap::new();
|
|
||||||
attributes.insert("id", "changed");
|
|
||||||
|
|
||||||
DiffTestCase {
|
|
||||||
description: "Add attributes",
|
|
||||||
old: html! { <div id="hey-there"></div> },
|
|
||||||
new: html! { <div id="changed"> </div> },
|
|
||||||
expected: vec![Patch::AddAttributes(0, attributes)],
|
|
||||||
}
|
|
||||||
.test();
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn replace_text_node() {
|
|
||||||
DiffTestCase {
|
|
||||||
description: "Replace text node",
|
|
||||||
old: html! { Old },
|
|
||||||
new: html! { New },
|
|
||||||
expected: vec![Patch::ChangeText(0, &VText::new("New"))],
|
|
||||||
}
|
|
||||||
.test();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Initially motivated by having two elements where all that changed was an event listener
|
|
||||||
// because right now we don't patch event listeners. So.. until we have a solution
|
|
||||||
// for that we can just give them different keys to force a replace.
|
|
||||||
#[test]
|
|
||||||
fn replace_if_different_keys() {
|
|
||||||
DiffTestCase {
|
|
||||||
description: "If two nodes have different keys always generate a full replace.",
|
|
||||||
old: html! { <div key="1"> </div> },
|
|
||||||
new: html! { <div key="2"> </div> },
|
|
||||||
expected: vec![Patch::Replace(0, &html! {<div key="2"> </div>})],
|
|
||||||
}
|
|
||||||
.test()
|
|
||||||
}
|
|
||||||
|
|
||||||
// // TODO: Key support
|
|
||||||
// #[test]
|
|
||||||
// fn reorder_chldren() {
|
|
||||||
// let mut attributes = HashMap::new();
|
|
||||||
// attributes.insert("class", "foo");
|
|
||||||
//
|
|
||||||
// let old_children = vec![
|
|
||||||
// // old node 0
|
|
||||||
// html! { <div key="hello", id="same-id", style="",></div> },
|
|
||||||
// // removed
|
|
||||||
// html! { <div key="gets-removed",> { "This node gets removed"} </div>},
|
|
||||||
// // old node 2
|
|
||||||
// html! { <div key="world", class="changed-class",></div>},
|
|
||||||
// // removed
|
|
||||||
// html! { <div key="this-got-removed",> { "This node gets removed"} </div>},
|
|
||||||
// ];
|
|
||||||
//
|
|
||||||
// let new_children = vec![
|
|
||||||
// html! { <div key="world", class="foo",></div> },
|
|
||||||
// html! { <div key="new",> </div>},
|
|
||||||
// html! { <div key="hello", id="same-id",></div>},
|
|
||||||
// ];
|
|
||||||
//
|
|
||||||
// test(DiffTestCase {
|
|
||||||
// old: html! { <div> { old_children } </div> },
|
|
||||||
// new: html! { <div> { new_children } </div> },
|
|
||||||
// expected: vec![
|
|
||||||
// // TODO: Come up with the patch structure for keyed nodes..
|
|
||||||
// // keying should only work if all children have keys..
|
|
||||||
// ],
|
|
||||||
// description: "Add attributes",
|
|
||||||
// })
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
@@ -1,103 +0,0 @@
|
|||||||
//! Diff virtual-doms and patch the real DOM
|
|
||||||
|
|
||||||
use crate::diff::diff;
|
|
||||||
use crate::patch::patch;
|
|
||||||
use std::collections::HashMap;
|
|
||||||
use virtual_node::DynClosure;
|
|
||||||
use virtual_node::VirtualNode;
|
|
||||||
use web_sys::{Element, Node};
|
|
||||||
|
|
||||||
/// Closures that we are holding on to to make sure that they don't get invalidated after a
|
|
||||||
/// VirtualNode is dropped.
|
|
||||||
///
|
|
||||||
/// The u32 is a unique identifier that is associated with the DOM element that this closure is
|
|
||||||
/// attached to.
|
|
||||||
///
|
|
||||||
/// TODO: Periodically check if the DOM element is still there, and if not drop the closure.
|
|
||||||
/// Maybe whenever a DOM node is replaced or truncated we figure out all of it's
|
|
||||||
/// descendants somehow and invalidate those closures..? Need to plan this out..
|
|
||||||
/// At it stands now this hashmap will grow anytime a new element with closures is
|
|
||||||
/// appended or replaced and we will never free those closures.
|
|
||||||
pub type ActiveClosures = HashMap<u32, Vec<DynClosure>>;
|
|
||||||
|
|
||||||
/// Used for keeping a real DOM node up to date based on the current VirtualNode
|
|
||||||
/// and a new incoming VirtualNode that represents our latest DOM state.
|
|
||||||
pub struct DomUpdater {
|
|
||||||
current_vdom: VirtualNode,
|
|
||||||
/// The closures that are currently attached to elements in the page.
|
|
||||||
///
|
|
||||||
/// We keep these around so that they don't get dropped (and thus stop working);
|
|
||||||
///
|
|
||||||
/// FIXME: Drop them when the element is no longer in the page. Need to figure out
|
|
||||||
/// a good strategy for when to do this.
|
|
||||||
pub active_closures: ActiveClosures,
|
|
||||||
root_node: Node,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl DomUpdater {
|
|
||||||
/// Create a new `DomUpdater`.
|
|
||||||
///
|
|
||||||
/// A root `Node` will be created but not added to your DOM.
|
|
||||||
pub fn new(current_vdom: VirtualNode) -> DomUpdater {
|
|
||||||
let created_node = current_vdom.create_dom_node();
|
|
||||||
DomUpdater {
|
|
||||||
current_vdom,
|
|
||||||
active_closures: created_node.closures,
|
|
||||||
root_node: created_node.node,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Create a new `DomUpdater`.
|
|
||||||
///
|
|
||||||
/// A root `Node` will be created and appended (as a child) to your passed
|
|
||||||
/// in mount element.
|
|
||||||
pub fn new_append_to_mount(current_vdom: VirtualNode, mount: &Element) -> DomUpdater {
|
|
||||||
let created_node = current_vdom.create_dom_node();
|
|
||||||
mount
|
|
||||||
.append_child(&created_node.node)
|
|
||||||
.expect("Could not append child to mount");
|
|
||||||
DomUpdater {
|
|
||||||
current_vdom,
|
|
||||||
active_closures: created_node.closures,
|
|
||||||
root_node: created_node.node,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Create a new `DomUpdater`.
|
|
||||||
///
|
|
||||||
/// A root `Node` will be created and it will replace your passed in mount
|
|
||||||
/// element.
|
|
||||||
pub fn new_replace_mount(current_vdom: VirtualNode, mount: Element) -> DomUpdater {
|
|
||||||
let created_node = current_vdom.create_dom_node();
|
|
||||||
mount
|
|
||||||
.replace_with_with_node_1(&created_node.node)
|
|
||||||
.expect("Could not replace mount element");
|
|
||||||
DomUpdater {
|
|
||||||
current_vdom,
|
|
||||||
active_closures: created_node.closures,
|
|
||||||
root_node: created_node.node,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Diff the current virtual dom with the new virtual dom that is being passed in.
|
|
||||||
///
|
|
||||||
/// Then use that diff to patch the real DOM in the user's browser so that they are
|
|
||||||
/// seeing the latest state of the application.
|
|
||||||
pub fn update(&mut self, new_vdom: VirtualNode) {
|
|
||||||
let patches = diff(&self.current_vdom, &new_vdom);
|
|
||||||
|
|
||||||
let active_closures = patch(self.root_node.clone(), &patches).unwrap();
|
|
||||||
|
|
||||||
self.active_closures.extend(active_closures);
|
|
||||||
|
|
||||||
self.current_vdom = new_vdom;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Return the root node of your application, the highest ancestor of all other nodes in
|
|
||||||
/// your real DOM tree.
|
|
||||||
pub fn root_node(&self) -> Node {
|
|
||||||
// Note that we're cloning the `web_sys::Node`, not the DOM element.
|
|
||||||
// So we're effectively cloning a pointer here, which is fast.
|
|
||||||
self.root_node.clone()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,44 +0,0 @@
|
|||||||
//! virtual-dom-rs provides a virtual dom implementation as well as an `html!` macro
|
|
||||||
//! that you can use to generate a virtual dom.
|
|
||||||
//!
|
|
||||||
//! The virtual dom works on both the client and server. On the client we'll render
|
|
||||||
//! to an `HtmlElement`, and on the server we render to a `String`.
|
|
||||||
|
|
||||||
#![deny(missing_docs)]
|
|
||||||
// #![cfg_attr(test, feature(proc_macro_hygiene))]
|
|
||||||
|
|
||||||
extern crate wasm_bindgen;
|
|
||||||
|
|
||||||
// Used so that `html!` calls work when people depend on this crate since `html!` needs
|
|
||||||
// access to `Closure` when creating event handlers.
|
|
||||||
pub use wasm_bindgen::prelude::Closure;
|
|
||||||
#[cfg(target_arch = "wasm32")]
|
|
||||||
pub use wasm_bindgen::JsCast;
|
|
||||||
|
|
||||||
pub extern crate web_sys;
|
|
||||||
pub use web_sys::*;
|
|
||||||
|
|
||||||
pub use virtual_node::*;
|
|
||||||
|
|
||||||
mod diff;
|
|
||||||
pub use crate::diff::*;
|
|
||||||
|
|
||||||
mod patch;
|
|
||||||
pub use crate::patch::*;
|
|
||||||
|
|
||||||
pub use html_macro::html;
|
|
||||||
|
|
||||||
mod dom_updater;
|
|
||||||
pub use self::dom_updater::DomUpdater;
|
|
||||||
|
|
||||||
/// Exports structs and macros that you'll almost always want access to in a virtual-dom
|
|
||||||
/// powered application
|
|
||||||
pub mod prelude {
|
|
||||||
pub use crate::dom_updater::DomUpdater;
|
|
||||||
pub use crate::VirtualNode;
|
|
||||||
pub use html_macro::html;
|
|
||||||
pub use std::vec::IntoIter;
|
|
||||||
pub use virtual_node::IterableNodes;
|
|
||||||
pub use virtual_node::View;
|
|
||||||
pub use wasm_bindgen::prelude::Closure;
|
|
||||||
}
|
|
||||||
@@ -1,217 +0,0 @@
|
|||||||
use crate::dom_updater::ActiveClosures;
|
|
||||||
use crate::patch::Patch;
|
|
||||||
|
|
||||||
use std::cmp::min;
|
|
||||||
use std::collections::HashMap;
|
|
||||||
use std::collections::HashSet;
|
|
||||||
use wasm_bindgen::JsCast;
|
|
||||||
use wasm_bindgen::JsValue;
|
|
||||||
use web_sys::{Element, Node, Text};
|
|
||||||
|
|
||||||
/// Apply all of the patches to our old root node in order to create the new root node
|
|
||||||
/// that we desire.
|
|
||||||
/// This is usually used after diffing two virtual nodes.
|
|
||||||
pub fn patch<N: Into<Node>>(root_node: N, patches: &Vec<Patch>) -> Result<ActiveClosures, JsValue> {
|
|
||||||
let root_node: Node = root_node.into();
|
|
||||||
|
|
||||||
let mut cur_node_idx = 0;
|
|
||||||
|
|
||||||
let mut nodes_to_find = HashSet::new();
|
|
||||||
|
|
||||||
for patch in patches {
|
|
||||||
nodes_to_find.insert(patch.node_idx());
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut element_nodes_to_patch = HashMap::new();
|
|
||||||
let mut text_nodes_to_patch = HashMap::new();
|
|
||||||
|
|
||||||
// Closures that were added to the DOM during this patch operation.
|
|
||||||
let mut active_closures = HashMap::new();
|
|
||||||
|
|
||||||
find_nodes(
|
|
||||||
root_node,
|
|
||||||
&mut cur_node_idx,
|
|
||||||
&mut nodes_to_find,
|
|
||||||
&mut element_nodes_to_patch,
|
|
||||||
&mut text_nodes_to_patch,
|
|
||||||
);
|
|
||||||
|
|
||||||
for patch in patches {
|
|
||||||
let patch_node_idx = patch.node_idx();
|
|
||||||
|
|
||||||
if let Some(element) = element_nodes_to_patch.get(&patch_node_idx) {
|
|
||||||
let new_closures = apply_element_patch(&element, &patch)?;
|
|
||||||
active_closures.extend(new_closures);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(text_node) = text_nodes_to_patch.get(&patch_node_idx) {
|
|
||||||
apply_text_patch(&text_node, &patch)?;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
unreachable!("Getting here means we didn't find the element or next node that we were supposed to patch.")
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(active_closures)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn find_nodes(
|
|
||||||
root_node: Node,
|
|
||||||
cur_node_idx: &mut usize,
|
|
||||||
nodes_to_find: &mut HashSet<usize>,
|
|
||||||
element_nodes_to_patch: &mut HashMap<usize, Element>,
|
|
||||||
text_nodes_to_patch: &mut HashMap<usize, Text>,
|
|
||||||
) {
|
|
||||||
if nodes_to_find.len() == 0 {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// We use child_nodes() instead of children() because children() ignores text nodes
|
|
||||||
let children = root_node.child_nodes();
|
|
||||||
let child_node_count = children.length();
|
|
||||||
|
|
||||||
// If the root node matches, mark it for patching
|
|
||||||
if nodes_to_find.get(&cur_node_idx).is_some() {
|
|
||||||
match root_node.node_type() {
|
|
||||||
Node::ELEMENT_NODE => {
|
|
||||||
element_nodes_to_patch.insert(*cur_node_idx, root_node.unchecked_into());
|
|
||||||
}
|
|
||||||
Node::TEXT_NODE => {
|
|
||||||
text_nodes_to_patch.insert(*cur_node_idx, root_node.unchecked_into());
|
|
||||||
}
|
|
||||||
other => unimplemented!("Unsupported root node type: {}", other),
|
|
||||||
}
|
|
||||||
nodes_to_find.remove(&cur_node_idx);
|
|
||||||
}
|
|
||||||
|
|
||||||
*cur_node_idx += 1;
|
|
||||||
|
|
||||||
for i in 0..child_node_count {
|
|
||||||
let node = children.item(i).unwrap();
|
|
||||||
|
|
||||||
match node.node_type() {
|
|
||||||
Node::ELEMENT_NODE => {
|
|
||||||
find_nodes(
|
|
||||||
node,
|
|
||||||
cur_node_idx,
|
|
||||||
nodes_to_find,
|
|
||||||
element_nodes_to_patch,
|
|
||||||
text_nodes_to_patch,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
Node::TEXT_NODE => {
|
|
||||||
if nodes_to_find.get(&cur_node_idx).is_some() {
|
|
||||||
text_nodes_to_patch.insert(*cur_node_idx, node.unchecked_into());
|
|
||||||
}
|
|
||||||
|
|
||||||
*cur_node_idx += 1;
|
|
||||||
}
|
|
||||||
Node::COMMENT_NODE => {
|
|
||||||
// At this time we do not support user entered comment nodes, so if we see a comment
|
|
||||||
// then it was a delimiter created by virtual-dom-rs in order to ensure that two
|
|
||||||
// neighboring text nodes did not get merged into one by the browser. So we skip
|
|
||||||
// over this virtual-dom-rs generated comment node.
|
|
||||||
}
|
|
||||||
_other => {
|
|
||||||
// Ignoring unsupported child node type
|
|
||||||
// TODO: What do we do with this situation? Log a warning?
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn apply_element_patch(node: &Element, patch: &Patch) -> Result<ActiveClosures, JsValue> {
|
|
||||||
let active_closures = HashMap::new();
|
|
||||||
|
|
||||||
match patch {
|
|
||||||
Patch::AddAttributes(_node_idx, attributes) => {
|
|
||||||
for (attrib_name, attrib_val) in attributes.iter() {
|
|
||||||
node.set_attribute(attrib_name, attrib_val)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(active_closures)
|
|
||||||
}
|
|
||||||
Patch::RemoveAttributes(_node_idx, attributes) => {
|
|
||||||
for attrib_name in attributes.iter() {
|
|
||||||
node.remove_attribute(attrib_name)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(active_closures)
|
|
||||||
}
|
|
||||||
Patch::Replace(_node_idx, new_node) => {
|
|
||||||
let created_node = new_node.create_dom_node();
|
|
||||||
|
|
||||||
node.replace_with_with_node_1(&created_node.node)?;
|
|
||||||
|
|
||||||
Ok(created_node.closures)
|
|
||||||
}
|
|
||||||
Patch::TruncateChildren(_node_idx, num_children_remaining) => {
|
|
||||||
let children = node.child_nodes();
|
|
||||||
let mut child_count = children.length();
|
|
||||||
|
|
||||||
// We skip over any separators that we placed between two text nodes
|
|
||||||
// -> `<!--ptns-->`
|
|
||||||
// and trim all children that come after our new desired `num_children_remaining`
|
|
||||||
let mut non_separator_children_found = 0;
|
|
||||||
|
|
||||||
for index in 0 as u32..child_count {
|
|
||||||
let child = children
|
|
||||||
.get(min(index, child_count - 1))
|
|
||||||
.expect("Potential child to truncate");
|
|
||||||
|
|
||||||
// If this is a comment node then we know that it is a `<!--ptns-->`
|
|
||||||
// text node separator that was created in virtual_node/mod.rs.
|
|
||||||
if child.node_type() == Node::COMMENT_NODE {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
non_separator_children_found += 1;
|
|
||||||
|
|
||||||
if non_separator_children_found <= *num_children_remaining as u32 {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
node.remove_child(&child).expect("Truncated children");
|
|
||||||
child_count -= 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(active_closures)
|
|
||||||
}
|
|
||||||
Patch::AppendChildren(_node_idx, new_nodes) => {
|
|
||||||
let parent = &node;
|
|
||||||
|
|
||||||
let mut active_closures = HashMap::new();
|
|
||||||
|
|
||||||
for new_node in new_nodes {
|
|
||||||
let created_node = new_node.create_dom_node();
|
|
||||||
|
|
||||||
parent.append_child(&created_node.node)?;
|
|
||||||
|
|
||||||
active_closures.extend(created_node.closures);
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(active_closures)
|
|
||||||
}
|
|
||||||
Patch::ChangeText(_node_idx, _new_node) => {
|
|
||||||
unreachable!("Elements should not receive ChangeText patches.")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn apply_text_patch(node: &Text, patch: &Patch) -> Result<(), JsValue> {
|
|
||||||
match patch {
|
|
||||||
Patch::ChangeText(_node_idx, new_node) => {
|
|
||||||
node.set_node_value(Some(&new_node.text));
|
|
||||||
}
|
|
||||||
Patch::Replace(_node_idx, new_node) => {
|
|
||||||
node.replace_with_with_node_1(&new_node.create_dom_node().node)?;
|
|
||||||
}
|
|
||||||
other => unreachable!(
|
|
||||||
"Text nodes should only receive ChangeText or Replace patches, not {:?}.",
|
|
||||||
other,
|
|
||||||
),
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
@@ -1,75 +0,0 @@
|
|||||||
//! Our Patch enum is intentionally kept in it's own file for easy inclusion into
|
|
||||||
//! The Percy Book.
|
|
||||||
|
|
||||||
use crate::{VText, VirtualNode};
|
|
||||||
use std::collections::HashMap;
|
|
||||||
|
|
||||||
mod apply_patches;
|
|
||||||
pub use apply_patches::patch;
|
|
||||||
|
|
||||||
/// A Patch encodes an operation that modifies a real DOM element.
|
|
||||||
///
|
|
||||||
/// To update the real DOM that a user sees you'll want to first diff your
|
|
||||||
/// old virtual dom and new virtual dom.
|
|
||||||
///
|
|
||||||
/// This diff operation will generate `Vec<Patch>` with zero or more patches that, when
|
|
||||||
/// applied to your real DOM, will make your real DOM look like your new virtual dom.
|
|
||||||
///
|
|
||||||
/// Each Patch has a u32 node index that helps us identify the real DOM node that it applies to.
|
|
||||||
///
|
|
||||||
/// Our old virtual dom's nodes are indexed depth first, as shown in this illustration
|
|
||||||
/// (0 being the root node, 1 being it's first child, 2 being it's first child's first child).
|
|
||||||
///
|
|
||||||
/// ```text
|
|
||||||
/// .─.
|
|
||||||
/// ( 0 )
|
|
||||||
/// `┬'
|
|
||||||
/// ┌────┴──────┐
|
|
||||||
/// │ │
|
|
||||||
/// ▼ ▼
|
|
||||||
/// .─. .─.
|
|
||||||
/// ( 1 ) ( 4 )
|
|
||||||
/// `┬' `─'
|
|
||||||
/// ┌────┴───┐ ├─────┬─────┐
|
|
||||||
/// │ │ │ │ │
|
|
||||||
/// ▼ ▼ ▼ ▼ ▼
|
|
||||||
/// .─. .─. .─. .─. .─.
|
|
||||||
/// ( 2 ) ( 3 ) ( 5 ) ( 6 ) ( 7 )
|
|
||||||
/// `─' `─' `─' `─' `─'
|
|
||||||
/// ```
|
|
||||||
///
|
|
||||||
/// The patching process is tested in a real browser in crates/virtual-dom-rs/tests/diff_patch.rs
|
|
||||||
#[derive(Debug, PartialEq)]
|
|
||||||
pub enum Patch<'a> {
|
|
||||||
/// Append a vector of child nodes to a parent node id.
|
|
||||||
AppendChildren(NodeIdx, Vec<&'a VirtualNode>),
|
|
||||||
/// For a `node_i32`, remove all children besides the first `len`
|
|
||||||
TruncateChildren(NodeIdx, usize),
|
|
||||||
/// Replace a node with another node. This typically happens when a node's tag changes.
|
|
||||||
/// ex: <div> becomes <span>
|
|
||||||
Replace(NodeIdx, &'a VirtualNode),
|
|
||||||
/// Add attributes that the new node has that the old node does not
|
|
||||||
AddAttributes(NodeIdx, HashMap<&'a str, &'a str>),
|
|
||||||
/// Remove attributes that the old node had that the new node doesn't
|
|
||||||
RemoveAttributes(NodeIdx, Vec<&'a str>),
|
|
||||||
/// Change the text of a Text node.
|
|
||||||
ChangeText(NodeIdx, &'a VText),
|
|
||||||
}
|
|
||||||
|
|
||||||
type NodeIdx = usize;
|
|
||||||
|
|
||||||
impl<'a> Patch<'a> {
|
|
||||||
/// Every Patch is meant to be applied to a specific node within the DOM. Get the
|
|
||||||
/// index of the DOM node that this patch should apply to. DOM nodes are indexed
|
|
||||||
/// depth first with the root node in the tree having index 0.
|
|
||||||
pub fn node_idx(&self) -> usize {
|
|
||||||
match self {
|
|
||||||
Patch::AppendChildren(node_idx, _) => *node_idx,
|
|
||||||
Patch::TruncateChildren(node_idx, _) => *node_idx,
|
|
||||||
Patch::Replace(node_idx, _) => *node_idx,
|
|
||||||
Patch::AddAttributes(node_idx, _) => *node_idx,
|
|
||||||
Patch::RemoveAttributes(node_idx, _) => *node_idx,
|
|
||||||
Patch::ChangeText(node_idx, _) => *node_idx,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,116 +0,0 @@
|
|||||||
//! Ensure that our DomUpdater maintains Rc's to closures so that they work even
|
|
||||||
//! after dropping virtual dom nodes.
|
|
||||||
//!
|
|
||||||
//! To run all tests in this file:
|
|
||||||
//!
|
|
||||||
//! wasm-pack test crates/virtual-dom-rs --chrome --headless -- --test closures
|
|
||||||
|
|
||||||
// #![feature(proc_macro_hygiene)]
|
|
||||||
|
|
||||||
use std::cell::RefCell;
|
|
||||||
use std::collections::hash_map::DefaultHasher;
|
|
||||||
use std::rc::Rc;
|
|
||||||
use virtual_dom_rs::prelude::*;
|
|
||||||
use virtual_dom_rs::DomUpdater;
|
|
||||||
use wasm_bindgen::JsCast;
|
|
||||||
use wasm_bindgen_test;
|
|
||||||
use wasm_bindgen_test::*;
|
|
||||||
use web_sys::*;
|
|
||||||
|
|
||||||
wasm_bindgen_test_configure!(run_in_browser);
|
|
||||||
|
|
||||||
// TODO: This test current fails in headless browsers but works in non headless browsers
|
|
||||||
// (tested in both geckodriver and chromedriver)
|
|
||||||
// Need to figure out why
|
|
||||||
#[wasm_bindgen_test]
|
|
||||||
fn closure_not_dropped() {
|
|
||||||
let text = Rc::new(RefCell::new("Start Text".to_string()));
|
|
||||||
|
|
||||||
let document = web_sys::window().unwrap().document().unwrap();
|
|
||||||
|
|
||||||
let mut dom_updater = None;
|
|
||||||
|
|
||||||
{
|
|
||||||
let mut input = make_input_component(Rc::clone(&text));
|
|
||||||
input
|
|
||||||
.as_velement_mut()
|
|
||||||
.expect("Not an element")
|
|
||||||
.attrs
|
|
||||||
.insert("id".into(), "old-input-elem".into());
|
|
||||||
|
|
||||||
let mount = document.create_element("div").unwrap();
|
|
||||||
mount.set_id("mount");
|
|
||||||
document.body().unwrap().append_child(&mount).unwrap();
|
|
||||||
|
|
||||||
dom_updater = Some(DomUpdater::new_replace_mount(input, mount));
|
|
||||||
|
|
||||||
let mut dom_updater = dom_updater.as_mut().unwrap();
|
|
||||||
|
|
||||||
// Input VirtualNode from above gets dropped at the end of this block,
|
|
||||||
// yet that element held Rc's to the Closure's that power the oninput event.
|
|
||||||
//
|
|
||||||
// We're patching the DOM with a new vdom, but since our new vdom doesn't contain any
|
|
||||||
// new elements, `.create_element` won't get called and so no new Closures will be
|
|
||||||
// created.
|
|
||||||
//
|
|
||||||
// So, we're testing that our old Closure's still work. The reason that they work is
|
|
||||||
// that dom_updater maintains Rc's to those Closures.
|
|
||||||
let mut new_node = make_input_component(Rc::clone(&text));
|
|
||||||
new_node
|
|
||||||
.as_velement_mut()
|
|
||||||
.expect("Not an element")
|
|
||||||
.attrs
|
|
||||||
.insert("id".into(), "new-input-elem".into());
|
|
||||||
|
|
||||||
dom_updater.update(new_node);
|
|
||||||
}
|
|
||||||
|
|
||||||
let dom_updater = dom_updater.as_ref().unwrap();
|
|
||||||
|
|
||||||
let input: HtmlInputElement = document
|
|
||||||
.get_element_by_id("new-input-elem")
|
|
||||||
.expect("Input element")
|
|
||||||
.dyn_into()
|
|
||||||
.unwrap();
|
|
||||||
let input_event = InputEvent::new("input").unwrap();
|
|
||||||
|
|
||||||
assert_eq!(&*text.borrow(), "Start Text");
|
|
||||||
|
|
||||||
// After dispatching the oninput event our `text` should have a value of the input elements value.
|
|
||||||
web_sys::EventTarget::from(input)
|
|
||||||
.dispatch_event(&input_event)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
assert_eq!(&*text.borrow(), "End Text");
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
dom_updater.active_closures.get(&1).as_ref().unwrap().len(),
|
|
||||||
1
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// We're just making sure that things compile - other tests give us confidence that the closure
|
|
||||||
// will work just fine.
|
|
||||||
//
|
|
||||||
// https://github.com/chinedufn/percy/issues/81
|
|
||||||
//
|
|
||||||
//#[wasm_bindgen_test]
|
|
||||||
//fn closure_with_no_params_compiles() {
|
|
||||||
// let _making_sure_this_works = html! {
|
|
||||||
// <div onclick=|| {}></div>
|
|
||||||
// };
|
|
||||||
//}
|
|
||||||
|
|
||||||
fn make_input_component(text_clone: Rc<RefCell<String>>) -> VirtualNode {
|
|
||||||
html! {
|
|
||||||
<input
|
|
||||||
// On input we'll set our Rc<RefCell<String>> value to the input elements value
|
|
||||||
oninput=move |event: Event| {
|
|
||||||
let input_elem = event.target().unwrap();
|
|
||||||
let input_elem = input_elem.dyn_into::<HtmlInputElement>().unwrap();
|
|
||||||
*text_clone.borrow_mut() = input_elem.value();
|
|
||||||
}
|
|
||||||
value="End Text"
|
|
||||||
>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,118 +0,0 @@
|
|||||||
//! Tests that ensure that we create the right DOM element from a VirtualNode
|
|
||||||
//!
|
|
||||||
//! To run all tests in this file:
|
|
||||||
//!
|
|
||||||
//! wasm-pack test crates/virtual-dom-rs --chrome --headless -- --test create_element
|
|
||||||
|
|
||||||
// #![feature(proc_macro_hygiene)]
|
|
||||||
|
|
||||||
extern crate wasm_bindgen_test;
|
|
||||||
extern crate web_sys;
|
|
||||||
use std::cell::Cell;
|
|
||||||
use std::rc::Rc;
|
|
||||||
use wasm_bindgen::JsCast;
|
|
||||||
use wasm_bindgen_test::*;
|
|
||||||
use web_sys::{Element, Event, EventTarget, MouseEvent};
|
|
||||||
|
|
||||||
use virtual_dom_rs::prelude::*;
|
|
||||||
|
|
||||||
wasm_bindgen_test_configure!(run_in_browser);
|
|
||||||
|
|
||||||
/// wasm-pack test crates/virtual-dom-rs --chrome --headless -- --test create_element nested_divs
|
|
||||||
#[wasm_bindgen_test]
|
|
||||||
fn nested_divs() {
|
|
||||||
let vdiv = html! { <div> <div> <div></div> </div> </div> };
|
|
||||||
let div: Element = vdiv.create_dom_node().node.unchecked_into();
|
|
||||||
|
|
||||||
assert_eq!(&div.inner_html(), "<div><div></div></div>");
|
|
||||||
}
|
|
||||||
|
|
||||||
/// wasm-pack test crates/virtual-dom-rs --chrome --headless -- --test create_element svg_element
|
|
||||||
/// TODO: Temporarily disabled until we figure out why it's failing in CI but not failing locally
|
|
||||||
// #[wasm_bindgen_test]
|
|
||||||
// fn svg_element() {
|
|
||||||
// let vdiv = html! { <div><svg xmlns="http://www.w3.org/2000/svg">
|
|
||||||
// <circle cx="50" cy="50" r="50"/>
|
|
||||||
// </svg></div> };
|
|
||||||
// let div: Element = vdiv.create_dom_node().node.unchecked_into();
|
|
||||||
|
|
||||||
// assert_eq!(
|
|
||||||
// &div.inner_html(),
|
|
||||||
// r#"<svg xmlns="http://www.w3.org/2000/svg"><circle cx="50" cy="50" r="50"></circle></svg>"#
|
|
||||||
// );
|
|
||||||
// }
|
|
||||||
|
|
||||||
/// wasm-pack test crates/virtual-dom-rs --chrome --headless -- --test create_element div_with_attributes
|
|
||||||
#[wasm_bindgen_test]
|
|
||||||
fn div_with_attributes() {
|
|
||||||
let vdiv = html! { <div id="id-here" class="two classes"></div> };
|
|
||||||
let div: Element = vdiv.create_dom_node().node.unchecked_into();
|
|
||||||
|
|
||||||
assert_eq!(&div.id(), "id-here");
|
|
||||||
|
|
||||||
assert!(div.class_list().contains("two"));
|
|
||||||
assert!(div.class_list().contains("classes"));
|
|
||||||
|
|
||||||
assert_eq!(div.class_list().length(), 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// wasm-pack test crates/virtual-dom-rs --chrome --headless -- --test create_element click_event
|
|
||||||
#[wasm_bindgen_test]
|
|
||||||
fn click_event() {
|
|
||||||
let clicked = Rc::new(Cell::new(false));
|
|
||||||
let clicked_clone = Rc::clone(&clicked);
|
|
||||||
|
|
||||||
let div = html! {
|
|
||||||
<div
|
|
||||||
onclick=move |_ev: MouseEvent| {
|
|
||||||
clicked_clone.set(true);
|
|
||||||
}
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
};
|
|
||||||
|
|
||||||
let click_event = Event::new("click").unwrap();
|
|
||||||
|
|
||||||
let div = div.create_dom_node().node;
|
|
||||||
|
|
||||||
(EventTarget::from(div))
|
|
||||||
.dispatch_event(&click_event)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
assert_eq!(*clicked, Cell::new(true));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// wasm-pack test crates/virtual-dom-rs --chrome --headless -- --test create_element inner_html
|
|
||||||
/// @book start inner-html
|
|
||||||
#[wasm_bindgen_test]
|
|
||||||
fn inner_html() {
|
|
||||||
let div = html! {
|
|
||||||
<div
|
|
||||||
unsafe_inner_html="<span>hi</span>"
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
};
|
|
||||||
let div: Element = div.create_dom_node().node.unchecked_into();
|
|
||||||
|
|
||||||
assert_eq!(div.inner_html(), "<span>hi</span>");
|
|
||||||
}
|
|
||||||
// @book end inner-html
|
|
||||||
|
|
||||||
/// wasm-pack test crates/virtual-dom-rs --chrome --headless -- --test create_element on_create_elem
|
|
||||||
/// @book start on-create-elem
|
|
||||||
#[wasm_bindgen_test]
|
|
||||||
fn on_create_elem() {
|
|
||||||
let div = html! {
|
|
||||||
<div
|
|
||||||
on_create_elem=|elem: web_sys::Element| {
|
|
||||||
elem.set_inner_html("Hello world");
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<span>This span should get replaced</span>
|
|
||||||
</div>
|
|
||||||
};
|
|
||||||
let div: Element = div.create_dom_node().node.unchecked_into();
|
|
||||||
|
|
||||||
assert_eq!(div.inner_html(), "Hello world");
|
|
||||||
}
|
|
||||||
// @book end on-create-elem
|
|
||||||
@@ -1,197 +0,0 @@
|
|||||||
//! Tests that ensure that diffing and patching work properly in a real browser.
|
|
||||||
//!
|
|
||||||
//! To run all tests in this file:
|
|
||||||
//!
|
|
||||||
//! wasm-pack test crates/virtual-dom-rs --chrome --headless -- --test diff_patch
|
|
||||||
|
|
||||||
// #![feature(proc_macro_hygiene)]
|
|
||||||
|
|
||||||
extern crate wasm_bindgen_test;
|
|
||||||
extern crate web_sys;
|
|
||||||
use wasm_bindgen_test::*;
|
|
||||||
|
|
||||||
use virtual_dom_rs::prelude::*;
|
|
||||||
|
|
||||||
wasm_bindgen_test_configure!(run_in_browser);
|
|
||||||
|
|
||||||
mod diff_patch_test_case;
|
|
||||||
use self::diff_patch_test_case::DiffPatchTest;
|
|
||||||
|
|
||||||
#[wasm_bindgen_test]
|
|
||||||
fn replace_child() {
|
|
||||||
DiffPatchTest {
|
|
||||||
desc: "Replace a root node attribute attribute and a child text node",
|
|
||||||
old: html! {
|
|
||||||
<div>
|
|
||||||
Original element
|
|
||||||
</div>
|
|
||||||
},
|
|
||||||
new: html! { <div> Patched element</div> },
|
|
||||||
override_expected: None,
|
|
||||||
}
|
|
||||||
.test();
|
|
||||||
}
|
|
||||||
|
|
||||||
#[wasm_bindgen_test]
|
|
||||||
fn truncate_children() {
|
|
||||||
DiffPatchTest {
|
|
||||||
desc: "Truncates extra children",
|
|
||||||
old: html! {
|
|
||||||
<div>
|
|
||||||
<div> <div> <b></b> <em></em> </div> </div>
|
|
||||||
</div>
|
|
||||||
},
|
|
||||||
new: html! {
|
|
||||||
<div>
|
|
||||||
<div> <div> <b></b> </div> </div>
|
|
||||||
</div>
|
|
||||||
},
|
|
||||||
override_expected: None,
|
|
||||||
}
|
|
||||||
.test();
|
|
||||||
|
|
||||||
DiffPatchTest {
|
|
||||||
desc: "https://github.com/chinedufn/percy/issues/48",
|
|
||||||
old: html! {
|
|
||||||
<div>
|
|
||||||
ab <p></p> c
|
|
||||||
</div>
|
|
||||||
},
|
|
||||||
new: html! {
|
|
||||||
<div>
|
|
||||||
ab <p></p>
|
|
||||||
</div>
|
|
||||||
},
|
|
||||||
override_expected: None,
|
|
||||||
}
|
|
||||||
.test();
|
|
||||||
}
|
|
||||||
|
|
||||||
#[wasm_bindgen_test]
|
|
||||||
fn remove_attributes() {
|
|
||||||
DiffPatchTest {
|
|
||||||
desc: "Removes attributes",
|
|
||||||
old: html! { <div style=""> </div>
|
|
||||||
},
|
|
||||||
new: html! { <div></div> },
|
|
||||||
override_expected: None,
|
|
||||||
}
|
|
||||||
.test();
|
|
||||||
}
|
|
||||||
|
|
||||||
#[wasm_bindgen_test]
|
|
||||||
fn append_children() {
|
|
||||||
DiffPatchTest {
|
|
||||||
desc: "Append a child node",
|
|
||||||
old: html! { <div> </div>
|
|
||||||
},
|
|
||||||
new: html! { <div> <span></span> </div> },
|
|
||||||
override_expected: None,
|
|
||||||
}
|
|
||||||
.test();
|
|
||||||
}
|
|
||||||
|
|
||||||
#[wasm_bindgen_test]
|
|
||||||
fn text_node_siblings() {
|
|
||||||
// NOTE: Since there are two text nodes next to eachother we expect a `<!--ptns-->` separator in
|
|
||||||
// between them.
|
|
||||||
// @see virtual_node/mod.rs -> create_dom_node() for more information
|
|
||||||
// TODO: A little more spacing than there should be in between the text nodes ... but doesn't
|
|
||||||
// impact the user experience so we can look into that later..
|
|
||||||
let override_expected = Some(
|
|
||||||
r#"<div id="after"><span> The button has been clicked: <!--ptns--> world </span></div>"#,
|
|
||||||
);
|
|
||||||
|
|
||||||
let old1 = VirtualNode::text("The button has been clicked: ");
|
|
||||||
let old2 = VirtualNode::text("hello");
|
|
||||||
|
|
||||||
let new1 = VirtualNode::text("The button has been clicked: ");
|
|
||||||
let new2 = VirtualNode::text("world");
|
|
||||||
|
|
||||||
DiffPatchTest {
|
|
||||||
desc: "Diff patch on text node siblings",
|
|
||||||
old: html! {
|
|
||||||
<div id="before">
|
|
||||||
<span> { {old1} {old2} } </span>
|
|
||||||
</div>
|
|
||||||
},
|
|
||||||
new: html! {
|
|
||||||
<div id="after">
|
|
||||||
<span> { {new1} {new2} } </span>
|
|
||||||
</div>
|
|
||||||
},
|
|
||||||
override_expected,
|
|
||||||
}
|
|
||||||
.test();
|
|
||||||
}
|
|
||||||
|
|
||||||
#[wasm_bindgen_test]
|
|
||||||
fn append_text_node() {
|
|
||||||
DiffPatchTest {
|
|
||||||
desc: "Append text node",
|
|
||||||
old: html! { <div> </div> },
|
|
||||||
new: html! { <div> Hello </div> },
|
|
||||||
override_expected: None,
|
|
||||||
}
|
|
||||||
.test();
|
|
||||||
}
|
|
||||||
|
|
||||||
#[wasm_bindgen_test]
|
|
||||||
fn append_sibling_text_nodes() {
|
|
||||||
let text1 = VirtualNode::text("Hello");
|
|
||||||
let text2 = VirtualNode::text("World");
|
|
||||||
|
|
||||||
DiffPatchTest {
|
|
||||||
desc: "Append sibling text nodes",
|
|
||||||
old: html! { <div> </div> },
|
|
||||||
new: html! { <div> {text1} {text2} </div> },
|
|
||||||
override_expected: None,
|
|
||||||
}
|
|
||||||
.test();
|
|
||||||
}
|
|
||||||
|
|
||||||
#[wasm_bindgen_test]
|
|
||||||
fn replace_with_children() {
|
|
||||||
DiffPatchTest {
|
|
||||||
desc: "Replace node that has children",
|
|
||||||
old: html! { <table><tr><th>0</th></tr><tr><td>1</td></tr></table> },
|
|
||||||
new: html! { <table><tr><td>2</td></tr><tr><th>3</th></tr></table> },
|
|
||||||
override_expected: None,
|
|
||||||
}
|
|
||||||
.test();
|
|
||||||
}
|
|
||||||
|
|
||||||
// https://github.com/chinedufn/percy/issues/62
|
|
||||||
#[wasm_bindgen_test]
|
|
||||||
fn replace_element_with_text_node() {
|
|
||||||
DiffPatchTest {
|
|
||||||
desc: "#62: Replace element with text node",
|
|
||||||
old: html! { <span> <br> </span> },
|
|
||||||
new: html! { <span> a </span> },
|
|
||||||
override_expected: None,
|
|
||||||
}
|
|
||||||
.test();
|
|
||||||
}
|
|
||||||
|
|
||||||
// https://github.com/chinedufn/percy/issues/68
|
|
||||||
#[wasm_bindgen_test]
|
|
||||||
fn text_root_node() {
|
|
||||||
DiffPatchTest {
|
|
||||||
desc: "Patching of text root node works",
|
|
||||||
old: html! { Old text },
|
|
||||||
new: html! { New text },
|
|
||||||
override_expected: None,
|
|
||||||
}
|
|
||||||
.test();
|
|
||||||
}
|
|
||||||
|
|
||||||
#[wasm_bindgen_test]
|
|
||||||
fn replace_text_with_element() {
|
|
||||||
DiffPatchTest {
|
|
||||||
desc: "Replacing a text node with an element works",
|
|
||||||
old: html! { <div>a</div> },
|
|
||||||
new: html! { <div><br></div> },
|
|
||||||
override_expected: None,
|
|
||||||
}
|
|
||||||
.test();
|
|
||||||
}
|
|
||||||
@@ -1,53 +0,0 @@
|
|||||||
//! Kept in its own file to more easily import into the book
|
|
||||||
|
|
||||||
use console_error_panic_hook;
|
|
||||||
use virtual_dom_rs::prelude::*;
|
|
||||||
use wasm_bindgen::JsCast;
|
|
||||||
use web_sys::{Element, Node};
|
|
||||||
|
|
||||||
/// A test case that both diffing and patching are working in a real browser
|
|
||||||
pub struct DiffPatchTest<'a> {
|
|
||||||
/// Description of the test case.
|
|
||||||
pub desc: &'static str,
|
|
||||||
/// The old virtual node.
|
|
||||||
pub old: VirtualNode,
|
|
||||||
/// The new virtual node.
|
|
||||||
pub new: VirtualNode,
|
|
||||||
/// By default we generate the expected based on `new.to_string()`. You can
|
|
||||||
/// use this field to override the expected HTML after patching.
|
|
||||||
pub override_expected: Option<&'a str>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> DiffPatchTest<'a> {
|
|
||||||
pub fn test(&mut self) {
|
|
||||||
console_error_panic_hook::set_once();
|
|
||||||
|
|
||||||
let document = web_sys::window().unwrap().document().unwrap();
|
|
||||||
|
|
||||||
// Create a DOM node of the virtual root node
|
|
||||||
let root_node: Node = self.old.create_dom_node().node;
|
|
||||||
|
|
||||||
// Clone since virtual_dom_rs::patch takes ownership of the root node.
|
|
||||||
let patched_root_node: Node = root_node.clone();
|
|
||||||
|
|
||||||
// Generate patches
|
|
||||||
let patches = virtual_dom_rs::diff(&self.old, &self.new);
|
|
||||||
|
|
||||||
// Patch our root node. It should now look like `self.new`
|
|
||||||
virtual_dom_rs::patch(root_node, &patches);
|
|
||||||
|
|
||||||
// Determine the expected outer HTML
|
|
||||||
let expected_outer_html = match self.override_expected {
|
|
||||||
Some(ref expected) => expected.to_string(),
|
|
||||||
None => self.new.to_string(),
|
|
||||||
};
|
|
||||||
|
|
||||||
let actual_outer_html = match patched_root_node.node_type() {
|
|
||||||
Node::ELEMENT_NODE => patched_root_node.unchecked_into::<Element>().outer_html(),
|
|
||||||
Node::TEXT_NODE => patched_root_node.text_content().unwrap_or("".into()),
|
|
||||||
_ => panic!("Unhandled node type"),
|
|
||||||
};
|
|
||||||
|
|
||||||
assert_eq!(&actual_outer_html, &expected_outer_html, "{}", self.desc);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,147 +0,0 @@
|
|||||||
//! Ensure that our DomUpdater maintains Rc's to closures so that they work even
|
|
||||||
//! after dropping virtual dom nodes.
|
|
||||||
//!
|
|
||||||
//! To run all tests in this file:
|
|
||||||
//!
|
|
||||||
//! wasm-pack test crates/virtual-dom-rs --chrome --headless -- --test dom_updater
|
|
||||||
|
|
||||||
// #![feature(proc_macro_hygiene)]
|
|
||||||
|
|
||||||
use console_error_panic_hook;
|
|
||||||
use std::cell::RefCell;
|
|
||||||
use std::collections::hash_map::DefaultHasher;
|
|
||||||
use std::hash::{Hash, Hasher};
|
|
||||||
use std::rc::Rc;
|
|
||||||
use virtual_dom_rs::prelude::*;
|
|
||||||
use virtual_dom_rs::DomUpdater;
|
|
||||||
use wasm_bindgen::JsCast;
|
|
||||||
use wasm_bindgen_test;
|
|
||||||
use wasm_bindgen_test::*;
|
|
||||||
use web_sys::*;
|
|
||||||
|
|
||||||
wasm_bindgen_test_configure!(run_in_browser);
|
|
||||||
|
|
||||||
// Verify that our DomUpdater's patch method works.
|
|
||||||
// We test a simple case here, since diff_patch.rs is responsible for testing more complex
|
|
||||||
// diffing and patching.
|
|
||||||
#[wasm_bindgen_test]
|
|
||||||
fn patches_dom() {
|
|
||||||
console_error_panic_hook::set_once();
|
|
||||||
|
|
||||||
let document = web_sys::window().unwrap().document().unwrap();
|
|
||||||
|
|
||||||
let vdom = html! { <div></div> };
|
|
||||||
|
|
||||||
let mut dom_updater = DomUpdater::new(vdom);
|
|
||||||
|
|
||||||
let new_vdom = html! { <div id="patched"></div> };
|
|
||||||
dom_updater.update(new_vdom);
|
|
||||||
|
|
||||||
document
|
|
||||||
.body()
|
|
||||||
.unwrap()
|
|
||||||
.append_child(&dom_updater.root_node());
|
|
||||||
assert_eq!(document.query_selector("#patched").unwrap().is_some(), true);
|
|
||||||
}
|
|
||||||
|
|
||||||
// When you replace a DOM node with another DOM node we need to make sure that the closures
|
|
||||||
// from the new DOM node are stored by the DomUpdater otherwise they'll get dropped and
|
|
||||||
// won't work.
|
|
||||||
#[wasm_bindgen_test]
|
|
||||||
fn updates_active_closure_on_replace() {
|
|
||||||
console_error_panic_hook::set_once();
|
|
||||||
|
|
||||||
let document = web_sys::window().unwrap().document().unwrap();
|
|
||||||
let body = document.body().unwrap();
|
|
||||||
|
|
||||||
let old = html! { <div> </div> };
|
|
||||||
let mut dom_updater = DomUpdater::new_append_to_mount(old, &body);
|
|
||||||
|
|
||||||
let text = Rc::new(RefCell::new("Start Text".to_string()));
|
|
||||||
let text_clone = Rc::clone(&text);
|
|
||||||
|
|
||||||
let id = "update-active-closures-on-replace";
|
|
||||||
|
|
||||||
{
|
|
||||||
let replace_node = html! {
|
|
||||||
<input
|
|
||||||
id=id
|
|
||||||
oninput=move |event: Event| {
|
|
||||||
let input_elem = event.target().unwrap();
|
|
||||||
let input_elem = input_elem.dyn_into::<HtmlInputElement>().unwrap();
|
|
||||||
*text_clone.borrow_mut() = input_elem.value();
|
|
||||||
}
|
|
||||||
value="End Text"
|
|
||||||
>
|
|
||||||
};
|
|
||||||
|
|
||||||
// New node replaces old node.
|
|
||||||
// We are testing that we've stored this new node's closures even though `new` will be dropped
|
|
||||||
// at the end of this block.
|
|
||||||
dom_updater.update(replace_node);
|
|
||||||
}
|
|
||||||
|
|
||||||
let input_event = InputEvent::new("input").unwrap();
|
|
||||||
|
|
||||||
assert_eq!(&*text.borrow(), "Start Text");
|
|
||||||
|
|
||||||
// After dispatching the oninput event our `text` should have a value of the input elements value.
|
|
||||||
let input = document.get_element_by_id(&id).unwrap();
|
|
||||||
web_sys::EventTarget::from(input)
|
|
||||||
.dispatch_event(&input_event)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
assert_eq!(&*text.borrow(), "End Text");
|
|
||||||
}
|
|
||||||
|
|
||||||
// When you replace a DOM node with another DOM node we need to make sure that the closures
|
|
||||||
// from the new DOM node are stored by the DomUpdater otherwise they'll get dropped and
|
|
||||||
// won't work.
|
|
||||||
#[wasm_bindgen_test]
|
|
||||||
fn updates_active_closures_on_append() {
|
|
||||||
console_error_panic_hook::set_once();
|
|
||||||
|
|
||||||
let document = web_sys::window().unwrap().document().unwrap();
|
|
||||||
let body = document.body().unwrap();
|
|
||||||
|
|
||||||
let old = html! { <div> </div> };
|
|
||||||
let mut dom_updater = DomUpdater::new_append_to_mount(old, &body);
|
|
||||||
|
|
||||||
let text = Rc::new(RefCell::new("Start Text".to_string()));
|
|
||||||
let text_clone = Rc::clone(&text);
|
|
||||||
|
|
||||||
let id = "update-active-closures-on-append";
|
|
||||||
|
|
||||||
{
|
|
||||||
let append_node = html! {
|
|
||||||
<div>
|
|
||||||
<input
|
|
||||||
id=id
|
|
||||||
oninput=move |event: Event| {
|
|
||||||
let input_elem = event.target().unwrap();
|
|
||||||
let input_elem = input_elem.dyn_into::<HtmlInputElement>().unwrap();
|
|
||||||
*text_clone.borrow_mut() = input_elem.value();
|
|
||||||
}
|
|
||||||
value="End Text"
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
};
|
|
||||||
|
|
||||||
// New node gets appended into the DOM.
|
|
||||||
// We are testing that we've stored this new node's closures even though `new` will be dropped
|
|
||||||
// at the end of this block.
|
|
||||||
dom_updater.update(append_node);
|
|
||||||
}
|
|
||||||
|
|
||||||
let input_event = InputEvent::new("input").unwrap();
|
|
||||||
|
|
||||||
assert_eq!(&*text.borrow(), "Start Text");
|
|
||||||
|
|
||||||
// After dispatching the oninput event our `text` should have a value of the input elements value.
|
|
||||||
let input = document.get_element_by_id(id).unwrap();
|
|
||||||
web_sys::EventTarget::from(input)
|
|
||||||
.dispatch_event(&input_event)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
assert_eq!(&*text.borrow(), "End Text");
|
|
||||||
}
|
|
||||||
@@ -1,47 +0,0 @@
|
|||||||
// #![feature(proc_macro_hygiene)]
|
|
||||||
|
|
||||||
extern crate wasm_bindgen_test;
|
|
||||||
extern crate web_sys;
|
|
||||||
use std::cell::Cell;
|
|
||||||
use std::rc::Rc;
|
|
||||||
use wasm_bindgen_test::*;
|
|
||||||
|
|
||||||
use std::cell::RefCell;
|
|
||||||
use wasm_bindgen::prelude::*;
|
|
||||||
use wasm_bindgen::JsCast;
|
|
||||||
use web_sys::*;
|
|
||||||
|
|
||||||
use virtual_dom_rs::prelude::*;
|
|
||||||
|
|
||||||
wasm_bindgen_test_configure!(run_in_browser);
|
|
||||||
|
|
||||||
// Make sure that we successfully attach an event listener and see it work.
|
|
||||||
#[wasm_bindgen_test]
|
|
||||||
fn on_input() {
|
|
||||||
let text = Rc::new(RefCell::new("Start Text".to_string()));
|
|
||||||
let text_clone = Rc::clone(&text);
|
|
||||||
|
|
||||||
let input = html! {
|
|
||||||
<input
|
|
||||||
// On input we'll set our Rc<RefCell<String>> value to the input elements value
|
|
||||||
oninput=move |event: Event| {
|
|
||||||
let input_elem = event.target().unwrap();
|
|
||||||
let input_elem = input_elem.dyn_into::<HtmlInputElement>().unwrap();
|
|
||||||
*text_clone.borrow_mut() = input_elem.value();
|
|
||||||
}
|
|
||||||
value="End Text"
|
|
||||||
>
|
|
||||||
};
|
|
||||||
|
|
||||||
let input_event = InputEvent::new("input").unwrap();
|
|
||||||
let input = input.create_dom_node().node;
|
|
||||||
|
|
||||||
assert_eq!(&*text.borrow(), "Start Text");
|
|
||||||
|
|
||||||
// After dispatching the oninput event our `text` should have a value of the input elements value.
|
|
||||||
web_sys::EventTarget::from(input)
|
|
||||||
.dispatch_event(&input_event)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
assert_eq!(&*text.borrow(), "End Text");
|
|
||||||
}
|
|
||||||
File renamed without changes.
File renamed without changes.
@@ -11,9 +11,10 @@ impl Display for SsrRenderer {
|
|||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
let node = self
|
let node = self
|
||||||
.dom
|
.dom
|
||||||
.components
|
.base_scope()
|
||||||
.get(self.dom.base_scope)
|
// .components
|
||||||
.unwrap()
|
// .get(self.dom.base_scope)
|
||||||
|
// .unwrap()
|
||||||
.frames
|
.frames
|
||||||
.current_head_node();
|
.current_head_node();
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +0,0 @@
|
|||||||
out
|
|
||||||
node_modules
|
|
||||||
client/server
|
|
||||||
.vscode-test
|
|
||||||
-44
@@ -1,44 +0,0 @@
|
|||||||
// 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"]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
-33
@@ -1,33 +0,0 @@
|
|||||||
{
|
|
||||||
"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"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
.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/**
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
[package]
|
|
||||||
name = "syntax-gen"
|
|
||||||
version = "0.1.0"
|
|
||||||
authors = ["Jonathan Kelley <jkelleyrtp@gmail.com>"]
|
|
||||||
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"
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
# 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
|
|
||||||
|
|
||||||
-81
@@ -1,81 +0,0 @@
|
|||||||
{
|
|
||||||
"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-textdocument": {
|
|
||||||
"version": "1.0.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/vscode-languageserver-textdocument/-/vscode-languageserver-textdocument-1.0.1.tgz",
|
|
||||||
"integrity": "sha512-UIcJDjX7IFkck7cSkNNyzIz5FyvpQfY7sdzVy+wkKN/BLaD4DQ0ppXQrKePomCxTS7RrolK1I0pey0bG9eh8dA=="
|
|
||||||
},
|
|
||||||
"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=="
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
{
|
|
||||||
"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",
|
|
||||||
"vscode-languageserver-textdocument": "^1.0.1"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
|
||||||
"@types/vscode": "^1.43.0"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,449 +0,0 @@
|
|||||||
/*---------------------------------------------------------------------------------------------
|
|
||||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
|
||||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
|
||||||
*--------------------------------------------------------------------------------------------*/
|
|
||||||
|
|
||||||
import { TextDocument } from "vscode-languageserver-textdocument";
|
|
||||||
import { 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if the request is coming from inside a special region
|
|
||||||
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";
|
|
||||||
}
|
|
||||||
@@ -1,147 +0,0 @@
|
|||||||
/* --------------------------------------------------------------------------------------------
|
|
||||||
* 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 { isInsideHtmlMacro } from "./rustSupport";
|
|
||||||
// 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<string, string>();
|
|
||||||
|
|
||||||
workspace.registerTextDocumentContentProvider("embedded-content", {
|
|
||||||
provideTextDocumentContent: (uri) => {
|
|
||||||
const originalUri = uri.path.slice(1).slice(0, -4);
|
|
||||||
console.error(originalUri);
|
|
||||||
const decodedUri = decodeURIComponent(originalUri);
|
|
||||||
return virtualDocumentContents.get(decodedUri);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
let clientOptions: LanguageClientOptions = {
|
|
||||||
documentSelector: [{ scheme: "file", language: "rust" }],
|
|
||||||
middleware: {
|
|
||||||
provideCompletionItem: async (
|
|
||||||
document,
|
|
||||||
position,
|
|
||||||
context,
|
|
||||||
token,
|
|
||||||
next
|
|
||||||
) => {
|
|
||||||
/*
|
|
||||||
1: Find the occurences of the html! macro using regex
|
|
||||||
2: Check if any of the occurences match the cursor offset
|
|
||||||
3: If so, direct the captured block to the html to the rsx language service
|
|
||||||
*/
|
|
||||||
const docSrc = document.getText();
|
|
||||||
const offset = document.offsetAt(position);
|
|
||||||
const matches = docSrc.matchAll(macroRegex);
|
|
||||||
|
|
||||||
// Lazily loop through matches, abort early if the cursor is after the match
|
|
||||||
// let start = 0;
|
|
||||||
// let end = 0;
|
|
||||||
let matchBody: string | undefined = undefined;
|
|
||||||
|
|
||||||
for (const match of matches) {
|
|
||||||
// // Check if the cursor is inbetween the previous end and the new start
|
|
||||||
// // This means the cursor is between html! invocations and we should bail early
|
|
||||||
// if (offset > end && offset < match.index) {
|
|
||||||
// // Ensure the match
|
|
||||||
// // defer to the "next?" symbol
|
|
||||||
// return await next(document, position, context, token);
|
|
||||||
// }
|
|
||||||
|
|
||||||
// Otherwise, move the counters forward
|
|
||||||
const start = match.index;
|
|
||||||
const end = start + match.length;
|
|
||||||
|
|
||||||
// Ensure the cursor is within the match
|
|
||||||
// Break if so
|
|
||||||
if (offset >= start && offset <= end) {
|
|
||||||
matchBody = match[1];
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If we looped through all the matches and the match wasn't defined, then bail
|
|
||||||
if (matchBody === undefined) {
|
|
||||||
return await next(document, position, context, token);
|
|
||||||
}
|
|
||||||
|
|
||||||
// If we're inside the style region, then provide CSS completions with the CSS provider
|
|
||||||
const originalUri = document.uri.toString();
|
|
||||||
virtualDocumentContents.set(originalUri, matchBody);
|
|
||||||
// getCSSVirtualContent(htmlLanguageService, document.getText())
|
|
||||||
|
|
||||||
const vdocUriString = `embedded-content://html/${encodeURIComponent(
|
|
||||||
originalUri
|
|
||||||
)}.html`;
|
|
||||||
|
|
||||||
const vdocUri = Uri.parse(vdocUriString);
|
|
||||||
return await commands.executeCommand<CompletionList>(
|
|
||||||
"vscode.executeCompletionItemProvider",
|
|
||||||
vdocUri,
|
|
||||||
position,
|
|
||||||
context.triggerCharacter
|
|
||||||
);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
// Create the language client and start the client.
|
|
||||||
client = new LanguageClient(
|
|
||||||
"languageServerExample",
|
|
||||||
"Language Server Example",
|
|
||||||
serverOptions,
|
|
||||||
clientOptions
|
|
||||||
);
|
|
||||||
// Start the client. This will also launch the server
|
|
||||||
client.start();
|
|
||||||
}
|
|
||||||
|
|
||||||
export function deactivate(): Thenable<void> | undefined {
|
|
||||||
if (!client) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
return client.stop();
|
|
||||||
}
|
|
||||||
|
|
||||||
const macroRegex = /html! {([\s\S]*?)}/g;
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
const macroRegex = /html! {([\s\S]*?)}/g;
|
|
||||||
|
|
||||||
export function isInsideHtmlMacro(
|
|
||||||
match: RegExpMatchArray,
|
|
||||||
cursor: number
|
|
||||||
): boolean {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
{
|
|
||||||
"compilerOptions": {
|
|
||||||
"module": "commonjs",
|
|
||||||
"target": "ESNext",
|
|
||||||
"lib": ["ESNext"],
|
|
||||||
"outDir": "out",
|
|
||||||
"rootDir": "src",
|
|
||||||
"sourceMap": true
|
|
||||||
},
|
|
||||||
"include": ["src"],
|
|
||||||
"exclude": ["node_modules", ".vscode-test"]
|
|
||||||
}
|
|
||||||
Generated
-1050
File diff suppressed because it is too large.
Load diff
@@ -1,37 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "dioxus-rsx-plugin",
|
|
||||||
"description": "Get HTML completion inside the html! macro for dioxus projects",
|
|
||||||
"author": "Jonathan Kelley",
|
|
||||||
"license": "MIT",
|
|
||||||
"version": "0.1.0",
|
|
||||||
"repository": {
|
|
||||||
"type": "git",
|
|
||||||
"url": "https://github.com/jkelleyrtp/dioxus"
|
|
||||||
},
|
|
||||||
"publisher": "jkelleyrtp",
|
|
||||||
"categories": [],
|
|
||||||
"keywords": [],
|
|
||||||
"engines": {
|
|
||||||
"vscode": "^1.43.0",
|
|
||||||
"node": "*"
|
|
||||||
},
|
|
||||||
"activationEvents": [
|
|
||||||
"onLanguage:rust"
|
|
||||||
],
|
|
||||||
"main": "./client/out/extension",
|
|
||||||
"scripts": {
|
|
||||||
"vscode:prepublish": "cd client && npm install && cd .. && npm run compile",
|
|
||||||
"compile": "tsc -b",
|
|
||||||
"watch": "tsc -b -w",
|
|
||||||
"// postinstall": "cd client && npm install && cd ../server && npm install && cd ..",
|
|
||||||
"test": "sh ./scripts/e2e.sh"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
|
||||||
"@types/mocha": "^5.2.7",
|
|
||||||
"@types/node": "^12.12.0",
|
|
||||||
"@typescript-eslint/eslint-plugin": "^3.0.2",
|
|
||||||
"@typescript-eslint/parser": "^3.0.2",
|
|
||||||
"eslint": "^7.1.0",
|
|
||||||
"typescript": "^4.0.2"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
//! Generate the syntax for the RSX language
|
|
||||||
|
|
||||||
fn main() {}
|
|
||||||
-67
@@ -1,67 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "lsp-sample-server",
|
|
||||||
"version": "1.0.0",
|
|
||||||
"lockfileVersion": 1,
|
|
||||||
"requires": true,
|
|
||||||
"dependencies": {
|
|
||||||
"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-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=="
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"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-languageserver": {
|
|
||||||
"version": "6.1.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/vscode-languageserver/-/vscode-languageserver-6.1.1.tgz",
|
|
||||||
"integrity": "sha512-DueEpkUAkD5XTR4MLYNr6bQIp/UFR0/IPApgXU3YfCBCB08u2sm9hRCs6DxYZELkk++STPjpcjksR2H8qI3cDQ==",
|
|
||||||
"requires": {
|
|
||||||
"vscode-languageserver-protocol": "^3.15.3"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"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-textdocument": {
|
|
||||||
"version": "1.0.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/vscode-languageserver-textdocument/-/vscode-languageserver-textdocument-1.0.1.tgz",
|
|
||||||
"integrity": "sha512-UIcJDjX7IFkck7cSkNNyzIz5FyvpQfY7sdzVy+wkKN/BLaD4DQ0ppXQrKePomCxTS7RrolK1I0pey0bG9eh8dA=="
|
|
||||||
},
|
|
||||||
"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=="
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "lsp-sample-server",
|
|
||||||
"description": "Example implementation of a language server in node.",
|
|
||||||
"version": "1.0.0",
|
|
||||||
"author": "Microsoft Corporation",
|
|
||||||
"license": "MIT",
|
|
||||||
"engines": {
|
|
||||||
"node": "*"
|
|
||||||
},
|
|
||||||
"repository": {
|
|
||||||
"type": "git",
|
|
||||||
"url": "https://github.com/Microsoft/vscode-extension-samples"
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"vscode-html-languageservice": "^3.0.3",
|
|
||||||
"vscode-languageserver": "^6.1.1",
|
|
||||||
"vscode-languageserver-textdocument": "^1.0.1"
|
|
||||||
},
|
|
||||||
"scripts": {}
|
|
||||||
}
|
|
||||||
@@ -1,54 +0,0 @@
|
|||||||
/* --------------------------------------------------------------------------------------------
|
|
||||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
|
||||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
|
||||||
* ------------------------------------------------------------------------------------------ */
|
|
||||||
|
|
||||||
import { getLanguageService } from "vscode-html-languageservice";
|
|
||||||
import {
|
|
||||||
createConnection,
|
|
||||||
InitializeParams,
|
|
||||||
ProposedFeatures,
|
|
||||||
TextDocuments,
|
|
||||||
TextDocumentSyncKind,
|
|
||||||
} from "vscode-languageserver";
|
|
||||||
import { TextDocument } from "vscode-languageserver-textdocument";
|
|
||||||
|
|
||||||
// Create a connection for the server. The connection uses Node's IPC as a transport.
|
|
||||||
// Also include all preview / proposed LSP features.
|
|
||||||
let connection = createConnection(ProposedFeatures.all);
|
|
||||||
|
|
||||||
// Create a simple text document manager. The text document manager
|
|
||||||
// supports full document sync only
|
|
||||||
let documents: TextDocuments<TextDocument> = new TextDocuments(TextDocument);
|
|
||||||
|
|
||||||
const htmlLanguageService = getLanguageService();
|
|
||||||
|
|
||||||
connection.onInitialize((_params: InitializeParams) => {
|
|
||||||
return {
|
|
||||||
capabilities: {
|
|
||||||
textDocumentSync: TextDocumentSyncKind.Full,
|
|
||||||
// Tell the client that the server supports code completion
|
|
||||||
completionProvider: {
|
|
||||||
resolveProvider: false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
connection.onInitialized(() => {});
|
|
||||||
|
|
||||||
connection.onCompletion(async (textDocumentPosition, token) => {
|
|
||||||
const document = documents.get(textDocumentPosition.textDocument.uri);
|
|
||||||
if (!document) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return htmlLanguageService.doComplete(
|
|
||||||
document,
|
|
||||||
textDocumentPosition.position,
|
|
||||||
htmlLanguageService.parseHTMLDocument(document)
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
documents.listen(connection);
|
|
||||||
connection.listen();
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
{
|
|
||||||
"compilerOptions": {
|
|
||||||
"target": "ESNext",
|
|
||||||
"lib": ["ESNext"],
|
|
||||||
"module": "commonjs",
|
|
||||||
"moduleResolution": "node",
|
|
||||||
"sourceMap": true,
|
|
||||||
"strict": true,
|
|
||||||
"outDir": "out",
|
|
||||||
"rootDir": "src"
|
|
||||||
},
|
|
||||||
"include": ["src"],
|
|
||||||
"exclude": ["node_modules", ".vscode-test"]
|
|
||||||
}
|
|
||||||
File diff suppressed because it is too large.
Load diff
@@ -1,27 +0,0 @@
|
|||||||
{
|
|
||||||
"compilerOptions": {
|
|
||||||
"module": "commonjs",
|
|
||||||
"target": "ESNext",
|
|
||||||
"lib": [
|
|
||||||
"ESNext"
|
|
||||||
],
|
|
||||||
"outDir": "out",
|
|
||||||
"rootDir": "src",
|
|
||||||
"sourceMap": true
|
|
||||||
},
|
|
||||||
"include": [
|
|
||||||
"src"
|
|
||||||
],
|
|
||||||
"exclude": [
|
|
||||||
"node_modules",
|
|
||||||
".vscode-test"
|
|
||||||
],
|
|
||||||
"references": [
|
|
||||||
{
|
|
||||||
"path": "./client"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"path": "./server"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@@ -11,10 +11,8 @@ license = "MIT/Apache-2.0"
|
|||||||
dioxus-core = { path = "../core", version = "0.1.2" }
|
dioxus-core = { path = "../core", version = "0.1.2" }
|
||||||
js-sys = "0.3"
|
js-sys = "0.3"
|
||||||
wasm-bindgen = "0.2.71"
|
wasm-bindgen = "0.2.71"
|
||||||
# wasm-bindgen = "0.2.70"
|
|
||||||
lazy_static = "1.4.0"
|
lazy_static = "1.4.0"
|
||||||
wasm-bindgen-futures = "0.4.20"
|
wasm-bindgen-futures = "0.4.20"
|
||||||
# futures = "0.3.12"
|
|
||||||
wasm-logger = "0.2.0"
|
wasm-logger = "0.2.0"
|
||||||
log = "0.4.14"
|
log = "0.4.14"
|
||||||
fxhash = "0.2.1"
|
fxhash = "0.2.1"
|
||||||
@@ -23,6 +21,9 @@ console_error_panic_hook = "0.1.6"
|
|||||||
generational-arena = "0.2.8"
|
generational-arena = "0.2.8"
|
||||||
wasm-bindgen-test = "0.3.21"
|
wasm-bindgen-test = "0.3.21"
|
||||||
once_cell = "1.7.2"
|
once_cell = "1.7.2"
|
||||||
|
|
||||||
|
# wasm-bindgen = "0.2.70"
|
||||||
|
# futures = "0.3.12"
|
||||||
# html-validation = { path = "../html-validation", version = "0.1.1" }
|
# html-validation = { path = "../html-validation", version = "0.1.1" }
|
||||||
|
|
||||||
async-channel = "1.6.1"
|
async-channel = "1.6.1"
|
||||||
@@ -57,11 +58,7 @@ features = [
|
|||||||
"DocumentType",
|
"DocumentType",
|
||||||
"CharacterData",
|
"CharacterData",
|
||||||
"HtmlOptionElement",
|
"HtmlOptionElement",
|
||||||
] # "FormEvent",
|
]
|
||||||
# "UIEvent",
|
|
||||||
# "ToggleEvent",
|
|
||||||
# "MediaEvent",
|
|
||||||
# "SelectionEvent",
|
|
||||||
|
|
||||||
[profile.release]
|
[profile.release]
|
||||||
debug = true
|
debug = true
|
||||||
@@ -69,7 +66,6 @@ debug = true
|
|||||||
[lib]
|
[lib]
|
||||||
crate-type = ["cdylib", "rlib"]
|
crate-type = ["cdylib", "rlib"]
|
||||||
|
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
uuid = { version = "0.8.2", features = ["v4", "wasm-bindgen"] }
|
uuid = { version = "0.8.2", features = ["v4", "wasm-bindgen"] }
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user