nvim-flutter / nvim-flutter/flutter-tools.nvim

[BUG] Opening a file inside a version-manager-installed SDK roots `dartls` in the Flutter SDK itself

Open
#534 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
Lua
Stars
1.4k
Forks
116
Avg merge
3m
Merged PRs (30d)
1

Description

Environment

  • flutter-tools.nvim 3.0.1 (0867b34)
  • Neovim 0.12.5
  • Flutter 3.47.1 (stable), installed via mise
  • Linux 7.1.9-arch1-2

Current behavior

Opening a framework source file (e.g. after gd on StatefulWidget) starts a
dartls client rooted at the Flutter SDK itself — a 2.3 GB tree — and the
buffer is left writable.

Reproduced with stock upstream, no user config beyond flutter_lookup_cmd:

-- init.lua
local L = vim.fn.stdpath("data") .. "/lazy/"
vim.opt.rtp:append(L .. "plenary.nvim")
vim.opt.rtp:append(L .. "flutter-tools.nvim")
require("flutter-tools").setup({ flutter_lookup_cmd = "mise where flutter" })
vim.api.nvim_create_autocmd("LspAttach", {
  callback = function(a)
    local c = vim.lsp.get_client_by_id(a.data.client_id)
    print("root=" .. tostring(c.config.root_dir),
          "modifiable=" .. tostring(vim.bo[a.buf].modifiable))
  end,
})

Opening <sdk>/packages/flutter/lib/src/widgets/basic.dart gives:

root       = /home/user/.local/share/mise/http-tarballs/<hash>_strip_1
modifiable = true

Cause

is_flutter_dependency_path() (lua/flutter-tools/utils/path.lua:193-201)
recognises SDK/dependency locations from a hardcoded list:

local path_parts = { [[.pub-cache]], [[Pub\Cache]], [[/fvm/versions/]] }

An SDK installed by mise lives under ~/.local/share/mise/http-tarballs/...,
and one installed by asdf under ~/.asdf/installs/flutter/<version>/. Neither
matches, so a framework source file is treated as an ordinary project file and
get_project_root_dir() (lsp/init.lua:170-182) runs the upward search it was
supposed to skip.

That search uses root_patterns = { ".git", "pubspec.yaml" }, and the Flutter
SDK root contains both:

$ ls -d "$(realpath "$(mise where flutter)")"/{.git,pubspec.yaml}
.../  .../pubspec.yaml
$ du -sh "$(realpath "$(mise where flutter)")"
2.3G

so the whole SDK becomes the root.

There is a second, independent path to the same outcome:
M.attach() (lsp/init.lua:293-296) repeats the upward search whenever
get_project_root_dir() returns nil, which it does whenever no dartls client
has attached yet:

c.root_dir = M.get_project_root_dir()
  or fs.dirname(fs.find(conf.root_patterns, { path = buffer_path, upward = true })[1])

So even with the dependency check corrected, this fallback can still select a
root inside the SDK (in my testing, <sdk>/packages/flutter).

Consequences

  1. The analysis context is the SDK rather than the user's project.
  2. Framework buffers stay writable. ftplugin/dart/init.lua sets
    modifiable = false through the same predicate, so the read-only protection
    is silently lost for these installs (modifiable = true above).
  3. It is expensive. Stepping through four framework files (framework.dart,
    app_bar.dart, scaffold.dart, basic.dart) took the analysis server to
    666 MB RSS at ~32% CPU in a scripted run; an interactive session reached
    1.6 GB. Neovim separately received ~69 000 diagnostics across ~170 files
    while only 4 buffers were loaded.

Point 3 may be inherent to onlyAnalyzeProjectsWithOpenFiles rather than a
consequence of the wrong root — I did not isolate the two, and I mention it as
practical impact rather than as a claim about the cause.

Expected behavior

A buffer inside the Flutter SDK should not produce a root_dir inside the SDK.
It should attach to the project's existing client, as already happens for
.pub-cache and fvm installs.

Suggested fix

Derive the check from the SDK path the plugin already resolves
(executable.get()paths.flutter_sdk, compared after fs_realpath, since
version managers commonly symlink their installs) rather than from a hardcoded
fragment list. That covers every installation method without new configuration.

Guarding the or fallback in M.attach() so it cannot select a root inside the
SDK would close the second path.

Steps to reproduce

  1. Install Flutter via mise (or asdf).
  2. Use the init.lua above.
  3. Open any file under <sdk>/packages/flutter/lib/src/.
  4. Observe root_dir inside the SDK and modifiable = true.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Reproduce with the provided mise-based init.lua, then read is_flutter_dependency_path() in lua/flutter-tools/utils/path.lua and get_project_root_dir()/M.attach() in lsp/init.lua. Trace how paths.flutter_sdk and fs_realpath are resolved, including the fallback root search. Done means SDK files avoid an SDK root, attach to the project client, and restore read-only framework buffers for version-manager installs.

Written by the indexing model from the issue text.

Assessment

Tech stack
flutter, lua
Domain
developer-experience, devtools
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Active
Clarity
Clearly specified
Newbie friendliness
68/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.