@tishlang/tish 1.6.0 → 1.8.0
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 +2 -0
- package/README.md +2 -0
- package/bin/tish +0 -0
- package/crates/js_to_tish/src/error.rs +2 -8
- package/crates/js_to_tish/src/transform/expr.rs +128 -137
- package/crates/js_to_tish/src/transform/stmt.rs +62 -32
- package/crates/tish/Cargo.toml +15 -5
- package/crates/tish/src/cargo_native_registry.rs +29 -0
- package/crates/tish/src/cli_help.rs +92 -39
- package/crates/tish/src/main.rs +172 -86
- package/crates/tish/src/repl_completion.rs +3 -3
- package/crates/tish/tests/cargo_example_compile.rs +4 -2
- package/crates/tish/tests/integration_test.rs +216 -54
- package/crates/tish/tests/run_optimize_stdout_parity.rs +3 -7
- package/crates/tish/tests/shortcircuit.rs +20 -5
- package/crates/tish_ast/src/ast.rs +92 -23
- package/crates/tish_build_utils/Cargo.toml +4 -0
- package/crates/tish_build_utils/src/lib.rs +136 -8
- package/crates/tish_builtins/Cargo.toml +5 -1
- package/crates/tish_builtins/src/array.rs +65 -33
- package/crates/tish_builtins/src/construct.rs +34 -39
- package/crates/tish_builtins/src/globals.rs +42 -26
- package/crates/tish_builtins/src/helpers.rs +2 -1
- package/crates/tish_builtins/src/lib.rs +5 -5
- package/crates/tish_builtins/src/math.rs +5 -3
- package/crates/tish_builtins/src/object.rs +3 -2
- package/crates/tish_builtins/src/string.rs +144 -22
- package/crates/tish_bytecode/src/chunk.rs +0 -1
- package/crates/tish_bytecode/src/compiler.rs +173 -71
- package/crates/tish_bytecode/src/opcode.rs +24 -6
- package/crates/tish_bytecode/src/peephole.rs +2 -2
- package/crates/tish_compile/Cargo.toml +1 -0
- package/crates/tish_compile/src/codegen.rs +1621 -453
- package/crates/tish_compile/src/infer.rs +75 -19
- package/crates/tish_compile/src/lib.rs +19 -8
- package/crates/tish_compile/src/resolve.rs +278 -137
- package/crates/tish_compile/src/types.rs +184 -24
- package/crates/tish_compile_js/Cargo.toml +1 -0
- package/crates/tish_compile_js/src/codegen.rs +181 -37
- package/crates/tish_compile_js/src/lib.rs +3 -1
- package/crates/tish_compile_js/src/tests_jsx.rs +30 -6
- package/crates/tish_compiler_wasm/src/lib.rs +16 -13
- package/crates/tish_compiler_wasm/src/resolve_virtual.rs +69 -59
- package/crates/tish_core/Cargo.toml +8 -0
- package/crates/tish_core/src/json.rs +107 -56
- package/crates/tish_core/src/lib.rs +4 -2
- package/crates/tish_core/src/macros.rs +5 -5
- package/crates/tish_core/src/uri.rs +9 -6
- package/crates/tish_core/src/value.rs +145 -43
- package/crates/tish_core/src/vmref.rs +178 -0
- package/crates/tish_cranelift/src/link.rs +6 -9
- package/crates/tish_cranelift/src/lower.rs +14 -8
- package/crates/tish_eval/Cargo.toml +17 -2
- package/crates/tish_eval/src/eval.rs +474 -165
- package/crates/tish_eval/src/http.rs +61 -0
- package/crates/tish_eval/src/lib.rs +12 -8
- package/crates/tish_eval/src/natives.rs +136 -38
- package/crates/tish_eval/src/promise.rs +14 -8
- package/crates/tish_eval/src/timers.rs +28 -19
- package/crates/tish_eval/src/value.rs +17 -6
- package/crates/tish_eval/src/value_convert.rs +13 -5
- package/crates/tish_fmt/src/lib.rs +149 -43
- package/crates/tish_lexer/src/lib.rs +232 -63
- package/crates/tish_lexer/src/token.rs +10 -6
- package/crates/tish_llvm/src/lib.rs +17 -8
- package/crates/tish_lsp/Cargo.toml +4 -1
- package/crates/tish_lsp/README.md +1 -1
- package/crates/tish_lsp/src/builtin_goto.rs +261 -0
- package/crates/tish_lsp/src/import_goto.rs +549 -0
- package/crates/tish_lsp/src/main.rs +504 -106
- package/crates/tish_native/src/build.rs +4 -8
- package/crates/tish_native/src/lib.rs +54 -21
- package/crates/tish_opt/src/lib.rs +84 -52
- package/crates/tish_parser/src/lib.rs +45 -13
- package/crates/tish_parser/src/parser.rs +505 -130
- package/crates/tish_resolve/Cargo.toml +13 -0
- package/crates/tish_resolve/src/lib.rs +3436 -0
- package/crates/tish_resolve/src/pos.rs +133 -0
- package/crates/tish_runtime/Cargo.toml +68 -3
- package/crates/tish_runtime/src/http.rs +1136 -145
- package/crates/tish_runtime/src/http_fetch.rs +38 -27
- package/crates/tish_runtime/src/http_hyper.rs +418 -0
- package/crates/tish_runtime/src/http_prefork.rs +189 -0
- package/crates/tish_runtime/src/lib.rs +375 -189
- package/crates/tish_runtime/src/promise.rs +199 -40
- package/crates/tish_runtime/src/promise_io.rs +2 -1
- package/crates/tish_runtime/src/timers.rs +37 -1
- package/crates/tish_runtime/src/ws.rs +65 -42
- package/crates/tish_runtime/tests/fetch_readable_stream.rs +5 -4
- package/crates/tish_ui/src/jsx.rs +317 -27
- package/crates/tish_ui/src/lib.rs +5 -2
- package/crates/tish_ui/src/runtime/hooks.rs +406 -45
- package/crates/tish_ui/src/runtime/mod.rs +36 -9
- package/crates/tish_vm/Cargo.toml +15 -5
- package/crates/tish_vm/src/vm.rs +725 -281
- package/crates/tish_vm/tests/peephole_jump_chain_logical_or.rs +11 -4
- package/crates/tish_wasm/src/lib.rs +55 -42
- package/crates/tish_wasm_runtime/Cargo.toml +2 -1
- package/crates/tish_wasm_runtime/src/lib.rs +1 -1
- package/crates/tishlang_cargo_bindgen/Cargo.toml +26 -0
- package/crates/tishlang_cargo_bindgen/src/classify.rs +265 -0
- package/crates/tishlang_cargo_bindgen/src/discover.rs +120 -0
- package/crates/tishlang_cargo_bindgen/src/infer.rs +372 -0
- package/crates/tishlang_cargo_bindgen/src/lib.rs +350 -0
- package/crates/tishlang_cargo_bindgen/src/main.rs +164 -0
- package/crates/tishlang_cargo_bindgen/src/metadata.rs +114 -0
- package/justfile +8 -0
- package/package.json +1 -1
- package/platform/darwin-arm64/tish +0 -0
- package/platform/darwin-x64/tish +0 -0
- package/platform/linux-arm64/tish +0 -0
- package/platform/linux-x64/tish +0 -0
- package/platform/win32-x64/tish.exe +0 -0
|
@@ -1,29 +1,39 @@
|
|
|
1
1
|
//! Tish Language Server — diagnostics, symbols, completion, format, go-to-definition, workspace symbols.
|
|
2
2
|
|
|
3
3
|
use std::collections::HashMap;
|
|
4
|
-
use std::path::
|
|
4
|
+
use std::path::PathBuf;
|
|
5
5
|
use std::sync::{Arc, RwLock};
|
|
6
6
|
|
|
7
7
|
use regex::Regex;
|
|
8
8
|
use tower_lsp::jsonrpc::Result;
|
|
9
9
|
use tower_lsp::lsp_types::{
|
|
10
|
-
CompletionItem, CompletionItemKind,
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
10
|
+
CompletionItem, CompletionItemKind, CompletionParams, CompletionResponse,
|
|
11
|
+
CompletionTriggerKind, Diagnostic, DiagnosticSeverity, DiagnosticTag, DidChangeTextDocumentParams,
|
|
12
|
+
DidCloseTextDocumentParams, DidOpenTextDocumentParams, DocumentFormattingParams,
|
|
13
|
+
DocumentSymbolParams, DocumentSymbolResponse, GotoDefinitionParams, GotoDefinitionResponse,
|
|
14
|
+
Hover, HoverContents, HoverParams, HoverProviderCapability, InitializeParams, InitializeResult,
|
|
15
|
+
Location, MarkupContent, MarkupKind, MessageType, NumberOrString, OneOf, Position, Range,
|
|
16
|
+
ReferenceParams, RenameOptions, RenameParams, ServerCapabilities, ServerInfo,
|
|
17
|
+
WorkDoneProgressOptions,
|
|
18
|
+
SymbolInformation, SymbolKind, TextDocumentPositionParams, TextDocumentSyncCapability,
|
|
19
|
+
TextDocumentSyncKind, Url, WorkspaceEdit, WorkspaceSymbolParams,
|
|
18
20
|
};
|
|
21
|
+
use tower_lsp::lsp_types::{PrepareRenameResponse, TextEdit};
|
|
19
22
|
use tower_lsp::{Client, LanguageServer, LspService, Server};
|
|
20
23
|
use walkdir::WalkDir;
|
|
21
24
|
|
|
25
|
+
mod builtin_goto;
|
|
26
|
+
mod import_goto;
|
|
27
|
+
|
|
22
28
|
#[derive(Debug)]
|
|
23
29
|
struct Backend {
|
|
24
30
|
client: Client,
|
|
25
31
|
docs: Arc<RwLock<HashMap<Url, String>>>,
|
|
26
32
|
roots: Arc<RwLock<Vec<PathBuf>>>,
|
|
33
|
+
/// `(project_root, cargo:spec)` → resolved dependency source root (for `cargo metadata` / registry).
|
|
34
|
+
cargo_src_cache: Arc<RwLock<HashMap<(PathBuf, String), PathBuf>>>,
|
|
35
|
+
/// Root of the `tishlang/tish` checkout (parent of `crates/`), for built-in / JSX goto-definition.
|
|
36
|
+
tishlang_source_root: Arc<RwLock<Option<PathBuf>>>,
|
|
27
37
|
}
|
|
28
38
|
|
|
29
39
|
#[tokio::main]
|
|
@@ -35,6 +45,8 @@ async fn main() {
|
|
|
35
45
|
client,
|
|
36
46
|
docs: Arc::new(RwLock::new(HashMap::new())),
|
|
37
47
|
roots: Arc::new(RwLock::new(Vec::new())),
|
|
48
|
+
cargo_src_cache: Arc::new(RwLock::new(HashMap::new())),
|
|
49
|
+
tishlang_source_root: Arc::new(RwLock::new(None)),
|
|
38
50
|
});
|
|
39
51
|
Server::new(stdin, stdout, socket).serve(service).await;
|
|
40
52
|
}
|
|
@@ -83,6 +95,43 @@ fn publish_parse_and_lint(client: &Client, uri: Url, text: &str) {
|
|
|
83
95
|
..Default::default()
|
|
84
96
|
});
|
|
85
97
|
}
|
|
98
|
+
for u in tishlang_resolve::collect_unresolved_identifiers(&program) {
|
|
99
|
+
diags.push(Diagnostic {
|
|
100
|
+
range: span_to_range(&u.span, text),
|
|
101
|
+
severity: Some(DiagnosticSeverity::ERROR),
|
|
102
|
+
code: Some(NumberOrString::String("tish-unresolved-name".into())),
|
|
103
|
+
message: format!("no binding in scope for `{}`", u.name),
|
|
104
|
+
..Default::default()
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
for ub in tishlang_resolve::collect_unused_bindings(&program, text) {
|
|
108
|
+
let (message, code) = match ub.kind {
|
|
109
|
+
tishlang_resolve::UnusedBindingKind::Import => (
|
|
110
|
+
format!("`{}` is imported but never used", ub.name),
|
|
111
|
+
"tish-unused-import",
|
|
112
|
+
),
|
|
113
|
+
tishlang_resolve::UnusedBindingKind::Parameter => (
|
|
114
|
+
format!("`{}` is declared but never read", ub.name),
|
|
115
|
+
"tish-unused-parameter",
|
|
116
|
+
),
|
|
117
|
+
tishlang_resolve::UnusedBindingKind::Variable => (
|
|
118
|
+
format!(
|
|
119
|
+
"`{}` is declared but its value is never read",
|
|
120
|
+
ub.name
|
|
121
|
+
),
|
|
122
|
+
"tish-unused-variable",
|
|
123
|
+
),
|
|
124
|
+
};
|
|
125
|
+
diags.push(Diagnostic {
|
|
126
|
+
range: span_to_range(&ub.span, text),
|
|
127
|
+
severity: Some(DiagnosticSeverity::HINT),
|
|
128
|
+
code: Some(NumberOrString::String(code.into())),
|
|
129
|
+
message,
|
|
130
|
+
tags: Some(vec![DiagnosticTag::UNNECESSARY]),
|
|
131
|
+
source: Some("tish".into()),
|
|
132
|
+
..Default::default()
|
|
133
|
+
});
|
|
134
|
+
}
|
|
86
135
|
}
|
|
87
136
|
Err(e) => {
|
|
88
137
|
let (l, c) = parse_error_pos(&e);
|
|
@@ -113,6 +162,30 @@ impl LanguageServer for Backend {
|
|
|
113
162
|
roots.push(p);
|
|
114
163
|
}
|
|
115
164
|
}
|
|
165
|
+
|
|
166
|
+
let mut src_root: Option<PathBuf> = None;
|
|
167
|
+
if let Some(opts) = ¶ms.initialization_options {
|
|
168
|
+
if let Some(s) = opts
|
|
169
|
+
.get("tishlangSourceRoot")
|
|
170
|
+
.and_then(|v| v.as_str())
|
|
171
|
+
.map(str::trim)
|
|
172
|
+
{
|
|
173
|
+
if !s.is_empty() {
|
|
174
|
+
src_root = Some(PathBuf::from(s));
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
if src_root.is_none() {
|
|
179
|
+
if let Ok(s) = std::env::var("TISHLANG_SOURCE_ROOT") {
|
|
180
|
+
let t = s.trim();
|
|
181
|
+
if !t.is_empty() {
|
|
182
|
+
src_root = Some(PathBuf::from(t));
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
let mut g = self.tishlang_source_root.write().unwrap();
|
|
187
|
+
*g = src_root.filter(|p| p.is_dir());
|
|
188
|
+
|
|
116
189
|
Ok(InitializeResult {
|
|
117
190
|
capabilities: ServerCapabilities {
|
|
118
191
|
text_document_sync: Some(TextDocumentSyncCapability::Kind(
|
|
@@ -122,7 +195,13 @@ impl LanguageServer for Backend {
|
|
|
122
195
|
trigger_characters: Some(vec![".".to_string()]),
|
|
123
196
|
..Default::default()
|
|
124
197
|
}),
|
|
198
|
+
hover_provider: Some(HoverProviderCapability::Simple(true)),
|
|
125
199
|
definition_provider: Some(OneOf::Left(true)),
|
|
200
|
+
references_provider: Some(OneOf::Left(true)),
|
|
201
|
+
rename_provider: Some(OneOf::Right(RenameOptions {
|
|
202
|
+
prepare_provider: Some(true),
|
|
203
|
+
work_done_progress_options: WorkDoneProgressOptions::default(),
|
|
204
|
+
})),
|
|
126
205
|
document_formatting_provider: Some(OneOf::Left(true)),
|
|
127
206
|
document_symbol_provider: Some(OneOf::Left(true)),
|
|
128
207
|
workspace_symbol_provider: Some(OneOf::Left(true)),
|
|
@@ -171,12 +250,8 @@ impl LanguageServer for Backend {
|
|
|
171
250
|
}
|
|
172
251
|
|
|
173
252
|
async fn completion(&self, params: CompletionParams) -> Result<Option<CompletionResponse>> {
|
|
174
|
-
let uri = params
|
|
175
|
-
|
|
176
|
-
.text_document
|
|
177
|
-
.uri
|
|
178
|
-
.clone();
|
|
179
|
-
let _pos = params.text_document_position.position;
|
|
253
|
+
let uri = params.text_document_position.text_document.uri.clone();
|
|
254
|
+
let pos = params.text_document_position.position;
|
|
180
255
|
let text = {
|
|
181
256
|
let g = self.docs.read().unwrap();
|
|
182
257
|
g.get(&uri).cloned()
|
|
@@ -201,32 +276,23 @@ impl LanguageServer for Backend {
|
|
|
201
276
|
.collect();
|
|
202
277
|
|
|
203
278
|
if let Ok(program) = tishlang_parser::parse(&text) {
|
|
204
|
-
for
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
label: name.to_string(),
|
|
216
|
-
kind: Some(CompletionItemKind::VARIABLE),
|
|
217
|
-
..Default::default()
|
|
218
|
-
});
|
|
219
|
-
}
|
|
220
|
-
_ => {}
|
|
221
|
-
}
|
|
279
|
+
for name in tishlang_resolve::completion_value_names_at_cursor(
|
|
280
|
+
&program,
|
|
281
|
+
&text,
|
|
282
|
+
pos.line,
|
|
283
|
+
pos.character,
|
|
284
|
+
) {
|
|
285
|
+
items.push(CompletionItem {
|
|
286
|
+
label: name.to_string(),
|
|
287
|
+
kind: Some(value_completion_kind(&program, name.as_ref())),
|
|
288
|
+
..Default::default()
|
|
289
|
+
});
|
|
222
290
|
}
|
|
223
291
|
}
|
|
224
292
|
|
|
225
293
|
if let Some(ctx) = params.context {
|
|
226
|
-
if matches!(
|
|
227
|
-
ctx.
|
|
228
|
-
CompletionTriggerKind::TRIGGER_CHARACTER
|
|
229
|
-
) && ctx.trigger_character.as_deref() == Some(".")
|
|
294
|
+
if matches!(ctx.trigger_kind, CompletionTriggerKind::TRIGGER_CHARACTER)
|
|
295
|
+
&& ctx.trigger_character.as_deref() == Some(".")
|
|
230
296
|
{
|
|
231
297
|
// After dot: could add member completion later
|
|
232
298
|
}
|
|
@@ -278,71 +344,277 @@ impl LanguageServer for Backend {
|
|
|
278
344
|
return Ok(None);
|
|
279
345
|
};
|
|
280
346
|
|
|
347
|
+
if let Some(def) =
|
|
348
|
+
tishlang_resolve::definition_span(&program, &text, position.line, position.character)
|
|
349
|
+
{
|
|
350
|
+
let range = span_to_range(&def, &text);
|
|
351
|
+
return Ok(Some(GotoDefinitionResponse::Scalar(Location {
|
|
352
|
+
uri: uri.clone(),
|
|
353
|
+
range,
|
|
354
|
+
})));
|
|
355
|
+
}
|
|
356
|
+
|
|
281
357
|
let word = word_at_position(&text, position);
|
|
282
358
|
if word.is_empty() {
|
|
283
359
|
return Ok(None);
|
|
284
360
|
}
|
|
285
361
|
|
|
286
|
-
let
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
362
|
+
if let Some(ref file_path) = uri.to_file_path().ok() {
|
|
363
|
+
let roots = self.roots.read().unwrap().clone();
|
|
364
|
+
if let Some(loc) = import_goto::definition_for_import(
|
|
365
|
+
&program,
|
|
366
|
+
file_path,
|
|
367
|
+
word.as_str(),
|
|
368
|
+
&roots,
|
|
369
|
+
self.cargo_src_cache.as_ref(),
|
|
370
|
+
) {
|
|
290
371
|
return Ok(Some(GotoDefinitionResponse::Scalar(loc)));
|
|
291
372
|
}
|
|
373
|
+
if let Some(loc) = import_goto::definition_for_native_receiver_member(
|
|
374
|
+
&program,
|
|
375
|
+
file_path,
|
|
376
|
+
&text,
|
|
377
|
+
&roots,
|
|
378
|
+
self.cargo_src_cache.as_ref(),
|
|
379
|
+
position.line,
|
|
380
|
+
position.character,
|
|
381
|
+
word.as_str(),
|
|
382
|
+
) {
|
|
383
|
+
return Ok(Some(GotoDefinitionResponse::Scalar(loc)));
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
if let Some(root) = self.tishlang_source_root.read().unwrap().clone() {
|
|
388
|
+
if let Some(bdef) = builtin_goto::definition_for_builtin(
|
|
389
|
+
&text,
|
|
390
|
+
position.line,
|
|
391
|
+
position.character,
|
|
392
|
+
word.as_str(),
|
|
393
|
+
) {
|
|
394
|
+
if let Some(loc) = builtin_goto::to_file_location(&root, &bdef) {
|
|
395
|
+
return Ok(Some(GotoDefinitionResponse::Scalar(loc)));
|
|
396
|
+
}
|
|
397
|
+
}
|
|
292
398
|
}
|
|
293
399
|
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
400
|
+
Ok(None)
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
async fn hover(&self, params: HoverParams) -> Result<Option<Hover>> {
|
|
404
|
+
let pos = params.text_document_position_params.position;
|
|
405
|
+
let uri = params.text_document_position_params.text_document.uri;
|
|
406
|
+
let text = {
|
|
407
|
+
let g = self.docs.read().unwrap();
|
|
408
|
+
g.get(&uri).cloned()
|
|
409
|
+
};
|
|
410
|
+
let Some(text) = text else {
|
|
411
|
+
return Ok(None);
|
|
412
|
+
};
|
|
413
|
+
let Ok(program) = tishlang_parser::parse(&text) else {
|
|
414
|
+
return Ok(None);
|
|
415
|
+
};
|
|
416
|
+
let Some(use_site) =
|
|
417
|
+
tishlang_resolve::name_at_cursor(&program, &text, pos.line, pos.character)
|
|
418
|
+
else {
|
|
419
|
+
return Ok(None);
|
|
420
|
+
};
|
|
421
|
+
let def = tishlang_resolve::definition_span(&program, &text, pos.line, pos.character);
|
|
422
|
+
let mut md = format!("**`{}`**", use_site.name);
|
|
423
|
+
match def {
|
|
424
|
+
Some(def) if def.start == use_site.span.start && def.end == use_site.span.end => {
|
|
425
|
+
md.push_str("\n\n_(binding site)_");
|
|
426
|
+
}
|
|
427
|
+
Some(def) => {
|
|
428
|
+
md.push_str(&format!(
|
|
429
|
+
"\n\nDefined at line {} col {}",
|
|
430
|
+
def.start.0, def.start.1
|
|
431
|
+
));
|
|
432
|
+
}
|
|
433
|
+
None => {
|
|
434
|
+
if tishlang_resolve::is_runtime_global_ident(use_site.name.as_ref()) {
|
|
435
|
+
md.push_str("\n\n_Interpreter root global (no lexical declaration in this file)._");
|
|
436
|
+
let word = word_at_position(&text, pos);
|
|
437
|
+
if !word.is_empty() {
|
|
438
|
+
if let Some(root) = self.tishlang_source_root.read().unwrap().clone() {
|
|
439
|
+
if let Some(bdef) = builtin_goto::definition_for_builtin(
|
|
440
|
+
&text,
|
|
441
|
+
pos.line,
|
|
442
|
+
pos.character,
|
|
443
|
+
word.as_str(),
|
|
444
|
+
) {
|
|
445
|
+
if let Some(loc) = builtin_goto::to_file_location(&root, &bdef) {
|
|
446
|
+
// VS Code treats `#L<1-based-line>` on file URLs like "go to line".
|
|
447
|
+
let line_1 = bdef.line.saturating_add(1);
|
|
448
|
+
let href = loc.uri.as_str();
|
|
449
|
+
md.push_str(&format!(
|
|
450
|
+
"\n\n[Open in Tish sources]({href}#L{line_1}) (`{}`)",
|
|
451
|
+
bdef.rel_path
|
|
452
|
+
));
|
|
453
|
+
}
|
|
306
454
|
}
|
|
307
|
-
tishlang_ast::ImportSpecifier::Default(n) => (n.as_ref(), n.as_ref()),
|
|
308
|
-
_ => continue,
|
|
309
|
-
};
|
|
310
|
-
if local != word.as_str() {
|
|
311
|
-
continue;
|
|
312
455
|
}
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
let
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
456
|
+
}
|
|
457
|
+
} else {
|
|
458
|
+
let word = word_at_position(&text, pos);
|
|
459
|
+
if word.is_empty() {
|
|
460
|
+
md.push_str("\n\n_No binding in scope for this name._");
|
|
461
|
+
} else if let Ok(fp) = uri.to_file_path() {
|
|
462
|
+
let roots = self.roots.read().unwrap().clone();
|
|
463
|
+
if let Some(nmd) = import_goto::native_member_definition(
|
|
464
|
+
&program,
|
|
465
|
+
&fp,
|
|
466
|
+
&text,
|
|
467
|
+
&roots,
|
|
468
|
+
self.cargo_src_cache.as_ref(),
|
|
469
|
+
pos.line,
|
|
470
|
+
pos.character,
|
|
471
|
+
word.as_str(),
|
|
472
|
+
) {
|
|
473
|
+
md.push_str(
|
|
474
|
+
"\n\n_Native host module member (e.g. `tish:macos`); implementation in Rust._",
|
|
475
|
+
);
|
|
476
|
+
if let Some(ref d) = nmd.doc {
|
|
477
|
+
md.push_str("\n\n");
|
|
478
|
+
md.push_str(d);
|
|
335
479
|
}
|
|
480
|
+
let loc = nmd.location;
|
|
481
|
+
let line_1 = loc.range.start.line.saturating_add(1);
|
|
482
|
+
let href = loc.uri.as_str();
|
|
483
|
+
md.push_str(&format!(
|
|
484
|
+
"\n\n[Open Rust implementation]({href}#L{line_1})"
|
|
485
|
+
));
|
|
486
|
+
} else {
|
|
487
|
+
md.push_str("\n\n_No binding in scope for this name._");
|
|
336
488
|
}
|
|
489
|
+
} else {
|
|
490
|
+
md.push_str("\n\n_No binding in scope for this name._");
|
|
337
491
|
}
|
|
338
492
|
}
|
|
339
493
|
}
|
|
340
494
|
}
|
|
495
|
+
Ok(Some(Hover {
|
|
496
|
+
range: Some(span_to_range(&use_site.span, &text)),
|
|
497
|
+
contents: HoverContents::Markup(MarkupContent {
|
|
498
|
+
kind: MarkupKind::Markdown,
|
|
499
|
+
value: md,
|
|
500
|
+
}),
|
|
501
|
+
}))
|
|
502
|
+
}
|
|
341
503
|
|
|
342
|
-
|
|
504
|
+
async fn references(&self, params: ReferenceParams) -> Result<Option<Vec<Location>>> {
|
|
505
|
+
let pos = params.text_document_position.position;
|
|
506
|
+
let uri = params.text_document_position.text_document.uri;
|
|
507
|
+
let text = {
|
|
508
|
+
let g = self.docs.read().unwrap();
|
|
509
|
+
g.get(&uri).cloned()
|
|
510
|
+
};
|
|
511
|
+
let Some(text) = text else {
|
|
512
|
+
return Ok(None);
|
|
513
|
+
};
|
|
514
|
+
let Ok(program) = tishlang_parser::parse(&text) else {
|
|
515
|
+
return Ok(None);
|
|
516
|
+
};
|
|
517
|
+
let Some(def) = tishlang_resolve::definition_span(&program, &text, pos.line, pos.character)
|
|
518
|
+
else {
|
|
519
|
+
return Ok(None);
|
|
520
|
+
};
|
|
521
|
+
let Some(nu) = tishlang_resolve::name_at_cursor(&program, &text, pos.line, pos.character)
|
|
522
|
+
else {
|
|
523
|
+
return Ok(None);
|
|
524
|
+
};
|
|
525
|
+
let spans =
|
|
526
|
+
tishlang_resolve::reference_spans_for_def(&program, &text, nu.name.as_ref(), def);
|
|
527
|
+
let locs: Vec<Location> = spans
|
|
528
|
+
.into_iter()
|
|
529
|
+
.map(|sp| Location {
|
|
530
|
+
uri: uri.clone(),
|
|
531
|
+
range: span_to_range(&sp, &text),
|
|
532
|
+
})
|
|
533
|
+
.collect();
|
|
534
|
+
Ok(Some(locs))
|
|
343
535
|
}
|
|
344
536
|
|
|
345
|
-
async fn
|
|
537
|
+
async fn prepare_rename(
|
|
538
|
+
&self,
|
|
539
|
+
params: TextDocumentPositionParams,
|
|
540
|
+
) -> Result<Option<PrepareRenameResponse>> {
|
|
541
|
+
let pos = params.position;
|
|
542
|
+
let uri = params.text_document.uri;
|
|
543
|
+
let text = {
|
|
544
|
+
let g = self.docs.read().unwrap();
|
|
545
|
+
g.get(&uri).cloned()
|
|
546
|
+
};
|
|
547
|
+
let Some(text) = text else {
|
|
548
|
+
return Ok(None);
|
|
549
|
+
};
|
|
550
|
+
let Ok(program) = tishlang_parser::parse(&text) else {
|
|
551
|
+
return Ok(None);
|
|
552
|
+
};
|
|
553
|
+
let Some(nu) = tishlang_resolve::name_at_cursor(&program, &text, pos.line, pos.character)
|
|
554
|
+
else {
|
|
555
|
+
return Ok(None);
|
|
556
|
+
};
|
|
557
|
+
let range = span_to_range(&nu.span, &text);
|
|
558
|
+
Ok(Some(PrepareRenameResponse::RangeWithPlaceholder {
|
|
559
|
+
range,
|
|
560
|
+
placeholder: nu.name.to_string(),
|
|
561
|
+
}))
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
async fn rename(&self, params: RenameParams) -> Result<Option<WorkspaceEdit>> {
|
|
565
|
+
let pos = params.text_document_position.position;
|
|
566
|
+
let uri = params.text_document_position.text_document.uri;
|
|
567
|
+
let new_name = params.new_name;
|
|
568
|
+
let text = {
|
|
569
|
+
let g = self.docs.read().unwrap();
|
|
570
|
+
g.get(&uri).cloned()
|
|
571
|
+
};
|
|
572
|
+
let Some(text) = text else {
|
|
573
|
+
return Ok(None);
|
|
574
|
+
};
|
|
575
|
+
let Ok(program) = tishlang_parser::parse(&text) else {
|
|
576
|
+
return Ok(None);
|
|
577
|
+
};
|
|
578
|
+
let Some(def) = tishlang_resolve::definition_span(&program, &text, pos.line, pos.character)
|
|
579
|
+
else {
|
|
580
|
+
return Ok(None);
|
|
581
|
+
};
|
|
582
|
+
let Some(nu) = tishlang_resolve::name_at_cursor(&program, &text, pos.line, pos.character)
|
|
583
|
+
else {
|
|
584
|
+
return Ok(None);
|
|
585
|
+
};
|
|
586
|
+
let spans = tishlang_resolve::reference_spans_for_def(
|
|
587
|
+
&program,
|
|
588
|
+
&text,
|
|
589
|
+
nu.name.as_ref(),
|
|
590
|
+
def,
|
|
591
|
+
);
|
|
592
|
+
let mut edits: Vec<TextEdit> = spans
|
|
593
|
+
.into_iter()
|
|
594
|
+
.map(|sp| TextEdit {
|
|
595
|
+
range: span_to_range(&sp, &text),
|
|
596
|
+
new_text: new_name.clone(),
|
|
597
|
+
})
|
|
598
|
+
.collect();
|
|
599
|
+
// Apply from end of document so earlier ranges stay valid when lengths change.
|
|
600
|
+
edits.sort_by(|a, b| {
|
|
601
|
+
(b.range.start.line, b.range.start.character).cmp(&(
|
|
602
|
+
a.range.start.line,
|
|
603
|
+
a.range.start.character,
|
|
604
|
+
))
|
|
605
|
+
});
|
|
606
|
+
let mut m = HashMap::new();
|
|
607
|
+
m.insert(uri, edits);
|
|
608
|
+
Ok(Some(WorkspaceEdit {
|
|
609
|
+
changes: Some(m),
|
|
610
|
+
..Default::default()
|
|
611
|
+
}))
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
async fn formatting(
|
|
615
|
+
&self,
|
|
616
|
+
params: DocumentFormattingParams,
|
|
617
|
+
) -> Result<Option<Vec<tower_lsp::lsp_types::TextEdit>>> {
|
|
346
618
|
let uri = params.text_document.uri;
|
|
347
619
|
let text = {
|
|
348
620
|
let g = self.docs.read().unwrap();
|
|
@@ -365,10 +637,7 @@ impl LanguageServer for Backend {
|
|
|
365
637
|
}
|
|
366
638
|
Err(e) => {
|
|
367
639
|
self.client
|
|
368
|
-
.show_message(
|
|
369
|
-
MessageType::ERROR,
|
|
370
|
-
format!("tish-fmt (formatter): {}", e),
|
|
371
|
-
)
|
|
640
|
+
.show_message(MessageType::ERROR, format!("tish-fmt (formatter): {}", e))
|
|
372
641
|
.await;
|
|
373
642
|
Ok(None)
|
|
374
643
|
}
|
|
@@ -419,7 +688,11 @@ fn collect_workspace_syms(
|
|
|
419
688
|
out: &mut Vec<SymbolInformation>,
|
|
420
689
|
) {
|
|
421
690
|
match s {
|
|
422
|
-
tishlang_ast::Statement::FunDecl {
|
|
691
|
+
tishlang_ast::Statement::FunDecl {
|
|
692
|
+
name,
|
|
693
|
+
name_span,
|
|
694
|
+
..
|
|
695
|
+
} => {
|
|
423
696
|
if name.to_lowercase().contains(query) {
|
|
424
697
|
out.push(SymbolInformation {
|
|
425
698
|
name: name.to_string(),
|
|
@@ -428,13 +701,17 @@ fn collect_workspace_syms(
|
|
|
428
701
|
deprecated: None,
|
|
429
702
|
location: Location {
|
|
430
703
|
uri: uri.clone(),
|
|
431
|
-
range: span_to_range(
|
|
704
|
+
range: span_to_range(name_span, text),
|
|
432
705
|
},
|
|
433
706
|
container_name: None,
|
|
434
707
|
});
|
|
435
708
|
}
|
|
436
709
|
}
|
|
437
|
-
tishlang_ast::Statement::VarDecl {
|
|
710
|
+
tishlang_ast::Statement::VarDecl {
|
|
711
|
+
name,
|
|
712
|
+
name_span,
|
|
713
|
+
..
|
|
714
|
+
} => {
|
|
438
715
|
if name.to_lowercase().contains(query) {
|
|
439
716
|
out.push(SymbolInformation {
|
|
440
717
|
name: name.to_string(),
|
|
@@ -443,7 +720,7 @@ fn collect_workspace_syms(
|
|
|
443
720
|
deprecated: None,
|
|
444
721
|
location: Location {
|
|
445
722
|
uri: uri.clone(),
|
|
446
|
-
range: span_to_range(
|
|
723
|
+
range: span_to_range(name_span, text),
|
|
447
724
|
},
|
|
448
725
|
container_name: None,
|
|
449
726
|
});
|
|
@@ -458,7 +735,7 @@ fn collect_workspace_syms(
|
|
|
458
735
|
}
|
|
459
736
|
}
|
|
460
737
|
|
|
461
|
-
fn find_export(
|
|
738
|
+
pub(crate) fn find_export(
|
|
462
739
|
program: &tishlang_ast::Program,
|
|
463
740
|
name: &str,
|
|
464
741
|
uri: &Url,
|
|
@@ -466,16 +743,24 @@ fn find_export(
|
|
|
466
743
|
) -> Option<Location> {
|
|
467
744
|
for s in &program.statements {
|
|
468
745
|
match s {
|
|
469
|
-
tishlang_ast::Statement::FunDecl {
|
|
746
|
+
tishlang_ast::Statement::FunDecl {
|
|
747
|
+
name: n,
|
|
748
|
+
name_span,
|
|
749
|
+
..
|
|
750
|
+
} if n.as_ref() == name => {
|
|
470
751
|
return Some(Location {
|
|
471
752
|
uri: uri.clone(),
|
|
472
|
-
range: span_to_range(
|
|
753
|
+
range: span_to_range(name_span, text),
|
|
473
754
|
});
|
|
474
755
|
}
|
|
475
|
-
tishlang_ast::Statement::VarDecl {
|
|
756
|
+
tishlang_ast::Statement::VarDecl {
|
|
757
|
+
name: n,
|
|
758
|
+
name_span,
|
|
759
|
+
..
|
|
760
|
+
} if n.as_ref() == name => {
|
|
476
761
|
return Some(Location {
|
|
477
762
|
uri: uri.clone(),
|
|
478
|
-
range: span_to_range(
|
|
763
|
+
range: span_to_range(name_span, text),
|
|
479
764
|
});
|
|
480
765
|
}
|
|
481
766
|
tishlang_ast::Statement::Export { declaration, .. } => match declaration.as_ref() {
|
|
@@ -499,13 +784,21 @@ fn find_decl_in_stmt(
|
|
|
499
784
|
text: &str,
|
|
500
785
|
) -> Option<Location> {
|
|
501
786
|
match s {
|
|
502
|
-
tishlang_ast::Statement::FunDecl {
|
|
787
|
+
tishlang_ast::Statement::FunDecl {
|
|
788
|
+
name,
|
|
789
|
+
name_span,
|
|
790
|
+
..
|
|
791
|
+
} if name.as_ref() == word => Some(Location {
|
|
503
792
|
uri: uri.clone(),
|
|
504
|
-
range: span_to_range(
|
|
793
|
+
range: span_to_range(name_span, text),
|
|
505
794
|
}),
|
|
506
|
-
tishlang_ast::Statement::VarDecl {
|
|
795
|
+
tishlang_ast::Statement::VarDecl {
|
|
796
|
+
name,
|
|
797
|
+
name_span,
|
|
798
|
+
..
|
|
799
|
+
} if name.as_ref() == word => Some(Location {
|
|
507
800
|
uri: uri.clone(),
|
|
508
|
-
range: span_to_range(
|
|
801
|
+
range: span_to_range(name_span, text),
|
|
509
802
|
}),
|
|
510
803
|
tishlang_ast::Statement::Block { statements, .. } => {
|
|
511
804
|
for x in statements {
|
|
@@ -519,10 +812,23 @@ fn find_decl_in_stmt(
|
|
|
519
812
|
}
|
|
520
813
|
}
|
|
521
814
|
|
|
522
|
-
fn span_to_range(span: &tishlang_ast::Span,
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
815
|
+
fn span_to_range(span: &tishlang_ast::Span, text: &str) -> Range {
|
|
816
|
+
if let Some(((sl, sc), (el, ec))) = tishlang_resolve::span_to_lsp_range_exclusive(text, span) {
|
|
817
|
+
Range {
|
|
818
|
+
start: pos(sl, sc),
|
|
819
|
+
end: pos(el, ec),
|
|
820
|
+
}
|
|
821
|
+
} else {
|
|
822
|
+
Range {
|
|
823
|
+
start: pos(
|
|
824
|
+
span.start.0.saturating_sub(1) as u32,
|
|
825
|
+
span.start.1.saturating_sub(1) as u32,
|
|
826
|
+
),
|
|
827
|
+
end: pos(
|
|
828
|
+
span.end.0.saturating_sub(1) as u32,
|
|
829
|
+
span.end.1.saturating_sub(1) as u32,
|
|
830
|
+
),
|
|
831
|
+
}
|
|
526
832
|
}
|
|
527
833
|
}
|
|
528
834
|
|
|
@@ -549,6 +855,92 @@ fn is_ident_char(c: char) -> bool {
|
|
|
549
855
|
c.is_alphanumeric() || c == '_'
|
|
550
856
|
}
|
|
551
857
|
|
|
858
|
+
fn value_completion_kind(program: &tishlang_ast::Program, name: &str) -> CompletionItemKind {
|
|
859
|
+
for s in &program.statements {
|
|
860
|
+
if let Some(k) = value_completion_kind_stmt(s, name) {
|
|
861
|
+
return k;
|
|
862
|
+
}
|
|
863
|
+
}
|
|
864
|
+
CompletionItemKind::VARIABLE
|
|
865
|
+
}
|
|
866
|
+
|
|
867
|
+
fn value_completion_kind_stmt(
|
|
868
|
+
s: &tishlang_ast::Statement,
|
|
869
|
+
name: &str,
|
|
870
|
+
) -> Option<CompletionItemKind> {
|
|
871
|
+
match s {
|
|
872
|
+
tishlang_ast::Statement::FunDecl { name: n, .. } if n.as_ref() == name => {
|
|
873
|
+
Some(CompletionItemKind::FUNCTION)
|
|
874
|
+
}
|
|
875
|
+
tishlang_ast::Statement::VarDecl { name: n, .. } if n.as_ref() == name => {
|
|
876
|
+
Some(CompletionItemKind::VARIABLE)
|
|
877
|
+
}
|
|
878
|
+
tishlang_ast::Statement::Import { specifiers, .. } => {
|
|
879
|
+
for sp in specifiers {
|
|
880
|
+
let local = match sp {
|
|
881
|
+
tishlang_ast::ImportSpecifier::Named { name: n, alias, .. } => {
|
|
882
|
+
alias.as_ref().map(|a| a.as_ref()).unwrap_or(n.as_ref())
|
|
883
|
+
}
|
|
884
|
+
tishlang_ast::ImportSpecifier::Default { name: n, .. } => n.as_ref(),
|
|
885
|
+
tishlang_ast::ImportSpecifier::Namespace { name: n, .. } => n.as_ref(),
|
|
886
|
+
};
|
|
887
|
+
if local == name {
|
|
888
|
+
return Some(CompletionItemKind::VARIABLE);
|
|
889
|
+
}
|
|
890
|
+
}
|
|
891
|
+
None
|
|
892
|
+
}
|
|
893
|
+
tishlang_ast::Statement::Block { statements, .. } => statements
|
|
894
|
+
.iter()
|
|
895
|
+
.find_map(|x| value_completion_kind_stmt(x, name)),
|
|
896
|
+
tishlang_ast::Statement::If {
|
|
897
|
+
then_branch,
|
|
898
|
+
else_branch,
|
|
899
|
+
..
|
|
900
|
+
} => value_completion_kind_stmt(then_branch, name).or_else(|| {
|
|
901
|
+
else_branch
|
|
902
|
+
.as_ref()
|
|
903
|
+
.and_then(|b| value_completion_kind_stmt(b, name))
|
|
904
|
+
}),
|
|
905
|
+
tishlang_ast::Statement::While { body, .. }
|
|
906
|
+
| tishlang_ast::Statement::ForOf { body, .. }
|
|
907
|
+
| tishlang_ast::Statement::DoWhile { body, .. } => value_completion_kind_stmt(body, name),
|
|
908
|
+
tishlang_ast::Statement::For { init, body, .. } => init
|
|
909
|
+
.as_ref()
|
|
910
|
+
.and_then(|i| value_completion_kind_stmt(i, name))
|
|
911
|
+
.or_else(|| value_completion_kind_stmt(body, name)),
|
|
912
|
+
tishlang_ast::Statement::Try {
|
|
913
|
+
body,
|
|
914
|
+
catch_body,
|
|
915
|
+
finally_body,
|
|
916
|
+
..
|
|
917
|
+
} => value_completion_kind_stmt(body, name)
|
|
918
|
+
.or_else(|| catch_body.as_ref().and_then(|b| value_completion_kind_stmt(b, name)))
|
|
919
|
+
.or_else(|| finally_body.as_ref().and_then(|b| value_completion_kind_stmt(b, name))),
|
|
920
|
+
tishlang_ast::Statement::Switch {
|
|
921
|
+
cases,
|
|
922
|
+
default_body,
|
|
923
|
+
..
|
|
924
|
+
} => {
|
|
925
|
+
for (_e, stmts) in cases {
|
|
926
|
+
if let Some(k) = stmts.iter().find_map(|st| value_completion_kind_stmt(st, name)) {
|
|
927
|
+
return Some(k);
|
|
928
|
+
}
|
|
929
|
+
}
|
|
930
|
+
default_body.as_ref().and_then(|stmts| {
|
|
931
|
+
stmts
|
|
932
|
+
.iter()
|
|
933
|
+
.find_map(|st| value_completion_kind_stmt(st, name))
|
|
934
|
+
})
|
|
935
|
+
}
|
|
936
|
+
tishlang_ast::Statement::Export { declaration, .. } => match declaration.as_ref() {
|
|
937
|
+
tishlang_ast::ExportDeclaration::Named(inner) => value_completion_kind_stmt(inner, name),
|
|
938
|
+
tishlang_ast::ExportDeclaration::Default(_) => None,
|
|
939
|
+
},
|
|
940
|
+
_ => None,
|
|
941
|
+
}
|
|
942
|
+
}
|
|
943
|
+
|
|
552
944
|
fn doc_symbol_stmt(
|
|
553
945
|
s: &tishlang_ast::Statement,
|
|
554
946
|
text: &str,
|
|
@@ -557,6 +949,7 @@ fn doc_symbol_stmt(
|
|
|
557
949
|
match s {
|
|
558
950
|
tishlang_ast::Statement::FunDecl {
|
|
559
951
|
name,
|
|
952
|
+
name_span,
|
|
560
953
|
span,
|
|
561
954
|
body,
|
|
562
955
|
..
|
|
@@ -570,7 +963,7 @@ fn doc_symbol_stmt(
|
|
|
570
963
|
tags: None,
|
|
571
964
|
deprecated: None,
|
|
572
965
|
range: span_to_range(span, text),
|
|
573
|
-
selection_range: span_to_range(
|
|
966
|
+
selection_range: span_to_range(name_span, text),
|
|
574
967
|
children: if children.is_empty() {
|
|
575
968
|
None
|
|
576
969
|
} else {
|
|
@@ -578,7 +971,12 @@ fn doc_symbol_stmt(
|
|
|
578
971
|
},
|
|
579
972
|
});
|
|
580
973
|
}
|
|
581
|
-
tishlang_ast::Statement::VarDecl {
|
|
974
|
+
tishlang_ast::Statement::VarDecl {
|
|
975
|
+
name,
|
|
976
|
+
name_span,
|
|
977
|
+
span,
|
|
978
|
+
..
|
|
979
|
+
} => {
|
|
582
980
|
out.push(tower_lsp::lsp_types::DocumentSymbol {
|
|
583
981
|
name: name.to_string(),
|
|
584
982
|
detail: None,
|
|
@@ -586,7 +984,7 @@ fn doc_symbol_stmt(
|
|
|
586
984
|
tags: None,
|
|
587
985
|
deprecated: None,
|
|
588
986
|
range: span_to_range(span, text),
|
|
589
|
-
selection_range: span_to_range(
|
|
987
|
+
selection_range: span_to_range(name_span, text),
|
|
590
988
|
children: None,
|
|
591
989
|
});
|
|
592
990
|
}
|