From 4a1f4580155249ecf0d1c413936f45df6efa4671 Mon Sep 17 00:00:00 2001 From: Eko Date: Wed, 27 Oct 2021 22:36:28 +0200 Subject: [PATCH] Added debug infos, some lsp fixes and new lsp feature textDocument/formatting --- NppLsp.v | 59 ++++++++++-- lsp/client.v | 67 ++++++++++++- lsp/lspprotocol.v | 216 ++++++++++++++++++++++++++++++++++++++++-- lsp/process_handler.v | 5 + scintilla/scintilla.v | 29 +++++- winapi/winapi.v | 5 + 6 files changed, 361 insertions(+), 20 deletions(-) diff --git a/NppLsp.v b/NppLsp.v index 2817f20..2ed4e87 100644 --- a/NppLsp.v +++ b/NppLsp.v @@ -241,10 +241,16 @@ fn get_funcs_array(mut nb_func &int) &FuncItem { 'Restart server for current language': restart_lsp_server 'Stop all configured lsp server': stop_all_server '-': voidptr(0) + 'Format document': format_document + 'Goto definition': goto_definition + 'Peek definition': peek_definition + 'Goto implementation': goto_implementation + 'Peek implementation': peek_implementation + '--': voidptr(0) 'toggle_console': toggle_console 'Open configuration file': open_config 'Apply current configuration': apply_config - '--': voidptr(0) + '---': voidptr(0) 'About': about } @@ -265,8 +271,10 @@ fn get_funcs_array(mut nb_func &int) &FuncItem { } fn check_lexer(buffer_id u64) { + p.console_window.log('checking current lexer', 0) p.current_language = npp.get_language_name_from_id(buffer_id) p.document_is_of_interest = p.current_language in p.lsp_config.lspservers + p.console_window.log('', 0) check_ls_status(true) } @@ -276,6 +284,7 @@ pub fn apply_config() { } fn read_main_config() { + p.console_window.log('rereading main config', 0) p.lsp_config = lsp.decode_config(p.main_config_file) } @@ -289,6 +298,7 @@ pub fn open_config() { } pub fn start_lsp_server() { + p.console_window.log('starting language server: ${p.current_language}', 0) check_ls_status(false) // create and send a fake nppn_bufferactivated event mut sci_header := sci.SCNotification{text: &char(0)} @@ -300,6 +310,7 @@ pub fn start_lsp_server() { } pub fn stop_lsp_server() { + p.console_window.log('stopping language server: ${p.current_language}', 0) p.proc_manager.stop(p.current_language) p.lsp_config.lspservers[p.current_language].initialized = false p.console_window.log('initialized = ${p.lsp_config.lspservers[p.current_language].initialized}', 0) @@ -307,11 +318,13 @@ pub fn stop_lsp_server() { } pub fn restart_lsp_server() { + p.console_window.log('restarting lsp server: ${p.current_language}', 0) stop_lsp_server() start_lsp_server() } pub fn stop_all_server() { + p.console_window.log('stop all running language server', 0) p.proc_manager.stop_all_running_processes() } @@ -329,30 +342,62 @@ pub fn about() { } fn check_ls_status(check_auto_start bool) { - + p.console_window.log('checking language server status: ${p.current_language}', 0) if p.current_language in p.proc_manager.running_processes { + p.console_window.log(' is already running', 0) p.cur_lang_srv_running = true return } if check_auto_start && !p.lsp_config.lspservers[p.current_language].auto_start_server { + p.console_window.log(' either unknown language or server should not be started automatically', 0) p.cur_lang_srv_running = false return } - + + p.console_window.log(' trying to start ${p.lsp_config.lspservers[p.current_language].executable}', 0) proc_status := p.proc_manager.start(p.current_language, p.lsp_config.lspservers[p.current_language].executable, p.lsp_config.lspservers[p.current_language].args.join(' ')) - - if proc_status == .running { + + match proc_status { + .running { + p.console_window.log(' running', 0) p.console_window.log('${p.current_language} server is running', 0) p.cur_lang_srv_running = true p.current_stdin = p.proc_manager.running_processes[p.current_language].stdin - } else { - p.cur_lang_srv_running = false + } + .error_no_executable { + p.console_window.log(' cannot find executable', 0) + p.cur_lang_srv_running = false + } + .failed_to_start { + p.console_window.log(' failed to start', 0) + p.cur_lang_srv_running = false + } } } +pub fn format_document() { + lsp.on_format_document(p.current_file_path) +} + +pub fn goto_definition() { + lsp.on_goto_definition(p.current_file_path) +} + +pub fn peek_definition() { + lsp.on_peek_definition(p.current_file_path) +} + +pub fn goto_implementation() { + lsp.on_goto_implementation(p.current_file_path) +} + +pub fn peek_implementation() { + lsp.on_peek_implementation(p.current_file_path) +} + [windows_stdcall] [export: DllMain] fn main(hinst voidptr, fdw_reason int, lp_reserved voidptr) bool{ diff --git a/lsp/client.v b/lsp/client.v index eda1a1d..9917ddd 100644 --- a/lsp/client.v +++ b/lsp/client.v @@ -137,6 +137,49 @@ pub fn on_buffer_modified(file_name string, } } +pub fn on_format_document(file_name string) { + if p.lsp_config.lspservers[p.current_language].initialized { + lsp.write_to( + p.current_stdin, + lsp.format_document(file_name, editor.get_tab_size(), editor.use_spaces(), true, true, true) + ) + } +} + +pub fn on_goto_definition(file_name string) { + if p.lsp_config.lspservers[p.current_language].initialized { + current_pos := editor.get_current_position() + current_line := editor.line_from_position(usize(current_pos)) + line_start_pos := editor.position_from_line(usize(current_line)) + char_pos := current_pos - line_start_pos + lsp.write_to( + p.current_stdin, + lsp.goto_definition(file_name, u32(current_line), u32(char_pos)) + ) + } +} + +pub fn on_goto_implementation(file_name string) { + if p.lsp_config.lspservers[p.current_language].initialized { + current_pos := editor.get_current_position() + current_line := editor.line_from_position(usize(current_pos)) + line_start_pos := editor.position_from_line(usize(current_line)) + char_pos := current_pos - line_start_pos + lsp.write_to( + p.current_stdin, + lsp.goto_implementation(file_name, u32(current_line), u32(char_pos)) + ) + } +} + +pub fn on_peek_definition(file_name string) { + p.console_window.log('on_peek_definition not implemented yet', 3) +} + +pub fn on_peek_implementation(file_name string) { + p.console_window.log('on_peek_implementation not implemented yet', 3) +} + fn notification_handler(json_message JsonMessage) { match json_message.method { 'textDocument/publishDiagnostics' { @@ -203,7 +246,7 @@ fn initialize_msg_response(json_message string) { } } -fn request_completion_response(json_message string) { +fn completion_response(json_message string) { cl := json2.decode(json_message) or { CompletionList{} } mut ci := []CompletionItem{} if cl.items.len != 0 { @@ -215,7 +258,7 @@ fn request_completion_response(json_message string) { if ci.len > 0 { editor.display_completion_list(ci.map(it.label).join('\n')) } } -fn request_signature_help_repsonse(json_message string) { +fn signature_help_repsonse(json_message string) { p.console_window.log(' signature help response received: $json_message', 0) sh := json2.decode(json_message) or { SignatureHelp{} } if sh.signatures.len > 0 { @@ -223,3 +266,23 @@ fn request_signature_help_repsonse(json_message string) { } p.console_window.log('$sh', 0) } + +fn format_document_repsonse(json_message string) { + p.console_window.log(' format document response received: $json_message', 0) + tea := json2.decode(json_message) or { TextEditArray{} } + editor.begin_undo_action() + for item in tea.items { + start_pos := u32(editor.position_from_line(usize(item.range.start.line))) + item.range.start.character + end_pos := u32(editor.position_from_line(usize(item.range.end.line))) + item.range.end.character + editor.replace_target(start_pos, end_pos, item.new_text) + } + editor.end_undo_action() +} + +fn goto_definition_repsonse(json_message string) { + p.console_window.log('NOT IMPLEMENTED YET: goto definition response received: $json_message', 3) +} + +fn goto_implementation_repsonse(json_message string) { + p.console_window.log('NOT IMPLEMENTED YET: goto implementation response received: $json_message', 3) +} diff --git a/lsp/lspprotocol.v b/lsp/lspprotocol.v index 2739df7..8182c78 100644 --- a/lsp/lspprotocol.v +++ b/lsp/lspprotocol.v @@ -18,13 +18,13 @@ struct Message { fn (m Message) encode() string { body := match m.msg_type { .request { - '{"jsonrpc": "2.0", "id": $m.id, "method": $m.method, "params": $m.params}' + '{"jsonrpc":"2.0","id":$m.id,"method":$m.method,"params":$m.params}' } .response { - '{"jsonrpc": "2.0", "id": $m.id}' + '{"jsonrpc":"2.0","id":$m.id}' } .notification { - '{"jsonrpc": "2.0", "method": $m.method, "params": $m.params}' + '{"jsonrpc":"2.0","method":$m.method,"params":$m.params}' } } return 'Content-Length: ${body.len}\r\n\r\n${body}' @@ -137,7 +137,7 @@ pub fn request_completion(file_path DocumentUri, line u32, char_pos u32, trigger method: '"textDocument/completion"' params: '{"textDocument":{"uri":"file:///$uri_path"}, "position":{"line":$line, "character":$char_pos}, "context":{"triggerKind":1, "triggerCharacter":"$trigger_character"}}' } - p.open_response_messages[m.id] = request_completion_response + p.open_response_messages[m.id] = completion_response return m.encode() } @@ -147,12 +147,53 @@ pub fn request_signature_help(file_path DocumentUri, line u32, char_pos u32, tri msg_type: JsonRpcMessageType.request id: p.lsp_config.lspservers[p.current_language].get_next_id() method: '"textDocument/signatureHelp"' - params: '{"textDocument":{"uri":"file:///$uri_path"}, "position":{"line":$line, "character":$char_pos}, "context":{"isRetrigger":false, "triggerCharacter":"$trigger_character", "triggerKind":1}}' + params: '{"textDocument":{"uri":"file:///$uri_path"},"position":{"line":$line,"character":$char_pos},"context":{"isRetrigger":false,"triggerCharacter":"$trigger_character","triggerKind":2}}' } - p.open_response_messages[m.id] = request_signature_help_repsonse + p.open_response_messages[m.id] = signature_help_repsonse return m.encode() } +pub fn format_document(file_path DocumentUri, + tab_size u32, + insert_spaces bool, + trim_trailing_whitespace bool, + insert_final_new_line bool, + trim_final_new_lines bool) string { + + uri_path := make_uri(file_path) + m := Message { + msg_type: JsonRpcMessageType.request + id: p.lsp_config.lspservers[p.current_language].get_next_id() + method: '"textDocument/formatting"' + params: '{"textDocument":{"uri":"file:///$uri_path"},"options":{"insertSpaces":$insert_spaces,"tabSize":$tab_size,"trimTrailingWhitespace":$trim_trailing_whitespace,"insertFinalNewline":$insert_final_new_line,"trimFinalNewlines":$trim_final_new_lines}}' + } + p.open_response_messages[m.id] = format_document_repsonse + return m.encode() +} + +pub fn goto_definition(file_path DocumentUri, line u32, char_position u32) string { + uri_path := make_uri(file_path) + m := Message { + msg_type: JsonRpcMessageType.request + id: p.lsp_config.lspservers[p.current_language].get_next_id() + method: '"textDocument/definition"' + params: '{"textDocument":{"uri":"file:///$uri_path"},"position":{"character":$char_position,"line":$line}}' + } + p.open_response_messages[m.id] = goto_definition_repsonse + return m.encode() +} + +pub fn goto_implementation(file_path DocumentUri, line u32, char_position u32) string { + uri_path := make_uri(file_path) + m := Message { + msg_type: JsonRpcMessageType.request + id: p.lsp_config.lspservers[p.current_language].get_next_id() + method: '"textDocument/implementation"' + params: '{"textDocument":{"uri":"file:///$uri_path"},"position":{"character":$char_position,"line":$line}}' + } + p.open_response_messages[m.id] = goto_implementation_repsonse + return m.encode() +} // **************************************************************************** // JSON Structures @@ -204,18 +245,33 @@ pub fn make_uri(path string) string { return path.replace_each(['\\', '/', ':', '%3A']) } +pub struct TextDocumentIdentifier { +pub mut: + uri DocumentUri +} + +pub fn (mut tdi TextDocumentIdentifier) from_json(f json2.Any) { + obj := f.as_map() + for k, v in obj { + match k { + 'uri' { tdi.uri = v.str() } + else {} + } + } +} + pub struct Position { pub mut: - line int - character int + line u32 + character u32 } pub fn (mut p Position) from_json(f json2.Any) { obj := f.as_map() for k, v in obj { match k { - 'line' { p.line = v.int() } - 'character' { p.character = v.int() } + 'line' { p.line = u32(v.int()) } + 'character' { p.character = u32(v.int()) } else {} } } @@ -240,6 +296,7 @@ pub fn (mut r Range) from_json(f json2.Any) { pub struct Location { pub mut: + valid bool = true uri DocumentUri range Range } @@ -255,6 +312,72 @@ pub fn (mut l Location) from_json(f json2.Any) { } } +pub struct LocationArray { +pub mut: + items []Location +} + +pub fn (mut la LocationArray) from_json(f json2.Any) { + for item in f.arr() { + la.items << json2.decode(item.str()) or { Location{} } + } +} + +pub struct LocationLink { +pub mut: + /** + * Span of the origin of this link. + * + * Used as the underlined span for mouse interaction. Defaults to the word + * range at the mouse position. + */ + origin_selection_range Range + + /** + * The target resource identifier of this link. + */ + target_uri DocumentUri + + /** + * The full target range of this link. If the target for example is a symbol + * then target range is the range enclosing this symbol not including + * leading/trailing whitespace but everything else like comments. This + * information is typically used to highlight the range in the editor. + */ + target_range Range + + /** + * The range that should be selected and revealed when this link is being + * followed, e.g the name of a function. Must be contained by the the + * `targetRange`. See also `DocumentSymbol#range` + */ + target_selection_range Range +} + +pub fn (mut ll LocationLink) from_json(f json2.Any) { + obj := f.as_map() + for k, v in obj { + match k { + 'originSelectionRange' { ll.origin_selection_range = json2.decode(v.str()) or { Range{} } } + 'targetUri' { ll.target_uri = v.str() } + 'targetRange' { ll.target_range = json2.decode(v.str()) or { Range{} } } + 'targetSelectionRange' { ll.target_selection_range = json2.decode(v.str()) or { Range{} } } + else {} + } + } +} + +pub struct LocationLinkArray { +pub mut: + items []LocationLink +} + +pub fn (mut lla LocationLinkArray) from_json(f json2.Any) { + for item in f.arr() { + lla.items << json2.decode(item.str()) or { LocationLink{} } + } +} + pub struct CompletionOptions { pub mut: resolve_provider bool @@ -673,3 +796,76 @@ pub fn (mut pi ParameterInformation) from_json(f json2.Any) { } } } + +pub struct TextEditArray { +pub mut: + items []TextEdit +} + +pub fn (mut tea TextEditArray) from_json(f json2.Any) { + for item in f.arr() { + tea.items << json2.decode(item.str()) or { TextEdit{} } + } +} + +pub struct TextEdit { +pub mut: + range Range + new_text string +} + +pub fn (mut te TextEdit) from_json(f json2.Any) { + obj := f.as_map() + for k, v in obj { + match k { + 'range' { te.range = json2.decode(v.str()) or { Range{} } } + 'newText' { te.new_text = v.str() } + else {} + } + } +} + +// pub struct DocumentFormattingParams{ +// pub mut: + // textDocument: TextDocumentIdentifier + // options FormattingOptions +// } + +// pub fn (mut dfp DocumentFormattingParams) from_json(f json2.Any) { + // obj := f.as_map() + // for k, v in obj { + // match k { + // 'textDocument' { json2.decodev.str() or { TextDocumentIdentifier{} } } + // 'options' { json2.decodev.str() or { FormattingOptions{} } } + // else {} + // } + // } +// } + +// pub struct FormattingOptions { +// pub mut: + // tab_size: u32 + // insert_spaces: bool + // trim_trailing_whitespace: bool + // insert_final_newline: bool + // trim_final_newlines: bool + + // /** + // * Signature for further properties. + // */ + // // [key: string]: boolean | integer | string; +// } + +// pub fn (mut fo FormattingOptions) from_json(f json2.Any) { + // obj := f.as_map() + // for k, v in obj { + // match k { + // 'tabSize' { fo.tab_size = u32(v.int()) } + // 'insertSpaces' { fo.insert_spaces = v.bool() } + // 'trimTrailingWhitespace' { fo.trim_trailing_whitespace = v.bool() } + // 'insertFinalNewline' { fo.insert_final_newline = v.bool() } + // 'trimFinalNewlines' { fo.trim_final_newlines = v.bool() } + // else {} + // } + // } +// } diff --git a/lsp/process_handler.v b/lsp/process_handler.v index 06bb145..4659894 100644 --- a/lsp/process_handler.v +++ b/lsp/process_handler.v @@ -26,6 +26,7 @@ pub mut: } pub fn (mut pm ProcessManager) start(language string, exe string, args string) ProcessStatus { + p.console_window.log('ProcessManager start $exe $args', 0) pm.check_running_processes() if language in pm.running_processes { return ProcessStatus.running } if !os.exists(exe) { return ProcessStatus.error_no_executable } @@ -53,6 +54,7 @@ pub fn (mut pm ProcessManager) stop_all_running_processes() { } fn (pm ProcessManager) create_child_process(exe string, args string) Process { + p.console_window.log('create_child_process $exe $args', 0) mut process := Process{ exe: exe args: args @@ -124,6 +126,9 @@ fn (pm ProcessManager) create_child_process(exe string, args string) Process { process.handle = proc_info.h_process process.pid = proc_info.dw_process_id return process + } else { + error_message := os.get_error_msg(api.get_last_error()) + p.console_window.log('create_child_process returned: $error_message', 4) } return Process{} } diff --git a/scintilla/scintilla.v b/scintilla/scintilla.v index f40c5c7..2b6ddb3 100644 --- a/scintilla/scintilla.v +++ b/scintilla/scintilla.v @@ -100,7 +100,7 @@ pub fn (e Editor) clear_diagnostics() { e.call(sci_annotationclearall, 0, 0) } -pub fn (e Editor) add_diagnostics_info(line int, message string, severity int) { +pub fn (e Editor) add_diagnostics_info(line u32, message string, severity int) { p.console_window.log('add_diagnostics_info', 0) mut style := match severity { @@ -159,3 +159,30 @@ pub fn (mut e Editor) init_styles() { e.call(sci_stylesetfore, e.warning_msg_id_style, 0x64e0ff) e.call(sci_stylesetfore, e.info_msg_id_style, 0xbfb2ab) } + +pub fn (e Editor) get_tab_size() u32 { + return u32(e.call(sci_gettabwidth, 0, 0)) +} + +pub fn (e Editor) use_spaces() bool { + return e.call(sci_getusetabs, 0, 0) == 0 +} + + +pub fn (e Editor) begin_undo_action() { + e.call(sci_beginundoaction, 0, 0) +} + +pub fn (e Editor) end_undo_action() { + e.call(sci_endundoaction, 0, 0) +} + +pub fn (e Editor) replace_target(start_pos u32, end_pos u32, new_text string) { + e.call(sci_settargetstart, usize(start_pos), 0) + e.call(sci_settargetend, usize(end_pos), 0) + e.call(sci_replacetarget, -1, isize(new_text.str)) +} + +pub fn (e Editor) get_current_position() u32 { + return u32(e.call(sci_getcurrentpos, 0, 0)) +} \ No newline at end of file diff --git a/winapi/winapi.v b/winapi/winapi.v index f5bbb8a..2e523fc 100644 --- a/winapi/winapi.v +++ b/winapi/winapi.v @@ -248,4 +248,9 @@ pub fn create_process( current_directory, startup_info, process_information) +} + +fn C.GetLastError() u32 +pub fn get_last_error() int { + return int(C.GetLastError()) } \ No newline at end of file