@tishlang/tish-format 1.0.12 → 1.0.13

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 (164) hide show
  1. package/Cargo.toml +49 -0
  2. package/LICENSE +13 -0
  3. package/README.md +138 -0
  4. package/bin/tish-format +0 -0
  5. package/crates/js_to_tish/Cargo.toml +11 -0
  6. package/crates/js_to_tish/README.md +18 -0
  7. package/crates/js_to_tish/src/error.rs +55 -0
  8. package/crates/js_to_tish/src/lib.rs +11 -0
  9. package/crates/js_to_tish/src/span_util.rs +35 -0
  10. package/crates/js_to_tish/src/transform/expr.rs +610 -0
  11. package/crates/js_to_tish/src/transform/stmt.rs +503 -0
  12. package/crates/js_to_tish/src/transform.rs +60 -0
  13. package/crates/tish/Cargo.toml +54 -0
  14. package/crates/tish/src/cargo_native_registry.rs +32 -0
  15. package/crates/tish/src/cli_help.rs +565 -0
  16. package/crates/tish/src/main.rs +781 -0
  17. package/crates/tish/src/repl_completion.rs +200 -0
  18. package/crates/tish/tests/cargo_example_compile.rs +67 -0
  19. package/crates/tish/tests/fixtures/cargo_example_project/Cargo.toml +3 -0
  20. package/crates/tish/tests/fixtures/cargo_example_project/crates/demo-shim/Cargo.toml +11 -0
  21. package/crates/tish/tests/fixtures/cargo_example_project/crates/demo-shim/src/lib.rs +12 -0
  22. package/crates/tish/tests/fixtures/cargo_example_project/package.json +10 -0
  23. package/crates/tish/tests/fixtures/cargo_example_project/src/main.tish +3 -0
  24. package/crates/tish/tests/integration_test.rs +1095 -0
  25. package/crates/tish/tests/run_optimize_stdout_parity.rs +50 -0
  26. package/crates/tish/tests/shortcircuit.rs +65 -0
  27. package/crates/tish_ast/Cargo.toml +9 -0
  28. package/crates/tish_ast/src/ast.rs +620 -0
  29. package/crates/tish_ast/src/lib.rs +5 -0
  30. package/crates/tish_build_utils/Cargo.toml +11 -0
  31. package/crates/tish_build_utils/src/lib.rs +577 -0
  32. package/crates/tish_builtins/Cargo.toml +20 -0
  33. package/crates/tish_builtins/src/array.rs +441 -0
  34. package/crates/tish_builtins/src/construct.rs +159 -0
  35. package/crates/tish_builtins/src/globals.rs +213 -0
  36. package/crates/tish_builtins/src/helpers.rs +35 -0
  37. package/crates/tish_builtins/src/lib.rs +16 -0
  38. package/crates/tish_builtins/src/math.rs +89 -0
  39. package/crates/tish_builtins/src/object.rs +36 -0
  40. package/crates/tish_builtins/src/string.rs +647 -0
  41. package/crates/tish_builtins/src/symbol.rs +83 -0
  42. package/crates/tish_bytecode/Cargo.toml +17 -0
  43. package/crates/tish_bytecode/src/chunk.rs +96 -0
  44. package/crates/tish_bytecode/src/compiler.rs +1760 -0
  45. package/crates/tish_bytecode/src/encoding.rs +100 -0
  46. package/crates/tish_bytecode/src/lib.rs +19 -0
  47. package/crates/tish_bytecode/src/opcode.rs +142 -0
  48. package/crates/tish_bytecode/src/peephole.rs +189 -0
  49. package/crates/tish_bytecode/src/serialize.rs +163 -0
  50. package/crates/tish_bytecode/tests/break_continue_bytecode.rs +44 -0
  51. package/crates/tish_bytecode/tests/constant_folding.rs +84 -0
  52. package/crates/tish_bytecode/tests/sort_optimization.rs +31 -0
  53. package/crates/tish_compile/Cargo.toml +26 -0
  54. package/crates/tish_compile/src/codegen.rs +5332 -0
  55. package/crates/tish_compile/src/infer.rs +292 -0
  56. package/crates/tish_compile/src/lib.rs +164 -0
  57. package/crates/tish_compile/src/resolve.rs +1388 -0
  58. package/crates/tish_compile/src/types.rs +501 -0
  59. package/crates/tish_compile_js/Cargo.toml +18 -0
  60. package/crates/tish_compile_js/examples/jsx_vdom_smoke.tish +8 -0
  61. package/crates/tish_compile_js/src/codegen.rs +871 -0
  62. package/crates/tish_compile_js/src/error.rs +20 -0
  63. package/crates/tish_compile_js/src/lib.rs +26 -0
  64. package/crates/tish_compile_js/src/tests_jsx.rs +350 -0
  65. package/crates/tish_compiler_wasm/Cargo.toml +21 -0
  66. package/crates/tish_compiler_wasm/src/lib.rs +57 -0
  67. package/crates/tish_compiler_wasm/src/resolve_virtual.rs +473 -0
  68. package/crates/tish_core/Cargo.toml +26 -0
  69. package/crates/tish_core/src/console_style.rs +160 -0
  70. package/crates/tish_core/src/json.rs +387 -0
  71. package/crates/tish_core/src/lib.rs +17 -0
  72. package/crates/tish_core/src/macros.rs +36 -0
  73. package/crates/tish_core/src/uri.rs +118 -0
  74. package/crates/tish_core/src/value.rs +696 -0
  75. package/crates/tish_core/src/vmref.rs +178 -0
  76. package/crates/tish_cranelift/Cargo.toml +19 -0
  77. package/crates/tish_cranelift/src/lib.rs +43 -0
  78. package/crates/tish_cranelift/src/link.rs +117 -0
  79. package/crates/tish_cranelift/src/lower.rs +85 -0
  80. package/crates/tish_cranelift_runtime/Cargo.toml +25 -0
  81. package/crates/tish_cranelift_runtime/src/lib.rs +45 -0
  82. package/crates/tish_eval/Cargo.toml +45 -0
  83. package/crates/tish_eval/src/eval.rs +3717 -0
  84. package/crates/tish_eval/src/http.rs +188 -0
  85. package/crates/tish_eval/src/lib.rs +99 -0
  86. package/crates/tish_eval/src/natives.rs +399 -0
  87. package/crates/tish_eval/src/promise.rs +179 -0
  88. package/crates/tish_eval/src/regex.rs +299 -0
  89. package/crates/tish_eval/src/timers.rs +120 -0
  90. package/crates/tish_eval/src/value.rs +318 -0
  91. package/crates/tish_eval/src/value_convert.rs +111 -0
  92. package/crates/tish_fmt/Cargo.toml +16 -0
  93. package/crates/tish_fmt/src/bin/tish-fmt.rs +41 -0
  94. package/crates/tish_fmt/src/lib.rs +2101 -0
  95. package/crates/tish_jsx_web/Cargo.toml +9 -0
  96. package/crates/tish_jsx_web/README.md +5 -0
  97. package/crates/tish_jsx_web/src/lib.rs +2 -0
  98. package/crates/tish_lexer/Cargo.toml +9 -0
  99. package/crates/tish_lexer/src/lib.rs +716 -0
  100. package/crates/tish_lexer/src/token.rs +163 -0
  101. package/crates/tish_lint/Cargo.toml +18 -0
  102. package/crates/tish_lint/src/bin/tish-lint.rs +195 -0
  103. package/crates/tish_lint/src/lib.rs +289 -0
  104. package/crates/tish_llvm/Cargo.toml +13 -0
  105. package/crates/tish_llvm/src/lib.rs +115 -0
  106. package/crates/tish_lsp/Cargo.toml +25 -0
  107. package/crates/tish_lsp/README.md +26 -0
  108. package/crates/tish_lsp/src/builtin_goto.rs +362 -0
  109. package/crates/tish_lsp/src/import_goto.rs +562 -0
  110. package/crates/tish_lsp/src/main.rs +1046 -0
  111. package/crates/tish_native/Cargo.toml +16 -0
  112. package/crates/tish_native/src/build.rs +427 -0
  113. package/crates/tish_native/src/config.rs +48 -0
  114. package/crates/tish_native/src/lib.rs +416 -0
  115. package/crates/tish_opt/Cargo.toml +13 -0
  116. package/crates/tish_opt/src/lib.rs +943 -0
  117. package/crates/tish_parser/Cargo.toml +11 -0
  118. package/crates/tish_parser/src/lib.rs +332 -0
  119. package/crates/tish_parser/src/parser.rs +2304 -0
  120. package/crates/tish_pg/Cargo.toml +34 -0
  121. package/crates/tish_pg/README.md +38 -0
  122. package/crates/tish_pg/src/error.rs +52 -0
  123. package/crates/tish_pg/src/lib.rs +955 -0
  124. package/crates/tish_resolve/Cargo.toml +13 -0
  125. package/crates/tish_resolve/src/lib.rs +3561 -0
  126. package/crates/tish_resolve/src/pos.rs +141 -0
  127. package/crates/tish_runtime/Cargo.toml +96 -0
  128. package/crates/tish_runtime/src/http.rs +1298 -0
  129. package/crates/tish_runtime/src/http_fetch.rs +471 -0
  130. package/crates/tish_runtime/src/http_hyper.rs +418 -0
  131. package/crates/tish_runtime/src/http_prefork.rs +189 -0
  132. package/crates/tish_runtime/src/lib.rs +1192 -0
  133. package/crates/tish_runtime/src/native_promise.rs +15 -0
  134. package/crates/tish_runtime/src/promise.rs +248 -0
  135. package/crates/tish_runtime/src/promise_io.rs +38 -0
  136. package/crates/tish_runtime/src/timers.rs +166 -0
  137. package/crates/tish_runtime/src/ws.rs +761 -0
  138. package/crates/tish_runtime/tests/fetch_readable_stream.rs +102 -0
  139. package/crates/tish_ui/Cargo.toml +17 -0
  140. package/crates/tish_ui/src/jsx.rs +682 -0
  141. package/crates/tish_ui/src/lib.rs +20 -0
  142. package/crates/tish_ui/src/runtime/hooks.rs +569 -0
  143. package/crates/tish_ui/src/runtime/mod.rs +180 -0
  144. package/crates/tish_vm/Cargo.toml +47 -0
  145. package/crates/tish_vm/src/lib.rs +39 -0
  146. package/crates/tish_vm/src/vm.rs +2192 -0
  147. package/crates/tish_vm/tests/fixtures/or_string_cmd.tish +2 -0
  148. package/crates/tish_vm/tests/lexical_scope_declare.rs +34 -0
  149. package/crates/tish_vm/tests/peephole_jump_chain_logical_or.rs +150 -0
  150. package/crates/tish_wasm/Cargo.toml +15 -0
  151. package/crates/tish_wasm/src/lib.rs +424 -0
  152. package/crates/tish_wasm_runtime/Cargo.toml +37 -0
  153. package/crates/tish_wasm_runtime/src/gpu.rs +413 -0
  154. package/crates/tish_wasm_runtime/src/lib.rs +42 -0
  155. package/crates/tishlang_cargo_bindgen/Cargo.toml +26 -0
  156. package/crates/tishlang_cargo_bindgen/src/classify.rs +263 -0
  157. package/crates/tishlang_cargo_bindgen/src/discover.rs +125 -0
  158. package/crates/tishlang_cargo_bindgen/src/infer.rs +382 -0
  159. package/crates/tishlang_cargo_bindgen/src/lib.rs +349 -0
  160. package/crates/tishlang_cargo_bindgen/src/main.rs +167 -0
  161. package/crates/tishlang_cargo_bindgen/src/metadata.rs +117 -0
  162. package/justfile +268 -0
  163. package/package.json +1 -1
  164. package/platform/darwin-arm64/tish-fmt +0 -0
