Compare commits
No commits in common. "a32f024f1762d064108c131494bfb90ac0303428" and "4585db043321c141cdcd14eaf04f874b6111420b" have entirely different histories.
a32f024f17
...
4585db0433
@ -4,6 +4,7 @@
|
|||||||
<script type="importmap">
|
<script type="importmap">
|
||||||
{
|
{
|
||||||
"imports": {
|
"imports": {
|
||||||
|
"codemirror": "./codemirror/index.js",
|
||||||
"@codemirror/view": "./@codemirror/view/index.js",
|
"@codemirror/view": "./@codemirror/view/index.js",
|
||||||
"@codemirror/state": "./@codemirror/state/index.js",
|
"@codemirror/state": "./@codemirror/state/index.js",
|
||||||
"@codemirror/language": "./@codemirror/language/index.js",
|
"@codemirror/language": "./@codemirror/language/index.js",
|
||||||
@ -29,6 +30,7 @@
|
|||||||
<link data-trunk rel="css" href="styles.css"/>
|
<link data-trunk rel="css" href="styles.css"/>
|
||||||
<link data-trunk rel="copy-file" href="assets/fonts/JetBrainsMono-Regular.woff2"/>
|
<link data-trunk rel="copy-file" href="assets/fonts/JetBrainsMono-Regular.woff2"/>
|
||||||
<link data-trunk rel="copy-file" href="node_modules/bootstrap-icons/font/fonts/bootstrap-icons.woff" data-target-path="fonts"/>
|
<link data-trunk rel="copy-file" href="node_modules/bootstrap-icons/font/fonts/bootstrap-icons.woff" data-target-path="fonts"/>
|
||||||
|
<link data-trunk rel="copy-file" href="node_modules/codemirror/dist/index.js" data-target-path="codemirror"></link>
|
||||||
<link data-trunk rel="copy-file" href="node_modules/@codemirror/view/dist/index.js" data-target-path="@codemirror/view"></link>
|
<link data-trunk rel="copy-file" href="node_modules/@codemirror/view/dist/index.js" data-target-path="@codemirror/view"></link>
|
||||||
<link data-trunk rel="copy-file" href="node_modules/@codemirror/state/dist/index.js" data-target-path="@codemirror/state"></link>
|
<link data-trunk rel="copy-file" href="node_modules/@codemirror/state/dist/index.js" data-target-path="@codemirror/state"></link>
|
||||||
<link data-trunk rel="copy-file" href="node_modules/@codemirror/language/dist/index.js" data-target-path="@codemirror/language"></link>
|
<link data-trunk rel="copy-file" href="node_modules/@codemirror/language/dist/index.js" data-target-path="@codemirror/language"></link>
|
||||||
|
23
package-lock.json
generated
23
package-lock.json
generated
@ -9,18 +9,13 @@
|
|||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@codemirror/autocomplete": "^6.18.1",
|
|
||||||
"@codemirror/commands": "^6.6.2",
|
|
||||||
"@codemirror/language": "^6.10.3",
|
|
||||||
"@codemirror/legacy-modes": "^6.4.1",
|
"@codemirror/legacy-modes": "^6.4.1",
|
||||||
"@codemirror/lint": "^6.8.2",
|
|
||||||
"@codemirror/search": "^6.5.6",
|
|
||||||
"@codemirror/state": "^6.4.1",
|
"@codemirror/state": "^6.4.1",
|
||||||
"@codemirror/view": "^6.34.1",
|
|
||||||
"@xterm/addon-fit": "^0.10.0",
|
"@xterm/addon-fit": "^0.10.0",
|
||||||
"@xterm/xterm": "^5.5.0",
|
"@xterm/xterm": "^5.5.0",
|
||||||
"bootstrap": "^5.3.3",
|
"bootstrap": "^5.3.3",
|
||||||
"bootstrap-icons": "^1.11.3"
|
"bootstrap-icons": "^1.11.3",
|
||||||
|
"codemirror": "^6.0.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@codemirror/autocomplete": {
|
"node_modules/@codemirror/autocomplete": {
|
||||||
@ -184,6 +179,20 @@
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"node_modules/codemirror": {
|
||||||
|
"version": "6.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/codemirror/-/codemirror-6.0.1.tgz",
|
||||||
|
"integrity": "sha512-J8j+nZ+CdWmIeFIGXEFbFPtpiYacFMDR8GlHK3IyHQJMCaVRfGx9NT+Hxivv1ckLWPvNdZqndbr/7lVhrf/Svg==",
|
||||||
|
"dependencies": {
|
||||||
|
"@codemirror/autocomplete": "^6.0.0",
|
||||||
|
"@codemirror/commands": "^6.0.0",
|
||||||
|
"@codemirror/language": "^6.0.0",
|
||||||
|
"@codemirror/lint": "^6.0.0",
|
||||||
|
"@codemirror/search": "^6.0.0",
|
||||||
|
"@codemirror/state": "^6.0.0",
|
||||||
|
"@codemirror/view": "^6.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/crelt": {
|
"node_modules/crelt": {
|
||||||
"version": "1.0.6",
|
"version": "1.0.6",
|
||||||
"resolved": "https://registry.npmjs.org/crelt/-/crelt-1.0.6.tgz",
|
"resolved": "https://registry.npmjs.org/crelt/-/crelt-1.0.6.tgz",
|
||||||
|
@ -9,17 +9,12 @@
|
|||||||
"author": "",
|
"author": "",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@codemirror/autocomplete": "^6.18.1",
|
|
||||||
"@codemirror/commands": "^6.6.2",
|
|
||||||
"@codemirror/language": "^6.10.3",
|
|
||||||
"@codemirror/legacy-modes": "^6.4.1",
|
"@codemirror/legacy-modes": "^6.4.1",
|
||||||
"@codemirror/lint": "^6.8.2",
|
|
||||||
"@codemirror/search": "^6.5.6",
|
|
||||||
"@codemirror/state": "^6.4.1",
|
"@codemirror/state": "^6.4.1",
|
||||||
"@codemirror/view": "^6.34.1",
|
|
||||||
"@xterm/addon-fit": "^0.10.0",
|
"@xterm/addon-fit": "^0.10.0",
|
||||||
"@xterm/xterm": "^5.5.0",
|
"@xterm/xterm": "^5.5.0",
|
||||||
"bootstrap": "^5.3.3",
|
"bootstrap": "^5.3.3",
|
||||||
"bootstrap-icons": "^1.11.3"
|
"bootstrap-icons": "^1.11.3",
|
||||||
|
"codemirror": "^6.0.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,13 +1,11 @@
|
|||||||
use std::rc::Rc;
|
use std::{ops::Deref, rc::Rc};
|
||||||
|
|
||||||
use anyhow::Error;
|
use anyhow::Error;
|
||||||
use itertools::Itertools;
|
use wasm_bindgen::{prelude::wasm_bindgen, JsValue};
|
||||||
use wasm_bindgen::{closure::Closure, prelude::wasm_bindgen};
|
use web_sys::{window, Node};
|
||||||
use wasm_bindgen_futures::js_sys::Object;
|
|
||||||
use web_sys::{window, HtmlElement};
|
|
||||||
use yew::{
|
use yew::{
|
||||||
function_component, html, use_effect_with, use_node_ref, use_state, use_state_eq, AttrValue,
|
function_component, html, use_effect_with, use_node_ref, use_state_eq, AttrValue, Callback,
|
||||||
Callback, Html, Properties, UseStateHandle,
|
Html, Properties, UseStateHandle,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{FrameId, GlobalLayoutState, GlobalMemoCell, PanelDirection, SplitPanel};
|
use crate::{FrameId, GlobalLayoutState, GlobalMemoCell, PanelDirection, SplitPanel};
|
||||||
@ -19,150 +17,50 @@ pub struct EditorViewProps {
|
|||||||
pub global_layout: UseStateHandle<Rc<GlobalLayoutState>>,
|
pub global_layout: UseStateHandle<Rc<GlobalLayoutState>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Properties, PartialEq, Clone)]
|
|
||||||
pub struct EditorViewDetailProps {
|
|
||||||
pub frame: FrameId,
|
|
||||||
pub global_memo: GlobalMemoCell,
|
|
||||||
pub global_layout: UseStateHandle<Rc<GlobalLayoutState>>,
|
|
||||||
pub editor_state: UseStateHandle<Rc<EditorViewState>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
fn close_editor(frame: &FrameId, global_layout: &UseStateHandle<Rc<GlobalLayoutState>>) {
|
fn close_editor(frame: &FrameId, global_layout: &UseStateHandle<Rc<GlobalLayoutState>>) {
|
||||||
let mut gl_new: GlobalLayoutState = global_layout.as_ref().clone();
|
let mut gl_new: GlobalLayoutState = global_layout.as_ref().clone();
|
||||||
gl_new.frame_views.remove(frame);
|
gl_new.frame_views.remove(frame);
|
||||||
global_layout.set(gl_new.into());
|
global_layout.set(gl_new.into());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, PartialEq)]
|
#[derive(PartialEq, Clone)]
|
||||||
pub struct EditorViewState {
|
pub struct EditorState {
|
||||||
error_msg: Option<AttrValue>,
|
error_msg: Option<AttrValue>,
|
||||||
available_files: Vec<AttrValue>,
|
available_files: Vec<AttrValue>,
|
||||||
open_file: AttrValue,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct EditorKeepClosures {
|
fn fetch_initial_editor_state_or_fail() -> anyhow::Result<EditorState> {
|
||||||
change_closure: Rc<Closure<dyn FnMut(ViewUpdate)>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
fn fetch_initial_editor_state_or_fail() -> anyhow::Result<EditorViewState> {
|
|
||||||
let win = window().ok_or_else(|| Error::msg("Can't get window"))?;
|
let win = window().ok_or_else(|| Error::msg("Can't get window"))?;
|
||||||
let local = win
|
win.local_storage()
|
||||||
.local_storage()
|
|
||||||
.map_err(|_| Error::msg("Error retrieving localStorage"))?
|
.map_err(|_| Error::msg("Error retrieving localStorage"))?
|
||||||
.ok_or_else(|| Error::msg("Local storage not available"))?;
|
.ok_or_else(|| Error::msg("Local storage not available"))?;
|
||||||
|
Ok(EditorState {
|
||||||
let n_keys = local
|
|
||||||
.length()
|
|
||||||
.map_err(|_| Error::msg("localStorage broken"))?;
|
|
||||||
let mut available_files: Vec<AttrValue> = vec![];
|
|
||||||
for i in 0..n_keys {
|
|
||||||
if let Some(key) = local
|
|
||||||
.key(i)
|
|
||||||
.map_err(|_| Error::msg("localStorage broken"))?
|
|
||||||
{
|
|
||||||
match key.strip_prefix("scriptfile_") {
|
|
||||||
None => {}
|
|
||||||
Some(filename) => {
|
|
||||||
available_files.push(filename.to_owned().into());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let init_str: AttrValue = "init.lua".into();
|
|
||||||
if !available_files.contains(&init_str) {
|
|
||||||
available_files.push(init_str);
|
|
||||||
local
|
|
||||||
.set(
|
|
||||||
"scriptfile_init.lua",
|
|
||||||
"-- Put any code to run on every client load here.\n",
|
|
||||||
)
|
|
||||||
.map_err(|_| Error::msg("localStorage broken"))?;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(EditorViewState {
|
|
||||||
error_msg: None,
|
error_msg: None,
|
||||||
available_files,
|
available_files: vec![],
|
||||||
open_file: "init.lua".into(),
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn fetch_initial_editor_state() -> Rc<EditorViewState> {
|
fn fetch_initial_editor_state() -> EditorState {
|
||||||
match fetch_initial_editor_state_or_fail() {
|
match fetch_initial_editor_state_or_fail() {
|
||||||
Ok(s) => s.into(),
|
Ok(s) => s,
|
||||||
Err(e) => EditorViewState {
|
Err(e) => EditorState {
|
||||||
error_msg: Some(format!("Can't load editor data: {}", e).into()),
|
error_msg: Some(format!("Can't load editor data: {}", e.to_string()).into()),
|
||||||
available_files: vec![],
|
available_files: vec![],
|
||||||
open_file: "init.lua".into(),
|
},
|
||||||
}
|
|
||||||
.into(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn try_run_script(script: &str, global_memo: &GlobalMemoCell) -> anyhow::Result<()> {
|
|
||||||
let script = load_file_contents(script)?;
|
|
||||||
global_memo
|
|
||||||
.lua_engine
|
|
||||||
.borrow_mut()
|
|
||||||
.execute(&script)
|
|
||||||
.map_err(Error::msg)?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn run_script<F>(script: &str, global_memo: &GlobalMemoCell, set_err: Rc<F>)
|
|
||||||
where
|
|
||||||
F: Fn(Option<&str>),
|
|
||||||
{
|
|
||||||
match try_run_script(script, global_memo) {
|
|
||||||
Ok(()) => {
|
|
||||||
set_err(None);
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
set_err(Some(&format!("Script error: {}", e)));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[function_component(EditorNav)]
|
#[function_component(EditorNav)]
|
||||||
fn editor_nav(props: &EditorViewDetailProps) -> Html {
|
fn editor_nav(props: &EditorViewProps) -> Html {
|
||||||
let global_memo = props.global_memo.clone();
|
|
||||||
let global_layout = props.global_layout.clone();
|
let global_layout = props.global_layout.clone();
|
||||||
let frame = props.frame.clone();
|
let frame = props.frame.clone();
|
||||||
let current_script = props.editor_state.open_file.clone();
|
|
||||||
|
|
||||||
let set_err_state = props.editor_state.clone();
|
|
||||||
let set_err = Rc::new(move |msg: Option<&str>| {
|
|
||||||
let mut new_state = (*set_err_state.as_ref()).clone();
|
|
||||||
new_state.error_msg = msg.map(|v| AttrValue::from(String::from(v)));
|
|
||||||
set_err_state.set(new_state.into());
|
|
||||||
});
|
|
||||||
html! {
|
html! {
|
||||||
<div class="editornav">
|
<div class="editornav">
|
||||||
<nav class="navbar navbar-expand-lg bg-body-tertiary">
|
<nav class="navbar navbar-expand-lg bg-body-tertiary">
|
||||||
<button class="btn" aria-label="Run" title="Run"
|
|
||||||
onclick={move |_ev| run_script(¤t_script, &global_memo, set_err.clone())}>
|
|
||||||
<i class="bi bi-file-earmark-play"></i></button>
|
|
||||||
<button class="btn" aria-label="New file" title="New file"><i class="bi bi-file-earmark-plus"></i></button>
|
<button class="btn" aria-label="New file" title="New file"><i class="bi bi-file-earmark-plus"></i></button>
|
||||||
<div class="flex-fill"/>
|
<div class="flex-fill"/>
|
||||||
<button class="btn" onclick={move |_ev| close_editor(&frame, &global_layout)} aria-label="Close Editor" title="Close editor"><i class="bi bi-arrow-return-left"></i></button>
|
<button class="btn" onclick={move |_ev| close_editor(&frame, &global_layout)} aria-label="Close Editor" title="Close editor"><i class="bi bi-arrow-return-left"></i></button>
|
||||||
</nav>
|
</nav>
|
||||||
<ul class="p-2 list-group">
|
|
||||||
{props
|
|
||||||
.editor_state
|
|
||||||
.available_files
|
|
||||||
.iter()
|
|
||||||
.map(|f| {
|
|
||||||
let mut classes = vec!["list-group-item"];
|
|
||||||
let mut aria_current = None;
|
|
||||||
if *f == props.editor_state.open_file {
|
|
||||||
aria_current = Some("true");
|
|
||||||
classes.push("active");
|
|
||||||
}
|
|
||||||
html! { <li aria-current={aria_current} class={classes.iter().join(" ")}>{f}</li>}
|
|
||||||
})
|
|
||||||
.collect::<Vec<Html>>()
|
|
||||||
}
|
|
||||||
</ul>
|
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -170,84 +68,21 @@ fn editor_nav(props: &EditorViewDetailProps) -> Html {
|
|||||||
#[wasm_bindgen(getter_with_clone)]
|
#[wasm_bindgen(getter_with_clone)]
|
||||||
struct EditorViewConfig {
|
struct EditorViewConfig {
|
||||||
pub doc: String,
|
pub doc: String,
|
||||||
}
|
|
||||||
|
|
||||||
#[wasm_bindgen(getter_with_clone)]
|
|
||||||
struct EditorStateConfig {
|
|
||||||
pub doc: String,
|
|
||||||
pub extensions: Vec<CMExtension>,
|
pub extensions: Vec<CMExtension>,
|
||||||
|
pub parent: Node,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[wasm_bindgen(getter_with_clone)]
|
#[wasm_bindgen(module = codemirror)]
|
||||||
struct HighlightingConfig {
|
|
||||||
pub fallback: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[wasm_bindgen(module = "@codemirror/state")]
|
|
||||||
extern "C" {
|
|
||||||
type EditorState;
|
|
||||||
#[wasm_bindgen(extends = Object)]
|
|
||||||
type CMDocument;
|
|
||||||
|
|
||||||
#[wasm_bindgen(static_method_of = EditorState)]
|
|
||||||
fn create(settings: EditorStateConfig) -> EditorState;
|
|
||||||
|
|
||||||
#[wasm_bindgen(method, getter)]
|
|
||||||
fn doc(st: &EditorState) -> CMDocument;
|
|
||||||
|
|
||||||
#[wasm_bindgen(js_namespace = ["EditorState", "allowMultipleSelections"], js_name = of)]
|
|
||||||
fn editorstate_allow_multiple_selections_of(v: bool) -> CMExtension;
|
|
||||||
}
|
|
||||||
|
|
||||||
#[wasm_bindgen(module = "@codemirror/view")]
|
|
||||||
extern "C" {
|
extern "C" {
|
||||||
type EditorView;
|
type EditorView;
|
||||||
type ViewUpdate;
|
|
||||||
#[derive(Clone)]
|
|
||||||
type KeyBinding;
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
type CMExtension;
|
type CMExtension;
|
||||||
|
|
||||||
#[wasm_bindgen(constructor)]
|
#[wasm_bindgen(constructor)]
|
||||||
fn new(settings: EditorViewConfig) -> EditorView;
|
fn new(settings: EditorViewConfig) -> EditorView;
|
||||||
|
|
||||||
#[wasm_bindgen(js_namespace = ["EditorView", "updateListener"], js_name = of)]
|
#[wasm_bindgen(js_name = basicSetup, thread_local)]
|
||||||
fn editorview_updatelistener_of(cb: &Closure<dyn FnMut(ViewUpdate)>) -> CMExtension;
|
static BASIC_SETUP: CMExtension;
|
||||||
|
|
||||||
#[wasm_bindgen(method, getter)]
|
|
||||||
fn dom(upd: &EditorView) -> HtmlElement;
|
|
||||||
|
|
||||||
#[wasm_bindgen(method, getter)]
|
|
||||||
fn state(v: &EditorView) -> EditorState;
|
|
||||||
|
|
||||||
#[wasm_bindgen(method, js_name = setState)]
|
|
||||||
fn set_state(v: &EditorView, st: &EditorState);
|
|
||||||
|
|
||||||
#[wasm_bindgen(method, getter, js_name = docChanged)]
|
|
||||||
fn doc_changed(upd: &ViewUpdate) -> bool;
|
|
||||||
|
|
||||||
#[wasm_bindgen(method, getter)]
|
|
||||||
fn view(upd: &ViewUpdate) -> EditorView;
|
|
||||||
|
|
||||||
#[wasm_bindgen(js_name = highlightSpecialChars)]
|
|
||||||
fn highlight_special_chars() -> CMExtension;
|
|
||||||
#[wasm_bindgen(js_name = drawSelection)]
|
|
||||||
fn draw_selection() -> CMExtension;
|
|
||||||
#[wasm_bindgen(js_name = highlightActiveLine)]
|
|
||||||
fn highlight_active_line() -> CMExtension;
|
|
||||||
#[wasm_bindgen(js_name = dropCursor)]
|
|
||||||
fn drop_cursor() -> CMExtension;
|
|
||||||
#[wasm_bindgen(js_name = rectangularSelection)]
|
|
||||||
fn rectangular_selection() -> CMExtension;
|
|
||||||
#[wasm_bindgen(js_name = crosshairCursor)]
|
|
||||||
fn crosshair_cursor() -> CMExtension;
|
|
||||||
#[wasm_bindgen(js_name = lineNumbers)]
|
|
||||||
fn line_numbers() -> CMExtension;
|
|
||||||
#[wasm_bindgen(js_name = highlightActiveLineGutter)]
|
|
||||||
fn highlight_active_line_gutter() -> CMExtension;
|
|
||||||
|
|
||||||
#[wasm_bindgen(js_namespace = keymap, js_name = of)]
|
|
||||||
fn keymap_of(keys: Vec<KeyBinding>) -> CMExtension;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[wasm_bindgen(module = "@codemirror/legacy-modes/mode/lua")]
|
#[wasm_bindgen(module = "@codemirror/legacy-modes/mode/lua")]
|
||||||
@ -260,169 +95,25 @@ extern "C" {
|
|||||||
extern "C" {
|
extern "C" {
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
type StreamLanguage;
|
type StreamLanguage;
|
||||||
#[derive(Clone)]
|
|
||||||
type CMHighlightStyle;
|
|
||||||
|
|
||||||
#[wasm_bindgen(js_namespace = StreamLanguage, js_name = define)]
|
#[wasm_bindgen(js_namespace = StreamLanguage, js_name = define)]
|
||||||
fn streamlanguage_define(input: StreamLanguage) -> CMExtension;
|
fn streamlanguage_define(input: StreamLanguage) -> CMExtension;
|
||||||
|
|
||||||
#[wasm_bindgen(js_name = defaultHighlightStyle, thread_local)]
|
|
||||||
static DEFAULT_HIGHLIGHT_STYLE: CMHighlightStyle;
|
|
||||||
|
|
||||||
#[wasm_bindgen(js_name = syntaxHighlighting)]
|
|
||||||
fn syntax_highlighting(style: CMHighlightStyle, config: HighlightingConfig) -> CMExtension;
|
|
||||||
|
|
||||||
#[wasm_bindgen(js_name = indentOnInput)]
|
|
||||||
fn indent_on_input() -> CMExtension;
|
|
||||||
#[wasm_bindgen(js_name = bracketMatching)]
|
|
||||||
fn bracket_matching() -> CMExtension;
|
|
||||||
#[wasm_bindgen(js_name = foldGutter)]
|
|
||||||
fn fold_gutter() -> CMExtension;
|
|
||||||
|
|
||||||
#[wasm_bindgen(js_name = foldKeymap, thread_local)]
|
|
||||||
static FOLD_KEYMAP: Vec<KeyBinding>;
|
|
||||||
}
|
|
||||||
|
|
||||||
#[wasm_bindgen(module = "@codemirror/commands")]
|
|
||||||
extern "C" {
|
|
||||||
#[wasm_bindgen(js_name = defaultKeymap, thread_local)]
|
|
||||||
static DEFAULT_KEYMAP: Vec<KeyBinding>;
|
|
||||||
#[wasm_bindgen(js_name = historyKeymap, thread_local)]
|
|
||||||
static HISTORY_KEYMAP: Vec<KeyBinding>;
|
|
||||||
|
|
||||||
#[wasm_bindgen]
|
|
||||||
fn history() -> CMExtension;
|
|
||||||
}
|
|
||||||
|
|
||||||
#[wasm_bindgen(module = "@codemirror/autocomplete")]
|
|
||||||
extern "C" {
|
|
||||||
#[wasm_bindgen]
|
|
||||||
fn autocompletion() -> CMExtension;
|
|
||||||
#[wasm_bindgen(js_name = closeBrackets)]
|
|
||||||
fn close_brackets() -> CMExtension;
|
|
||||||
|
|
||||||
#[wasm_bindgen(js_name = completionKeymap, thread_local)]
|
|
||||||
static COMPLETION_KEYMAP: Vec<KeyBinding>;
|
|
||||||
#[wasm_bindgen(js_name = closeBracketsKeymap, thread_local)]
|
|
||||||
static CLOSE_BRACKETS_KEYMAP: Vec<KeyBinding>;
|
|
||||||
}
|
|
||||||
|
|
||||||
#[wasm_bindgen(module = "@codemirror/search")]
|
|
||||||
extern "C" {
|
|
||||||
#[wasm_bindgen(js_name = searchKeymap, thread_local)]
|
|
||||||
static SEARCH_KEYMAP: Vec<KeyBinding>;
|
|
||||||
|
|
||||||
#[wasm_bindgen(js_name = highlightSelectionMatches)]
|
|
||||||
fn highlight_selection_matches() -> CMExtension;
|
|
||||||
}
|
|
||||||
|
|
||||||
#[wasm_bindgen(module = "@codemirror/lint")]
|
|
||||||
extern "C" {
|
|
||||||
#[wasm_bindgen(js_name = lintKeymap, thread_local)]
|
|
||||||
static LINT_KEYMAP: Vec<KeyBinding>;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn try_save_document(file: &str, contents: &str) -> anyhow::Result<()> {
|
|
||||||
let win = window().ok_or_else(|| Error::msg("Can't get window"))?;
|
|
||||||
let local = win
|
|
||||||
.local_storage()
|
|
||||||
.map_err(|_| Error::msg("Error retrieving localStorage"))?
|
|
||||||
.ok_or_else(|| Error::msg("Local storage not available"))?;
|
|
||||||
local
|
|
||||||
.set(&format!("scriptfile_{}", file), contents)
|
|
||||||
.map_err(|_| Error::msg("Error saving to localStorage"))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn load_file_contents(file: &str) -> anyhow::Result<String> {
|
|
||||||
let win = window().ok_or_else(|| Error::msg("Can't get window"))?;
|
|
||||||
let local = win
|
|
||||||
.local_storage()
|
|
||||||
.map_err(|_| Error::msg("Error retrieving localStorage"))?
|
|
||||||
.ok_or_else(|| Error::msg("Local storage not available"))?;
|
|
||||||
Ok(local
|
|
||||||
.get(&format!("scriptfile_{}", file))
|
|
||||||
.map_err(|_| Error::msg("Error retrieving localStorage"))?
|
|
||||||
.unwrap_or_else(|| "".to_owned()))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[function_component(EditorArea)]
|
#[function_component(EditorArea)]
|
||||||
fn editor_area(props: &EditorViewDetailProps) -> Html {
|
fn editor_area(props: &EditorViewProps) -> Html {
|
||||||
let node_ref = use_node_ref();
|
let node_ref = use_node_ref();
|
||||||
let editor_state = props.editor_state.clone();
|
use_effect_with(node_ref.clone(), |node_ref| match node_ref.get() {
|
||||||
let closures = use_state(|| EditorKeepClosures {
|
|
||||||
change_closure: Closure::new(move |view_update: ViewUpdate| {
|
|
||||||
if view_update.doc_changed() {
|
|
||||||
if let Some(contents) = view_update.view().state().doc().to_string().as_string() {
|
|
||||||
match try_save_document(&editor_state.open_file, &contents) {
|
|
||||||
Ok(()) => {}
|
|
||||||
Err(e) => {
|
|
||||||
let mut new_state = (*editor_state.as_ref()).clone();
|
|
||||||
new_state.error_msg = Some(format!("Can't save: {}", e).into());
|
|
||||||
editor_state.set(new_state.into());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.into(),
|
|
||||||
});
|
|
||||||
let view = use_state(move || {
|
|
||||||
EditorView::new(EditorViewConfig {
|
|
||||||
doc: "Loading...".to_owned(),
|
|
||||||
})
|
|
||||||
});
|
|
||||||
{
|
|
||||||
let view = view.clone();
|
|
||||||
use_effect_with(props.editor_state.open_file.clone(), move |open_file| {
|
|
||||||
view.set_state(&EditorState::create(EditorStateConfig {
|
|
||||||
doc: load_file_contents(open_file)
|
|
||||||
.unwrap_or_else(|e| format!("Error loading content: {}", e.to_string())),
|
|
||||||
extensions: vec![
|
|
||||||
// BASIC_SETUP.with(CMExtension::clone),
|
|
||||||
line_numbers(),
|
|
||||||
highlight_active_line_gutter(),
|
|
||||||
highlight_special_chars(),
|
|
||||||
history(),
|
|
||||||
fold_gutter(),
|
|
||||||
draw_selection(),
|
|
||||||
drop_cursor(),
|
|
||||||
editorstate_allow_multiple_selections_of(true),
|
|
||||||
indent_on_input(),
|
|
||||||
syntax_highlighting(
|
|
||||||
DEFAULT_HIGHLIGHT_STYLE.with(CMHighlightStyle::clone),
|
|
||||||
HighlightingConfig { fallback: true },
|
|
||||||
),
|
|
||||||
bracket_matching(),
|
|
||||||
close_brackets(),
|
|
||||||
autocompletion(),
|
|
||||||
rectangular_selection(),
|
|
||||||
crosshair_cursor(),
|
|
||||||
highlight_active_line(),
|
|
||||||
highlight_selection_matches(),
|
|
||||||
keymap_of(
|
|
||||||
vec![
|
|
||||||
&CLOSE_BRACKETS_KEYMAP,
|
|
||||||
&DEFAULT_KEYMAP,
|
|
||||||
&SEARCH_KEYMAP,
|
|
||||||
&HISTORY_KEYMAP,
|
|
||||||
&FOLD_KEYMAP,
|
|
||||||
&COMPLETION_KEYMAP,
|
|
||||||
&LINT_KEYMAP,
|
|
||||||
]
|
|
||||||
.into_iter()
|
|
||||||
.flat_map(|k| k.with(|v| v.clone()).into_iter())
|
|
||||||
.collect(),
|
|
||||||
),
|
|
||||||
StreamLanguage::streamlanguage_define(LUA.with(StreamLanguage::clone)),
|
|
||||||
editorview_updatelistener_of(&closures.change_closure),
|
|
||||||
],
|
|
||||||
}));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
use_effect_with(node_ref.clone(), move |node_ref| match node_ref.get() {
|
|
||||||
None => {}
|
None => {}
|
||||||
Some(node) => {
|
Some(node) => {
|
||||||
let _ = node.append_child(&view.dom());
|
EditorView::new(EditorViewConfig {
|
||||||
|
doc: "Hello World".to_owned(),
|
||||||
|
extensions: vec![
|
||||||
|
BASIC_SETUP.with(CMExtension::clone),
|
||||||
|
StreamLanguage::streamlanguage_define(LUA.with(StreamLanguage::clone)),
|
||||||
|
],
|
||||||
|
parent: node,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
html! {
|
html! {
|
||||||
@ -450,35 +141,23 @@ fn error_bar(props: &ErrorBarProps) -> Html {
|
|||||||
|
|
||||||
#[function_component(CodeEditorView)]
|
#[function_component(CodeEditorView)]
|
||||||
pub fn editor_view(props: &EditorViewProps) -> Html {
|
pub fn editor_view(props: &EditorViewProps) -> Html {
|
||||||
let editor_state: UseStateHandle<Rc<EditorViewState>> =
|
let editor_state: UseStateHandle<EditorState> = use_state_eq(fetch_initial_editor_state);
|
||||||
use_state_eq(fetch_initial_editor_state);
|
|
||||||
|
|
||||||
let detail_props: EditorViewDetailProps = EditorViewDetailProps {
|
|
||||||
frame: props.frame.clone(),
|
|
||||||
global_memo: props.global_memo.clone(),
|
|
||||||
global_layout: props.global_layout.clone(),
|
|
||||||
editor_state: editor_state.clone(),
|
|
||||||
};
|
|
||||||
|
|
||||||
html! {
|
html! {
|
||||||
<>
|
<>
|
||||||
{match editor_state.error_msg.as_ref() {
|
{match editor_state.error_msg.as_ref() {
|
||||||
None => { html! { <></> }}
|
None => { html! { <></> }}
|
||||||
Some(msg) => {
|
Some(msg) => html! {
|
||||||
let editor_state = editor_state.clone();
|
|
||||||
html! {
|
|
||||||
<ErrorBar msg={msg.clone()}
|
<ErrorBar msg={msg.clone()}
|
||||||
dismiss={move |()| editor_state.set(EditorViewState {
|
dismiss={move |()| editor_state.set(EditorState {
|
||||||
error_msg: None,
|
error_msg: None,
|
||||||
..((*editor_state.as_ref()).clone())
|
..(editor_state.deref().clone())
|
||||||
}.into())
|
})
|
||||||
}/>
|
}/> }
|
||||||
}
|
|
||||||
}
|
|
||||||
}}
|
}}
|
||||||
<SplitPanel direction={PanelDirection::Horizontal}
|
<SplitPanel direction={PanelDirection::Horizontal}
|
||||||
first={html!{<EditorNav ..detail_props.clone() />}}
|
first={html!{<EditorNav ..props.clone()/>}}
|
||||||
second={html!{<EditorArea ..detail_props.clone()/>}}
|
second={html!{<EditorArea ..props.clone()/>}}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
|
@ -5,7 +5,6 @@ use std::rc::Rc;
|
|||||||
use logging::LoggingEngine;
|
use logging::LoggingEngine;
|
||||||
use parsing::ParsedCommand;
|
use parsing::ParsedCommand;
|
||||||
use term_split::TermSplit;
|
use term_split::TermSplit;
|
||||||
use web_sys::console;
|
|
||||||
use yew::prelude::*;
|
use yew::prelude::*;
|
||||||
|
|
||||||
pub mod command_handler;
|
pub mod command_handler;
|
||||||
@ -22,7 +21,6 @@ pub mod telnet;
|
|||||||
pub mod term_split;
|
pub mod term_split;
|
||||||
pub mod timer_host;
|
pub mod timer_host;
|
||||||
pub mod websocket;
|
pub mod websocket;
|
||||||
use crate::editor_view::try_run_script;
|
|
||||||
use crate::frame_view::*;
|
use crate::frame_view::*;
|
||||||
use crate::lua_engine::{install_lua_globals, LuaState};
|
use crate::lua_engine::{install_lua_globals, LuaState};
|
||||||
use crate::split_panel::*;
|
use crate::split_panel::*;
|
||||||
@ -84,12 +82,6 @@ fn app() -> Html {
|
|||||||
use_memo((), |_| {
|
use_memo((), |_| {
|
||||||
install_lua_globals(&global_memo, global_layout.setter())
|
install_lua_globals(&global_memo, global_layout.setter())
|
||||||
.expect("Couldn't install Lua globals");
|
.expect("Couldn't install Lua globals");
|
||||||
match try_run_script("init.lua", &global_memo) {
|
|
||||||
Ok(_) => {}
|
|
||||||
Err(e) => {
|
|
||||||
console::log_1(&format!("Error running init.lua: {}", e).into());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
html! {
|
html! {
|
||||||
|
Loading…
Reference in New Issue
Block a user