mirror of
https://github.com/DioxusLabs/dioxus
synced 2024-11-22 12:13:04 +00:00
Feat: Clean up repo a bit
This commit is contained in:
parent
c28697e1fe
commit
a99147c85b
55 changed files with 76 additions and 6609 deletions
|
@ -5,10 +5,10 @@ members = [
|
|||
"packages/core",
|
||||
"packages/web",
|
||||
"packages/dioxus",
|
||||
|
||||
"packages/recoil",
|
||||
"packages/docsite",
|
||||
] # "packages/webview",
|
||||
|
||||
# "packages/docsite",
|
||||
# "packages/ssr",
|
||||
# "packages/cli",
|
||||
# "packages/webview",
|
||||
|
|
|
@ -77,17 +77,16 @@ Welcome to the first iteration of the Dioxus Virtual DOM! This release brings su
|
|||
- [x] keys on components
|
||||
- [x] Allow paths for components
|
||||
- [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] 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 BumpFrame logic out for better safety guarantees
|
||||
- [ ] make SSR follow HTML spec
|
||||
- [x] dirty tagging, compression
|
||||
- [ ] fix keys on elements
|
||||
- [ ] MIRI tests
|
||||
- [ ] code health
|
||||
- [ ] all synthetic events filled out
|
||||
- [ ] double check event targets and stuff
|
||||
- [ ] Documentation overhaul
|
||||
|
@ -95,6 +94,7 @@ Welcome to the first iteration of the Dioxus Virtual DOM! This release brings su
|
|||
- [ ] controlled components
|
||||
|
||||
lower priority features
|
||||
- [ ] Attributes on elements should implement format_args instead of string fmt
|
||||
- [ ] fragments
|
||||
- [ ] node refs (postpone for 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" }
|
||||
|
||||
# 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 = { version = "3.6.0", features = ["collections", "boxed"] }
|
||||
|
@ -36,3 +36,4 @@ serde = { version = "1", features = ["derive"], optional = true }
|
|||
|
||||
[features]
|
||||
default = []
|
||||
serialize = ["serde", "generational-arena/serde"]
|
||||
|
|
48
packages/core/src/arena.rs
Normal file
48
packages/core/src/arena.rs
Normal file
|
@ -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 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;
|
||||
pub type Result<T, E = Error> = std::result::Result<T, E>;
|
||||
|
||||
|
|
|
@ -65,6 +65,7 @@
|
|||
//! - dioxus-liveview (SSR + StringRenderer)
|
||||
//!
|
||||
|
||||
pub mod arena;
|
||||
pub mod component; // Logic for extending FC
|
||||
pub mod context; // Logic for providing hook + context functionality to user components
|
||||
pub mod debug_renderer;
|
||||
|
|
|
@ -19,16 +19,11 @@
|
|||
//!
|
||||
//! # Known Issues
|
||||
//! ----
|
||||
//! - stack machine approach does not work when 3rd party extensions inject elements (breaking our view of the dom)
|
||||
|
||||
use std::cell::Ref;
|
||||
//! - stack machine approach does not work when 3rd party extensions inject elements (breaking our view of the dom) - solvable by the renderer
|
||||
|
||||
use crate::innerlude::ScopeIdx;
|
||||
|
||||
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.
|
||||
/// 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.
|
||||
|
||||
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::{
|
||||
prelude::{builder::IntoDomTree, dioxus::events::on::MouseEvent, *},
|
||||
prelude::*,
|
||||
prelude::{builder::IntoDomTree, dioxus::events::on::MouseEvent},
|
||||
TextRenderer,
|
||||
};
|
||||
mod components {
|
||||
mod app;
|
||||
}
|
||||
|
||||
fn main() {
|
||||
TextRenderer::new(App);
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
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" }
|
||||
}}
|
||||
}
|
||||
))
|
||||
};
|
||||
}
|
||||
fn App(ctx: Context, props: &()) -> DomTree {}
|
||||
|
|
|
@ -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");
|
||||
}
|
|
@ -11,9 +11,10 @@ impl Display for SsrRenderer {
|
|||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
let node = self
|
||||
.dom
|
||||
.components
|
||||
.get(self.dom.base_scope)
|
||||
.unwrap()
|
||||
.base_scope()
|
||||
// .components
|
||||
// .get(self.dom.base_scope)
|
||||
// .unwrap()
|
||||
.frames
|
||||
.current_head_node();
|
||||
|
||||
|
|
4
packages/vscode-ext/.gitignore
vendored
4
packages/vscode-ext/.gitignore
vendored
|
@ -1,4 +0,0 @@
|
|||
out
|
||||
node_modules
|
||||
client/server
|
||||
.vscode-test
|
44
packages/vscode-ext/.vscode/launch.json
vendored
44
packages/vscode-ext/.vscode/launch.json
vendored
|
@ -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
packages/vscode-ext/.vscode/tasks.json
vendored
33
packages/vscode-ext/.vscode/tasks.json
vendored
|
@ -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
packages/vscode-ext/client/package-lock.json
generated
81
packages/vscode-ext/client/package-lock.json
generated
|
@ -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"]
|
||||
}
|
1050
packages/vscode-ext/package-lock.json
generated
1050
packages/vscode-ext/package-lock.json
generated
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
packages/vscode-ext/server/package-lock.json
generated
67
packages/vscode-ext/server/package-lock.json
generated
|
@ -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 one or more lines are too long
|
@ -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" }
|
||||
js-sys = "0.3"
|
||||
wasm-bindgen = "0.2.71"
|
||||
# wasm-bindgen = "0.2.70"
|
||||
lazy_static = "1.4.0"
|
||||
wasm-bindgen-futures = "0.4.20"
|
||||
# futures = "0.3.12"
|
||||
wasm-logger = "0.2.0"
|
||||
log = "0.4.14"
|
||||
fxhash = "0.2.1"
|
||||
|
@ -23,6 +21,9 @@ console_error_panic_hook = "0.1.6"
|
|||
generational-arena = "0.2.8"
|
||||
wasm-bindgen-test = "0.3.21"
|
||||
once_cell = "1.7.2"
|
||||
|
||||
# wasm-bindgen = "0.2.70"
|
||||
# futures = "0.3.12"
|
||||
# html-validation = { path = "../html-validation", version = "0.1.1" }
|
||||
|
||||
async-channel = "1.6.1"
|
||||
|
@ -57,11 +58,7 @@ features = [
|
|||
"DocumentType",
|
||||
"CharacterData",
|
||||
"HtmlOptionElement",
|
||||
] # "FormEvent",
|
||||
# "UIEvent",
|
||||
# "ToggleEvent",
|
||||
# "MediaEvent",
|
||||
# "SelectionEvent",
|
||||
]
|
||||
|
||||
[profile.release]
|
||||
debug = true
|
||||
|
@ -69,7 +66,6 @@ debug = true
|
|||
[lib]
|
||||
crate-type = ["cdylib", "rlib"]
|
||||
|
||||
|
||||
[dev-dependencies]
|
||||
uuid = { version = "0.8.2", features = ["v4", "wasm-bindgen"] }
|
||||
|
||||
|
|
Loading…
Reference in a new issue