diff options
| author | DanielGavin <danielgavin5@hotmail.com> | 2025-04-05 20:02:58 +0200 |
|---|---|---|
| committer | DanielGavin <danielgavin5@hotmail.com> | 2025-04-05 20:02:58 +0200 |
| commit | a500226c10e991506c42a4858ffd7d1d33f552e6 (patch) | |
| tree | 56f2df9234eeb0f00eb49848edc6c676cfba34ee /src | |
| parent | 1e44e3d78ad8a74ef09c7f54a6f6d3f7df517f8e (diff) | |
Add support for filewatching from the client
Diffstat (limited to 'src')
| -rw-r--r-- | src/server/build.odin | 102 | ||||
| -rw-r--r-- | src/server/requests.odin | 159 | ||||
| -rw-r--r-- | src/server/response.odin | 20 | ||||
| -rw-r--r-- | src/server/types.odin | 45 |
4 files changed, 243 insertions, 83 deletions
diff --git a/src/server/build.odin b/src/server/build.odin index a681201..d3b8572 100644 --- a/src/server/build.odin +++ b/src/server/build.odin @@ -166,6 +166,108 @@ try_build_package :: proc(pkg_name: string) { } } + +remove_index_file :: proc(uri: common.Uri) -> common.Error { + ok: bool + + fullpath := uri.path + + when ODIN_OS == .Windows { + fullpath, _ = filepath.to_slash(fullpath, context.temp_allocator) + } + + corrected_uri := common.create_uri(fullpath, context.temp_allocator) + + for k, &v in indexer.index.collection.packages { + for k2, v2 in v.symbols { + if strings.equal_fold(corrected_uri.uri, v2.uri) { + free_symbol(v2, indexer.index.collection.allocator) + delete_key(&v.symbols, k2) + } + } + + for method, &symbols in v.methods { + for i := len(symbols) - 1; i >= 0; i -= 1 { + #no_bounds_check symbol := symbols[i] + if strings.equal_fold(corrected_uri.uri, symbol.uri) { + unordered_remove(&symbols, i) + } + } + } + } + + return .None +} + +index_file :: proc(uri: common.Uri, text: string) -> common.Error { + ok: bool + + fullpath := uri.path + + p := parser.Parser { + err = log_error_handler, + warn = log_warning_handler, + flags = {.Optional_Semicolons}, + } + + when ODIN_OS == .Windows { + correct := common.get_case_sensitive_path(fullpath, context.temp_allocator) + fullpath, _ = filepath.to_slash(correct, context.temp_allocator) + } + + dir := filepath.base(filepath.dir(fullpath, context.temp_allocator)) + + pkg := new(ast.Package) + pkg.kind = .Normal + pkg.fullpath = fullpath + pkg.name = dir + + if dir == "runtime" { + pkg.kind = .Runtime + } + + file := ast.File { + fullpath = fullpath, + src = text, + pkg = pkg, + } + + ok = parser.parse_file(&p, &file) + + if !ok { + if !strings.contains(fullpath, "builtin.odin") && !strings.contains(fullpath, "intrinsics.odin") { + log.errorf("error in parse file for indexing %v", fullpath) + } + } + + corrected_uri := common.create_uri(fullpath, context.temp_allocator) + + for k, &v in indexer.index.collection.packages { + for k2, v2 in v.symbols { + if corrected_uri.uri == v2.uri { + free_symbol(v2, indexer.index.collection.allocator) + delete_key(&v.symbols, k2) + } + } + + for method, &symbols in v.methods { + for i := len(symbols) - 1; i >= 0; i -= 1 { + #no_bounds_check symbol := symbols[i] + if corrected_uri.uri == symbol.uri { + unordered_remove(&symbols, i) + } + } + } + } + + if ret := collect_symbols(&indexer.index.collection, file, corrected_uri.uri); ret != .None { + log.errorf("failed to collect symbols on save %v", ret) + } + + return .None +} + + setup_index :: proc() { build_cache.loaded_pkgs = make(map[string]PackageCacheInfo, 50, context.allocator) symbol_collection := make_symbol_collection(context.allocator, &common.config) diff --git a/src/server/requests.odin b/src/server/requests.odin index f884b0d..f4e2d4b 100644 --- a/src/server/requests.odin +++ b/src/server/requests.odin @@ -27,27 +27,6 @@ Header :: struct { content_type: string, } -RequestType :: enum { - Initialize, - Initialized, - Shutdown, - Exit, - DidOpen, - DidChange, - DidClose, - DidSave, - Definition, - Completion, - SignatureHelp, - DocumentSymbol, - SemanticTokensFull, - SemanticTokensRange, - FormatDocument, - Hover, - CancelRequest, - InlayHint, -} - RequestInfo :: struct { root: json.Value, params: json.Value, @@ -58,7 +37,6 @@ RequestInfo :: struct { result: common.Error, } - make_response_message :: proc(id: RequestId, params: ResponseParams) -> ResponseMessage { return ResponseMessage{jsonrpc = "2.0", id = id, result = params} } @@ -122,6 +100,11 @@ thread_request_main :: proc(data: rawptr) { #partial switch v in id_value { case json.String: id = v + //Hack to support dynamic registering without changing too much + if v == "REGISTER_DYNAMIC_CAPABILITIES" { + json.destroy_value(root) + continue + } case json.Integer: id = v case: @@ -260,15 +243,17 @@ call_map: map[string]proc(_: json.Value, _: RequestId, _: ^common.Config, _: ^Wr "window/progress" = request_noop, "workspace/symbol" = request_workspace_symbols, "workspace/didChangeConfiguration" = notification_workspace_did_change_configuration, + "workspace/didChangeWatchedFiles" = notification_did_change_watched_files, } notification_map: map[string]bool = { - "textDocument/didOpen" = true, - "textDocument/didChange" = true, - "textDocument/didClose" = true, - "textDocument/didSave" = true, - "initialized" = true, - "window/progress" = true, + "textDocument/didOpen" = true, + "textDocument/didChange" = true, + "textDocument/didClose" = true, + "textDocument/didSave" = true, + "initialized" = true, + "window/progress" = true, + "workspace/didChangeWatchedFiles" = true, } consume_requests :: proc(config: ^common.Config, writer: ^Writer) -> bool { @@ -721,9 +706,34 @@ request_initialize :: proc( try_build_package(pkg) } + register_dynamic_capabilities(writer) + return .None } +register_dynamic_capabilities :: proc(writer: ^Writer) { + params: RegistrationParams + + registration: Registration + + registration.id = "GLOBAL_ODIN_FILES" + registration.method = "workspace/didChangeWatchedFiles" + registration.registerOptions = DidChangeWatchedFilesRegistrationOptions { + watchers = []FileSystemWatcher{{globPattern = "**/*.odin"}}, + } + + params.registrations = {registration} + + request_message := RequestMessage { + jsonrpc = "2.0", + method = "client/registerCapability", + params = params, + id = "REGISTER_DYNAMIC_CAPABILITIES", + } + + send_request(request_message, writer) +} + request_initialized :: proc( params: json.Value, id: RequestId, @@ -1012,69 +1022,19 @@ notification_did_save :: proc( return .ParseError } - fullpath := uri.path - - p := parser.Parser { - err = log_error_handler, - warn = log_warning_handler, - flags = {.Optional_Semicolons}, + if result := index_file(uri, save_params.text); result != .None { + return result } + fullpath := uri.path + when ODIN_OS == .Windows { correct := common.get_case_sensitive_path(fullpath, context.temp_allocator) fullpath, _ = filepath.to_slash(correct, context.temp_allocator) } - dir := filepath.base(filepath.dir(fullpath, context.temp_allocator)) - - pkg := new(ast.Package) - pkg.kind = .Normal - pkg.fullpath = fullpath - pkg.name = dir - - if dir == "runtime" { - pkg.kind = .Runtime - } - - file := ast.File { - fullpath = fullpath, - src = save_params.text, - pkg = pkg, - } - - ok = parser.parse_file(&p, &file) - - if !ok { - if !strings.contains(fullpath, "builtin.odin") && !strings.contains(fullpath, "intrinsics.odin") { - log.errorf("error in parse file for indexing %v", fullpath) - } - } - corrected_uri := common.create_uri(fullpath, context.temp_allocator) - for k, &v in indexer.index.collection.packages { - for k2, v2 in v.symbols { - if corrected_uri.uri == v2.uri { - free_symbol(v2, indexer.index.collection.allocator) - delete_key(&v.symbols, k2) - } - } - - for method, &symbols in v.methods { - for i := 0; i < len(symbols); i += 1 { - #no_bounds_check symbol := symbols[i] - if corrected_uri.uri == symbol.uri { - unordered_remove(&symbols, i) - i -= 1 - } - } - } - } - - if ret := collect_symbols(&indexer.index.collection, file, corrected_uri.uri); ret != .None { - log.errorf("failed to collect symbols on save %v", ret) - } - check(config.profile.checker_path[:], corrected_uri, writer, config) return .None @@ -1427,6 +1387,41 @@ request_references :: proc( return .None } +notification_did_change_watched_files :: proc( + params: json.Value, + id: RequestId, + config: ^common.Config, + writer: ^Writer, +) -> common.Error { + params_object, ok := params.(json.Object) + + if !ok { + return .ParseError + } + + did_change_watched_files_params: DidChangeWatchedFilesParams + + if unmarshal(params, did_change_watched_files_params, context.temp_allocator) != nil { + return .ParseError + } + + for change in did_change_watched_files_params.changes { + if change.type == cast(int)FileChangeType.Deleted { + if uri, ok := common.parse_uri(change.uri, context.temp_allocator); ok { + remove_index_file(uri) + } + } else { + if uri, ok := common.parse_uri(change.uri, context.temp_allocator); ok { + if data, ok := os.read_entire_file(uri.path); ok { + index_file(uri, cast(string)data) + } + } + } + } + + return .None +} + notification_workspace_did_change_configuration :: proc( params: json.Value, id: RequestId, diff --git a/src/server/response.odin b/src/server/response.odin index 1019eb6..7765b34 100644 --- a/src/server/response.odin +++ b/src/server/response.odin @@ -23,6 +23,26 @@ send_notification :: proc(notification: Notification, writer: ^Writer) -> bool { return true } +send_request :: proc(request: RequestMessage, writer: ^Writer) -> bool { + data, error := marshal(request, {}, context.temp_allocator) + + header := fmt.tprintf("Content-Length: %v\r\n\r\n", len(data)) + + if error != nil { + return false + } + + if !write_sized(writer, transmute([]u8)header) { + return false + } + + if !write_sized(writer, data) { + return false + } + + return true +} + send_response :: proc(response: ResponseMessage, writer: ^Writer) -> bool { data, error := marshal(response, {}, context.temp_allocator) diff --git a/src/server/types.odin b/src/server/types.odin index 2dd75b4..c9107ff 100644 --- a/src/server/types.odin +++ b/src/server/types.odin @@ -33,6 +33,15 @@ ResponseParams :: union { common.Range, } +RequestMessage :: struct { + jsonrpc: string, + method: string, + id: RequestId, + params: union { + RegistrationParams, + }, +} + ResponseMessage :: struct { jsonrpc: string, id: RequestId, @@ -84,6 +93,33 @@ RequestInitializeParams :: struct { clientInfo: ClientInfo, } +FileChangeType :: enum { + Created = 1, + Changed = 2, + Deleted = 3, +} + +FileEvent :: struct { + uri: string, + type: int, +} + +DidChangeWatchedFilesParams :: struct { + changes: [dynamic]FileEvent, +} + +Registration :: struct { + id: string, + method: string, + registerOptions: union { + DidChangeWatchedFilesRegistrationOptions, + }, +} + +RegistrationParams :: struct { + registrations: []Registration, +} + ClientInfo :: struct { name: string, } @@ -109,11 +145,14 @@ ServerCapabilities :: struct { documentLinkProvider: DocumentLinkOptions, } +DidChangeWatchedFilesRegistrationOptions :: struct { + watchers: []FileSystemWatcher, +} + RenameOptions :: struct { prepareProvider: bool, } - CompletionOptions :: struct { resolveProvider: bool, triggerCharacters: []string, @@ -341,6 +380,10 @@ TextDocumentSyncOptions :: struct { save: SaveOptions, } +FileSystemWatcher :: struct { + globPattern: string, +} + OlsConfig :: struct { collections: [dynamic]OlsConfigCollection, thread_pool_count: Maybe(int), |