|
| 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 |
0 commit comments