Skip to content

Instantly share code, notes, and snippets.

@christopherfujino
Last active March 11, 2024 16:21
Show Gist options
  • Save christopherfujino/80be0f4cd88f75c4991b478e6b071153 to your computer and use it in GitHub Desktop.
Save christopherfujino/80be0f4cd88f75c4991b478e6b071153 to your computer and use it in GitHub Desktop.
Debug Adapter Protocol for Flutter/Dart in Neovim

Debug Adapter Protocol with Flutter/Dart in Neovim

Ingredients

Resources

Dart Proof of Concept

Consider the program:

// Filename `fib.dart`
void main() {
  final int n = fib(33);
  print(n);
}

int fib(int n) {
  // We will set a breakpoint here
  if (n == 0 || n == 1) {
    return n;
  }
  return fib(n - 1) + fib(n - 2);
}

Update your vimrc to configure the adapter plugin to know how to launch Dart's DAP server:

# Since the mfussenegger/nvim-dap plugin is written in Lua, you must embed Lua in your vimrc
lua << EOF
  local dap = require('dap')

  dap.adapters.dart = {
    type = "executable",
    command = "dart",
    -- This command was introduced upstream in https://github.com/dart-lang/sdk/commit/b68ccc9a
    args = {"debug_adapter"}
  }
  dap.configurations.dart = {
    {
      type = "dart",
      request = "launch",
      name = "Launch Dart Program",
      -- The nvim-dap plugin populates this variable with the filename of the current buffer
      program = "${file}",
      -- The nvim-dap plugin populates this variable with the editor's current working directory
      cwd = "${workspaceFolder}",
      args = {"--help"}, -- Note for Dart apps this is args, for Flutter apps toolArgs
    }
  }
EOF

After updating your .vimrc, (re)start nvim, opening our source file fib.dart..

If you would like to monitor verbose logs, enter command-line mode (:) and enter lua require('dap').set_log_level('TRACE'). Find your editor's cache dir with :echo stdpath('cache') (for me this is $HOME/.config/nvim). nvim-dap should have created a log file under this directory; tail it in a separate terminal window with tail -f <your editor's cache dir>/dap.log.

Navigate to the comment where we indicated we would be setting a breakpoint, enter command-line mode (:), and enter lua require('dap').set_breakpoint() (.toggle_breakpoint() would also work). The buffer should now indicate a letter B to indicate the breakpoint has been set. Now launch the program with :lua require('dap').continue() (this will parse the config that we set in our .vimrc). The debugger should launch the program and run until it hits the breakpoint (if not, check the logging to see if there was a configuration problem).

With the program paused, we can now launch a repl to inspect the state of the VM with :lua require('dap').repl.open(). Navigate to the new vim window that was opened and enter insert mode to attach STDIN to the subprocess. You should see the prompt: dap> . Here is a sample repl session:

Connecting to VM Service at ws://127.0.0.1:60321/bUcCdeiBh4w=/ws
dap> n
33

We can verify that value of the argument n is 33. Without closing the repl window, return to command-line mode and enter :lua require('dap').continue() to resume the program. The program should continue executing until it recursively calls itself with n - 1. When execution pauses again, re-enter insert mode in the repl window and check the value of n again (it should be 32).

See :help dap.txt (https://github.com/mfussenegger/nvim-dap/blob/master/doc/dap.txt) for the full neovim/lua api for interacting with the underlying Dart DAP server.

Flutter Proof of Concept

Create a new Flutter counter app with: flutter create flutter_counter; cd flutter_counter. You will need to update your .vimrc again to teach nvim-dap how to launch the Flutter wrapper for the Dart DAP server:

# Since the mfussenegger/nvim-dap plugin is written in Lua, you must embed Lua in your vimrc
lua << EOF
  local dap = require('dap')

  dap.adapters.dart = {
    type = "executable",
    -- As of this writing, this functionality is open for review in https://github.com/flutter/flutter/pull/91802
    command = "flutter",
    args = {"debug_adapter"}
  }
  dap.configurations.dart = {
    {
      type = "dart",
      request = "launch",
      name = "Launch Flutter Program",
      -- The nvim-dap plugin populates this variable with the filename of the current buffer
      program = "${file}",
      -- The nvim-dap plugin populates this variable with the editor's current working directory
      cwd = "${workspaceFolder}",
      -- This gets forwarded to the Flutter CLI tool, substitute `linux` for whatever device you wish to launch
      toolArgs = {"-d", "linux"}
    }
  }
EOF

To be safe, restart neovim (I experienced problems re-sourcing my vimrc that a restart solved ¯\_(ツ)_/¯).

As before, you can monitor verbose logs by entering :lua require('dap').set_log_level('TRACE') and tail -f <your editor's cache dir>/dap.log.

Open lib/main.dart and set a breakpoint at in _MyHomePageState's _incrementCounter() method on a new (empty) line after _counter++: :lua require('dap').set_breakpoint(). Start the app with :lua require('dap').continue(). The nvim-dap plugin will warn in the nvim buffer that it is taking too long--this is because launching the Flutter app takes a long time. Refer to the logs in $CACHE_DIR/dap.log to verify if anything went wrong. Either your app will start (paused) or there should be an error message in the logs. To start the Flutter app, issue :lua require('dap').continue() (you may receive a stdin prompt in your nvim buffer indicating the program is paused but not on an exception or breakpoint, you can enter 3 here, or restart debug adapter).

Press the + button to trigger the _incrementCounter() handler and hit the breakpoint.

Open the repl with :lua require('dap').repl.open(). Attach stdin by entering insert mode in the new nvim window. You should see the dap> prompt, meaning you are attached to the VM repl. Enter dap> _counter to verify its value is 1.

TODO

Write vimscript convenience functions that assist with:

  1. Selecting between dart debug_adapter and flutter debug_adapter
  2. Specifying toolArgs
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment