patch 9.2.0060: No support for the DAP channel mode
Commit:
https://github.com/vim/vim/commit/b7eb0c2d38198e5f6cc7e806e514f88b2c9ef5a2
Author: Foxe Chen <[email protected]>
Date: Wed Feb 25 20:53:21 2026 +0000
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 <[email protected]>
Signed-off-by: Christian Brabandt <[email protected]>
diff --git a/runtime/doc/channel.txt b/runtime/doc/channel.txt
index 5c1de248d..36694af9f 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": <list|dict>
}
-<
+==============================================================================
+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 2d3858b4d..f86fe37eb 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 6c8726696..17535bdbd 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 abe9bddca..462281dbd 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 "
". The header ends with an additional "
".
*
* 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 5ce9a20cd..c81421aeb 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 ed112e064..d09726fe7 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 dcd052669..8ee72c9f3 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 000000000..23e13912f
--- /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)}
".encode("ascii")
+ return header + payload
+
+
+def parse_messages(buffer):
+ messages = []
+
+ while True:
+ hdr_end = buffer.find(b"
")
+ if hdr_end == -1:
+ break
+
+ header = buffer[:hdr_end].decode("ascii", errors="ignore")
+ content_length = None
+
+ for line in header.split("
"):
+ 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 1db0171fe..55cfca20d 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,
/**/
--
--
You received this message from the "vim_dev" maillist.
Do not top-post! Type your reply below the text you are replying to.
For more information, visit http://www.vim.org/maillist.php
---
You received this message because you are subscribed to the Google Groups
"vim_dev" group.
To unsubscribe from this group and stop receiving emails from it, send an email
to [email protected].
To view this discussion visit
https://groups.google.com/d/msgid/vim_dev/E1vvLzI-00CyIR-2c%40256bit.org.