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.

Raspunde prin e-mail lui