diff --git a/packages/duckdb-wasm-shell/crate/src/prompt_buffer.rs b/packages/duckdb-wasm-shell/crate/src/prompt_buffer.rs index 4534c46b8..e41c70576 100644 --- a/packages/duckdb-wasm-shell/crate/src/prompt_buffer.rs +++ b/packages/duckdb-wasm-shell/crate/src/prompt_buffer.rs @@ -437,7 +437,7 @@ impl PromptBuffer { self.output_buffer, "{reset}{fg}{bold}", reset = vt100::MODES_OFF, - fg = vt100::COLOR_FG_GREEN, + fg = vt100::COLOR_FG_BRIGHT_YELLOW, bold = vt100::MODE_BOLD ) .unwrap(); diff --git a/packages/duckdb-wasm-shell/crate/src/shell.rs b/packages/duckdb-wasm-shell/crate/src/shell.rs index 0daa86446..8a2109fc5 100644 --- a/packages/duckdb-wasm-shell/crate/src/shell.rs +++ b/packages/duckdb-wasm-shell/crate/src/shell.rs @@ -1,6 +1,6 @@ use crate::arrow_printer::{pretty_format_batches, UTF8_BORDERS_NO_HORIZONTAL}; use crate::duckdb::{ - AsyncDuckDB, AsyncDuckDBConnection, DataProtocol, PACKAGE_NAME, PACKAGE_VERSION, DuckDBConfig, + AsyncDuckDB, AsyncDuckDBConnection, DataProtocol, DuckDBConfig, PACKAGE_NAME, PACKAGE_VERSION, }; use crate::key_event::{Key, KeyEvent}; use crate::platform; @@ -64,6 +64,13 @@ pub enum DatabaseType { RemoteReadOnly, } +use std::sync::{Mutex, OnceLock}; + +fn past_queries() -> &'static Mutex> { + static ARRAY: OnceLock>> = OnceLock::new(); + ARRAY.get_or_init(|| Mutex::new(VecDeque::new())) +} + /// The shell is the primary entrypoint for the web shell api. /// It is stored as thread_local singleton and maintains all the state for the interactions with DuckDB pub struct Shell { @@ -176,6 +183,20 @@ impl Shell { s.prompt(); s.focus(); }); + + for entry in &(*past_queries().lock().unwrap()) { + Shell::with_mut(|s| { + s.write(&format!( + "{bold}{green}", + bold = vt100::MODE_BOLD, + green = vt100::COLOR_FG_BRIGHT_YELLOW + )); + s.write(entry); + s.write(&format!("{normal}", normal = vt100::MODES_OFF)); + }); + Self::on_sql(entry.to_string()).await; + } + Ok(()) } @@ -821,6 +842,16 @@ impl Shell { }); } + /// Pass information on init queries + pub async fn pass_init_queries(queries: Vec) -> Result<(), js_sys::Error> { + let mut h = VecDeque::with_capacity(queries.len()); + for entry in &queries[0..queries.len()] { + h.push_back(entry.clone()); + } + *past_queries().lock().unwrap() = h; + Ok(()) + } + /// Flush output buffer to the terminal pub fn flush(&mut self) { self.input.flush(&self.terminal); diff --git a/packages/duckdb-wasm-shell/crate/src/shell_api.rs b/packages/duckdb-wasm-shell/crate/src/shell_api.rs index a9888f9ac..05766f123 100644 --- a/packages/duckdb-wasm-shell/crate/src/shell_api.rs +++ b/packages/duckdb-wasm-shell/crate/src/shell_api.rs @@ -81,6 +81,13 @@ pub fn load_history(history: &js_sys::Array, cursor: usize) { Shell::load_history(h, cursor); } +#[wasm_bindgen(js_name = "passInitQueries")] +pub async fn pass_init_queries(queries: &js_sys::Array) -> Result<(), js_sys::Error> { + let q: Vec = queries.iter().map(|ref v| v.as_string().unwrap()).collect(); + Shell::pass_init_queries(q).await?; + Ok(()) +} + #[wasm_bindgen(js_name = "configureDatabase")] pub async fn configure_database(db: JsAsyncDuckDB) -> Result<(), js_sys::Error> { Shell::configure_database(AsyncDuckDB::from_bindings(db)).await?; diff --git a/packages/duckdb-wasm-shell/src/shell.ts b/packages/duckdb-wasm-shell/src/shell.ts index 5bb2ad9b7..cef9b453a 100644 --- a/packages/duckdb-wasm-shell/src/shell.ts +++ b/packages/duckdb-wasm-shell/src/shell.ts @@ -20,6 +20,7 @@ class ShellRuntime { database: duckdb.AsyncDuckDB | null; history: HistoryStore; resizeHandler: (_event: UIEvent) => void; + hash: string; constructor(protected container: HTMLDivElement) { this.database = null; @@ -28,6 +29,7 @@ class ShellRuntime { const rect = container.getBoundingClientRect(); shell.resize(rect.width, rect.height); }; + this.hash = ""; } public async pickFiles(this: ShellRuntime): Promise { @@ -51,6 +53,13 @@ class ShellRuntime { return await navigator.clipboard.writeText(value); } public async pushInputToHistory(this: ShellRuntime, value: string) { + const encode = encodeURIComponent(extraswaps(value)); + if (this.hash === "") + this.hash = "queries=v0"; + this.hash += ","; + this.hash += encode; + if (window.location.hash.startsWith("#savequeries")) + window.location.hash = "savequeries&" + this.hash; this.history.push(value); } } @@ -70,6 +79,24 @@ function formatBytes(value: number): string { return `${size} ${exp ? `${k}MGTPEZY`[exp - 1] + suffix : `byte${size !== 1 ? 's' : ''}`}`; } +function extraswaps(input: string): string { + // As long as this function is symmetrical, all good + let res : string = ""; + for (let i=0; i { shell.configureDatabase(runtime.database); }); + const hash = window.location.hash; + const splits = hash.split(','); + const sqls : Array = []; + for (let i=1; i< splits.length; i++) { + sqls.push(extraswaps(decodeURIComponent(splits[i]))); + } + await step('Rewinding history!', async () => { + shell.passInitQueries(sqls); + }); }