@tishlang/tish 1.7.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 +1 -0
- package/README.md +2 -0
- package/bin/tish +0 -0
- package/crates/js_to_tish/src/transform/expr.rs +28 -8
- package/crates/js_to_tish/src/transform/stmt.rs +49 -22
- package/crates/tish/Cargo.toml +15 -5
- package/crates/tish/src/cargo_native_registry.rs +29 -0
- package/crates/tish/src/cli_help.rs +16 -10
- package/crates/tish/src/main.rs +87 -32
- package/crates/tish/src/repl_completion.rs +3 -3
- package/crates/tish/tests/cargo_example_compile.rs +1 -1
- package/crates/tish/tests/integration_test.rs +19 -7
- package/crates/tish/tests/shortcircuit.rs +1 -1
- package/crates/tish_ast/src/ast.rs +80 -9
- package/crates/tish_build_utils/Cargo.toml +4 -0
- package/crates/tish_build_utils/src/lib.rs +105 -2
- package/crates/tish_builtins/Cargo.toml +5 -1
- package/crates/tish_builtins/src/array.rs +13 -12
- package/crates/tish_builtins/src/construct.rs +34 -33
- package/crates/tish_builtins/src/globals.rs +12 -11
- package/crates/tish_builtins/src/helpers.rs +2 -1
- package/crates/tish_builtins/src/object.rs +3 -2
- package/crates/tish_builtins/src/string.rs +73 -3
- package/crates/tish_bytecode/src/compiler.rs +12 -14
- package/crates/tish_bytecode/src/opcode.rs +12 -3
- package/crates/tish_compile/Cargo.toml +1 -0
- package/crates/tish_compile/src/codegen.rs +745 -199
- package/crates/tish_compile/src/infer.rs +6 -0
- package/crates/tish_compile/src/lib.rs +4 -3
- package/crates/tish_compile/src/resolve.rs +180 -82
- package/crates/tish_compile/src/types.rs +175 -11
- package/crates/tish_compile_js/Cargo.toml +1 -0
- package/crates/tish_compile_js/src/codegen.rs +152 -29
- package/crates/tish_compile_js/src/lib.rs +3 -1
- package/crates/tish_compiler_wasm/src/resolve_virtual.rs +31 -12
- package/crates/tish_core/Cargo.toml +8 -0
- package/crates/tish_core/src/json.rs +102 -53
- package/crates/tish_core/src/lib.rs +3 -1
- package/crates/tish_core/src/macros.rs +5 -5
- package/crates/tish_core/src/value.rs +53 -15
- package/crates/tish_core/src/vmref.rs +178 -0
- package/crates/tish_eval/Cargo.toml +17 -2
- package/crates/tish_eval/src/eval.rs +90 -28
- package/crates/tish_eval/src/http.rs +61 -0
- package/crates/tish_eval/src/lib.rs +3 -3
- package/crates/tish_eval/src/natives.rs +41 -0
- package/crates/tish_eval/src/value.rs +7 -3
- package/crates/tish_eval/src/value_convert.rs +13 -5
- package/crates/tish_fmt/src/lib.rs +120 -30
- package/crates/tish_lexer/src/lib.rs +20 -5
- package/crates/tish_lexer/src/token.rs +4 -0
- package/crates/tish_llvm/src/lib.rs +3 -1
- 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 +502 -102
- package/crates/tish_native/src/build.rs +3 -2
- package/crates/tish_native/src/lib.rs +6 -2
- package/crates/tish_opt/src/lib.rs +17 -2
- package/crates/tish_parser/src/lib.rs +10 -3
- package/crates/tish_parser/src/parser.rs +346 -56
- 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 +1123 -141
- package/crates/tish_runtime/src/http_fetch.rs +15 -14
- 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 +159 -29
- package/crates/tish_runtime/src/promise.rs +199 -36
- 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 +26 -28
- package/crates/tish_ui/src/jsx.rs +279 -8
- 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 +506 -259
- package/crates/tish_vm/tests/peephole_jump_chain_logical_or.rs +3 -1
- package/crates/tish_wasm/src/lib.rs +17 -14
- 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 +1 -0
- package/crates/tishlang_cargo_bindgen/src/discover.rs +68 -0
- package/crates/tishlang_cargo_bindgen/src/lib.rs +5 -4
- 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
10
|
CompletionItem, CompletionItemKind, CompletionParams, CompletionResponse,
|
|
11
|
-
CompletionTriggerKind, Diagnostic, DiagnosticSeverity, DidChangeTextDocumentParams,
|
|
11
|
+
CompletionTriggerKind, Diagnostic, DiagnosticSeverity, DiagnosticTag, DidChangeTextDocumentParams,
|
|
12
12
|
DidCloseTextDocumentParams, DidOpenTextDocumentParams, DocumentFormattingParams,
|
|
13
13
|
DocumentSymbolParams, DocumentSymbolResponse, GotoDefinitionParams, GotoDefinitionResponse,
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
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)),
|
|
@@ -172,7 +251,7 @@ impl LanguageServer for Backend {
|
|
|
172
251
|
|
|
173
252
|
async fn completion(&self, params: CompletionParams) -> Result<Option<CompletionResponse>> {
|
|
174
253
|
let uri = params.text_document_position.text_document.uri.clone();
|
|
175
|
-
let
|
|
254
|
+
let pos = params.text_document_position.position;
|
|
176
255
|
let text = {
|
|
177
256
|
let g = self.docs.read().unwrap();
|
|
178
257
|
g.get(&uri).cloned()
|
|
@@ -197,24 +276,17 @@ impl LanguageServer for Backend {
|
|
|
197
276
|
.collect();
|
|
198
277
|
|
|
199
278
|
if let Ok(program) = tishlang_parser::parse(&text) {
|
|
200
|
-
for
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
label: name.to_string(),
|
|
212
|
-
kind: Some(CompletionItemKind::VARIABLE),
|
|
213
|
-
..Default::default()
|
|
214
|
-
});
|
|
215
|
-
}
|
|
216
|
-
_ => {}
|
|
217
|
-
}
|
|
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
|
+
});
|
|
218
290
|
}
|
|
219
291
|
}
|
|
220
292
|
|
|
@@ -272,65 +344,271 @@ impl LanguageServer for Backend {
|
|
|
272
344
|
return Ok(None);
|
|
273
345
|
};
|
|
274
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
|
+
|
|
275
357
|
let word = word_at_position(&text, position);
|
|
276
358
|
if word.is_empty() {
|
|
277
359
|
return Ok(None);
|
|
278
360
|
}
|
|
279
361
|
|
|
280
|
-
let
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
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
|
+
) {
|
|
371
|
+
return Ok(Some(GotoDefinitionResponse::Scalar(loc)));
|
|
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
|
+
) {
|
|
284
383
|
return Ok(Some(GotoDefinitionResponse::Scalar(loc)));
|
|
285
384
|
}
|
|
286
385
|
}
|
|
287
386
|
|
|
288
|
-
if let Some(
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
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
|
+
}
|
|
398
|
+
}
|
|
399
|
+
|
|
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
|
+
));
|
|
325
453
|
}
|
|
326
454
|
}
|
|
327
455
|
}
|
|
328
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);
|
|
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._");
|
|
488
|
+
}
|
|
489
|
+
} else {
|
|
490
|
+
md.push_str("\n\n_No binding in scope for this name._");
|
|
491
|
+
}
|
|
329
492
|
}
|
|
330
493
|
}
|
|
331
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
|
+
}
|
|
332
503
|
|
|
333
|
-
|
|
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))
|
|
535
|
+
}
|
|
536
|
+
|
|
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
|
+
}))
|
|
334
612
|
}
|
|
335
613
|
|
|
336
614
|
async fn formatting(
|
|
@@ -410,7 +688,11 @@ fn collect_workspace_syms(
|
|
|
410
688
|
out: &mut Vec<SymbolInformation>,
|
|
411
689
|
) {
|
|
412
690
|
match s {
|
|
413
|
-
tishlang_ast::Statement::FunDecl {
|
|
691
|
+
tishlang_ast::Statement::FunDecl {
|
|
692
|
+
name,
|
|
693
|
+
name_span,
|
|
694
|
+
..
|
|
695
|
+
} => {
|
|
414
696
|
if name.to_lowercase().contains(query) {
|
|
415
697
|
out.push(SymbolInformation {
|
|
416
698
|
name: name.to_string(),
|
|
@@ -419,13 +701,17 @@ fn collect_workspace_syms(
|
|
|
419
701
|
deprecated: None,
|
|
420
702
|
location: Location {
|
|
421
703
|
uri: uri.clone(),
|
|
422
|
-
range: span_to_range(
|
|
704
|
+
range: span_to_range(name_span, text),
|
|
423
705
|
},
|
|
424
706
|
container_name: None,
|
|
425
707
|
});
|
|
426
708
|
}
|
|
427
709
|
}
|
|
428
|
-
tishlang_ast::Statement::VarDecl {
|
|
710
|
+
tishlang_ast::Statement::VarDecl {
|
|
711
|
+
name,
|
|
712
|
+
name_span,
|
|
713
|
+
..
|
|
714
|
+
} => {
|
|
429
715
|
if name.to_lowercase().contains(query) {
|
|
430
716
|
out.push(SymbolInformation {
|
|
431
717
|
name: name.to_string(),
|
|
@@ -434,7 +720,7 @@ fn collect_workspace_syms(
|
|
|
434
720
|
deprecated: None,
|
|
435
721
|
location: Location {
|
|
436
722
|
uri: uri.clone(),
|
|
437
|
-
range: span_to_range(
|
|
723
|
+
range: span_to_range(name_span, text),
|
|
438
724
|
},
|
|
439
725
|
container_name: None,
|
|
440
726
|
});
|
|
@@ -449,7 +735,7 @@ fn collect_workspace_syms(
|
|
|
449
735
|
}
|
|
450
736
|
}
|
|
451
737
|
|
|
452
|
-
fn find_export(
|
|
738
|
+
pub(crate) fn find_export(
|
|
453
739
|
program: &tishlang_ast::Program,
|
|
454
740
|
name: &str,
|
|
455
741
|
uri: &Url,
|
|
@@ -457,16 +743,24 @@ fn find_export(
|
|
|
457
743
|
) -> Option<Location> {
|
|
458
744
|
for s in &program.statements {
|
|
459
745
|
match s {
|
|
460
|
-
tishlang_ast::Statement::FunDecl {
|
|
746
|
+
tishlang_ast::Statement::FunDecl {
|
|
747
|
+
name: n,
|
|
748
|
+
name_span,
|
|
749
|
+
..
|
|
750
|
+
} if n.as_ref() == name => {
|
|
461
751
|
return Some(Location {
|
|
462
752
|
uri: uri.clone(),
|
|
463
|
-
range: span_to_range(
|
|
753
|
+
range: span_to_range(name_span, text),
|
|
464
754
|
});
|
|
465
755
|
}
|
|
466
|
-
tishlang_ast::Statement::VarDecl {
|
|
756
|
+
tishlang_ast::Statement::VarDecl {
|
|
757
|
+
name: n,
|
|
758
|
+
name_span,
|
|
759
|
+
..
|
|
760
|
+
} if n.as_ref() == name => {
|
|
467
761
|
return Some(Location {
|
|
468
762
|
uri: uri.clone(),
|
|
469
|
-
range: span_to_range(
|
|
763
|
+
range: span_to_range(name_span, text),
|
|
470
764
|
});
|
|
471
765
|
}
|
|
472
766
|
tishlang_ast::Statement::Export { declaration, .. } => match declaration.as_ref() {
|
|
@@ -490,18 +784,22 @@ fn find_decl_in_stmt(
|
|
|
490
784
|
text: &str,
|
|
491
785
|
) -> Option<Location> {
|
|
492
786
|
match s {
|
|
493
|
-
tishlang_ast::Statement::FunDecl {
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
787
|
+
tishlang_ast::Statement::FunDecl {
|
|
788
|
+
name,
|
|
789
|
+
name_span,
|
|
790
|
+
..
|
|
791
|
+
} if name.as_ref() == word => Some(Location {
|
|
792
|
+
uri: uri.clone(),
|
|
793
|
+
range: span_to_range(name_span, text),
|
|
794
|
+
}),
|
|
795
|
+
tishlang_ast::Statement::VarDecl {
|
|
796
|
+
name,
|
|
797
|
+
name_span,
|
|
798
|
+
..
|
|
799
|
+
} if name.as_ref() == word => Some(Location {
|
|
800
|
+
uri: uri.clone(),
|
|
801
|
+
range: span_to_range(name_span, text),
|
|
802
|
+
}),
|
|
505
803
|
tishlang_ast::Statement::Block { statements, .. } => {
|
|
506
804
|
for x in statements {
|
|
507
805
|
if let Some(l) = find_decl_in_stmt(x, word, uri, text) {
|
|
@@ -514,16 +812,23 @@ fn find_decl_in_stmt(
|
|
|
514
812
|
}
|
|
515
813
|
}
|
|
516
814
|
|
|
517
|
-
fn span_to_range(span: &tishlang_ast::Span,
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
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
|
+
}
|
|
527
832
|
}
|
|
528
833
|
}
|
|
529
834
|
|
|
@@ -550,6 +855,92 @@ fn is_ident_char(c: char) -> bool {
|
|
|
550
855
|
c.is_alphanumeric() || c == '_'
|
|
551
856
|
}
|
|
552
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
|
+
|
|
553
944
|
fn doc_symbol_stmt(
|
|
554
945
|
s: &tishlang_ast::Statement,
|
|
555
946
|
text: &str,
|
|
@@ -557,7 +948,11 @@ fn doc_symbol_stmt(
|
|
|
557
948
|
) {
|
|
558
949
|
match s {
|
|
559
950
|
tishlang_ast::Statement::FunDecl {
|
|
560
|
-
name,
|
|
951
|
+
name,
|
|
952
|
+
name_span,
|
|
953
|
+
span,
|
|
954
|
+
body,
|
|
955
|
+
..
|
|
561
956
|
} => {
|
|
562
957
|
let mut children = Vec::new();
|
|
563
958
|
collect_child_syms(body, text, &mut children);
|
|
@@ -568,7 +963,7 @@ fn doc_symbol_stmt(
|
|
|
568
963
|
tags: None,
|
|
569
964
|
deprecated: None,
|
|
570
965
|
range: span_to_range(span, text),
|
|
571
|
-
selection_range: span_to_range(
|
|
966
|
+
selection_range: span_to_range(name_span, text),
|
|
572
967
|
children: if children.is_empty() {
|
|
573
968
|
None
|
|
574
969
|
} else {
|
|
@@ -576,7 +971,12 @@ fn doc_symbol_stmt(
|
|
|
576
971
|
},
|
|
577
972
|
});
|
|
578
973
|
}
|
|
579
|
-
tishlang_ast::Statement::VarDecl {
|
|
974
|
+
tishlang_ast::Statement::VarDecl {
|
|
975
|
+
name,
|
|
976
|
+
name_span,
|
|
977
|
+
span,
|
|
978
|
+
..
|
|
979
|
+
} => {
|
|
580
980
|
out.push(tower_lsp::lsp_types::DocumentSymbol {
|
|
581
981
|
name: name.to_string(),
|
|
582
982
|
detail: None,
|
|
@@ -584,7 +984,7 @@ fn doc_symbol_stmt(
|
|
|
584
984
|
tags: None,
|
|
585
985
|
deprecated: None,
|
|
586
986
|
range: span_to_range(span, text),
|
|
587
|
-
selection_range: span_to_range(
|
|
987
|
+
selection_range: span_to_range(name_span, text),
|
|
588
988
|
children: None,
|
|
589
989
|
});
|
|
590
990
|
}
|