@tishlang/tish 1.7.0 → 1.9.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 (99) 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/transform/expr.rs +28 -8
  5. package/crates/js_to_tish/src/transform/stmt.rs +49 -22
  6. package/crates/tish/Cargo.toml +14 -5
  7. package/crates/tish/src/cargo_native_registry.rs +29 -0
  8. package/crates/tish/src/cli_help.rs +16 -10
  9. package/crates/tish/src/main.rs +87 -32
  10. package/crates/tish/src/repl_completion.rs +3 -3
  11. package/crates/tish/tests/cargo_example_compile.rs +1 -1
  12. package/crates/tish/tests/integration_test.rs +19 -7
  13. package/crates/tish/tests/shortcircuit.rs +1 -1
  14. package/crates/tish_ast/src/ast.rs +80 -9
  15. package/crates/tish_build_utils/Cargo.toml +4 -0
  16. package/crates/tish_build_utils/src/lib.rs +105 -2
  17. package/crates/tish_builtins/Cargo.toml +5 -1
  18. package/crates/tish_builtins/src/array.rs +13 -12
  19. package/crates/tish_builtins/src/construct.rs +34 -33
  20. package/crates/tish_builtins/src/globals.rs +12 -11
  21. package/crates/tish_builtins/src/helpers.rs +2 -1
  22. package/crates/tish_builtins/src/object.rs +3 -2
  23. package/crates/tish_builtins/src/string.rs +73 -3
  24. package/crates/tish_bytecode/src/compiler.rs +12 -14
  25. package/crates/tish_bytecode/src/opcode.rs +12 -3
  26. package/crates/tish_compile/Cargo.toml +1 -0
  27. package/crates/tish_compile/src/codegen.rs +745 -199
  28. package/crates/tish_compile/src/infer.rs +6 -0
  29. package/crates/tish_compile/src/lib.rs +4 -3
  30. package/crates/tish_compile/src/resolve.rs +180 -82
  31. package/crates/tish_compile/src/types.rs +175 -11
  32. package/crates/tish_compile_js/Cargo.toml +1 -0
  33. package/crates/tish_compile_js/src/codegen.rs +152 -29
  34. package/crates/tish_compile_js/src/lib.rs +3 -1
  35. package/crates/tish_compiler_wasm/src/resolve_virtual.rs +31 -12
  36. package/crates/tish_core/Cargo.toml +8 -0
  37. package/crates/tish_core/src/json.rs +102 -53
  38. package/crates/tish_core/src/lib.rs +3 -1
  39. package/crates/tish_core/src/macros.rs +5 -5
  40. package/crates/tish_core/src/value.rs +53 -15
  41. package/crates/tish_core/src/vmref.rs +178 -0
  42. package/crates/tish_eval/Cargo.toml +17 -2
  43. package/crates/tish_eval/src/eval.rs +90 -28
  44. package/crates/tish_eval/src/http.rs +61 -0
  45. package/crates/tish_eval/src/lib.rs +3 -3
  46. package/crates/tish_eval/src/natives.rs +41 -0
  47. package/crates/tish_eval/src/value.rs +7 -3
  48. package/crates/tish_eval/src/value_convert.rs +13 -5
  49. package/crates/tish_fmt/src/lib.rs +120 -30
  50. package/crates/tish_lexer/src/lib.rs +20 -5
  51. package/crates/tish_lexer/src/token.rs +4 -0
  52. package/crates/tish_llvm/src/lib.rs +3 -1
  53. package/crates/tish_lsp/Cargo.toml +4 -1
  54. package/crates/tish_lsp/README.md +1 -1
  55. package/crates/tish_lsp/src/builtin_goto.rs +261 -0
  56. package/crates/tish_lsp/src/import_goto.rs +549 -0
  57. package/crates/tish_lsp/src/main.rs +502 -102
  58. package/crates/tish_native/src/build.rs +3 -2
  59. package/crates/tish_native/src/lib.rs +6 -2
  60. package/crates/tish_opt/src/lib.rs +17 -2
  61. package/crates/tish_parser/src/lib.rs +10 -3
  62. package/crates/tish_parser/src/parser.rs +346 -56
  63. package/crates/tish_pg/Cargo.toml +34 -0
  64. package/crates/tish_pg/README.md +38 -0
  65. package/crates/tish_pg/src/error.rs +52 -0
  66. package/crates/tish_pg/src/lib.rs +967 -0
  67. package/crates/tish_resolve/Cargo.toml +13 -0
  68. package/crates/tish_resolve/src/lib.rs +3436 -0
  69. package/crates/tish_resolve/src/pos.rs +133 -0
  70. package/crates/tish_runtime/Cargo.toml +68 -3
  71. package/crates/tish_runtime/src/http.rs +1123 -141
  72. package/crates/tish_runtime/src/http_fetch.rs +15 -14
  73. package/crates/tish_runtime/src/http_hyper.rs +418 -0
  74. package/crates/tish_runtime/src/http_prefork.rs +189 -0
  75. package/crates/tish_runtime/src/lib.rs +159 -29
  76. package/crates/tish_runtime/src/promise.rs +199 -36
  77. package/crates/tish_runtime/src/promise_io.rs +2 -1
  78. package/crates/tish_runtime/src/timers.rs +37 -1
  79. package/crates/tish_runtime/src/ws.rs +26 -28
  80. package/crates/tish_ui/src/jsx.rs +279 -8
  81. package/crates/tish_ui/src/lib.rs +5 -2
  82. package/crates/tish_ui/src/runtime/hooks.rs +406 -45
  83. package/crates/tish_ui/src/runtime/mod.rs +36 -9
  84. package/crates/tish_vm/Cargo.toml +15 -5
  85. package/crates/tish_vm/src/vm.rs +506 -259
  86. package/crates/tish_vm/tests/peephole_jump_chain_logical_or.rs +3 -1
  87. package/crates/tish_wasm/src/lib.rs +17 -14
  88. package/crates/tish_wasm_runtime/Cargo.toml +2 -1
  89. package/crates/tish_wasm_runtime/src/lib.rs +1 -1
  90. package/crates/tishlang_cargo_bindgen/Cargo.toml +1 -0
  91. package/crates/tishlang_cargo_bindgen/src/discover.rs +68 -0
  92. package/crates/tishlang_cargo_bindgen/src/lib.rs +5 -4
  93. package/justfile +8 -0
  94. package/package.json +1 -1
  95. package/platform/darwin-arm64/tish +0 -0
  96. package/platform/darwin-x64/tish +0 -0
  97. package/platform/linux-arm64/tish +0 -0
  98. package/platform/linux-x64/tish +0 -0
  99. 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
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
- InitializeParams, InitializeResult, Location, MessageType, NumberOrString, OneOf, Position,
15
- Range, ServerCapabilities, ServerInfo, SymbolInformation, SymbolKind,
16
- TextDocumentPositionParams, TextDocumentSyncCapability, TextDocumentSyncKind, Url,
17
- WorkspaceSymbolParams,
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)),
@@ -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 _pos = params.text_document_position.position;
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 s in &program.statements {
201
- match s {
202
- tishlang_ast::Statement::FunDecl { name, .. } => {
203
- items.push(CompletionItem {
204
- label: name.to_string(),
205
- kind: Some(CompletionItemKind::FUNCTION),
206
- ..Default::default()
207
- });
208
- }
209
- tishlang_ast::Statement::VarDecl { name, .. } => {
210
- items.push(CompletionItem {
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 path = uri.to_file_path().ok();
281
-
282
- for s in &program.statements {
283
- 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
+ ) {
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(ref base) = path {
289
- for s in &program.statements {
290
- if let tishlang_ast::Statement::Import {
291
- specifiers, from, ..
292
- } = s
293
- {
294
- for sp in specifiers {
295
- let (imported, local) = match sp {
296
- tishlang_ast::ImportSpecifier::Named { name, alias } => (
297
- name.as_ref(),
298
- alias.as_ref().map(|a| a.as_ref()).unwrap_or(name.as_ref()),
299
- ),
300
- tishlang_ast::ImportSpecifier::Default(n) => (n.as_ref(), n.as_ref()),
301
- _ => continue,
302
- };
303
- if local != word.as_str() {
304
- continue;
305
- }
306
- let from_s = from.as_ref();
307
- if !from_s.starts_with("./") && !from_s.starts_with("../") {
308
- continue;
309
- }
310
- let dir = base.parent().unwrap_or(Path::new(""));
311
- let target = dir.join(from_s.trim_start_matches("./"));
312
- let target = if target.extension().is_none() {
313
- target.with_extension("tish")
314
- } else {
315
- target
316
- };
317
- if let Ok(can) = target.canonicalize() {
318
- if let Ok(u) = Url::from_file_path(&can) {
319
- if let Ok(src) = std::fs::read_to_string(&can) {
320
- if let Ok(prog) = tishlang_parser::parse(&src) {
321
- if let Some(loc) = find_export(&prog, imported, &u, &src) {
322
- return Ok(Some(GotoDefinitionResponse::Scalar(loc)));
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
- 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))
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 { name, span, .. } => {
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(span, text),
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 { name, span, .. } => {
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(span, text),
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 { 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 => {
461
751
  return Some(Location {
462
752
  uri: uri.clone(),
463
- range: span_to_range(span, text),
753
+ range: span_to_range(name_span, text),
464
754
  });
465
755
  }
466
- 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 => {
467
761
  return Some(Location {
468
762
  uri: uri.clone(),
469
- range: span_to_range(span, text),
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 { name, span, .. } if name.as_ref() == word => {
494
- Some(Location {
495
- uri: uri.clone(),
496
- range: span_to_range(span, text),
497
- })
498
- }
499
- tishlang_ast::Statement::VarDecl { name, span, .. } if name.as_ref() == word => {
500
- Some(Location {
501
- uri: uri.clone(),
502
- range: span_to_range(span, text),
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, _text: &str) -> Range {
518
- Range {
519
- start: pos(
520
- span.start.0.saturating_sub(1) as u32,
521
- span.start.1.saturating_sub(1) as u32,
522
- ),
523
- end: pos(
524
- span.end.0.saturating_sub(1) as u32,
525
- span.end.1.saturating_sub(1) as u32,
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, span, body, ..
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(span, text),
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 { name, span, .. } => {
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(span, text),
987
+ selection_range: span_to_range(name_span, text),
588
988
  children: None,
589
989
  });
590
990
  }