@@ -0,0 +1,1046 @@
1
+ //! Tish Language Server — diagnostics, symbols, completion, format, go-to-definition, workspace symbols.
2
+
3
+ use std::collections::HashMap;
4
+ use std::path::PathBuf;
5
+ use std::sync::{Arc, RwLock};
6
+
7
+ use regex::Regex;
8
+ use tower_lsp::jsonrpc::Result;
9
+ use tower_lsp::lsp_types::{
10
+ CompletionItem, CompletionItemKind, CompletionParams, CompletionResponse,
11
+ CompletionTriggerKind, Diagnostic, DiagnosticSeverity, DiagnosticTag,
12
+ DidChangeTextDocumentParams, DidCloseTextDocumentParams, DidOpenTextDocumentParams,
13
+ DocumentFormattingParams, DocumentSymbol, DocumentSymbolParams, DocumentSymbolResponse,
14
+ GotoDefinitionParams, GotoDefinitionResponse, Hover, HoverContents, HoverParams,
15
+ HoverProviderCapability, InitializeParams, InitializeResult, Location, MarkupContent,
16
+ MarkupKind, MessageType, NumberOrString, OneOf, Position, Range, ReferenceParams,
17
+ RenameOptions, RenameParams, ServerCapabilities, ServerInfo, SymbolInformation, SymbolKind,
18
+ SymbolTag, TextDocumentPositionParams, TextDocumentSyncCapability, TextDocumentSyncKind, Url,
19
+ WorkDoneProgressOptions, WorkspaceEdit, WorkspaceSymbolParams,
20
+ };
21
+ use tower_lsp::lsp_types::{PrepareRenameResponse, TextEdit};
22
+ use tower_lsp::{Client, LanguageServer, LspService, Server};
23
+ use walkdir::WalkDir;
24
+
25
+ mod builtin_goto;
26
+ mod import_goto;
27
+
28
+ #[derive(Debug)]
29
+ struct Backend {
30
+ client: Client,
31
+ docs: Arc<RwLock<HashMap<Url, String>>>,
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>>>,
37
+ }
38
+
39
+ #[tokio::main]
40
+ async fn main() {
41
+ let stdin = tokio::io::stdin();
42
+ let stdout = tokio::io::stdout();
43
+
44
+ let (service, socket) = LspService::new(|client| Backend {
45
+ client,
46
+ docs: Arc::new(RwLock::new(HashMap::new())),
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)),
50
+ });
51
+ Server::new(stdin, stdout, socket).serve(service).await;
52
+ }
53
+
54
+ fn parse_error_pos(err: &str) -> (u32, u32) {
55
+ static RE: std::sync::OnceLock<Regex> = std::sync::OnceLock::new();
56
+ let re = RE.get_or_init(|| Regex::new(r"start: \((\d+), (\d+)\)").unwrap());
57
+ if let Some(c) = re.captures(err) {
58
+ let line: u32 = c.get(1).and_then(|m| m.as_str().parse().ok()).unwrap_or(1);
59
+ let col: u32 = c.get(2).and_then(|m| m.as_str().parse().ok()).unwrap_or(1);
60
+ return (line.saturating_sub(1), col.saturating_sub(1));
61
+ }
62
+ (0, 0)
63
+ }
64
+
65
+ fn pos(line: u32, col: u32) -> Position {
66
+ Position {
67
+ line,
68
+ character: col,
69
+ }
70
+ }
71
+
72
+ fn diag_range(line: u32, col: u32, text: &str) -> Range {
73
+ let line_str = text.lines().nth(line as usize).unwrap_or("");
74
+ let end_char = line_str.len().max(col as usize + 1) as u32;
75
+ Range {
76
+ start: pos(line, col),
77
+ end: pos(line, end_char.min(col + 80)),
78
+ }
79
+ }
80
+
81
+ /// `lsp-types` still requires the `deprecated` field on these structs, but marks it
82
+ /// `#[deprecated(note = "Use tags instead")]`. Use `tags` with [`SymbolTag::Deprecated`] when a
83
+ /// symbol is actually deprecated; this helper keeps a single `#[allow(deprecated)]` boundary.
84
+ #[allow(deprecated)]
85
+ fn symbol_information(
86
+ name: String,
87
+ kind: SymbolKind,
88
+ tags: Option<Vec<SymbolTag>>,
89
+ location: Location,
90
+ container_name: Option<String>,
91
+ ) -> SymbolInformation {
92
+ SymbolInformation {
93
+ name,
94
+ kind,
95
+ tags,
96
+ deprecated: None,
97
+ location,
98
+ container_name,
99
+ }
100
+ }
101
+
102
+ #[allow(deprecated)]
103
+ fn document_symbol(
104
+ name: String,
105
+ detail: Option<String>,
106
+ kind: SymbolKind,
107
+ tags: Option<Vec<SymbolTag>>,
108
+ range: Range,
109
+ selection_range: Range,
110
+ children: Option<Vec<DocumentSymbol>>,
111
+ ) -> DocumentSymbol {
112
+ DocumentSymbol {
113
+ name,
114
+ detail,
115
+ kind,
116
+ tags,
117
+ deprecated: None,
118
+ range,
119
+ selection_range,
120
+ children,
121
+ }
122
+ }
123
+
124
+ fn publish_parse_and_lint(client: &Client, uri: Url, text: &str) {
125
+ let mut diags = Vec::new();
126
+ match tishlang_parser::parse(text) {
127
+ Ok(program) => {
128
+ for d in tishlang_lint::lint_program(&program) {
129
+ let sev = match d.severity {
130
+ tishlang_lint::Severity::Error => DiagnosticSeverity::ERROR,
131
+ tishlang_lint::Severity::Warning => DiagnosticSeverity::WARNING,
132
+ };
133
+ diags.push(Diagnostic {
134
+ range: diag_range(d.line.saturating_sub(1), d.col.saturating_sub(1), text),
135
+ severity: Some(sev),
136
+ code: Some(NumberOrString::String(d.code.to_string())),
137
+ message: d.message,
138
+ ..Default::default()
139
+ });
140
+ }
141
+ for u in tishlang_resolve::collect_unresolved_identifiers(&program) {
142
+ diags.push(Diagnostic {
143
+ range: span_to_range(&u.span, text),
144
+ severity: Some(DiagnosticSeverity::ERROR),
145
+ code: Some(NumberOrString::String("tish-unresolved-name".into())),
146
+ message: format!("no binding in scope for `{}`", u.name),
147
+ ..Default::default()
148
+ });
149
+ }
150
+ for ub in tishlang_resolve::collect_unused_bindings(&program, text) {
151
+ let (message, code) = match ub.kind {
152
+ tishlang_resolve::UnusedBindingKind::Import => (
153
+ format!("`{}` is imported but never used", ub.name),
154
+ "tish-unused-import",
155
+ ),
156
+ tishlang_resolve::UnusedBindingKind::Parameter => (
157
+ format!("`{}` is declared but never read", ub.name),
158
+ "tish-unused-parameter",
159
+ ),
160
+ tishlang_resolve::UnusedBindingKind::Variable => (
161
+ format!("`{}` is declared but its value is never read", ub.name),
162
+ "tish-unused-variable",
163
+ ),
164
+ };
165
+ diags.push(Diagnostic {
166
+ range: span_to_range(&ub.span, text),
167
+ severity: Some(DiagnosticSeverity::HINT),
168
+ code: Some(NumberOrString::String(code.into())),
169
+ message,
170
+ tags: Some(vec![DiagnosticTag::UNNECESSARY]),
171
+ source: Some("tish".into()),
172
+ ..Default::default()
173
+ });
174
+ }
175
+ }
176
+ Err(e) => {
177
+ let (l, c) = parse_error_pos(&e);
178
+ diags.push(Diagnostic {
179
+ range: diag_range(l, c, text),
180
+ severity: Some(DiagnosticSeverity::ERROR),
181
+ message: e,
182
+ ..Default::default()
183
+ });
184
+ }
185
+ }
186
+ let _ = client.publish_diagnostics(uri, diags, None);
187
+ }
188
+
189
+ #[tower_lsp::async_trait]
190
+ impl LanguageServer for Backend {
191
+ async fn initialize(&self, params: InitializeParams) -> Result<InitializeResult> {
192
+ let mut roots = self.roots.write().unwrap();
193
+ roots.clear();
194
+ if let Some(folders) = params.workspace_folders {
195
+ for f in folders {
196
+ if let Ok(p) = f.uri.to_file_path() {
197
+ roots.push(p);
198
+ }
199
+ }
200
+ } else if let Some(uri) = params.root_uri {
201
+ if let Ok(p) = uri.to_file_path() {
202
+ roots.push(p);
203
+ }
204
+ }
205
+
206
+ let mut src_root: Option<PathBuf> = None;
207
+ if let Some(opts) = &params.initialization_options {
208
+ if let Some(s) = opts
209
+ .get("tishlangSourceRoot")
210
+ .and_then(|v| v.as_str())
211
+ .map(str::trim)
212
+ {
213
+ if !s.is_empty() {
214
+ src_root = Some(PathBuf::from(s));
215
+ }
216
+ }
217
+ }
218
+ if src_root.is_none() {
219
+ if let Ok(s) = std::env::var("TISHLANG_SOURCE_ROOT") {
220
+ let t = s.trim();
221
+ if !t.is_empty() {
222
+ src_root = Some(PathBuf::from(t));
223
+ }
224
+ }
225
+ }
226
+ let mut g = self.tishlang_source_root.write().unwrap();
227
+ *g = src_root.filter(|p| p.is_dir());
228
+
229
+ Ok(InitializeResult {
230
+ capabilities: ServerCapabilities {
231
+ text_document_sync: Some(TextDocumentSyncCapability::Kind(
232
+ TextDocumentSyncKind::FULL,
233
+ )),
234
+ completion_provider: Some(tower_lsp::lsp_types::CompletionOptions {
235
+ trigger_characters: Some(vec![".".to_string()]),
236
+ ..Default::default()
237
+ }),
238
+ hover_provider: Some(HoverProviderCapability::Simple(true)),
239
+ definition_provider: Some(OneOf::Left(true)),
240
+ references_provider: Some(OneOf::Left(true)),
241
+ rename_provider: Some(OneOf::Right(RenameOptions {
242
+ prepare_provider: Some(true),
243
+ work_done_progress_options: WorkDoneProgressOptions::default(),
244
+ })),
245
+ document_formatting_provider: Some(OneOf::Left(true)),
246
+ document_symbol_provider: Some(OneOf::Left(true)),
247
+ workspace_symbol_provider: Some(OneOf::Left(true)),
248
+ ..Default::default()
249
+ },
250
+ server_info: Some(ServerInfo {
251
+ name: "tish-lsp".into(),
252
+ version: Some(env!("CARGO_PKG_VERSION").into()),
253
+ }),
254
+ })
255
+ }
256
+
257
+ async fn initialized(&self, _: tower_lsp::lsp_types::InitializedParams) {
258
+ self.client
259
+ .log_message(MessageType::INFO, "tish-lsp ready")
260
+ .await;
261
+ }
262
+
263
+ async fn shutdown(&self) -> Result<()> {
264
+ Ok(())
265
+ }
266
+
267
+ async fn did_open(&self, p: DidOpenTextDocumentParams) {
268
+ let uri = p.text_document.uri;
269
+ let text = p.text_document.text;
270
+ self.docs.write().unwrap().insert(uri.clone(), text.clone());
271
+ publish_parse_and_lint(&self.client, uri, &text);
272
+ }
273
+
274
+ async fn did_change(&self, p: DidChangeTextDocumentParams) {
275
+ let uri = p.text_document.uri;
276
+ if let Some(chg) = p.content_changes.into_iter().last() {
277
+ self.docs
278
+ .write()
279
+ .unwrap()
280
+ .insert(uri.clone(), chg.text.clone());
281
+ publish_parse_and_lint(&self.client, uri, &chg.text);
282
+ }
283
+ }
284
+
285
+ async fn did_close(&self, p: DidCloseTextDocumentParams) {
286
+ self.docs.write().unwrap().remove(&p.text_document.uri);
287
+ let _ = self
288
+ .client
289
+ .publish_diagnostics(p.text_document.uri, vec![], None);
290
+ }
291
+
292
+ async fn completion(&self, params: CompletionParams) -> Result<Option<CompletionResponse>> {
293
+ let uri = params.text_document_position.text_document.uri.clone();
294
+ let pos = params.text_document_position.position;
295
+ let text = {
296
+ let g = self.docs.read().unwrap();
297
+ g.get(&uri).cloned()
298
+ };
299
+ let Some(text) = text else {
300
+ return Ok(None);
301
+ };
302
+
303
+ let keywords = [
304
+ "fn", "async", "let", "const", "if", "else", "while", "for", "return", "break",
305
+ "continue", "switch", "case", "default", "try", "catch", "finally", "throw", "import",
306
+ "export", "from", "typeof", "void", "await", "of", "in", "true", "false", "null",
307
+ "function", "do",
308
+ ];
309
+ let mut items: Vec<CompletionItem> = keywords
310
+ .iter()
311
+ .map(|k| CompletionItem {
312
+ label: (*k).to_string(),
313
+ kind: Some(CompletionItemKind::KEYWORD),
314
+ ..Default::default()
315
+ })
316
+ .collect();
317
+
318
+ if let Ok(program) = tishlang_parser::parse(&text) {
319
+ for name in tishlang_resolve::completion_value_names_at_cursor(
320
+ &program,
321
+ &text,
322
+ pos.line,
323
+ pos.character,
324
+ ) {
325
+ items.push(CompletionItem {
326
+ label: name.to_string(),
327
+ kind: Some(value_completion_kind(&program, name.as_ref())),
328
+ ..Default::default()
329
+ });
330
+ }
331
+ }
332
+
333
+ if let Some(ctx) = params.context {
334
+ if matches!(ctx.trigger_kind, CompletionTriggerKind::TRIGGER_CHARACTER)
335
+ && ctx.trigger_character.as_deref() == Some(".")
336
+ {
337
+ // After dot: could add member completion later
338
+ }
339
+ }
340
+
341
+ Ok(Some(CompletionResponse::Array(items)))
342
+ }
343
+
344
+ async fn document_symbol(
345
+ &self,
346
+ params: DocumentSymbolParams,
347
+ ) -> Result<Option<DocumentSymbolResponse>> {
348
+ let uri = params.text_document.uri;
349
+ let text = {
350
+ let g = self.docs.read().unwrap();
351
+ g.get(&uri).cloned()
352
+ };
353
+ let Some(text) = text else {
354
+ return Ok(None);
355
+ };
356
+ let Ok(program) = tishlang_parser::parse(&text) else {
357
+ return Ok(None);
358
+ };
359
+
360
+ let mut syms: Vec<DocumentSymbol> = Vec::new();
361
+ for s in &program.statements {
362
+ doc_symbol_stmt(s, &text, &mut syms);
363
+ }
364
+ Ok(Some(DocumentSymbolResponse::Nested(syms)))
365
+ }
366
+
367
+ async fn goto_definition(
368
+ &self,
369
+ params: GotoDefinitionParams,
370
+ ) -> Result<Option<GotoDefinitionResponse>> {
371
+ let TextDocumentPositionParams {
372
+ text_document,
373
+ position,
374
+ } = params.text_document_position_params;
375
+ let uri = text_document.uri;
376
+ let text = {
377
+ let g = self.docs.read().unwrap();
378
+ g.get(&uri).cloned()
379
+ };
380
+ let Some(text) = text else {
381
+ return Ok(None);
382
+ };
383
+ let Ok(program) = tishlang_parser::parse(&text) else {
384
+ return Ok(None);
385
+ };
386
+
387
+ if let Some(def) =
388
+ tishlang_resolve::definition_span(&program, &text, position.line, position.character)
389
+ {
390
+ let range = span_to_range(&def, &text);
391
+ return Ok(Some(GotoDefinitionResponse::Scalar(Location {
392
+ uri: uri.clone(),
393
+ range,
394
+ })));
395
+ }
396
+
397
+ let word = word_at_position(&text, position);
398
+ if word.is_empty() {
399
+ return Ok(None);
400
+ }
401
+
402
+ if let Some(ref file_path) = uri.to_file_path().ok() {
403
+ let roots = self.roots.read().unwrap().clone();
404
+ if let Some(loc) = import_goto::definition_for_import(
405
+ &program,
406
+ file_path,
407
+ word.as_str(),
408
+ &roots,
409
+ self.cargo_src_cache.as_ref(),
410
+ ) {
411
+ return Ok(Some(GotoDefinitionResponse::Scalar(loc)));
412
+ }
413
+ if let Some(loc) = import_goto::definition_for_native_receiver_member(
414
+ &program,
415
+ file_path,
416
+ &text,
417
+ &roots,
418
+ self.cargo_src_cache.as_ref(),
419
+ position.line,
420
+ position.character,
421
+ word.as_str(),
422
+ ) {
423
+ return Ok(Some(GotoDefinitionResponse::Scalar(loc)));
424
+ }
425
+ }
426
+
427
+ if let Some(root) = self.tishlang_source_root.read().unwrap().clone() {
428
+ if let Some(bdef) = builtin_goto::definition_for_builtin(
429
+ &text,
430
+ position.line,
431
+ position.character,
432
+ word.as_str(),
433
+ ) {
434
+ if let Some(loc) = builtin_goto::to_file_location(&root, &bdef) {
435
+ return Ok(Some(GotoDefinitionResponse::Scalar(loc)));
436
+ }
437
+ }
438
+ }
439
+
440
+ Ok(None)
441
+ }
442
+
443
+ async fn hover(&self, params: HoverParams) -> Result<Option<Hover>> {
444
+ let pos = params.text_document_position_params.position;
445
+ let uri = params.text_document_position_params.text_document.uri;
446
+ let text = {
447
+ let g = self.docs.read().unwrap();
448
+ g.get(&uri).cloned()
449
+ };
450
+ let Some(text) = text else {
451
+ return Ok(None);
452
+ };
453
+ let Ok(program) = tishlang_parser::parse(&text) else {
454
+ return Ok(None);
455
+ };
456
+ let Some(use_site) =
457
+ tishlang_resolve::name_at_cursor(&program, &text, pos.line, pos.character)
458
+ else {
459
+ return Ok(None);
460
+ };
461
+ let def = tishlang_resolve::definition_span(&program, &text, pos.line, pos.character);
462
+ let mut md = format!("**`{}`**", use_site.name);
463
+ match def {
464
+ Some(def) if def.start == use_site.span.start && def.end == use_site.span.end => {
465
+ md.push_str("\n\n_(binding site)_");
466
+ }
467
+ Some(def) => {
468
+ md.push_str(&format!(
469
+ "\n\nDefined at line {} col {}",
470
+ def.start.0, def.start.1
471
+ ));
472
+ }
473
+ None => {
474
+ if tishlang_resolve::is_runtime_global_ident(use_site.name.as_ref()) {
475
+ md.push_str(
476
+ "\n\n_Interpreter root global (no lexical declaration in this file)._",
477
+ );
478
+ let word = word_at_position(&text, pos);
479
+ if !word.is_empty() {
480
+ if let Some(root) = self.tishlang_source_root.read().unwrap().clone() {
481
+ if let Some(bdef) = builtin_goto::definition_for_builtin(
482
+ &text,
483
+ pos.line,
484
+ pos.character,
485
+ word.as_str(),
486
+ ) {
487
+ if let Some(loc) = builtin_goto::to_file_location(&root, &bdef) {
488
+ // VS Code treats `#L<1-based-line>` on file URLs like "go to line".
489
+ let line_1 = bdef.line.saturating_add(1);
490
+ let href = loc.uri.as_str();
491
+ md.push_str(&format!(
492
+ "\n\n[Open in Tish sources]({href}#L{line_1}) (`{}`)",
493
+ bdef.rel_path
494
+ ));
495
+ }
496
+ }
497
+ }
498
+ }
499
+ } else {
500
+ let word = word_at_position(&text, pos);
501
+ if word.is_empty() {
502
+ md.push_str("\n\n_No binding in scope for this name._");
503
+ } else if let Ok(fp) = uri.to_file_path() {
504
+ let roots = self.roots.read().unwrap().clone();
505
+ if let Some(nmd) = import_goto::native_member_definition(
506
+ &program,
507
+ &fp,
508
+ &text,
509
+ &roots,
510
+ self.cargo_src_cache.as_ref(),
511
+ pos.line,
512
+ pos.character,
513
+ word.as_str(),
514
+ ) {
515
+ md.push_str(
516
+ "\n\n_Native host module member (e.g. `tish:macos`); implementation in Rust._",
517
+ );
518
+ if let Some(ref d) = nmd.doc {
519
+ md.push_str("\n\n");
520
+ md.push_str(d);
521
+ }
522
+ let loc = nmd.location;
523
+ let line_1 = loc.range.start.line.saturating_add(1);
524
+ let href = loc.uri.as_str();
525
+ md.push_str(&format!(
526
+ "\n\n[Open Rust implementation]({href}#L{line_1})"
527
+ ));
528
+ } else {
529
+ md.push_str("\n\n_No binding in scope for this name._");
530
+ }
531
+ } else {
532
+ md.push_str("\n\n_No binding in scope for this name._");
533
+ }
534
+ }
535
+ }
536
+ }
537
+ Ok(Some(Hover {
538
+ range: Some(span_to_range(&use_site.span, &text)),
539
+ contents: HoverContents::Markup(MarkupContent {
540
+ kind: MarkupKind::Markdown,
541
+ value: md,
542
+ }),
543
+ }))
544
+ }
545
+
546
+ async fn references(&self, params: ReferenceParams) -> Result<Option<Vec<Location>>> {
547
+ let pos = params.text_document_position.position;
548
+ let uri = params.text_document_position.text_document.uri;
549
+ let text = {
550
+ let g = self.docs.read().unwrap();
551
+ g.get(&uri).cloned()
552
+ };
553
+ let Some(text) = text else {
554
+ return Ok(None);
555
+ };
556
+ let Ok(program) = tishlang_parser::parse(&text) else {
557
+ return Ok(None);
558
+ };
559
+ let Some(def) = tishlang_resolve::definition_span(&program, &text, pos.line, pos.character)
560
+ else {
561
+ return Ok(None);
562
+ };
563
+ let Some(nu) = tishlang_resolve::name_at_cursor(&program, &text, pos.line, pos.character)
564
+ else {
565
+ return Ok(None);
566
+ };
567
+ let spans =
568
+ tishlang_resolve::reference_spans_for_def(&program, &text, nu.name.as_ref(), def);
569
+ let locs: Vec<Location> = spans
570
+ .into_iter()
571
+ .map(|sp| Location {
572
+ uri: uri.clone(),
573
+ range: span_to_range(&sp, &text),
574
+ })
575
+ .collect();
576
+ Ok(Some(locs))
577
+ }
578
+
579
+ async fn prepare_rename(
580
+ &self,
581
+ params: TextDocumentPositionParams,
582
+ ) -> Result<Option<PrepareRenameResponse>> {
583
+ let pos = params.position;
584
+ let uri = params.text_document.uri;
585
+ let text = {
586
+ let g = self.docs.read().unwrap();
587
+ g.get(&uri).cloned()
588
+ };
589
+ let Some(text) = text else {
590
+ return Ok(None);
591
+ };
592
+ let Ok(program) = tishlang_parser::parse(&text) else {
593
+ return Ok(None);
594
+ };
595
+ let Some(nu) = tishlang_resolve::name_at_cursor(&program, &text, pos.line, pos.character)
596
+ else {
597
+ return Ok(None);
598
+ };
599
+ let range = span_to_range(&nu.span, &text);
600
+ Ok(Some(PrepareRenameResponse::RangeWithPlaceholder {
601
+ range,
602
+ placeholder: nu.name.to_string(),
603
+ }))
604
+ }
605
+
606
+ async fn rename(&self, params: RenameParams) -> Result<Option<WorkspaceEdit>> {
607
+ let pos = params.text_document_position.position;
608
+ let uri = params.text_document_position.text_document.uri;
609
+ let new_name = params.new_name;
610
+ let text = {
611
+ let g = self.docs.read().unwrap();
612
+ g.get(&uri).cloned()
613
+ };
614
+ let Some(text) = text else {
615
+ return Ok(None);
616
+ };
617
+ let Ok(program) = tishlang_parser::parse(&text) else {
618
+ return Ok(None);
619
+ };
620
+ let Some(def) = tishlang_resolve::definition_span(&program, &text, pos.line, pos.character)
621
+ else {
622
+ return Ok(None);
623
+ };
624
+ let Some(nu) = tishlang_resolve::name_at_cursor(&program, &text, pos.line, pos.character)
625
+ else {
626
+ return Ok(None);
627
+ };
628
+ let spans =
629
+ tishlang_resolve::reference_spans_for_def(&program, &text, nu.name.as_ref(), def);
630
+ let mut edits: Vec<TextEdit> = spans
631
+ .into_iter()
632
+ .map(|sp| TextEdit {
633
+ range: span_to_range(&sp, &text),
634
+ new_text: new_name.clone(),
635
+ })
636
+ .collect();
637
+ // Apply from end of document so earlier ranges stay valid when lengths change.
638
+ edits.sort_by(|a, b| {
639
+ (b.range.start.line, b.range.start.character)
640
+ .cmp(&(a.range.start.line, a.range.start.character))
641
+ });
642
+ let mut m = HashMap::new();
643
+ m.insert(uri, edits);
644
+ Ok(Some(WorkspaceEdit {
645
+ changes: Some(m),
646
+ ..Default::default()
647
+ }))
648
+ }
649
+
650
+ async fn formatting(
651
+ &self,
652
+ params: DocumentFormattingParams,
653
+ ) -> Result<Option<Vec<tower_lsp::lsp_types::TextEdit>>> {
654
+ let uri = params.text_document.uri;
655
+ let text = {
656
+ let g = self.docs.read().unwrap();
657
+ g.get(&uri).cloned()
658
+ };
659
+ let Some(text) = text else {
660
+ return Ok(None);
661
+ };
662
+ match tishlang_fmt::format_source(&text) {
663
+ Ok(formatted) => {
664
+ let lines = text.lines().count() as u32;
665
+ let last_line = text.lines().last().map(|l| l.len() as u32).unwrap_or(0);
666
+ Ok(Some(vec![tower_lsp::lsp_types::TextEdit {
667
+ range: Range {
668
+ start: pos(0, 0),
669
+ end: pos(lines.saturating_sub(1), last_line),
670
+ },
671
+ new_text: formatted,
672
+ }]))
673
+ }
674
+ Err(e) => {
675
+ self.client
676
+ .show_message(MessageType::ERROR, format!("tish-fmt (formatter): {}", e))
677
+ .await;
678
+ Ok(None)
679
+ }
680
+ }
681
+ }
682
+
683
+ async fn symbol(
684
+ &self,
685
+ params: WorkspaceSymbolParams,
686
+ ) -> Result<Option<Vec<SymbolInformation>>> {
687
+ let query = params.query.to_lowercase();
688
+ if query.is_empty() {
689
+ return Ok(Some(vec![]));
690
+ }
691
+ let roots = self.roots.read().unwrap().clone();
692
+ let mut out = Vec::new();
693
+
694
+ for root in roots {
695
+ for e in WalkDir::new(&root)
696
+ .into_iter()
697
+ .filter_map(|e| e.ok())
698
+ .filter(|e| e.path().extension().map(|x| x == "tish").unwrap_or(false))
699
+ {
700
+ let path = e.path();
701
+ let Ok(src) = std::fs::read_to_string(path) else {
702
+ continue;
703
+ };
704
+ let Ok(program) = tishlang_parser::parse(&src) else {
705
+ continue;
706
+ };
707
+ let Ok(uri) = Url::from_file_path(path) else {
708
+ continue;
709
+ };
710
+ for s in &program.statements {
711
+ collect_workspace_syms(s, &src, &uri, &query, &mut out);
712
+ }
713
+ }
714
+ }
715
+ Ok(Some(out))
716
+ }
717
+ }
718
+
719
+ fn collect_workspace_syms(
720
+ s: &tishlang_ast::Statement,
721
+ text: &str,
722
+ uri: &Url,
723
+ query: &str,
724
+ out: &mut Vec<SymbolInformation>,
725
+ ) {
726
+ match s {
727
+ tishlang_ast::Statement::FunDecl {
728
+ name, name_span, ..
729
+ } => {
730
+ if name.to_lowercase().contains(query) {
731
+ out.push(symbol_information(
732
+ name.to_string(),
733
+ SymbolKind::FUNCTION,
734
+ None,
735
+ Location {
736
+ uri: uri.clone(),
737
+ range: span_to_range(name_span, text),
738
+ },
739
+ None,
740
+ ));
741
+ }
742
+ }
743
+ tishlang_ast::Statement::VarDecl {
744
+ name, name_span, ..
745
+ } => {
746
+ if name.to_lowercase().contains(query) {
747
+ out.push(symbol_information(
748
+ name.to_string(),
749
+ SymbolKind::VARIABLE,
750
+ None,
751
+ Location {
752
+ uri: uri.clone(),
753
+ range: span_to_range(name_span, text),
754
+ },
755
+ None,
756
+ ));
757
+ }
758
+ }
759
+ tishlang_ast::Statement::Block { statements, .. } => {
760
+ for x in statements {
761
+ collect_workspace_syms(x, text, uri, query, out);
762
+ }
763
+ }
764
+ _ => {}
765
+ }
766
+ }
767
+
768
+ pub(crate) fn find_export(
769
+ program: &tishlang_ast::Program,
770
+ name: &str,
771
+ uri: &Url,
772
+ text: &str,
773
+ ) -> Option<Location> {
774
+ for s in &program.statements {
775
+ match s {
776
+ tishlang_ast::Statement::FunDecl {
777
+ name: n, name_span, ..
778
+ } if n.as_ref() == name => {
779
+ return Some(Location {
780
+ uri: uri.clone(),
781
+ range: span_to_range(name_span, text),
782
+ });
783
+ }
784
+ tishlang_ast::Statement::VarDecl {
785
+ name: n, name_span, ..
786
+ } if n.as_ref() == name => {
787
+ return Some(Location {
788
+ uri: uri.clone(),
789
+ range: span_to_range(name_span, text),
790
+ });
791
+ }
792
+ tishlang_ast::Statement::Export { declaration, .. } => match declaration.as_ref() {
793
+ tishlang_ast::ExportDeclaration::Named(inner) => {
794
+ if let Some(loc) = find_decl_in_stmt(inner, name, uri, text) {
795
+ return Some(loc);
796
+ }
797
+ }
798
+ _ => {}
799
+ },
800
+ _ => {}
801
+ }
802
+ }
803
+ None
804
+ }
805
+
806
+ fn find_decl_in_stmt(
807
+ s: &tishlang_ast::Statement,
808
+ word: &str,
809
+ uri: &Url,
810
+ text: &str,
811
+ ) -> Option<Location> {
812
+ match s {
813
+ tishlang_ast::Statement::FunDecl {
814
+ name, name_span, ..
815
+ } if name.as_ref() == word => Some(Location {
816
+ uri: uri.clone(),
817
+ range: span_to_range(name_span, text),
818
+ }),
819
+ tishlang_ast::Statement::VarDecl {
820
+ name, name_span, ..
821
+ } if name.as_ref() == word => Some(Location {
822
+ uri: uri.clone(),
823
+ range: span_to_range(name_span, text),
824
+ }),
825
+ tishlang_ast::Statement::Block { statements, .. } => {
826
+ for x in statements {
827
+ if let Some(l) = find_decl_in_stmt(x, word, uri, text) {
828
+ return Some(l);
829
+ }
830
+ }
831
+ None
832
+ }
833
+ _ => None,
834
+ }
835
+ }
836
+
837
+ fn span_to_range(span: &tishlang_ast::Span, text: &str) -> Range {
838
+ if let Some(((sl, sc), (el, ec))) = tishlang_resolve::span_to_lsp_range_exclusive(text, span) {
839
+ Range {
840
+ start: pos(sl, sc),
841
+ end: pos(el, ec),
842
+ }
843
+ } else {
844
+ Range {
845
+ start: pos(
846
+ span.start.0.saturating_sub(1) as u32,
847
+ span.start.1.saturating_sub(1) as u32,
848
+ ),
849
+ end: pos(
850
+ span.end.0.saturating_sub(1) as u32,
851
+ span.end.1.saturating_sub(1) as u32,
852
+ ),
853
+ }
854
+ }
855
+ }
856
+
857
+ fn word_at_position(text: &str, position: Position) -> String {
858
+ let line = text.lines().nth(position.line as usize).unwrap_or("");
859
+ let col = position.character as usize;
860
+ let bytes: Vec<(usize, char)> = line.char_indices().collect();
861
+ let mut start = col.min(bytes.len().saturating_sub(1));
862
+ while start > 0 && !is_ident_char(bytes.get(start).map(|(_, c)| *c).unwrap_or(' ')) {
863
+ start = start.saturating_sub(1);
864
+ }
865
+ let mut i = start;
866
+ while i < bytes.len() && is_ident_char(bytes[i].1) {
867
+ i += 1;
868
+ }
869
+ if start < bytes.len() {
870
+ line[bytes[start].0..bytes.get(i).map(|(p, _)| *p).unwrap_or(line.len())].to_string()
871
+ } else {
872
+ String::new()
873
+ }
874
+ }
875
+
876
+ fn is_ident_char(c: char) -> bool {
877
+ c.is_alphanumeric() || c == '_'
878
+ }
879
+
880
+ fn value_completion_kind(program: &tishlang_ast::Program, name: &str) -> CompletionItemKind {
881
+ for s in &program.statements {
882
+ if let Some(k) = value_completion_kind_stmt(s, name) {
883
+ return k;
884
+ }
885
+ }
886
+ CompletionItemKind::VARIABLE
887
+ }
888
+
889
+ fn value_completion_kind_stmt(
890
+ s: &tishlang_ast::Statement,
891
+ name: &str,
892
+ ) -> Option<CompletionItemKind> {
893
+ match s {
894
+ tishlang_ast::Statement::FunDecl { name: n, .. } if n.as_ref() == name => {
895
+ Some(CompletionItemKind::FUNCTION)
896
+ }
897
+ tishlang_ast::Statement::VarDecl { name: n, .. } if n.as_ref() == name => {
898
+ Some(CompletionItemKind::VARIABLE)
899
+ }
900
+ tishlang_ast::Statement::Import { specifiers, .. } => {
901
+ for sp in specifiers {
902
+ let local = match sp {
903
+ tishlang_ast::ImportSpecifier::Named { name: n, alias, .. } => {
904
+ alias.as_ref().map(|a| a.as_ref()).unwrap_or(n.as_ref())
905
+ }
906
+ tishlang_ast::ImportSpecifier::Default { name: n, .. } => n.as_ref(),
907
+ tishlang_ast::ImportSpecifier::Namespace { name: n, .. } => n.as_ref(),
908
+ };
909
+ if local == name {
910
+ return Some(CompletionItemKind::VARIABLE);
911
+ }
912
+ }
913
+ None
914
+ }
915
+ tishlang_ast::Statement::Block { statements, .. } => statements
916
+ .iter()
917
+ .find_map(|x| value_completion_kind_stmt(x, name)),
918
+ tishlang_ast::Statement::If {
919
+ then_branch,
920
+ else_branch,
921
+ ..
922
+ } => value_completion_kind_stmt(then_branch, name).or_else(|| {
923
+ else_branch
924
+ .as_ref()
925
+ .and_then(|b| value_completion_kind_stmt(b, name))
926
+ }),
927
+ tishlang_ast::Statement::While { body, .. }
928
+ | tishlang_ast::Statement::ForOf { body, .. }
929
+ | tishlang_ast::Statement::DoWhile { body, .. } => value_completion_kind_stmt(body, name),
930
+ tishlang_ast::Statement::For { init, body, .. } => init
931
+ .as_ref()
932
+ .and_then(|i| value_completion_kind_stmt(i, name))
933
+ .or_else(|| value_completion_kind_stmt(body, name)),
934
+ tishlang_ast::Statement::Try {
935
+ body,
936
+ catch_body,
937
+ finally_body,
938
+ ..
939
+ } => value_completion_kind_stmt(body, name)
940
+ .or_else(|| {
941
+ catch_body
942
+ .as_ref()
943
+ .and_then(|b| value_completion_kind_stmt(b, name))
944
+ })
945
+ .or_else(|| {
946
+ finally_body
947
+ .as_ref()
948
+ .and_then(|b| value_completion_kind_stmt(b, name))
949
+ }),
950
+ tishlang_ast::Statement::Switch {
951
+ cases,
952
+ default_body,
953
+ ..
954
+ } => {
955
+ for (_e, stmts) in cases {
956
+ if let Some(k) = stmts
957
+ .iter()
958
+ .find_map(|st| value_completion_kind_stmt(st, name))
959
+ {
960
+ return Some(k);
961
+ }
962
+ }
963
+ default_body.as_ref().and_then(|stmts| {
964
+ stmts
965
+ .iter()
966
+ .find_map(|st| value_completion_kind_stmt(st, name))
967
+ })
968
+ }
969
+ tishlang_ast::Statement::Export { declaration, .. } => match declaration.as_ref() {
970
+ tishlang_ast::ExportDeclaration::Named(inner) => {
971
+ value_completion_kind_stmt(inner, name)
972
+ }
973
+ tishlang_ast::ExportDeclaration::Default(_) => None,
974
+ },
975
+ _ => None,
976
+ }
977
+ }
978
+
979
+ fn doc_symbol_stmt(
980
+ s: &tishlang_ast::Statement,
981
+ text: &str,
982
+ out: &mut Vec<DocumentSymbol>,
983
+ ) {
984
+ match s {
985
+ tishlang_ast::Statement::FunDecl {
986
+ name,
987
+ name_span,
988
+ span,
989
+ body,
990
+ ..
991
+ } => {
992
+ let mut children = Vec::new();
993
+ collect_child_syms(body, text, &mut children);
994
+ out.push(document_symbol(
995
+ name.to_string(),
996
+ None,
997
+ SymbolKind::FUNCTION,
998
+ None,
999
+ span_to_range(span, text),
1000
+ span_to_range(name_span, text),
1001
+ if children.is_empty() {
1002
+ None
1003
+ } else {
1004
+ Some(children)
1005
+ },
1006
+ ));
1007
+ }
1008
+ tishlang_ast::Statement::VarDecl {
1009
+ name,
1010
+ name_span,
1011
+ span,
1012
+ ..
1013
+ } => {
1014
+ out.push(document_symbol(
1015
+ name.to_string(),
1016
+ None,
1017
+ SymbolKind::VARIABLE,
1018
+ None,
1019
+ span_to_range(span, text),
1020
+ span_to_range(name_span, text),
1021
+ None,
1022
+ ));
1023
+ }
1024
+ tishlang_ast::Statement::Block { statements, .. } => {
1025
+ for x in statements {
1026
+ doc_symbol_stmt(x, text, out);
1027
+ }
1028
+ }
1029
+ _ => {}
1030
+ }
1031
+ }
1032
+
1033
+ fn collect_child_syms(
1034
+ s: &tishlang_ast::Statement,
1035
+ text: &str,
1036
+ out: &mut Vec<DocumentSymbol>,
1037
+ ) {
1038
+ match s {
1039
+ tishlang_ast::Statement::Block { statements, .. } => {
1040
+ for x in statements {
1041
+ doc_symbol_stmt(x, text, out);
1042
+ }
1043
+ }
1044
+ _ => doc_symbol_stmt(s, text, out),
1045
+ }
1046
+ }