diff options
| author | DanielGavin <danielgavin5@hotmail.com> | 2020-11-04 20:26:46 +0100 |
|---|---|---|
| committer | DanielGavin <danielgavin5@hotmail.com> | 2020-11-04 20:26:46 +0100 |
| commit | 877a696d876cc64fb7c17aad3725f125da58e1eb (patch) | |
| tree | add4ecdc46288eccd320a5ce62231823150a54d0 /src | |
| parent | 35ca720dd6fa200c70a5727507fc6e432bd1a44b (diff) | |
fixed incremental sync + added diagnostic notification
Diffstat (limited to 'src')
| -rw-r--r-- | src/analysis.odin | 92 | ||||
| -rw-r--r-- | src/documents.odin | 121 | ||||
| -rw-r--r-- | src/log.odin | 8 | ||||
| -rw-r--r-- | src/main.odin | 10 | ||||
| -rw-r--r-- | src/position.odin | 55 | ||||
| -rw-r--r-- | src/requests.odin | 16 | ||||
| -rw-r--r-- | src/types.odin | 30 |
7 files changed, 279 insertions, 53 deletions
diff --git a/src/analysis.odin b/src/analysis.odin index 85f0393..8cba000 100644 --- a/src/analysis.odin +++ b/src/analysis.odin @@ -1 +1,91 @@ -package main
\ No newline at end of file +package main + +import "core:odin/parser" +import "core:odin/ast" +import "core:odin/tokenizer" +import "core:fmt" +import "core:log" + +ParserError :: struct { + message: string, + line: int, + column: int, + file: string, + offset: int, +}; + +StructSymbol :: struct { + +}; + +ProcedureSymbol :: struct { + +}; + +Symbol :: union { + StructSymbol, + ProcedureSymbol, +}; + +DocumentSymbols :: struct { + globals: map [string] Symbol, +}; + +DocumentPositionContext :: struct { + symbol: Symbol, +}; + +current_errors: [dynamic] ParserError; + +parser_error_handler :: proc(pos: tokenizer.Pos, msg: string, args: ..any) { + error := ParserError { line = pos.line, column = pos.column, file = pos.file, + offset = pos.offset, message = fmt.tprintf(msg, ..args) }; + append(¤t_errors, error); +} + +parser_warning_handler :: proc(pos: tokenizer.Pos, msg: string, args: ..any) { + +} + + +/* + Parses and walks through the ast saving all the global symbols for the document. Local symbols are not saved + because they are determined by the position. + + Document is responsible in freeing the DocumentSymbols +*/ + +parse_document_symbols :: proc(document: ^Document) -> (DocumentSymbols, [dynamic] ParserError, bool) { + + p := parser.Parser { + err = parser_error_handler, + warn = parser_warning_handler, + }; + + current_errors = make([dynamic] ParserError, context.temp_allocator); + + + ast := ast.File { + fullpath = document.path, + src = document.text[:document.used_text], + }; + + parser.parse_file(&p, &ast); + + return DocumentSymbols {}, current_errors, true; +} + + + +/* + Figure out what exactly is at the given position and whether it is in a function, struct, etc. +*/ +get_document_position_context :: proc(document: ^Document, position: Position) -> DocumentPositionContext { + + position_context: DocumentPositionContext; + + return position_context; +} + + + diff --git a/src/documents.odin b/src/documents.odin index 0be8cae..ecab990 100644 --- a/src/documents.odin +++ b/src/documents.odin @@ -11,9 +11,10 @@ Package :: struct { Document :: struct { uri: string, path: string, - text: [] u8, //transmuted version of text plus potential unused space + text: [] u8, used_text: int, //allow for the text to be reallocated with more data than needed client_owned: bool, + diagnosed_errors: bool, }; DocumentStorage :: struct { @@ -23,7 +24,7 @@ DocumentStorage :: struct { document_storage: DocumentStorage; -document_open :: proc(uri_string: string, text: string) -> Error { +document_open :: proc(uri_string: string, text: string, writer: ^Writer) -> Error { uri, parsed_ok := parse_uri(uri_string); @@ -33,7 +34,6 @@ document_open :: proc(uri_string: string, text: string) -> Error { if document := &document_storage.documents[uri.path]; document != nil { - //According to the specification you can't call open more than once without closing it. if document.client_owned { log.errorf("Client called open on an already open document: %v ", document.path); return .InvalidRequest; @@ -47,11 +47,10 @@ document_open :: proc(uri_string: string, text: string) -> Error { document.text = transmute([] u8)text; document.used_text = len(document.text); - if err := document_refresh(document); err != .None { + if err := document_refresh(document, writer); err != .None { return err; } - document_refresh(document); } else { @@ -64,7 +63,7 @@ document_open :: proc(uri_string: string, text: string) -> Error { used_text = len(text), }; - if err := document_refresh(&document); err != .None { + if err := document_refresh(&document, writer); err != .None { return err; } @@ -79,7 +78,10 @@ document_open :: proc(uri_string: string, text: string) -> Error { return .None; } -document_apply_changes :: proc(uri_string: string, changes: [dynamic] TextDocumentContentChangeEvent) -> Error { +/* + Function that applies changes to the given document through incremental syncronization + */ +document_apply_changes :: proc(uri_string: string, changes: [dynamic] TextDocumentContentChangeEvent, writer: ^Writer) -> Error { uri, parsed_ok := parse_uri(uri_string, context.temp_allocator); @@ -96,24 +98,25 @@ document_apply_changes :: proc(uri_string: string, changes: [dynamic] TextDocume for change in changes { - absolute_range, ok := get_absolute_range(change.range, document.text); + absolute_range, ok := get_absolute_range(change.range, document.text[:document.used_text]); if !ok { return .ParseError; } //lower bound is before the change - lower := document.text[:absolute_range.start]; + lower := document.text[:absolute_range.start]; //new change between lower and upper middle := change.text; //upper bound is after the change - upper := document.text[min(len(document.text), absolute_range.end+1):]; + upper := document.text[absolute_range.end:document.used_text]; //total new size needed document.used_text = len(lower) + len(change.text) + len(upper); + //Reduce the amount of allocation by allocating more memory than needed if document.used_text > len(document.text) { new_text := make([]u8, document.used_text * 2); @@ -128,33 +131,15 @@ document_apply_changes :: proc(uri_string: string, changes: [dynamic] TextDocume } else { - //no need to copy the lower since it is already in the document. - copy(document.text[len(lower):], middle); + //order matters here, we need to make sure we swap the data already in the text before the middle + copy(document.text, lower); copy(document.text[len(lower)+len(middle):], upper); + copy(document.text[len(lower):], middle); } - - /* - fmt.println(string(document.text[:document.used_text])); - - fmt.println("LOWER"); - fmt.println(string(lower)); - - fmt.println("CHANGE"); - fmt.println(change.text); - fmt.println(len(change.text)); - - fmt.println("UPPER"); - fmt.println(string(upper)); - */ - } - - - - - return .None; + return document_refresh(document, writer); } document_close :: proc(uri_string: string) -> Error { @@ -177,7 +162,77 @@ document_close :: proc(uri_string: string) -> Error { return .None; } -document_refresh :: proc(document: ^Document) -> Error { + + +document_refresh :: proc(document: ^Document, writer: ^Writer) -> Error { + + + document_symbols, errors, ok := parse_document_symbols(document); + + if !ok { + return .ParseError; + } + + if len(errors) > 0 { + document.diagnosed_errors = true; + + params := NotificationPublishDiagnosticsParams { + uri = document.uri, + diagnostics = make([] Diagnostic, len(errors), context.temp_allocator), + }; + + for error, i in errors { + + params.diagnostics[i] = Diagnostic { + range = Range { + start = Position { + line = error.line - 1, + character = 0, + }, + end = Position { + line = error.line, + character = 0, + }, + }, + severity = DiagnosticSeverity.Error, + code = "test", + message = error.message, + }; + + } + + notifaction := Notification { + jsonrpc = "2.0", + method = "textDocument/publishDiagnostics", + params = params, + }; + + send_notification(notifaction, writer); + + } + + if len(errors) == 0 { + + //send empty diagnosis to remove the clients errors + if document.diagnosed_errors { + + notifaction := Notification { + jsonrpc = "2.0", + method = "textDocument/publishDiagnostics", + + params = NotificationPublishDiagnosticsParams { + uri = document.uri, + diagnostics = make([] Diagnostic, len(errors), context.temp_allocator), + }, + }; + + document.diagnosed_errors = false; + + send_notification(notifaction, writer); + } + + } + return .None; } diff --git a/src/log.odin b/src/log.odin index fd370c9..82a52a4 100644 --- a/src/log.odin +++ b/src/log.odin @@ -48,13 +48,13 @@ lsp_logger_proc :: proc(logger_data: rawptr, level: log.Level, text: string, opt } } - message := fmt.tprintf("%s %s", buf, text); + message := fmt.tprintf("%s", text); notification := Notification { - jsonrpc = "2.0", - method = "window/logMessage", + jsonrpc = "2.0", + method = "window/logMessage", params = NotificationLoggingParams { - type = 1, + type = 1, message = message, } }; diff --git a/src/main.odin b/src/main.odin index 896f275..e388be5 100644 --- a/src/main.odin +++ b/src/main.odin @@ -40,7 +40,7 @@ run :: proc(reader: ^Reader, writer: ^Writer) { return; } - + value: json.Value; value, success = read_and_parse_body(reader, header); @@ -56,12 +56,14 @@ run :: proc(reader: ^Reader, writer: ^Writer) { return; } + free_all(context.temp_allocator); + } } end :: proc() { - + } @@ -69,7 +71,9 @@ main :: proc() { reader := make_reader(os_read, cast(rawptr)os.stdin); writer := make_writer(os_write, cast(rawptr)os.stdout); - + + context.logger = create_lsp_logger(&writer); + run(&reader, &writer); } diff --git a/src/position.odin b/src/position.odin index a0856fb..18cb547 100644 --- a/src/position.odin +++ b/src/position.odin @@ -8,7 +8,6 @@ import "core:fmt" This file handles the conversion between utf-16 and utf-8 offsets in the text document */ - AbsoluteRange :: struct { start: int, end: int, @@ -18,8 +17,10 @@ get_absolute_range :: proc(range: Range, document_text: [] u8) -> (AbsoluteRange absolute: AbsoluteRange; - if len(document_text) <= 2 { - return absolute, false; + if len(document_text) == 0 { + absolute.start = 0; + absolute.end = 0; + return absolute, true; } line_count := 0; @@ -32,6 +33,12 @@ get_absolute_range :: proc(range: Range, document_text: [] u8) -> (AbsoluteRange absolute.start = index + get_character_offset_u16_to_u8(range.start.character, document_text[index:]); + //if the last line was indexed at zero we have to move it back to index 1. + //This happens when line = 0 + if index == 0 { + index = 1; + } + if !get_index_at_line(&index, &line_count, &last, document_text, range.end.line) { return absolute, false; } @@ -44,10 +51,16 @@ get_absolute_range :: proc(range: Range, document_text: [] u8) -> (AbsoluteRange get_index_at_line :: proc(current_index: ^int, current_line: ^int, last: ^u8, document_text: []u8, end_line: int) -> bool { + if end_line == 0 { + current_index^ = 0; + return true; + } + if current_line^ == end_line { return true; } + for ; current_index^ < len(document_text); current_index^ += 1 { current := document_text[current_index^]; @@ -86,13 +99,15 @@ get_character_offset_u16_to_u8 :: proc(character_offset: int, document_text: [] utf8_idx := 0; utf16_idx := 0; - fmt.println(character_offset); - for utf16_idx < character_offset { r, w := utf8.decode_rune(document_text[utf8_idx:]); - if r < 0x10000 { + if r == '\n' { + return utf8_idx; + } + + else if r < 0x10000 { utf16_idx += 1; } @@ -105,4 +120,32 @@ get_character_offset_u16_to_u8 :: proc(character_offset: int, document_text: [] } return utf8_idx; +} + + +get_end_line_u16 :: proc(document_text: [] u8) -> int { + + utf8_idx := 0; + utf16_idx := 0; + + for utf8_idx < len(document_text) { + r, w := utf8.decode_rune(document_text[utf8_idx:]); + + if r == '\n' { + return utf16_idx; + } + + else if r < 0x10000 { + utf16_idx += 1; + } + + else { + utf16_idx += 2; + } + + utf8_idx += w; + + } + + return utf16_idx; }
\ No newline at end of file diff --git a/src/requests.odin b/src/requests.odin index 50d6dd7..84d131c 100644 --- a/src/requests.odin +++ b/src/requests.odin @@ -130,7 +130,6 @@ read_and_parse_body :: proc(reader: ^Reader, header: Header) -> (json.Value, boo handle_request :: proc(request: json.Value, config: ^Config, writer: ^Writer) -> bool { - log.info("Handling request"); root, ok := request.value.(json.Object); @@ -164,7 +163,8 @@ handle_request :: proc(request: json.Value, config: ^Config, writer: ^Writer) -> "exit" = notification_exit, "textDocument/didOpen" = notification_did_open, "textDocument/didChange" = notification_did_change, - "textDocument/didClose" = notification_did_close}; + "textDocument/didClose" = notification_did_close, + "textDocument/didSave" = notification_did_save }; fn: proc(json.Value, RequestId, ^Config, ^Writer) -> Error; fn, ok = call_map[method]; @@ -226,6 +226,7 @@ request_initialize :: proc(params: json.Value, id: RequestId, config: ^Config, w params = ResponseInitializeParams { capabilities = ServerCapabilities { textDocumentSync = 2, //incremental + definitionProvider = true, }, }, id = id, @@ -271,7 +272,7 @@ notification_did_open :: proc(params: json.Value, id: RequestId, config: ^Config return .ParseError; } - return document_open(open_params.textDocument.uri, open_params.textDocument.text); + return document_open(open_params.textDocument.uri, open_params.textDocument.text, writer); } notification_did_change :: proc(params: json.Value, id: RequestId, config: ^Config, writer: ^Writer) -> Error { @@ -288,7 +289,7 @@ notification_did_change :: proc(params: json.Value, id: RequestId, config: ^Conf return .ParseError; } - document_apply_changes(change_params.textDocument.uri, change_params.contentChanges); + document_apply_changes(change_params.textDocument.uri, change_params.contentChanges, writer); return .None; } @@ -310,3 +311,10 @@ notification_did_close :: proc(params: json.Value, id: RequestId, config: ^Confi return document_close(close_params.textDocument.uri); } +notification_did_save :: proc(params: json.Value, id: RequestId, config: ^Config, writer: ^Writer) -> Error { + + + + return .None; +} + diff --git a/src/types.odin b/src/types.odin index 2edd135..274caae 100644 --- a/src/types.odin +++ b/src/types.odin @@ -2,6 +2,12 @@ package main import "core:encoding/json" +/* + General types +*/ + +//TODO(Daniel, move some of the more specific structs to their appropriate place) + RequestId :: union { string, i64, @@ -53,8 +59,14 @@ NotificationLoggingParams :: struct { message: string, }; +NotificationPublishDiagnosticsParams :: struct { + uri: string, + diagnostics: [] Diagnostic, +}; + NotificationParams :: union { NotificationLoggingParams, + NotificationPublishDiagnosticsParams, }; Notification :: struct { @@ -86,6 +98,7 @@ MarkupKind :: enum { ServerCapabilities :: struct { textDocumentSync: int, + definitionProvider: bool, }; CompletionClientCapabilities :: struct { @@ -139,6 +152,20 @@ TextDocumentItem :: struct { text: string, }; +DiagnosticSeverity :: enum { + Error = 1, + Warning = 2, + Information = 3, + Hint = 4, +}; + +Diagnostic :: struct { + range: Range, + severity: DiagnosticSeverity, + code: string, + message: string, +}; + DidOpenTextDocumentParams :: struct { textDocument: TextDocumentItem, }; @@ -148,7 +175,6 @@ DidChangeTextDocumentParams :: struct { contentChanges: [dynamic] TextDocumentContentChangeEvent, }; -DidCloseTextDocumentParams :: struct{ +DidCloseTextDocumentParams :: struct { textDocument: TextDocumentIdentifier, }; - |