@tishlang/tish-format 1.0.12 → 2.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Cargo.toml +51 -0
- package/LICENSE +13 -0
- package/bin/tish-format +0 -0
- package/crates/js_to_tish/Cargo.toml +11 -0
- package/crates/js_to_tish/README.md +18 -0
- package/crates/js_to_tish/src/error.rs +55 -0
- package/crates/js_to_tish/src/lib.rs +11 -0
- package/crates/js_to_tish/src/span_util.rs +35 -0
- package/crates/js_to_tish/src/transform/expr.rs +611 -0
- package/crates/js_to_tish/src/transform/stmt.rs +503 -0
- package/crates/js_to_tish/src/transform.rs +60 -0
- package/crates/tish/Cargo.toml +62 -0
- package/crates/tish/build.rs +21 -0
- package/crates/tish/src/cargo_native_registry.rs +32 -0
- package/crates/tish/src/cli_help.rs +576 -0
- package/crates/tish/src/main.rs +853 -0
- package/crates/tish/src/repl_completion.rs +199 -0
- package/crates/tish/tests/cargo_example_compile.rs +67 -0
- package/crates/tish/tests/error_source_location.rs +36 -0
- package/crates/tish/tests/fixtures/cargo_example_project/Cargo.toml +3 -0
- package/crates/tish/tests/fixtures/cargo_example_project/crates/demo-shim/Cargo.toml +11 -0
- package/crates/tish/tests/fixtures/cargo_example_project/crates/demo-shim/src/lib.rs +12 -0
- package/crates/tish/tests/fixtures/cargo_example_project/package.json +10 -0
- package/crates/tish/tests/fixtures/cargo_example_project/src/main.tish +3 -0
- package/crates/tish/tests/fixtures/runtime_error_location.tish +5 -0
- package/crates/tish/tests/fixtures/trycatch_runtime_errors.tish +15 -0
- package/crates/tish/tests/fixtures/tty_capability.tish +9 -0
- package/crates/tish/tests/integration_test.rs +1406 -0
- package/crates/tish/tests/run_optimize_stdout_parity.rs +50 -0
- package/crates/tish/tests/shortcircuit.rs +65 -0
- package/crates/tish/tests/trycatch_runtime_errors.rs +45 -0
- package/crates/tish/tests/tty_capability.rs +43 -0
- package/crates/tish_ast/Cargo.toml +9 -0
- package/crates/tish_ast/src/ast.rs +649 -0
- package/crates/tish_ast/src/lib.rs +5 -0
- package/crates/tish_build_utils/Cargo.toml +11 -0
- package/crates/tish_build_utils/src/lib.rs +577 -0
- package/crates/tish_builtins/Cargo.toml +22 -0
- package/crates/tish_builtins/src/array.rs +803 -0
- package/crates/tish_builtins/src/collections.rs +481 -0
- package/crates/tish_builtins/src/construct.rs +199 -0
- package/crates/tish_builtins/src/date.rs +538 -0
- package/crates/tish_builtins/src/globals.rs +293 -0
- package/crates/tish_builtins/src/helpers.rs +35 -0
- package/crates/tish_builtins/src/iterator.rs +129 -0
- package/crates/tish_builtins/src/lib.rs +21 -0
- package/crates/tish_builtins/src/math.rs +89 -0
- package/crates/tish_builtins/src/number.rs +96 -0
- package/crates/tish_builtins/src/object.rs +36 -0
- package/crates/tish_builtins/src/string.rs +646 -0
- package/crates/tish_builtins/src/symbol.rs +83 -0
- package/crates/tish_builtins/src/typedarrays.rs +298 -0
- package/crates/tish_bytecode/Cargo.toml +17 -0
- package/crates/tish_bytecode/src/chunk.rs +164 -0
- package/crates/tish_bytecode/src/compiler.rs +2604 -0
- package/crates/tish_bytecode/src/encoding.rs +102 -0
- package/crates/tish_bytecode/src/lib.rs +20 -0
- package/crates/tish_bytecode/src/opcode.rs +185 -0
- package/crates/tish_bytecode/src/peephole.rs +189 -0
- package/crates/tish_bytecode/src/serialize.rs +193 -0
- package/crates/tish_bytecode/tests/break_continue_bytecode.rs +44 -0
- package/crates/tish_bytecode/tests/constant_folding.rs +84 -0
- package/crates/tish_bytecode/tests/sort_optimization.rs +31 -0
- package/crates/tish_compile/Cargo.toml +27 -0
- package/crates/tish_compile/src/check.rs +774 -0
- package/crates/tish_compile/src/codegen.rs +7317 -0
- package/crates/tish_compile/src/infer.rs +1681 -0
- package/crates/tish_compile/src/lib.rs +206 -0
- package/crates/tish_compile/src/resolve.rs +1951 -0
- package/crates/tish_compile/src/types.rs +605 -0
- package/crates/tish_compile_js/Cargo.toml +18 -0
- package/crates/tish_compile_js/examples/jsx_vdom_smoke.tish +8 -0
- package/crates/tish_compile_js/src/codegen.rs +938 -0
- package/crates/tish_compile_js/src/error.rs +20 -0
- package/crates/tish_compile_js/src/lib.rs +26 -0
- package/crates/tish_compile_js/src/tests_jsx.rs +414 -0
- package/crates/tish_compiler_wasm/Cargo.toml +21 -0
- package/crates/tish_compiler_wasm/src/lib.rs +57 -0
- package/crates/tish_compiler_wasm/src/resolve_virtual.rs +473 -0
- package/crates/tish_core/Cargo.toml +32 -0
- package/crates/tish_core/src/console_style.rs +170 -0
- package/crates/tish_core/src/json.rs +430 -0
- package/crates/tish_core/src/lib.rs +20 -0
- package/crates/tish_core/src/macros.rs +36 -0
- package/crates/tish_core/src/shape.rs +85 -0
- package/crates/tish_core/src/uri.rs +118 -0
- package/crates/tish_core/src/value.rs +1350 -0
- package/crates/tish_core/src/vmref.rs +183 -0
- package/crates/tish_cranelift/Cargo.toml +19 -0
- package/crates/tish_cranelift/src/lib.rs +43 -0
- package/crates/tish_cranelift/src/link.rs +130 -0
- package/crates/tish_cranelift/src/lower.rs +85 -0
- package/crates/tish_cranelift_runtime/Cargo.toml +26 -0
- package/crates/tish_cranelift_runtime/src/lib.rs +45 -0
- package/crates/tish_eval/Cargo.toml +51 -0
- package/crates/tish_eval/src/eval.rs +4265 -0
- package/crates/tish_eval/src/http.rs +191 -0
- package/crates/tish_eval/src/lib.rs +99 -0
- package/crates/tish_eval/src/natives.rs +551 -0
- package/crates/tish_eval/src/promise.rs +179 -0
- package/crates/tish_eval/src/regex.rs +299 -0
- package/crates/tish_eval/src/timers.rs +120 -0
- package/crates/tish_eval/src/value.rs +336 -0
- package/crates/tish_eval/src/value_convert.rs +117 -0
- package/crates/tish_ffi/Cargo.toml +26 -0
- package/crates/tish_ffi/src/lib.rs +518 -0
- package/crates/tish_ffi/tests/fixtures/testmod/Cargo.toml +18 -0
- package/crates/tish_ffi/tests/fixtures/testmod/src/lib.rs +46 -0
- package/crates/tish_ffi/tests/loader.rs +65 -0
- package/crates/tish_fmt/Cargo.toml +16 -0
- package/crates/tish_fmt/src/bin/tish-fmt.rs +41 -0
- package/crates/tish_fmt/src/lib.rs +2157 -0
- package/crates/tish_jsx_web/Cargo.toml +9 -0
- package/crates/tish_jsx_web/README.md +5 -0
- package/crates/tish_jsx_web/src/lib.rs +2 -0
- package/crates/tish_lexer/Cargo.toml +9 -0
- package/crates/tish_lexer/src/lib.rs +1104 -0
- package/crates/tish_lexer/src/token.rs +170 -0
- package/crates/tish_lint/Cargo.toml +18 -0
- package/crates/tish_lint/src/bin/tish-lint.rs +195 -0
- package/crates/tish_lint/src/lib.rs +281 -0
- package/crates/tish_llvm/Cargo.toml +13 -0
- package/crates/tish_llvm/src/lib.rs +115 -0
- package/crates/tish_lsp/Cargo.toml +25 -0
- package/crates/tish_lsp/README.md +26 -0
- package/crates/tish_lsp/src/builtin_goto.rs +362 -0
- package/crates/tish_lsp/src/import_goto.rs +564 -0
- package/crates/tish_lsp/src/main.rs +1459 -0
- package/crates/tish_native/Cargo.toml +16 -0
- package/crates/tish_native/src/build.rs +481 -0
- package/crates/tish_native/src/config.rs +48 -0
- package/crates/tish_native/src/lib.rs +416 -0
- package/crates/tish_opt/Cargo.toml +13 -0
- package/crates/tish_opt/src/lib.rs +1046 -0
- package/crates/tish_parser/Cargo.toml +11 -0
- package/crates/tish_parser/src/lib.rs +386 -0
- package/crates/tish_parser/src/parser.rs +2726 -0
- package/crates/tish_pg/Cargo.toml +34 -0
- package/crates/tish_pg/README.md +38 -0
- package/crates/tish_pg/src/error.rs +52 -0
- package/crates/tish_pg/src/lib.rs +955 -0
- package/crates/tish_resolve/Cargo.toml +13 -0
- package/crates/tish_resolve/src/lib.rs +3601 -0
- package/crates/tish_resolve/src/pos.rs +141 -0
- package/crates/tish_runtime/Cargo.toml +100 -0
- package/crates/tish_runtime/src/http.rs +1347 -0
- package/crates/tish_runtime/src/http_fetch.rs +492 -0
- package/crates/tish_runtime/src/http_hyper.rs +441 -0
- package/crates/tish_runtime/src/http_prefork.rs +189 -0
- package/crates/tish_runtime/src/lib.rs +1447 -0
- package/crates/tish_runtime/src/native_promise.rs +15 -0
- package/crates/tish_runtime/src/promise.rs +558 -0
- package/crates/tish_runtime/src/promise_io.rs +38 -0
- package/crates/tish_runtime/src/timers.rs +172 -0
- package/crates/tish_runtime/src/tty.rs +226 -0
- package/crates/tish_runtime/src/ws.rs +778 -0
- package/crates/tish_runtime/tests/fetch_readable_stream.rs +102 -0
- package/crates/tish_ui/Cargo.toml +17 -0
- package/crates/tish_ui/src/jsx.rs +692 -0
- package/crates/tish_ui/src/lib.rs +20 -0
- package/crates/tish_ui/src/runtime/hooks.rs +573 -0
- package/crates/tish_ui/src/runtime/mod.rs +183 -0
- package/crates/tish_vm/Cargo.toml +60 -0
- package/crates/tish_vm/src/jit.rs +1050 -0
- package/crates/tish_vm/src/lib.rs +41 -0
- package/crates/tish_vm/src/vm.rs +3536 -0
- package/crates/tish_vm/tests/concurrent_shared_state.rs +140 -0
- package/crates/tish_vm/tests/fixtures/or_string_cmd.tish +2 -0
- package/crates/tish_vm/tests/lexical_scope_declare.rs +34 -0
- package/crates/tish_vm/tests/peephole_jump_chain_logical_or.rs +150 -0
- package/crates/tish_wasm/Cargo.toml +15 -0
- package/crates/tish_wasm/src/lib.rs +428 -0
- package/crates/tish_wasm_runtime/Cargo.toml +37 -0
- package/crates/tish_wasm_runtime/src/gpu.rs +429 -0
- package/crates/tish_wasm_runtime/src/lib.rs +42 -0
- package/crates/tishlang_cargo_bindgen/Cargo.toml +26 -0
- package/crates/tishlang_cargo_bindgen/src/classify.rs +261 -0
- package/crates/tishlang_cargo_bindgen/src/discover.rs +125 -0
- package/crates/tishlang_cargo_bindgen/src/infer.rs +382 -0
- package/crates/tishlang_cargo_bindgen/src/lib.rs +349 -0
- package/crates/tishlang_cargo_bindgen/src/main.rs +167 -0
- package/crates/tishlang_cargo_bindgen/src/metadata.rs +117 -0
- package/justfile +276 -0
- package/package.json +2 -2
- package/platform/darwin-arm64/tish-fmt +0 -0
- package/platform/darwin-x64/tish-fmt +0 -0
- package/platform/linux-arm64/tish-fmt +0 -0
- package/platform/linux-x64/tish-fmt +0 -0
- package/platform/win32-x64/tish-fmt.exe +0 -0
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
//! setTimeout, setInterval, clearTimeout, clearInterval for compiled Tish and VM.
|
|
2
|
+
//! Callbacks run when blocking ops (e.g. ws.receiveTimeout) yield in their poll loop.
|
|
3
|
+
|
|
4
|
+
use std::cell::RefCell;
|
|
5
|
+
use std::collections::HashMap;
|
|
6
|
+
use std::sync::atomic::{AtomicU64, Ordering};
|
|
7
|
+
use std::time::{Duration, Instant};
|
|
8
|
+
|
|
9
|
+
use tishlang_core::Value;
|
|
10
|
+
|
|
11
|
+
static NEXT_ID: AtomicU64 = AtomicU64::new(1);
|
|
12
|
+
|
|
13
|
+
fn next_id() -> u64 {
|
|
14
|
+
NEXT_ID.fetch_add(1, Ordering::SeqCst)
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
struct TimerEntry {
|
|
18
|
+
due: Instant,
|
|
19
|
+
callback: Value,
|
|
20
|
+
args: Vec<Value>,
|
|
21
|
+
interval_ms: u64,
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
thread_local! {
|
|
25
|
+
static REGISTRY: RefCell<HashMap<u64, TimerEntry>> = RefCell::new(HashMap::new());
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
fn extract_num(v: Option<&Value>) -> u64 {
|
|
29
|
+
v.and_then(|x| match x {
|
|
30
|
+
Value::Number(n) if n.is_finite() && *n >= 0.0 => Some(*n as u64),
|
|
31
|
+
_ => None,
|
|
32
|
+
})
|
|
33
|
+
.unwrap_or(0)
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/// Sleep for ms, running due timers before sleeping. Use this instead of thread::sleep
|
|
37
|
+
/// in blocking loops so setTimeout callbacks can fire.
|
|
38
|
+
#[allow(dead_code)] // Used by embedders with blocking poll loops; AppKit uses [`drain_timers`] instead.
|
|
39
|
+
pub fn sleep_with_drain(ms: u64) {
|
|
40
|
+
run_due_timers();
|
|
41
|
+
std::thread::sleep(Duration::from_millis(ms));
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/// Run all due timer callbacks (e.g. from an AppKit / GUI event pump).
|
|
45
|
+
#[inline]
|
|
46
|
+
pub fn drain_timers() {
|
|
47
|
+
run_due_timers();
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/// Run all due timer callbacks (including timers scheduled by other timers).
|
|
51
|
+
fn run_due_timers() {
|
|
52
|
+
for _ in 0..64 {
|
|
53
|
+
let due = take_due_timers();
|
|
54
|
+
if due.is_empty() {
|
|
55
|
+
break;
|
|
56
|
+
}
|
|
57
|
+
for (id, callback, args, interval_ms) in due {
|
|
58
|
+
if let Value::Function(f) = &callback {
|
|
59
|
+
let _ = f.call(&args);
|
|
60
|
+
}
|
|
61
|
+
if interval_ms > 0 {
|
|
62
|
+
re_register_interval(id, callback, args, interval_ms);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
fn take_due_timers() -> Vec<(u64, Value, Vec<Value>, u64)> {
|
|
69
|
+
let now = Instant::now();
|
|
70
|
+
REGISTRY.with(|r| {
|
|
71
|
+
let mut reg = r.borrow_mut();
|
|
72
|
+
let mut due: Vec<_> = reg
|
|
73
|
+
.iter()
|
|
74
|
+
.filter(|(_, e)| e.due <= now)
|
|
75
|
+
.map(|(id, e)| (e.due, *id, e.callback.clone(), e.args.clone(), e.interval_ms))
|
|
76
|
+
.collect();
|
|
77
|
+
// Deterministic JS timer order: earliest `due` first, ties broken by registration order
|
|
78
|
+
// (the monotonic id). REGISTRY is a HashMap whose iteration order is otherwise arbitrary,
|
|
79
|
+
// which scrambled same-delay timers (e.g. three `setTimeout(_, 0)` firing out of order).
|
|
80
|
+
due.sort_by(|a, b| a.0.cmp(&b.0).then(a.1.cmp(&b.1)));
|
|
81
|
+
for (_, id, _, _, _) in &due {
|
|
82
|
+
reg.remove(id);
|
|
83
|
+
}
|
|
84
|
+
due.into_iter()
|
|
85
|
+
.map(|(_, id, cb, args, iv)| (id, cb, args, iv))
|
|
86
|
+
.collect()
|
|
87
|
+
})
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
fn re_register_interval(id: u64, callback: Value, args: Vec<Value>, interval_ms: u64) {
|
|
91
|
+
let due = Instant::now() + Duration::from_millis(interval_ms);
|
|
92
|
+
REGISTRY.with(|r| {
|
|
93
|
+
r.borrow_mut().insert(
|
|
94
|
+
id,
|
|
95
|
+
TimerEntry {
|
|
96
|
+
due,
|
|
97
|
+
callback,
|
|
98
|
+
args,
|
|
99
|
+
interval_ms,
|
|
100
|
+
},
|
|
101
|
+
);
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/// setTimeout(callback, delayMs, ...args) - returns timer id.
|
|
106
|
+
/// Callbacks run when run_due_timers() is invoked (e.g. from ws.receiveTimeout poll loop).
|
|
107
|
+
pub fn set_timeout(args: &[Value]) -> Value {
|
|
108
|
+
let callback = args.first().cloned().unwrap_or(Value::Null);
|
|
109
|
+
let delay_ms = extract_num(args.get(1)).min(3_600_000);
|
|
110
|
+
let extra_args: Vec<Value> = args.iter().skip(2).cloned().collect();
|
|
111
|
+
if matches!(callback, Value::Null) {
|
|
112
|
+
return Value::Number(next_id() as f64);
|
|
113
|
+
}
|
|
114
|
+
let id = next_id();
|
|
115
|
+
let due = Instant::now() + Duration::from_millis(delay_ms);
|
|
116
|
+
REGISTRY.with(|r| {
|
|
117
|
+
r.borrow_mut().insert(
|
|
118
|
+
id,
|
|
119
|
+
TimerEntry {
|
|
120
|
+
due,
|
|
121
|
+
callback,
|
|
122
|
+
args: extra_args,
|
|
123
|
+
interval_ms: 0,
|
|
124
|
+
},
|
|
125
|
+
);
|
|
126
|
+
});
|
|
127
|
+
Value::Number(id as f64)
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/// setInterval(callback, intervalMs, ...args) — first run after `intervalMs`, then repeats.
|
|
131
|
+
pub fn set_interval(args: &[Value]) -> Value {
|
|
132
|
+
let callback = args.first().cloned().unwrap_or(Value::Null);
|
|
133
|
+
let interval_ms = extract_num(args.get(1)).min(3_600_000);
|
|
134
|
+
let extra_args: Vec<Value> = args.iter().skip(2).cloned().collect();
|
|
135
|
+
if matches!(callback, Value::Null) {
|
|
136
|
+
return Value::Number(next_id() as f64);
|
|
137
|
+
}
|
|
138
|
+
let id = next_id();
|
|
139
|
+
let due = Instant::now() + Duration::from_millis(interval_ms);
|
|
140
|
+
REGISTRY.with(|r| {
|
|
141
|
+
r.borrow_mut().insert(
|
|
142
|
+
id,
|
|
143
|
+
TimerEntry {
|
|
144
|
+
due,
|
|
145
|
+
callback,
|
|
146
|
+
args: extra_args,
|
|
147
|
+
interval_ms,
|
|
148
|
+
},
|
|
149
|
+
);
|
|
150
|
+
});
|
|
151
|
+
Value::Number(id as f64)
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/// clearTimeout(id) - removes timer.
|
|
155
|
+
pub fn clear_timeout(args: &[Value]) -> Value {
|
|
156
|
+
let id = args
|
|
157
|
+
.first()
|
|
158
|
+
.and_then(|v| match v {
|
|
159
|
+
Value::Number(n) if n.is_finite() && *n >= 0.0 => Some(*n as u64),
|
|
160
|
+
_ => None,
|
|
161
|
+
})
|
|
162
|
+
.unwrap_or(0);
|
|
163
|
+
REGISTRY.with(|r| {
|
|
164
|
+
r.borrow_mut().remove(&id);
|
|
165
|
+
});
|
|
166
|
+
Value::Null
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/// clearInterval(id) — same registry as clearTimeout.
|
|
170
|
+
pub fn clear_interval(args: &[Value]) -> Value {
|
|
171
|
+
clear_timeout(args)
|
|
172
|
+
}
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
//! Interactive terminal I/O for Tish (issue #101), behind the `tty` feature.
|
|
2
|
+
//!
|
|
3
|
+
//! Exposes raw mode, the alternate screen, terminal size, and key/resize **events** via
|
|
4
|
+
//! `crossterm`, so Tish programs can build interactive TUIs (menus, forms, live keyboard
|
|
5
|
+
//! navigation). Imported as `import { … } from 'tish:tty'`.
|
|
6
|
+
//!
|
|
7
|
+
//! The Value-agnostic core (`size`, `is_tty`, `set_raw_mode`, `read_event`, …) returns plain
|
|
8
|
+
//! Rust data so every backend — the bytecode VM (via the `tty_*` wrappers here) and the
|
|
9
|
+
//! tree-walk interpreter (whose `Value` is a distinct type) — can build its own `Value` from
|
|
10
|
+
//! the same logic. Errors surface as `null`/`false` rather than panicking.
|
|
11
|
+
|
|
12
|
+
use std::io::{IsTerminal, Write};
|
|
13
|
+
use std::sync::Arc;
|
|
14
|
+
use std::time::Duration;
|
|
15
|
+
|
|
16
|
+
use tishlang_core::{ObjectMap, Value};
|
|
17
|
+
|
|
18
|
+
use crossterm::event::{self, Event, KeyCode, KeyEventKind, KeyModifiers};
|
|
19
|
+
use crossterm::terminal;
|
|
20
|
+
|
|
21
|
+
/// A terminal event delivered by [`read_event`]. Plain data so each backend maps it to its
|
|
22
|
+
/// own `Value` object.
|
|
23
|
+
pub enum TtyEvent {
|
|
24
|
+
Key {
|
|
25
|
+
key: String,
|
|
26
|
+
ctrl: bool,
|
|
27
|
+
alt: bool,
|
|
28
|
+
shift: bool,
|
|
29
|
+
},
|
|
30
|
+
Resize {
|
|
31
|
+
cols: u16,
|
|
32
|
+
rows: u16,
|
|
33
|
+
},
|
|
34
|
+
/// Mouse / focus / paste — reported generically so an input loop can ignore it.
|
|
35
|
+
Other,
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// ── Value-agnostic core (shared by every backend) ───────────────────────────────────────
|
|
39
|
+
|
|
40
|
+
/// Terminal `(cols, rows)`, or `None` if stdout is not connected to a terminal.
|
|
41
|
+
///
|
|
42
|
+
/// Gated on `stdout().is_terminal()` to match Node (`process.stdout.columns` is
|
|
43
|
+
/// `undefined` when stdout is not a TTY). Without the gate, `crossterm::terminal::size()`
|
|
44
|
+
/// can still succeed via the controlling terminal even when stdout is redirected — so a
|
|
45
|
+
/// piped run (e.g. CI, `tish run x.tish | cat`) would report a bogus size instead of null.
|
|
46
|
+
pub fn size() -> Option<(u16, u16)> {
|
|
47
|
+
if std::io::stdout().is_terminal() {
|
|
48
|
+
terminal::size().ok()
|
|
49
|
+
} else {
|
|
50
|
+
None
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/// Whether stdin **and** stdout are connected to a terminal.
|
|
55
|
+
pub fn is_tty() -> bool {
|
|
56
|
+
std::io::stdin().is_terminal() && std::io::stdout().is_terminal()
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/// Enter (cbreak) or leave raw mode. Returns `true` on success.
|
|
60
|
+
pub fn set_raw_mode(enabled: bool) -> bool {
|
|
61
|
+
if enabled {
|
|
62
|
+
terminal::enable_raw_mode().is_ok()
|
|
63
|
+
} else {
|
|
64
|
+
terminal::disable_raw_mode().is_ok()
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/// Switch to / from the alternate screen buffer (full-screen apps).
|
|
69
|
+
pub fn enter_alt_screen() -> bool {
|
|
70
|
+
let mut out = std::io::stdout();
|
|
71
|
+
let ok = crossterm::execute!(out, terminal::EnterAlternateScreen).is_ok();
|
|
72
|
+
let _ = out.flush();
|
|
73
|
+
ok
|
|
74
|
+
}
|
|
75
|
+
pub fn leave_alt_screen() -> bool {
|
|
76
|
+
let mut out = std::io::stdout();
|
|
77
|
+
let ok = crossterm::execute!(out, terminal::LeaveAlternateScreen).is_ok();
|
|
78
|
+
let _ = out.flush();
|
|
79
|
+
ok
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/// Read the next terminal event. `timeout_ms = None` blocks; `Some(ms)` polls for `ms`
|
|
83
|
+
/// milliseconds (0 = non-blocking) and returns `None` on timeout. Key-release events
|
|
84
|
+
/// (Windows) are skipped, yielding `None`.
|
|
85
|
+
pub fn read_event(timeout_ms: Option<u64>) -> Option<TtyEvent> {
|
|
86
|
+
if let Some(ms) = timeout_ms {
|
|
87
|
+
match event::poll(Duration::from_millis(ms)) {
|
|
88
|
+
Ok(true) => {}
|
|
89
|
+
_ => return None,
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
match event::read().ok()? {
|
|
93
|
+
Event::Key(k) => {
|
|
94
|
+
if k.kind == KeyEventKind::Release {
|
|
95
|
+
return None;
|
|
96
|
+
}
|
|
97
|
+
Some(TtyEvent::Key {
|
|
98
|
+
key: key_code_name(k.code),
|
|
99
|
+
ctrl: k.modifiers.contains(KeyModifiers::CONTROL),
|
|
100
|
+
alt: k.modifiers.contains(KeyModifiers::ALT),
|
|
101
|
+
shift: k.modifiers.contains(KeyModifiers::SHIFT),
|
|
102
|
+
})
|
|
103
|
+
}
|
|
104
|
+
Event::Resize(cols, rows) => Some(TtyEvent::Resize { cols, rows }),
|
|
105
|
+
_ => Some(TtyEvent::Other),
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/// Read one line of **cooked** input from stdin (line mode), with the trailing newline
|
|
110
|
+
/// stripped. Returns `None` at EOF. For raw key-by-key input use [`read_event`].
|
|
111
|
+
pub fn read_line() -> Option<String> {
|
|
112
|
+
use std::io::BufRead;
|
|
113
|
+
let mut s = String::new();
|
|
114
|
+
match std::io::stdin().lock().read_line(&mut s) {
|
|
115
|
+
Ok(0) => None,
|
|
116
|
+
Ok(_) => {
|
|
117
|
+
while s.ends_with('\n') || s.ends_with('\r') {
|
|
118
|
+
s.pop();
|
|
119
|
+
}
|
|
120
|
+
Some(s)
|
|
121
|
+
}
|
|
122
|
+
Err(_) => None,
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/// Normalize a crossterm key code to a stable JS-friendly name (`"a"`, `"Enter"`, `"Up"`,
|
|
127
|
+
/// `"Esc"`, `"F1"`, …).
|
|
128
|
+
fn key_code_name(code: KeyCode) -> String {
|
|
129
|
+
match code {
|
|
130
|
+
KeyCode::Char(c) => c.to_string(),
|
|
131
|
+
KeyCode::Enter => "Enter".into(),
|
|
132
|
+
KeyCode::Esc => "Esc".into(),
|
|
133
|
+
KeyCode::Backspace => "Backspace".into(),
|
|
134
|
+
KeyCode::Tab => "Tab".into(),
|
|
135
|
+
KeyCode::BackTab => "BackTab".into(),
|
|
136
|
+
KeyCode::Delete => "Delete".into(),
|
|
137
|
+
KeyCode::Insert => "Insert".into(),
|
|
138
|
+
KeyCode::Home => "Home".into(),
|
|
139
|
+
KeyCode::End => "End".into(),
|
|
140
|
+
KeyCode::PageUp => "PageUp".into(),
|
|
141
|
+
KeyCode::PageDown => "PageDown".into(),
|
|
142
|
+
KeyCode::Up => "Up".into(),
|
|
143
|
+
KeyCode::Down => "Down".into(),
|
|
144
|
+
KeyCode::Left => "Left".into(),
|
|
145
|
+
KeyCode::Right => "Right".into(),
|
|
146
|
+
KeyCode::F(n) => format!("F{n}"),
|
|
147
|
+
KeyCode::Null => "Null".into(),
|
|
148
|
+
other => format!("{other:?}"),
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// ── core::Value wrappers for the bytecode VM / native runtime ────────────────────────────
|
|
153
|
+
|
|
154
|
+
fn obj(pairs: Vec<(&str, Value)>) -> Value {
|
|
155
|
+
let mut m = ObjectMap::default();
|
|
156
|
+
for (k, v) in pairs {
|
|
157
|
+
m.insert(Arc::from(k), v);
|
|
158
|
+
}
|
|
159
|
+
Value::object(m)
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/// `size()` → `{ cols, rows }` or `null`.
|
|
163
|
+
pub fn tty_size(_args: &[Value]) -> Value {
|
|
164
|
+
match size() {
|
|
165
|
+
Some((cols, rows)) => obj(vec![
|
|
166
|
+
("cols", Value::Number(cols as f64)),
|
|
167
|
+
("rows", Value::Number(rows as f64)),
|
|
168
|
+
]),
|
|
169
|
+
None => Value::Null,
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/// `isTTY()` → bool.
|
|
174
|
+
pub fn tty_is_tty(_args: &[Value]) -> Value {
|
|
175
|
+
Value::Bool(is_tty())
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/// `setRawMode(enabled)` → bool (success).
|
|
179
|
+
pub fn tty_set_raw_mode(args: &[Value]) -> Value {
|
|
180
|
+
Value::Bool(set_raw_mode(args.first().map(|v| v.is_truthy()).unwrap_or(false)))
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/// `enterAltScreen()` / `leaveAltScreen()` → bool.
|
|
184
|
+
pub fn tty_enter_alt_screen(_args: &[Value]) -> Value {
|
|
185
|
+
Value::Bool(enter_alt_screen())
|
|
186
|
+
}
|
|
187
|
+
pub fn tty_leave_alt_screen(_args: &[Value]) -> Value {
|
|
188
|
+
Value::Bool(leave_alt_screen())
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/// `readLine()` → one line of cooked stdin (no trailing newline), or `null` at EOF.
|
|
192
|
+
pub fn tty_read_line(_args: &[Value]) -> Value {
|
|
193
|
+
match read_line() {
|
|
194
|
+
Some(s) => Value::String(s.into()),
|
|
195
|
+
None => Value::Null,
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/// `read(timeoutMs?)` → an event object (`{ type, … }`) or `null`.
|
|
200
|
+
pub fn tty_read(args: &[Value]) -> Value {
|
|
201
|
+
let timeout = match args.first() {
|
|
202
|
+
Some(Value::Number(ms)) => Some(ms.max(0.0) as u64),
|
|
203
|
+
_ => None,
|
|
204
|
+
};
|
|
205
|
+
match read_event(timeout) {
|
|
206
|
+
Some(TtyEvent::Key {
|
|
207
|
+
key,
|
|
208
|
+
ctrl,
|
|
209
|
+
alt,
|
|
210
|
+
shift,
|
|
211
|
+
}) => obj(vec![
|
|
212
|
+
("type", Value::String("key".into())),
|
|
213
|
+
("key", Value::String(key.into())),
|
|
214
|
+
("ctrl", Value::Bool(ctrl)),
|
|
215
|
+
("alt", Value::Bool(alt)),
|
|
216
|
+
("shift", Value::Bool(shift)),
|
|
217
|
+
]),
|
|
218
|
+
Some(TtyEvent::Resize { cols, rows }) => obj(vec![
|
|
219
|
+
("type", Value::String("resize".into())),
|
|
220
|
+
("cols", Value::Number(cols as f64)),
|
|
221
|
+
("rows", Value::Number(rows as f64)),
|
|
222
|
+
]),
|
|
223
|
+
Some(TtyEvent::Other) => obj(vec![("type", Value::String("other".into()))]),
|
|
224
|
+
None => Value::Null,
|
|
225
|
+
}
|
|
226
|
+
}
|