@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.
Files changed (113) hide show
  1. package/Cargo.toml +2 -0
  2. package/README.md +2 -0
  3. package/bin/tish +0 -0
  4. package/crates/js_to_tish/src/error.rs +2 -8
  5. package/crates/js_to_tish/src/transform/expr.rs +128 -137
  6. package/crates/js_to_tish/src/transform/stmt.rs +62 -32
  7. package/crates/tish/Cargo.toml +15 -5
  8. package/crates/tish/src/cargo_native_registry.rs +29 -0
  9. package/crates/tish/src/cli_help.rs +92 -39
  10. package/crates/tish/src/main.rs +172 -86
  11. package/crates/tish/src/repl_completion.rs +3 -3
  12. package/crates/tish/tests/cargo_example_compile.rs +4 -2
  13. package/crates/tish/tests/integration_test.rs +216 -54
  14. package/crates/tish/tests/run_optimize_stdout_parity.rs +3 -7
  15. package/crates/tish/tests/shortcircuit.rs +20 -5
  16. package/crates/tish_ast/src/ast.rs +92 -23
  17. package/crates/tish_build_utils/Cargo.toml +4 -0
  18. package/crates/tish_build_utils/src/lib.rs +136 -8
  19. package/crates/tish_builtins/Cargo.toml +5 -1
  20. package/crates/tish_builtins/src/array.rs +65 -33
  21. package/crates/tish_builtins/src/construct.rs +34 -39
  22. package/crates/tish_builtins/src/globals.rs +42 -26
  23. package/crates/tish_builtins/src/helpers.rs +2 -1
  24. package/crates/tish_builtins/src/lib.rs +5 -5
  25. package/crates/tish_builtins/src/math.rs +5 -3
  26. package/crates/tish_builtins/src/object.rs +3 -2
  27. package/crates/tish_builtins/src/string.rs +144 -22
  28. package/crates/tish_bytecode/src/chunk.rs +0 -1
  29. package/crates/tish_bytecode/src/compiler.rs +173 -71
  30. package/crates/tish_bytecode/src/opcode.rs +24 -6
  31. package/crates/tish_bytecode/src/peephole.rs +2 -2
  32. package/crates/tish_compile/Cargo.toml +1 -0
  33. package/crates/tish_compile/src/codegen.rs +1621 -453
  34. package/crates/tish_compile/src/infer.rs +75 -19
  35. package/crates/tish_compile/src/lib.rs +19 -8
  36. package/crates/tish_compile/src/resolve.rs +278 -137
  37. package/crates/tish_compile/src/types.rs +184 -24
  38. package/crates/tish_compile_js/Cargo.toml +1 -0
  39. package/crates/tish_compile_js/src/codegen.rs +181 -37
  40. package/crates/tish_compile_js/src/lib.rs +3 -1
  41. package/crates/tish_compile_js/src/tests_jsx.rs +30 -6
  42. package/crates/tish_compiler_wasm/src/lib.rs +16 -13
  43. package/crates/tish_compiler_wasm/src/resolve_virtual.rs +69 -59
  44. package/crates/tish_core/Cargo.toml +8 -0
  45. package/crates/tish_core/src/json.rs +107 -56
  46. package/crates/tish_core/src/lib.rs +4 -2
  47. package/crates/tish_core/src/macros.rs +5 -5
  48. package/crates/tish_core/src/uri.rs +9 -6
  49. package/crates/tish_core/src/value.rs +145 -43
  50. package/crates/tish_core/src/vmref.rs +178 -0
  51. package/crates/tish_cranelift/src/link.rs +6 -9
  52. package/crates/tish_cranelift/src/lower.rs +14 -8
  53. package/crates/tish_eval/Cargo.toml +17 -2
  54. package/crates/tish_eval/src/eval.rs +474 -165
  55. package/crates/tish_eval/src/http.rs +61 -0
  56. package/crates/tish_eval/src/lib.rs +12 -8
  57. package/crates/tish_eval/src/natives.rs +136 -38
  58. package/crates/tish_eval/src/promise.rs +14 -8
  59. package/crates/tish_eval/src/timers.rs +28 -19
  60. package/crates/tish_eval/src/value.rs +17 -6
  61. package/crates/tish_eval/src/value_convert.rs +13 -5
  62. package/crates/tish_fmt/src/lib.rs +149 -43
  63. package/crates/tish_lexer/src/lib.rs +232 -63
  64. package/crates/tish_lexer/src/token.rs +10 -6
  65. package/crates/tish_llvm/src/lib.rs +17 -8
  66. package/crates/tish_lsp/Cargo.toml +4 -1
  67. package/crates/tish_lsp/README.md +1 -1
  68. package/crates/tish_lsp/src/builtin_goto.rs +261 -0
  69. package/crates/tish_lsp/src/import_goto.rs +549 -0
  70. package/crates/tish_lsp/src/main.rs +504 -106
  71. package/crates/tish_native/src/build.rs +4 -8
  72. package/crates/tish_native/src/lib.rs +54 -21
  73. package/crates/tish_opt/src/lib.rs +84 -52
  74. package/crates/tish_parser/src/lib.rs +45 -13
  75. package/crates/tish_parser/src/parser.rs +505 -130
  76. package/crates/tish_resolve/Cargo.toml +13 -0
  77. package/crates/tish_resolve/src/lib.rs +3436 -0
  78. package/crates/tish_resolve/src/pos.rs +133 -0
  79. package/crates/tish_runtime/Cargo.toml +68 -3
  80. package/crates/tish_runtime/src/http.rs +1136 -145
  81. package/crates/tish_runtime/src/http_fetch.rs +38 -27
  82. package/crates/tish_runtime/src/http_hyper.rs +418 -0
  83. package/crates/tish_runtime/src/http_prefork.rs +189 -0
  84. package/crates/tish_runtime/src/lib.rs +375 -189
  85. package/crates/tish_runtime/src/promise.rs +199 -40
  86. package/crates/tish_runtime/src/promise_io.rs +2 -1
  87. package/crates/tish_runtime/src/timers.rs +37 -1
  88. package/crates/tish_runtime/src/ws.rs +65 -42
  89. package/crates/tish_runtime/tests/fetch_readable_stream.rs +5 -4
  90. package/crates/tish_ui/src/jsx.rs +317 -27
  91. package/crates/tish_ui/src/lib.rs +5 -2
  92. package/crates/tish_ui/src/runtime/hooks.rs +406 -45
  93. package/crates/tish_ui/src/runtime/mod.rs +36 -9
  94. package/crates/tish_vm/Cargo.toml +15 -5
  95. package/crates/tish_vm/src/vm.rs +725 -281
  96. package/crates/tish_vm/tests/peephole_jump_chain_logical_or.rs +11 -4
  97. package/crates/tish_wasm/src/lib.rs +55 -42
  98. package/crates/tish_wasm_runtime/Cargo.toml +2 -1
  99. package/crates/tish_wasm_runtime/src/lib.rs +1 -1
  100. package/crates/tishlang_cargo_bindgen/Cargo.toml +26 -0
  101. package/crates/tishlang_cargo_bindgen/src/classify.rs +265 -0
  102. package/crates/tishlang_cargo_bindgen/src/discover.rs +120 -0
  103. package/crates/tishlang_cargo_bindgen/src/infer.rs +372 -0
  104. package/crates/tishlang_cargo_bindgen/src/lib.rs +350 -0
  105. package/crates/tishlang_cargo_bindgen/src/main.rs +164 -0
  106. package/crates/tishlang_cargo_bindgen/src/metadata.rs +114 -0
  107. package/justfile +8 -0
  108. package/package.json +1 -1
  109. package/platform/darwin-arm64/tish +0 -0
  110. package/platform/darwin-x64/tish +0 -0
  111. package/platform/linux-arm64/tish +0 -0
  112. package/platform/linux-x64/tish +0 -0
  113. 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::{Path, PathBuf};
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, NumberOrString,
11
- CompletionParams, CompletionResponse, CompletionTriggerKind, Diagnostic, DiagnosticSeverity,
12
- DidChangeTextDocumentParams, DidCloseTextDocumentParams, DidOpenTextDocumentParams,
13
- DocumentFormattingParams, DocumentSymbolParams, DocumentSymbolResponse, GotoDefinitionParams,
14
- GotoDefinitionResponse, InitializeParams, InitializeResult, Location, MessageType, OneOf,
15
- Position, Range, ServerCapabilities, ServerInfo, SymbolInformation, SymbolKind,
16
- TextDocumentPositionParams, TextDocumentSyncCapability, TextDocumentSyncKind, Url,
17
- WorkspaceSymbolParams,
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) = &params.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
- .text_document_position
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 s in &program.statements {
205
- match s {
206
- tishlang_ast::Statement::FunDecl { name, .. } => {
207
- items.push(CompletionItem {
208
- label: name.to_string(),
209
- kind: Some(CompletionItemKind::FUNCTION),
210
- ..Default::default()
211
- });
212
- }
213
- tishlang_ast::Statement::VarDecl { name, .. } => {
214
- items.push(CompletionItem {
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.trigger_kind,
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 path = uri.to_file_path().ok();
287
-
288
- for s in &program.statements {
289
- if let Some(loc) = find_decl_in_stmt(s, &word, &uri, &text) {
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
- if let Some(ref base) = path {
295
- for s in &program.statements {
296
- if let tishlang_ast::Statement::Import {
297
- specifiers,
298
- from,
299
- ..
300
- } = s
301
- {
302
- for sp in specifiers {
303
- let (imported, local) = match sp {
304
- tishlang_ast::ImportSpecifier::Named { name, alias } => {
305
- (name.as_ref(), alias.as_ref().map(|a| a.as_ref()).unwrap_or(name.as_ref()))
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
- let from_s = from.as_ref();
314
- if !from_s.starts_with("./") && !from_s.starts_with("../") {
315
- continue;
316
- }
317
- let dir = base.parent().unwrap_or(Path::new(""));
318
- let target = dir.join(from_s.trim_start_matches("./"));
319
- let target = if target.extension().is_none() {
320
- target.with_extension("tish")
321
- } else {
322
- target
323
- };
324
- if let Ok(can) = target.canonicalize() {
325
- if let Ok(u) = Url::from_file_path(&can) {
326
- if let Ok(src) = std::fs::read_to_string(&can) {
327
- if let Ok(prog) = tishlang_parser::parse(&src) {
328
- if let Some(loc) =
329
- find_export(&prog, imported, &u, &src)
330
- {
331
- return Ok(Some(GotoDefinitionResponse::Scalar(loc)));
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
- Ok(None)
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 formatting(&self, params: DocumentFormattingParams) -> Result<Option<Vec<tower_lsp::lsp_types::TextEdit>>> {
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 { name, span, .. } => {
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(span, text),
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 { name, span, .. } => {
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(span, text),
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 { name: n, span, .. } if n.as_ref() == name => {
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(span, text),
753
+ range: span_to_range(name_span, text),
473
754
  });
474
755
  }
475
- tishlang_ast::Statement::VarDecl { name: n, span, .. } if n.as_ref() == name => {
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(span, text),
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 { name, span, .. } if name.as_ref() == word => Some(Location {
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(span, text),
793
+ range: span_to_range(name_span, text),
505
794
  }),
506
- tishlang_ast::Statement::VarDecl { name, span, .. } if name.as_ref() == word => Some(Location {
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(span, text),
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, _text: &str) -> Range {
523
- Range {
524
- start: pos(span.start.0.saturating_sub(1) as u32, span.start.1.saturating_sub(1) as u32),
525
- end: pos(span.end.0.saturating_sub(1) as u32, span.end.1.saturating_sub(1) as u32),
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(span, text),
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 { name, span, .. } => {
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(span, text),
987
+ selection_range: span_to_range(name_span, text),
590
988
  children: None,
591
989
  });
592
990
  }