From: Foxe Chen Date: Wed, 25 Feb 2026 20:53:21 +0000 (+0000) Subject: patch 9.2.0060: No support for the DAP channel mode X-Git-Tag: v9.2.0060^0 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=b7eb0c2d38198e5f6cc7e806e514f88b2c9ef5a2;p=thirdparty%2Fvim.git patch 9.2.0060: No support for the DAP channel mode Problem: No support for the DAP channel mode Solution: Add native channel support for the debug-adapter-protocol (Foxe Chen) closes: #19432 Signed-off-by: Foxe Chen Signed-off-by: Christian Brabandt --- diff --git a/runtime/doc/channel.txt b/runtime/doc/channel.txt index 5c1de248d6..36694af9f9 100644 --- a/runtime/doc/channel.txt +++ b/runtime/doc/channel.txt @@ -1,4 +1,4 @@ -*channel.txt* For Vim version 9.2. Last change: 2026 Feb 18 +*channel.txt* For Vim version 9.2. Last change: 2026 Feb 25 VIM REFERENCE MANUAL by Bram Moolenaar @@ -26,6 +26,7 @@ The Netbeans interface also uses a channel. |netbeans| 13. Controlling a job |job-control| 14. Using a prompt buffer |prompt-buffer| 15. Language Server Protocol |language-server-protocol| +16. Debug Adapter Protocol |debug-adapter-protocol| *E1277* {only when compiled with the |+channel| feature for channel stuff} @@ -56,6 +57,7 @@ NL every message ends in a NL (newline) character JSON JSON encoding |json_encode()| JS JavaScript style JSON-like encoding |js_encode()| LSP Language Server Protocol encoding |language-server-protocol| +DAP Debug Adapter Protocol encoding |debug-adapter-protocol| Common combination are: - Using a job connected through pipes in NL mode. E.g., to run a style @@ -143,6 +145,7 @@ unreachable on the network. "nl" - Use messages that end in a NL character "raw" - Use raw messages "lsp" - Use language server protocol encoding + "dap" - Use debug adapter protocol encoding *channel-callback* *E921* "callback" A function that is called when a message is received that is not handled otherwise (e.g. a JSON message with ID zero). It @@ -153,8 +156,9 @@ unreachable on the network. endfunc let channel = ch_open("localhost:8765", {"callback": "Handle"}) < - When "mode" is "json" or "js" or "lsp" the "msg" argument is - the body of the received message, converted to Vim types. + When "mode" is any of "json", "js", "lsp" or "dap" the "msg" + argument is the body of the received message, converted to Vim + types. When "mode" is "nl" the "msg" argument is one message, excluding the NL. When "mode" is "raw" the "msg" argument is the whole message @@ -537,7 +541,8 @@ ch_evalexpr({handle}, {expr} [, {options}]) *ch_evalexpr()* according to the type of channel. The function cannot be used with a raw channel. See |channel-use|. {handle} can be a Channel or a Job that has a Channel. - When using the "lsp" channel mode, {expr} must be a |Dict|. + When using the "lsp" or "dap" channel mode, {expr} must be a + |Dict|. *E917* {options} must be a Dictionary. It must not have a "callback" entry. It can have a "timeout" entry to specify the timeout @@ -545,8 +550,8 @@ ch_evalexpr({handle}, {expr} [, {options}]) *ch_evalexpr()* ch_evalexpr() waits for a response and returns the decoded expression. When there is an error or timeout it returns an - empty |String| or, when using the "lsp" channel mode, returns an - empty |Dict|. + empty |String| or, when using the "lsp" or "dap" channel mode, + returns an empty |Dict|. Note that while waiting for the response, Vim handles other messages. You need to make sure this doesn't cause trouble. @@ -627,7 +632,7 @@ ch_info({handle}) *ch_info()* "err_io" "out", "null", "pipe", "file" or "buffer" "err_timeout" timeout in msec "in_status" "open" or "closed" - "in_mode" "NL", "RAW", "JSON", "JS" or "LSP" + "in_mode" "NL", "RAW", "JSON", "JS" or "LSP" or "DAP" "in_io" "null", "pipe", "file" or "buffer" "in_timeout" timeout in msec @@ -733,14 +738,15 @@ ch_sendexpr({handle}, {expr} [, {options}]) *ch_sendexpr()* with a raw channel. See |channel-use|. *E912* {handle} can be a Channel or a Job that has a Channel. - When using the "lsp" channel mode, {expr} must be a |Dict|. + When using the "lsp" or "dap" channel mode, {expr} must be a + |Dict|. - If the channel mode is "lsp", then returns a Dict. Otherwise - returns an empty String. If the "callback" item is present in - {options}, then the returned Dict contains the ID of the - request message. The ID can be used to send a cancellation - request to the LSP server (if needed). Returns an empty Dict - on error. + If the channel mode is "lsp" or "dap", then returns a Dict. + Otherwise returns an empty String. If the "callback" item is + present in {options}, then the returned Dict contains the ID + of the request message. The ID can be used to send a + cancellation request to the LSP server or debug adapter (if + needed). Returns an empty Dict on error. If a response message is not expected for {expr}, then don't specify the "callback" item in {options}. @@ -1607,5 +1613,33 @@ The "params" field is optional: > "params": } -< +============================================================================== +16. Debug Adapter Protocol *debug-adapter-protocol* + +The debug adapter protocol is very similar to the language server protocol, +with the main difference being that it does not use the JSON-RPC format. The +specification can be found here: + + https://microsoft.github.io/debug-adapter-protocol/specification + +The protocol uses the same header format as the LSP protocol. + +To encode and send a DAP request/notification message in a Vim |Dict| into a +JSON message and to receive and decode a DAP JSON response/notification +message into a Vim |Dict|, connect to the debug adapter with the +|channel-mode| set to "dap". + +For messages received on a channel with |channel-mode| set to "dap", Vim will +process the HTTP header and decode the JSON payload into a Vim |Dict| type. +When sending messages on a channel using the |ch_evalexpr()| or +|ch_sendexpr()| functions, Vim will add the HTTP header and encode the Vim +expression into JSON. + +Vim will automatically add the "seq" field to the JSON DAP message, and manage +the "request_seq" field as well for responses. However it will not add the +"type" field, it should be manually specified in the |Dict|. + +Otherwise the behaviour is the same as how Vim handles the "lsp" channel mode +|language-server-protocol|. + vim:tw=78:ts=8:noet:ft=help:norl: diff --git a/runtime/doc/tags b/runtime/doc/tags index 2d3858b4d7..f86fe37eb5 100644 --- a/runtime/doc/tags +++ b/runtime/doc/tags @@ -6989,6 +6989,7 @@ dav pi_netrw.txt /*dav* davs pi_netrw.txt /*davs* daw motion.txt /*daw* dd change.txt /*dd* +debug-adapter-protocol channel.txt /*debug-adapter-protocol* debug-gcc debug.txt /*debug-gcc* debug-highlight debugger.txt /*debug-highlight* debug-leaks debug.txt /*debug-leaks* diff --git a/runtime/doc/version9.txt b/runtime/doc/version9.txt index 6c87266966..17535bdbd7 100644 --- a/runtime/doc/version9.txt +++ b/runtime/doc/version9.txt @@ -1,4 +1,4 @@ -*version9.txt* For Vim version 9.2. Last change: 2026 Feb 24 +*version9.txt* For Vim version 9.2. Last change: 2026 Feb 25 VIM REFERENCE MANUAL by Bram Moolenaar @@ -52592,6 +52592,7 @@ Other ~ ----- - The new |xdg.vim| script for full XDG compatibility is included. - |ConPTY| support is considered stable as of Windows 11. +- Support for "dap" channel mode for the |debug-adapter-protocol|. *changed-9.3* Changed~ diff --git a/src/channel.c b/src/channel.c index abe9bddca9..462281dbd3 100644 --- a/src/channel.c +++ b/src/channel.c @@ -1981,9 +1981,9 @@ channel_collapse(channel_T *channel, ch_part_T part, int want_nl) last_node = node->rq_next; len = node->rq_buflen + last_node->rq_buflen; - if (want_nl || mode == CH_MODE_LSP) + if (want_nl || mode == CH_MODE_LSP || mode == CH_MODE_DAP) while (last_node->rq_next != NULL - && (mode == CH_MODE_LSP + && (mode == CH_MODE_LSP || mode == CH_MODE_DAP || channel_first_nl(last_node) == NULL)) { last_node = last_node->rq_next; @@ -2134,16 +2134,22 @@ channel_fill(js_read_T *reader) } /* - * Process the HTTP header in a Language Server Protocol (LSP) message. + * Process the HTTP header in a Language Server Protocol (LSP) message or + * Debug Adapter Protocol (DAP) message. * * The message format is described in the LSP specification: * https://microsoft.github.io/language-server-protocol/specification * + * For DAP: + * https://microsoft.github.io/debug-adapter-protocol/specification + * * It has the following two fields: * * Content-Length: ... * Content-Type: application/vscode-jsonrpc; charset=utf-8 * + * For DAP, there is no "Content-Type" field (as of now). + * * Each field ends with "\r\n". The header ends with an additional "\r\n". * * Returns OK if a valid header is received and FAIL if some fields in the @@ -2151,7 +2157,7 @@ channel_fill(js_read_T *reader) * need to wait for more data to arrive. */ static int -channel_process_lsp_http_hdr(js_read_T *reader) +channel_process_lspdap_http_hdr(js_read_T *reader) { char_u *line_start; char_u *p; @@ -2235,8 +2241,9 @@ channel_parse_json(channel_T *channel, ch_part_T part) reader.js_cookie = channel; reader.js_cookie_arg = part; - if (chanpart->ch_mode == CH_MODE_LSP) - status = channel_process_lsp_http_hdr(&reader); + if (chanpart->ch_mode == CH_MODE_LSP + || chanpart->ch_mode == CH_MODE_DAP) + status = channel_process_lspdap_http_hdr(&reader); // When a message is incomplete we wait for a short while for more to // arrive. After the delay drop the input, otherwise a truncated string @@ -2253,12 +2260,13 @@ channel_parse_json(channel_T *channel, ch_part_T part) { // Only accept the response when it is a list with at least two // items. - if (chanpart->ch_mode == CH_MODE_LSP && listtv.v_type != VAR_DICT) + if ((chanpart->ch_mode == CH_MODE_LSP || chanpart->ch_mode == CH_MODE_DAP) + && listtv.v_type != VAR_DICT) { ch_error(channel, "Did not receive a LSP dict, discarding"); clear_tv(&listtv); } - else if (chanpart->ch_mode != CH_MODE_LSP + else if (chanpart->ch_mode != CH_MODE_LSP && chanpart->ch_mode != CH_MODE_DAP && (listtv.v_type != VAR_LIST || listtv.vval.v_list->lv_len < 2)) { if (listtv.v_type != VAR_LIST) @@ -2467,7 +2475,7 @@ channel_has_block_id(chanpart_T *chanpart, int id) /* * Get a message from the JSON queue for channel "channel". * When "id" is positive it must match the first number in the list. - * When "id" is zero or negative jut get the first message. But not one + * When "id" is zero or negative just get the first message. But not one * in the ch_block_ids list. * When "without_callback" is TRUE also get messages that were pushed back. * Return OK when found and return the value in "rettv". @@ -2489,7 +2497,8 @@ channel_get_json( list_T *l; typval_T *tv; - if (channel->ch_part[part].ch_mode != CH_MODE_LSP) + if (channel->ch_part[part].ch_mode != CH_MODE_LSP + && channel->ch_part[part].ch_mode != CH_MODE_DAP) { l = item->jq_value->vval.v_list; CHECK_LIST_MATERIALIZE(l); @@ -2500,29 +2509,50 @@ channel_get_json( dict_T *d; dictitem_T *di; - // LSP message payload is a JSON-RPC dict. - // For RPC requests and responses, the 'id' item will be present. - // For notifications, it will not be present. - if (id > 0) + if (channel->ch_part[part].ch_mode == CH_MODE_LSP) { - if (item->jq_value->v_type != VAR_DICT) - goto nextitem; - d = item->jq_value->vval.v_dict; - if (d == NULL) - goto nextitem; - // When looking for a response message from the LSP server, - // ignore new LSP request and notification messages.  LSP - // request and notification messages have the "method" field in - // the header and the response messages do not have this field. - if (dict_has_key(d, "method")) - goto nextitem; - di = dict_find(d, (char_u *)"id", -1); - if (di == NULL) - goto nextitem; - tv = &di->di_tv; + // LSP message payload is a JSON-RPC dict. For RPC requests and + // responses, the 'id' item will be present. For notifications, + // it will not be present. + if (id > 0) + { + if (item->jq_value->v_type != VAR_DICT) + goto nextitem; + d = item->jq_value->vval.v_dict; + if (d == NULL) + goto nextitem; + // When looking for a response message from the LSP server, + // ignore new LSP request and notification messages.  LSP + // request and notification messages have the "method" field + // in the header and the response messages do not have this + // field. + if (dict_has_key(d, "method")) + goto nextitem; + di = dict_find(d, (char_u *)"id", -1); + if (di == NULL) + goto nextitem; + tv = &di->di_tv; + } + else + tv = item->jq_value; } else - tv = item->jq_value; + { + if (id > 0) + { + if (item->jq_value->v_type != VAR_DICT) + goto nextitem; + d = item->jq_value->vval.v_dict; + if (d == NULL) + goto nextitem; + di = dict_find(d, (char_u *)"request_seq", -1); + if (di == NULL) + goto nextitem; + tv = &di->di_tv; + } + else + tv = item->jq_value; + } } if ((without_callback || !item->jq_no_callback) @@ -2904,7 +2934,8 @@ channel_use_json_head(channel_T *channel, ch_part_T part) ch_mode_T ch_mode = channel->ch_part[part].ch_mode; return ch_mode == CH_MODE_JSON || ch_mode == CH_MODE_JS - || ch_mode == CH_MODE_LSP; + || ch_mode == CH_MODE_LSP + || ch_mode == CH_MODE_DAP; } /* @@ -2961,10 +2992,10 @@ may_invoke_callback(channel_T *channel, ch_part_T part) // Get any json message in the queue. if (channel_get_json(channel, part, -1, FALSE, &listtv) == FAIL) { - if (ch_mode == CH_MODE_LSP) - // In the "lsp" mode, the http header and the json payload may - // be received in multiple messages. So concatenate all the - // received messages. + if (ch_mode == CH_MODE_LSP || ch_mode == CH_MODE_DAP) + // In the "lsp" or "dap" mode, the http header and the json + // payload may be received in multiple messages. So concatenate + // all the received messages. (void)channel_collapse(channel, part, FALSE); // Parse readahead, return when there is still no message. @@ -2973,7 +3004,7 @@ may_invoke_callback(channel_T *channel, ch_part_T part) return FALSE; } - if (ch_mode == CH_MODE_LSP) + if (ch_mode == CH_MODE_LSP || ch_mode == CH_MODE_DAP) { dict_T *d = listtv->vval.v_dict; dictitem_T *di; @@ -2981,7 +3012,10 @@ may_invoke_callback(channel_T *channel, ch_part_T part) seq_nr = 0; if (d != NULL) { - di = dict_find(d, (char_u *)"id", -1); + if (ch_mode == CH_MODE_LSP) + di = dict_find(d, (char_u *)"id", -1); + else + di = dict_find(d, (char_u *)"seq", -1); if (di != NULL && di->di_tv.v_type == VAR_NUMBER) seq_nr = di->di_tv.vval.v_number; } @@ -3098,13 +3132,14 @@ may_invoke_callback(channel_T *channel, ch_part_T part) called_otc = FALSE; if (seq_nr > 0) { - // JSON or JS or LSP mode: invoke the one-time callback with the + // JSON or JS or LSP or DAP mode: invoke the one-time callback with the // matching nr int lsp_req_msg = FALSE; - // Don't use a LSP server request message with the same sequence number - // as the client request message as the response message. - if (ch_mode == CH_MODE_LSP && argv[1].v_type == VAR_DICT + // Don't use a LSP/DAP server request message with the same sequence + // number as the client request message as the response message. + if ((ch_mode == CH_MODE_LSP || ch_mode == CH_MODE_DAP) + && argv[1].v_type == VAR_DICT && dict_has_key(argv[1].vval.v_dict, "method")) lsp_req_msg = TRUE; @@ -3123,7 +3158,8 @@ may_invoke_callback(channel_T *channel, ch_part_T part) } } - if (seq_nr > 0 && (ch_mode != CH_MODE_LSP || called_otc)) + if (seq_nr > 0 && ((ch_mode != CH_MODE_LSP && ch_mode != CH_MODE_DAP) + || called_otc)) { if (!called_otc) { @@ -3315,6 +3351,7 @@ channel_part_info(channel_T *channel, dict_T *dict, char *name, ch_part_T part) case CH_MODE_JSON: s = "JSON"; break; case CH_MODE_JS: s = "JS"; break; case CH_MODE_LSP: s = "LSP"; break; + case CH_MODE_DAP: s = "DAP"; break; } dict_add_string(dict, namebuf, (char_u *)s); @@ -3982,10 +4019,10 @@ channel_read_json_block( for (;;) { - if (mode == CH_MODE_LSP) - // In the "lsp" mode, the http header and the json payload may be - // received in multiple messages. So concatenate all the received - // messages. + if (mode == CH_MODE_LSP || mode == CH_MODE_DAP) + // In the "lsp" or "dap" mode, the http header and the json payload + // may be received in multiple messages. So concatenate all the + // received messages. (void)channel_collapse(channel, part, FALSE); more = channel_parse_json(channel, part); @@ -4558,7 +4595,7 @@ ch_expr_common(typval_T *argvars, typval_T *rettv, int eval) return; } - if (ch_mode == CH_MODE_LSP) + if (ch_mode == CH_MODE_LSP || ch_mode == CH_MODE_DAP) { dict_T *d; dictitem_T *di; @@ -4571,11 +4608,15 @@ ch_expr_common(typval_T *argvars, typval_T *rettv, int eval) return; d = argvars[1].vval.v_dict; - di = dict_find(d, (char_u *)"id", -1); + if (ch_mode == CH_MODE_LSP) + di = dict_find(d, (char_u *)"id", -1); + else + di = dict_find(d, (char_u *)"seq", -1); if (di != NULL && di->di_tv.v_type != VAR_NUMBER) { - // only number type is supported for the 'id' item - semsg(_(e_invalid_value_for_argument_str), "id"); + // only number type is supported for the 'id' or 'seq' item + semsg(_(e_invalid_value_for_argument_str), + ch_mode == CH_MODE_LSP ? "id" : "seq"); return; } @@ -4583,7 +4624,16 @@ ch_expr_common(typval_T *argvars, typval_T *rettv, int eval) if (dict_has_key(argvars[2].vval.v_dict, "callback")) callback_present = TRUE; - if (eval || callback_present) + if (ch_mode == CH_MODE_DAP) + { + // DAP message always has a sequence number (id) + id = ++channel->ch_last_msg_id; + if (di == NULL) + dict_add_number(d, "seq", id); + else + di->di_tv.vval.v_number = id; + } + else if (eval || callback_present) { // When evaluating an expression or sending an expression with a // callback, always assign a generated ID @@ -4601,7 +4651,7 @@ ch_expr_common(typval_T *argvars, typval_T *rettv, int eval) if (di != NULL) id = di->di_tv.vval.v_number; } - if (!dict_has_key(d, "jsonrpc")) + if (ch_mode == CH_MODE_LSP && !dict_has_key(d, "jsonrpc")) dict_add_string(d, "jsonrpc", (char_u *)"2.0"); text = json_encode_lsp_msg(&argvars[1]); } @@ -4626,7 +4676,7 @@ ch_expr_common(typval_T *argvars, typval_T *rettv, int eval) if (channel_read_json_block(channel, part_read, timeout, id, &listtv) == OK) { - if (ch_mode == CH_MODE_LSP) + if (ch_mode == CH_MODE_LSP || ch_mode == CH_MODE_DAP) { *rettv = *listtv; // Change the type to avoid the value being freed. @@ -4646,7 +4696,13 @@ ch_expr_common(typval_T *argvars, typval_T *rettv, int eval) } } free_job_options(&opt); - if (ch_mode == CH_MODE_LSP && !eval && callback_present) + if (ch_mode == CH_MODE_DAP && !eval) + { + // A DAP message always has a sequence number. + if (rettv->vval.v_dict != NULL) + dict_add_number(rettv->vval.v_dict, "seq", id); + } + else if (ch_mode == CH_MODE_LSP && !eval && callback_present) { // if ch_sendexpr() is used to send a LSP message and a callback // function is specified, then return the generated identifier for the diff --git a/src/job.c b/src/job.c index 5ce9a20cdd..c81421aeb8 100644 --- a/src/job.c +++ b/src/job.c @@ -33,6 +33,8 @@ handle_mode(typval_T *item, jobopt_T *opt, ch_mode_T *modep, int jo) *modep = CH_MODE_JSON; else if (STRCMP(val, "lsp") == 0) *modep = CH_MODE_LSP; + else if (STRCMP(val, "dap") == 0) + *modep = CH_MODE_DAP; else { semsg(_(e_invalid_argument_str), val); diff --git a/src/structs.h b/src/structs.h index ed112e0644..d09726fe79 100644 --- a/src/structs.h +++ b/src/structs.h @@ -2599,7 +2599,9 @@ typedef enum CH_MODE_RAW, CH_MODE_JSON, CH_MODE_JS, - CH_MODE_LSP // Language Server Protocol (http + json) + CH_MODE_LSP, // Language Server Protocol (http + json) + CH_MODE_DAP // Debug Adapter Protocol (like LSP, but does not + // strictly follow JSON-RPC standard) } ch_mode_T; typedef enum { diff --git a/src/testdir/test_channel.vim b/src/testdir/test_channel.vim index dcd0526690..8ee72c9f34 100644 --- a/src/testdir/test_channel.vim +++ b/src/testdir/test_channel.vim @@ -2764,6 +2764,64 @@ func Test_channel_lsp_mode() call RunServer('test_channel_lsp.py', 'LspTests', []) endfunc +" Test for the 'dap' channel mode. Don't need to test much since most of the +" logic is same as 'lsp' mode. +func DapTests(port) + let ch = ch_open(s:localhost .. a:port, #{ + \ mode: 'dap', + \ }) + + if ch_status(ch) == "fail" + call assert_report("Can't open the dap channel") + return + endif + + " check for channel information + let info = ch_info(ch) + call assert_equal('DAP', info.sock_mode) + + let resp = ch_evalexpr(ch, #{ + \ type: 'request', + \ command: 'initialize' + \ }) + call assert_equal({ + \ 'seq': 1, + \ 'request_seq': 1, + \ 'type': 'response', + \ 'success': v:true, + \ 'body': {'supportsConfigurationDoneRequest': v:true}, + \ 'command': 'initialize' + \ }, resp) + + let resp = ch_read(ch) + + call assert_equal({ + \ 'seq': 2, + \ 'type': 'event', + \ 'event': 'initialized', + \ 'body': {} + \ }, resp) + + let resp = ch_evalexpr(ch, #{ + \ type: 'request', + \ command: 'test' + \ }) + + call assert_equal({ + \ 'seq': 3, + \ 'request_seq': 2, + \ 'type': 'response', + \ 'success': v:true, + \ 'body': {}, + \ 'command': 'test' + \ }, resp) +endfunc + +func Test_channel_dap_mode() + let g:giveup_same_error = 0 + call RunServer('test_channel_dap.py', 'DapTests', []) +endfunc + func Test_error_callback_terminal() CheckUnix CheckFeature terminal diff --git a/src/testdir/test_channel_dap.py b/src/testdir/test_channel_dap.py new file mode 100644 index 0000000000..23e13912fe --- /dev/null +++ b/src/testdir/test_channel_dap.py @@ -0,0 +1,135 @@ +#!/usr/bin/env python3 + +# Used by Test_channel_dap_mode in test_channel.vim to test DAP functionality. + +import json +import socket +import threading +import time + +try: + import socketserver +except ImportError: + import SocketServer as socketserver + +def make_dap_message(obj): + payload = json.dumps(obj).encode("utf-8") + header = f"Content-Length: {len(payload)}\r\n\r\n".encode("ascii") + return header + payload + + +def parse_messages(buffer): + messages = [] + + while True: + hdr_end = buffer.find(b"\r\n\r\n") + if hdr_end == -1: + break + + header = buffer[:hdr_end].decode("ascii", errors="ignore") + content_length = None + + for line in header.split("\r\n"): + if line.lower().startswith("content-length:"): + content_length = int(line.split(":")[1].strip()) + + if content_length is None: + break + + total_len = hdr_end + 4 + content_length + if len(buffer) < total_len: + break # partial + + body = buffer[hdr_end + 4:total_len] + messages.append(json.loads(body.decode("utf-8"))) + buffer = buffer[total_len:] + + return messages, buffer + + +class DAPHandler(socketserver.BaseRequestHandler): + + def setup(self): + self.request.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) + self.seq = 1 # server sequence counter + + def send(self, obj): + obj["seq"] = self.seq + self.seq += 1 + self.request.sendall(make_dap_message(obj)) + + def send_response(self, request, body=None, success=True): + self.send({ + "type": "response", + "request_seq": request["seq"], + "success": success, + "command": request["command"], + "body": body or {} + }) + + def send_event(self, event, body=None): + self.send({ + "type": "event", + "event": event, + "body": body or {} + }) + + def handle_request(self, msg): + cmd = msg.get("command") + + if cmd == "initialize": + self.send_response(msg, { + "supportsConfigurationDoneRequest": True + }) + self.send_event("initialized") + else: + self.send_response(msg) + + return True + + def handle(self): + buffer = b"" + + while True: + data = self.request.recv(4096) + if not data: + break + + buffer += data + messages, buffer = parse_messages(buffer) + + for msg in messages: + if msg.get("type") == "request": + if not self.handle_request(msg): + return + + +class ThreadedDAPServer(socketserver.ThreadingMixIn, socketserver.TCPServer): + allow_reuse_address = True + +def write_port_to_file(port, filename="Xportnr"): + with open(filename, "w") as f: + f.write(str(port)) + +def main(): + server = ThreadedDAPServer(("localhost", 0), DAPHandler) + + # Get the actual assigned port + ip, assigned_port = server.server_address + + # Write port so client/test can read it + write_port_to_file(assigned_port) + + thread = threading.Thread(target=server.serve_forever) + thread.daemon = True + thread.start() + + try: + while thread.is_alive(): + thread.join(1) + except KeyboardInterrupt: + server.shutdown() + +if __name__ == "__main__": + main() + diff --git a/src/version.c b/src/version.c index 1db0171fed..55cfca20d5 100644 --- a/src/version.c +++ b/src/version.c @@ -734,6 +734,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ +/**/ + 60, /**/ 59, /**/