Skip to content

Commit c7dfaa9

Browse files
authored
Fix "new Flutter Version" error on FlutterRun (#495)
* Improve type definitions for module executable This commit is basically a small code-cleanup for the `executable.lua` module, but it mainly adds Lua Doc comments to some public functions. * Fix flutter version/welcome banner lua error Executing any `flutter` command (including `flutter debug_adapter` and `flutter debug-adapter`) may cause the `New Flutter Version` or `Welcome to Flutter` banner to be printed to STDOUT. This is problematic, because it interferes with `nvim-dap`s ability to parse RPC calls (obviously, because the banners are not formatted to look like a valid RPC call). `nvim-dap` is unable to properly detect the `Content-Length` header of the first RPC call (because of the banner/s) -> `error(...)` gets called -> user receives an error when trying to run their project. This commit intends to fix that, by detecting and clearing any banners, before the debug adapter is started.
1 parent 69db9cd commit c7dfaa9

File tree

3 files changed

+268
-62
lines changed

3 files changed

+268
-62
lines changed

lua/flutter-tools/banner.lua

Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
local lazy = require("flutter-tools.lazy")
2+
local executable = lazy.require("flutter-tools.executable") ---@module "flutter-tools.executable"
3+
local Job = require("plenary.job") ---@module "plenary.job"
4+
5+
---@class flutter.DetectedBanners
6+
---
7+
--- True, if the banner that matches `PATTERNS.FLUTTER_NEW_VERSION` has been
8+
--- detected.
9+
---
10+
--- This banner will be detected if the file
11+
--- `$FLUTTER_SDK/bin/cache/flutter_version_check.stamp` does not exist, or
12+
--- reached a certain age. (Assuming `$FLUTTER_SDK` is the path to the directory
13+
--- that contains your Flutter SDK).
14+
---
15+
--- See the [version.dart from the Flutter SDK](https://github.com/flutter/flutter/blob/3.35.7/packages/flutter_tools/lib/src/version.dart#L1303).
16+
---@field has_flutter_new_version boolean
17+
---
18+
--- True, if the banner that matches `PATTERNS.FLUTTER_WELCOME` has been
19+
--- detected.
20+
---
21+
--- This banner will be detected if the file `$HOME/.config/flutter/tool_state`
22+
--- does not exist, or you changed your Flutter SDK to one with a different
23+
--- welcome banner.
24+
---
25+
--- See the [first_run.dart from the Flutter SDK](https://github.com/flutter/flutter/blob/3.35.7/packages/flutter_tools/lib/src/reporting/first_run.dart#L13).
26+
---@field has_flutter_welcome boolean
27+
28+
---@private
29+
---@alias flutter.internal.OnBannersClearedListener fun(detect_banners: flutter.DetectedBanners):nil
30+
31+
---@type flutter.DetectedBanners?
32+
local cached_banners = nil
33+
34+
---@type flutter.internal.OnBannersClearedListener[]
35+
local on_cleared_listeners = {}
36+
37+
local has_started_cleansing = false
38+
39+
local M = {
40+
PATTERNS = {
41+
FLUTTER_NEW_VERSION = "A new version of Flutter is available!",
42+
FLUTTER_WELCOME = "Welcome to Flutter!",
43+
},
44+
}
45+
46+
---@param lines string[]
47+
---@return flutter.DetectedBanners
48+
local function detect_banners(lines)
49+
---@type flutter.DetectedBanners
50+
local banners = {
51+
has_flutter_new_version = false,
52+
has_flutter_welcome = false,
53+
}
54+
55+
for _, line in ipairs(lines) do
56+
if nil ~= line:match(M.PATTERNS.FLUTTER_NEW_VERSION) then
57+
banners.has_flutter_new_version = true
58+
end
59+
60+
if nil ~= line:match(M.PATTERNS.FLUTTER_WELCOME) then banners.has_flutter_welcome = true end
61+
end
62+
63+
return banners
64+
end
65+
66+
--- Calls every listener from `on_cleared_listeners`, once `do_clear_banners` is
67+
--- done. Internally caches all listeners and then resets `on_cleared_listeners`
68+
--- to an empty table.
69+
---
70+
---@param detected_banners flutter.DetectedBanners
71+
local function on_cleared_banners(detected_banners)
72+
local listeners = vim.deepcopy(on_cleared_listeners)
73+
on_cleared_listeners = {}
74+
vim.schedule(function()
75+
for _, cb in ipairs(listeners) do
76+
cb(detected_banners)
77+
end
78+
end)
79+
end
80+
81+
local function do_clear_banners(is_flutter_project)
82+
assert(nil == cached_banners)
83+
assert(not has_started_cleansing)
84+
85+
has_started_cleansing = true
86+
87+
executable.get(function(paths)
88+
if is_flutter_project then
89+
Job:new({
90+
command = paths.flutter_bin,
91+
args = { "--version" },
92+
enable_recording = true,
93+
on_exit = function(self, code, _)
94+
-- Exit code should always be 0.
95+
assert(0 == code)
96+
97+
-- 'flutter --version' writes everything to STDOUT including the
98+
-- "Welcome" and "New Flutter Version" banner.
99+
---@type string[]
100+
local lines = self:result()
101+
102+
local banners = detect_banners(lines)
103+
cached_banners = banners
104+
105+
on_cleared_banners(cached_banners)
106+
end,
107+
}):start()
108+
else
109+
-- Only flutter CLI shows startup banners that interfer with the
110+
-- Debug-/Jobrunner.
111+
--
112+
-- dart CLI does currently not show anything, maybe there will
113+
-- be a banner in the future.
114+
cached_banners = {
115+
has_flutter_welcome = false,
116+
has_flutter_new_version = false,
117+
}
118+
119+
on_cleared_banners(cached_banners)
120+
end
121+
end)
122+
end
123+
124+
--- Clear and detect any startup banners from the Flutter or Dart CLI tool.
125+
--- `on_cleared` is called, after all banners have been cleared.
126+
---
127+
---@param is_flutter_project boolean
128+
---@param on_cleared fun(detected_banners: flutter.DetectedBanners)
129+
function M.clear_startup_banners(is_flutter_project, on_cleared)
130+
if nil ~= cached_banners then
131+
vim.schedule(function() on_cleared(cached_banners) end)
132+
return
133+
end
134+
135+
table.insert(on_cleared_listeners, on_cleared)
136+
137+
if not has_started_cleansing then do_clear_banners(is_flutter_project) end
138+
end
139+
140+
--- Reset the internally cached banners.
141+
function M.reset_cache()
142+
cached_banners = nil
143+
on_cleared_listeners = {}
144+
has_started_cleansing = false
145+
end
146+
147+
return M

lua/flutter-tools/commands.lua

Lines changed: 30 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ local job_runner = lazy.require("flutter-tools.runners.job_runner") ---@module "
1111
local debugger_runner = lazy.require("flutter-tools.runners.debugger_runner") ---@module "flutter-tools.runners.debugger_runner"
1212
local path = lazy.require("flutter-tools.utils.path") ---@module "flutter-tools.utils.path"
1313
local dev_log = lazy.require("flutter-tools.log") ---@module "flutter-tools.log"
14+
local banner = lazy.require("flutter-tools.banner") ---@module "flutter-tools.banner"
1415
local parser = lazy.require("flutter-tools.utils.yaml_parser")
1516
local config_utils = lazy.require("flutter-tools.utils.config_utils") ---@module "flutter-tools.utils.config_utils"
1617

@@ -34,6 +35,8 @@ local runner = nil
3435

3536
local fvm_name = path.is_windows and "fvm.bat" or "fvm"
3637

38+
local has_notified_new_flutter_version = false
39+
3740
local function use_debugger_runner(force_debug)
3841
if force_debug or config.debugger.enabled then
3942
local dap_ok, _ = pcall(require, "dap")
@@ -249,10 +252,14 @@ local function run(opts, project_conf, launch_config)
249252
project_conf.pre_run_callback(callback_args)
250253
end
251254
end
255+
252256
local cwd = config_utils.get_cwd(project_conf)
253-
-- To determinate if the project is a flutter project we need to check if the pubspec.yaml
254-
-- file has a flutter dependency in it. We need to get cwd first to pick correct pubspec.yaml file.
257+
258+
-- To determinate if the project is a flutter project we need to check if
259+
-- the pubspec.yaml file has a flutter dependency in it. We need to get
260+
-- cwd first to pick correct pubspec.yaml file.
255261
local is_flutter_project = has_flutter_dependency_in_pubspec(cwd)
262+
256263
local default_run_args = config.default_run_args
257264
local run_args
258265
if is_flutter_project then
@@ -262,25 +269,34 @@ local function run(opts, project_conf, launch_config)
262269
ui.notify("Starting dart project...")
263270
if default_run_args then run_args = default_run_args.dart end
264271
end
272+
265273
if run_args then
266274
if type(run_args) == "string" then
267275
vim.list_extend(args, vim.split(run_args, " "))
268276
elseif type(run_args) == "table" then
269277
vim.list_extend(args, run_args)
270278
end
271279
end
272-
runner = use_debugger_runner(opts.force_debug) and debugger_runner or job_runner
273-
runner:run(
274-
opts,
275-
paths,
276-
args,
277-
cwd,
278-
on_run_data,
279-
on_run_exit,
280-
is_flutter_project,
281-
project_conf,
282-
launch_config
283-
)
280+
281+
banner.clear_startup_banners(is_flutter_project, function(detected_banners)
282+
if detected_banners.has_flutter_new_version and not has_notified_new_flutter_version then
283+
has_notified_new_flutter_version = true
284+
ui.notify(banner.PATTERNS.FLUTTER_NEW_VERSION, ui.INFO)
285+
end
286+
287+
runner = use_debugger_runner(opts.force_debug) and debugger_runner or job_runner
288+
runner:run(
289+
opts,
290+
paths,
291+
args,
292+
cwd,
293+
on_run_data,
294+
on_run_exit,
295+
is_flutter_project,
296+
project_conf,
297+
launch_config
298+
)
299+
end)
284300
end)
285301
end
286302

0 commit comments

Comments
 (0)