From 5b8bf09f1ff3aa9f49d3d5e6ceacd9009a7611e5 Mon Sep 17 00:00:00 2001 From: Gage Vander Clay Date: Fri, 8 May 2026 16:55:14 -0500 Subject: [PATCH 1/5] feat(ui): use floating prompt for filter fallback --- CHANGELOG.md | 6 + lua/android/logcat/session/controls.lua | 2 + lua/android/ui/picker.lua | 35 ++++- .../logcat/controls/filter_input_test.lua | 22 +++ .../android/logcat/controls/package_test.lua | 9 +- .../ui/picker_filter_input/fallback_input.lua | 129 ++++++++++++++++-- 6 files changed, 190 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 66ef381..b1c2671 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,12 @@ The format is based on Keep a Changelog and this project adheres to SemVer. ## [Unreleased] +### Changed + +- Filter inputs now use the plugin's live-updating floating prompt when + Telescope is unavailable, and the logcat filter fallback now labels the + floating input as `Logcat filter:`. + ## [0.6.0] - 2026-03-25 ### Added diff --git a/lua/android/logcat/session/controls.lua b/lua/android/logcat/session/controls.lua index e1b4a35..6f76e04 100644 --- a/lua/android/logcat/session/controls.lua +++ b/lua/android/logcat/session/controls.lua @@ -104,6 +104,8 @@ local function start_filter_edit(session, deps) local build_context = resolve_build_context(session) picker().filter_input({ prompt_title = "Logcat filter", + input_title = "Logcat filter:", + input_prompt = "", items = session.filter_history or {}, default = session.filter, panel_names = function(query) diff --git a/lua/android/ui/picker.lua b/lua/android/ui/picker.lua index 69e75d5..7b74d17 100644 --- a/lua/android/ui/picker.lua +++ b/lua/android/ui/picker.lua @@ -70,13 +70,46 @@ local function filter_results_by_query(results, format, query) end local function fallback_filter_input(options) + local title = options.prompt_title or "Filter" + local prompt = options.input_prompt + if prompt == nil then + prompt = title + end + local input_title = options.input_title + if input_title == nil then + input_title = options.input_prompt and "" or title + end + local ok_input, input = pcall(require, "android.ui.input") + if ok_input and input and type(input.prompt) == "function" then + input.prompt({ + title = input_title, + prompt = prompt, + default = options.default or "", + on_change = options.on_change, + on_submit = function(value) + if options.on_change then + options.on_change(value) + end + if options.on_accept then + options.on_accept(value) + end + end, + on_cancel = options.on_cancel, + }) + return + end + if not vim.ui or not vim.ui.input then vim.notify("vim.ui.input not available", vim.log.levels.WARN) return end + local fallback_prompt = prompt + if fallback_prompt == "" and input_title and input_title ~= "" then + fallback_prompt = input_title .. " " + end vim.ui.input({ - prompt = options.prompt_title or "Filter", + prompt = fallback_prompt, default = options.default or "", }, function(value) if value == nil then diff --git a/lua/tests/android/logcat/controls/filter_input_test.lua b/lua/tests/android/logcat/controls/filter_input_test.lua index ff681a0..58f2d66 100644 --- a/lua/tests/android/logcat/controls/filter_input_test.lua +++ b/lua/tests/android/logcat/controls/filter_input_test.lua @@ -157,6 +157,27 @@ local function filter_picker_receives_history() end) end +local function filter_picker_receives_prompt_text() + local state = state_with("com.saved", "Old") + local captured = {} + local stubs = { + ["android.ui.picker"] = { + filter_input = function(opts) + captured.prompt_title = opts.prompt_title + captured.input_title = opts.input_title + captured.input_prompt = opts.input_prompt + end, + }, + } + + logcat_helpers.with_logcat_context({ state = state, stubs = stubs }, function(ctx) + logcat_helpers.start_filter_edit(ctx) + assert.eq(captured.prompt_title, "Logcat filter", "prompt title") + assert.eq(captured.input_title, "Logcat filter:", "input title") + assert.eq(captured.input_prompt, "", "input prompt") + end) +end + local function filter_history_persists() local state = logcat_helpers.build_state({ logcat = { package = "com.saved", filter = "Old", filter_history = { "old" } }, @@ -209,6 +230,7 @@ function M.run() header_rerenders_after_filter_input() filter_input_cancel_keeps_latest() filter_picker_receives_history() + filter_picker_receives_prompt_text() filter_history_persists() filter_history_persists_on_cancel() end diff --git a/lua/tests/android/logcat/controls/package_test.lua b/lua/tests/android/logcat/controls/package_test.lua index ebdd537..627d6a5 100644 --- a/lua/tests/android/logcat/controls/package_test.lua +++ b/lua/tests/android/logcat/controls/package_test.lua @@ -189,15 +189,16 @@ local function header_enter_prompts_for_package_and_filter_modal() logcat_helpers.press_enter(ctx, 2) local call = ctx.vim_state.input_calls[1] or {} - local prompt = input_calls[1] and input_calls[1].prompt_title or "" + local filter_call = input_calls[1] or {} local summary = string.format( - "%d|%s|%d|%s", + "%d|%s|%d|%s|%s", #ctx.vim_state.input_calls, call.prompt or "", #input_calls, - prompt + filter_call.prompt_title or "", + filter_call.input_title or "" ) - assert.eq(summary, "1|Logcat package: |1|Logcat filter", "header enter") + assert.eq(summary, "1|Logcat package: |1|Logcat filter|Logcat filter:", "header enter") end) end diff --git a/lua/tests/android/ui/picker_filter_input/fallback_input.lua b/lua/tests/android/ui/picker_filter_input/fallback_input.lua index 4b67f09..5f28966 100644 --- a/lua/tests/android/ui/picker_filter_input/fallback_input.lua +++ b/lua/tests/android/ui/picker_filter_input/fallback_input.lua @@ -2,22 +2,87 @@ local M = {} local assert = require("tests.helpers.assert") -local function run_filter_input_without_telescope() +local function run_filter_input_without_telescope(opts) + local options = opts or {} + local flags = { on_change = false, on_accept = false, on_cancel = false } + local captured = { title = nil, prompt = nil, default = nil } + local original_input = vim.ui and vim.ui.input + local original_preload = package.preload["telescope.pickers"] + local original_loaded = package.loaded["telescope.pickers"] + local original_android_preload = package.preload["android.ui.input"] + local original_android_input = package.loaded["android.ui.input"] + + package.preload["telescope.pickers"] = function() + error("missing telescope.pickers") + end + package.loaded["telescope.pickers"] = nil + + package.loaded["android.ui.input"] = { + prompt = function(input_opts) + captured.title = input_opts.title + captured.prompt = input_opts.prompt + captured.default = input_opts.default + if options.cancel then + input_opts.on_cancel() + return + end + if input_opts.on_change then + input_opts.on_change("live") + end + input_opts.on_submit("typed") + end, + } + + vim.ui = vim.ui or {} + vim.ui.input = function() + error("vim.ui.input should not be used when floating input is available") + end + + package.loaded["android.ui.picker"] = nil + local picker = require("android.ui.picker") + picker.filter_input({ + items = { "one" }, + prompt_title = options.prompt_title or "Filter", + input_title = options.input_title, + input_prompt = options.input_prompt, + default = "old", + on_change = function(value) flags.on_change = value end, + on_accept = function(value) flags.on_accept = value end, + on_cancel = function() flags.on_cancel = true end, + }) + + vim.ui.input = original_input + package.preload["telescope.pickers"] = original_preload + package.loaded["telescope.pickers"] = original_loaded + package.preload["android.ui.input"] = original_android_preload + package.loaded["android.ui.input"] = original_android_input + + return { captured = captured, flags = flags } +end + +local function run_filter_input_without_floating_input(opts) + local options = opts or {} local flags = { on_change = false, on_accept = false, on_cancel = false } local captured = { prompt = nil, default = nil } local original_input = vim.ui and vim.ui.input local original_preload = package.preload["telescope.pickers"] local original_loaded = package.loaded["telescope.pickers"] + local original_android_preload = package.preload["android.ui.input"] + local original_android_input = package.loaded["android.ui.input"] package.preload["telescope.pickers"] = function() error("missing telescope.pickers") end package.loaded["telescope.pickers"] = nil + package.preload["android.ui.input"] = function() + error("missing android.ui.input") + end + package.loaded["android.ui.input"] = nil vim.ui = vim.ui or {} - vim.ui.input = function(opts, cb) - captured.prompt = opts.prompt - captured.default = opts.default + vim.ui.input = function(input_opts, cb) + captured.prompt = input_opts.prompt + captured.default = input_opts.default cb("typed") end @@ -26,6 +91,8 @@ local function run_filter_input_without_telescope() picker.filter_input({ items = { "one" }, prompt_title = "Filter", + input_title = options.input_title or "Filter:", + input_prompt = options.input_prompt or "Filter: ", default = "old", on_change = function(value) flags.on_change = value end, on_accept = function(value) flags.on_accept = value end, @@ -35,15 +102,41 @@ local function run_filter_input_without_telescope() vim.ui.input = original_input package.preload["telescope.pickers"] = original_preload package.loaded["telescope.pickers"] = original_loaded + package.preload["android.ui.input"] = original_android_preload + package.loaded["android.ui.input"] = original_android_input return { captured = captured, flags = flags } end +local function filter_input_fallback_sets_title() + local result = run_filter_input_without_telescope() + assert.eq(result.captured.title, "Filter", "input title") +end + local function filter_input_fallback_sets_prompt() local result = run_filter_input_without_telescope() assert.eq(result.captured.prompt, "Filter", "input prompt") end +local function filter_input_fallback_uses_custom_prompt() + local result = run_filter_input_without_telescope({ input_prompt = "Filter: " }) + assert.eq(result.captured.prompt, "Filter: ", "input prompt") +end + +local function filter_input_fallback_omits_title_for_custom_prompt() + local result = run_filter_input_without_telescope({ input_prompt = "Filter: " }) + assert.eq(result.captured.title, "", "input title") +end + +local function filter_input_fallback_uses_custom_title() + local result = run_filter_input_without_telescope({ + input_title = "Filter:", + input_prompt = "", + }) + assert.eq(result.captured.title, "Filter:", "input title") + assert.eq(result.captured.prompt, "", "input prompt") +end + local function filter_input_fallback_sets_default() local result = run_filter_input_without_telescope() assert.eq(result.captured.default, "old", "input default") @@ -59,17 +152,37 @@ local function filter_input_fallback_calls_on_accept() assert.eq(result.flags.on_accept, "typed", "on_accept called") end -local function filter_input_fallback_does_not_call_on_cancel() - local result = run_filter_input_without_telescope() - assert.eq(result.flags.on_cancel, false, "on_cancel not called") +local function filter_input_fallback_calls_on_cancel() + local result = run_filter_input_without_telescope({ cancel = true }) + assert.eq(result.flags.on_cancel, true, "on_cancel called") +end + +local function filter_input_falls_back_to_vim_ui_input() + local result = run_filter_input_without_floating_input() + assert.eq(result.captured.prompt, "Filter: ", "vim.ui.input prompt") + assert.eq(result.flags.on_accept, "typed", "vim.ui.input accept") +end + +local function filter_input_vim_ui_uses_title_when_prompt_empty() + local result = run_filter_input_without_floating_input({ + input_title = "Filter:", + input_prompt = "", + }) + assert.eq(result.captured.prompt, "Filter: ", "vim.ui.input prompt") end function M.run() + filter_input_fallback_sets_title() filter_input_fallback_sets_prompt() + filter_input_fallback_uses_custom_prompt() + filter_input_fallback_omits_title_for_custom_prompt() + filter_input_fallback_uses_custom_title() filter_input_fallback_sets_default() filter_input_fallback_calls_on_change() filter_input_fallback_calls_on_accept() - filter_input_fallback_does_not_call_on_cancel() + filter_input_fallback_calls_on_cancel() + filter_input_falls_back_to_vim_ui_input() + filter_input_vim_ui_uses_title_when_prompt_empty() end return M From 4a9dacfd4ed6d130dda7feb51b7aa8d0f3006422 Mon Sep 17 00:00:00 2001 From: Gage Vander Clay Date: Fri, 8 May 2026 17:21:20 -0500 Subject: [PATCH 2/5] fix(ui): start floating prompt at default end --- lua/android/ui/input.lua | 2 +- lua/tests/android/ui/input_test.lua | 16 +++++++++++++++- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/lua/android/ui/input.lua b/lua/android/ui/input.lua index cb445a7..d99decc 100644 --- a/lua/android/ui/input.lua +++ b/lua/android/ui/input.lua @@ -118,7 +118,7 @@ function M.prompt(opts) }) end - vim.cmd("startinsert") + vim.cmd("startinsert!") end return M diff --git a/lua/tests/android/ui/input_test.lua b/lua/tests/android/ui/input_test.lua index 09bd862..ed9e4b1 100644 --- a/lua/tests/android/ui/input_test.lua +++ b/lua/tests/android/ui/input_test.lua @@ -27,6 +27,7 @@ local function with_input(lines, run) local state = { autocmd = nil, callbacks = {}, + commands = {}, } save(vim, "o") @@ -44,7 +45,9 @@ local function with_input(lines, run) }) save(vim, "cmd") - vim.cmd = function() end + vim.cmd = function(cmd) + state.commands[#state.commands + 1] = cmd + end save(vim, "keymap") vim.keymap = vim.keymap or {} @@ -129,9 +132,20 @@ local function on_change_skips_empty_prompt() end) end +local function startinsert_uses_append_mode() + with_input({ "old" }, function(input, state) + input.prompt({ + default = "old", + }) + + assert.eq(state.commands[#state.commands], "startinsert!", "appends at end") + end) +end + function M.run() on_change_strips_prompt_prefix() on_change_skips_empty_prompt() + startinsert_uses_append_mode() end return M From 9e5742214f4c4e89b9d1c5589e9e6b44bc2a6d50 Mon Sep 17 00:00:00 2001 From: Gage Vander Clay Date: Fri, 8 May 2026 17:45:58 -0500 Subject: [PATCH 3/5] fix(ui): widen floating prompt default --- lua/android/ui/input.lua | 25 +++++++++++++++++++++--- lua/tests/android/ui/input_test.lua | 30 ++++++++++++++++++++++++++++- 2 files changed, 51 insertions(+), 4 deletions(-) diff --git a/lua/android/ui/input.lua b/lua/android/ui/input.lua index d99decc..084af74 100644 --- a/lua/android/ui/input.lua +++ b/lua/android/ui/input.lua @@ -1,7 +1,26 @@ local M = {} -local function calc_width(prompt, default) - local base = math.max(#prompt + #default + 4, 24) +local MIN_WIDTH = 56 + +local function display_width(value) + if not value or value == "" then + return 0 + end + + if vim.fn and vim.fn.strdisplaywidth then + local ok, width = pcall(vim.fn.strdisplaywidth, value) + if ok and type(width) == "number" then + return width + end + end + + return #value +end + +local function calc_width(prompt, default, title) + local content_width = display_width(prompt) + display_width(default) + local title_width = display_width(title) + local base = math.max(content_width + 4, title_width + 4, MIN_WIDTH) local max = math.max(20, vim.o.columns - 4) return math.min(base, max) end @@ -43,7 +62,7 @@ function M.prompt(opts) vim.api.nvim_buf_set_lines(buf, 0, -1, false, { default }) end - local width = calc_width(prompt, default) + local width = calc_width(prompt, default, title) local height = 1 local row, col = center_position(width, height) local win = vim.api.nvim_open_win(buf, true, { diff --git a/lua/tests/android/ui/input_test.lua b/lua/tests/android/ui/input_test.lua index ed9e4b1..9380797 100644 --- a/lua/tests/android/ui/input_test.lua +++ b/lua/tests/android/ui/input_test.lua @@ -28,6 +28,7 @@ local function with_input(lines, run) autocmd = nil, callbacks = {}, commands = {}, + open_opts = nil, } save(vim, "o") @@ -68,7 +69,10 @@ local function with_input(lines, run) vim.api.nvim_create_buf = function() return 1 end save(vim.api, "nvim_open_win") - vim.api.nvim_open_win = function() return 2 end + vim.api.nvim_open_win = function(_, _, opts) + state.open_opts = opts + return 2 + end save(vim.api, "nvim_win_is_valid") vim.api.nvim_win_is_valid = function() return true end @@ -142,10 +146,34 @@ local function startinsert_uses_append_mode() end) end +local function uses_wider_default_width() + with_input({ "" }, function(input, state) + input.prompt({ + title = "Logcat filter:", + }) + + assert.eq(state.open_opts.width, 56, "default width") + end) +end + +local function clamps_width_to_editor() + with_input({ "" }, function(input, state) + vim.o.columns = 40 + + input.prompt({ + title = "Logcat filter:", + }) + + assert.eq(state.open_opts.width, 36, "clamped width") + end) +end + function M.run() on_change_strips_prompt_prefix() on_change_skips_empty_prompt() startinsert_uses_append_mode() + uses_wider_default_width() + clamps_width_to_editor() end return M From 4be51380fb608a467d471bc868b3907e7f7cf811 Mon Sep 17 00:00:00 2001 From: Gage Vander Clay Date: Fri, 8 May 2026 17:53:45 -0500 Subject: [PATCH 4/5] refactor(ui): simplify fallback filter labels --- lua/android/logcat/session/controls.lua | 2 - lua/android/ui/picker.lua | 20 +++------ .../logcat/controls/filter_input_test.lua | 4 -- .../android/logcat/controls/package_test.lua | 7 ++- .../ui/picker_filter_input/fallback_input.lua | 44 ++----------------- 5 files changed, 13 insertions(+), 64 deletions(-) diff --git a/lua/android/logcat/session/controls.lua b/lua/android/logcat/session/controls.lua index 6f76e04..e1b4a35 100644 --- a/lua/android/logcat/session/controls.lua +++ b/lua/android/logcat/session/controls.lua @@ -104,8 +104,6 @@ local function start_filter_edit(session, deps) local build_context = resolve_build_context(session) picker().filter_input({ prompt_title = "Logcat filter", - input_title = "Logcat filter:", - input_prompt = "", items = session.filter_history or {}, default = session.filter, panel_names = function(query) diff --git a/lua/android/ui/picker.lua b/lua/android/ui/picker.lua index 7b74d17..414baa1 100644 --- a/lua/android/ui/picker.lua +++ b/lua/android/ui/picker.lua @@ -71,19 +71,15 @@ end local function fallback_filter_input(options) local title = options.prompt_title or "Filter" - local prompt = options.input_prompt - if prompt == nil then - prompt = title - end - local input_title = options.input_title - if input_title == nil then - input_title = options.input_prompt and "" or title + local label = title + if label:sub(-1) ~= ":" then + label = label .. ":" end local ok_input, input = pcall(require, "android.ui.input") if ok_input and input and type(input.prompt) == "function" then input.prompt({ - title = input_title, - prompt = prompt, + title = label, + prompt = "", default = options.default or "", on_change = options.on_change, on_submit = function(value) @@ -104,12 +100,8 @@ local function fallback_filter_input(options) return end - local fallback_prompt = prompt - if fallback_prompt == "" and input_title and input_title ~= "" then - fallback_prompt = input_title .. " " - end vim.ui.input({ - prompt = fallback_prompt, + prompt = label .. " ", default = options.default or "", }, function(value) if value == nil then diff --git a/lua/tests/android/logcat/controls/filter_input_test.lua b/lua/tests/android/logcat/controls/filter_input_test.lua index 58f2d66..e9a7e5d 100644 --- a/lua/tests/android/logcat/controls/filter_input_test.lua +++ b/lua/tests/android/logcat/controls/filter_input_test.lua @@ -164,8 +164,6 @@ local function filter_picker_receives_prompt_text() ["android.ui.picker"] = { filter_input = function(opts) captured.prompt_title = opts.prompt_title - captured.input_title = opts.input_title - captured.input_prompt = opts.input_prompt end, }, } @@ -173,8 +171,6 @@ local function filter_picker_receives_prompt_text() logcat_helpers.with_logcat_context({ state = state, stubs = stubs }, function(ctx) logcat_helpers.start_filter_edit(ctx) assert.eq(captured.prompt_title, "Logcat filter", "prompt title") - assert.eq(captured.input_title, "Logcat filter:", "input title") - assert.eq(captured.input_prompt, "", "input prompt") end) end diff --git a/lua/tests/android/logcat/controls/package_test.lua b/lua/tests/android/logcat/controls/package_test.lua index 627d6a5..1ba9964 100644 --- a/lua/tests/android/logcat/controls/package_test.lua +++ b/lua/tests/android/logcat/controls/package_test.lua @@ -191,14 +191,13 @@ local function header_enter_prompts_for_package_and_filter_modal() local call = ctx.vim_state.input_calls[1] or {} local filter_call = input_calls[1] or {} local summary = string.format( - "%d|%s|%d|%s|%s", + "%d|%s|%d|%s", #ctx.vim_state.input_calls, call.prompt or "", #input_calls, - filter_call.prompt_title or "", - filter_call.input_title or "" + filter_call.prompt_title or "" ) - assert.eq(summary, "1|Logcat package: |1|Logcat filter|Logcat filter:", "header enter") + assert.eq(summary, "1|Logcat package: |1|Logcat filter", "header enter") end) end diff --git a/lua/tests/android/ui/picker_filter_input/fallback_input.lua b/lua/tests/android/ui/picker_filter_input/fallback_input.lua index 5f28966..87a0c0c 100644 --- a/lua/tests/android/ui/picker_filter_input/fallback_input.lua +++ b/lua/tests/android/ui/picker_filter_input/fallback_input.lua @@ -43,8 +43,6 @@ local function run_filter_input_without_telescope(opts) picker.filter_input({ items = { "one" }, prompt_title = options.prompt_title or "Filter", - input_title = options.input_title, - input_prompt = options.input_prompt, default = "old", on_change = function(value) flags.on_change = value end, on_accept = function(value) flags.on_accept = value end, @@ -60,8 +58,7 @@ local function run_filter_input_without_telescope(opts) return { captured = captured, flags = flags } end -local function run_filter_input_without_floating_input(opts) - local options = opts or {} +local function run_filter_input_without_floating_input() local flags = { on_change = false, on_accept = false, on_cancel = false } local captured = { prompt = nil, default = nil } local original_input = vim.ui and vim.ui.input @@ -91,8 +88,6 @@ local function run_filter_input_without_floating_input(opts) picker.filter_input({ items = { "one" }, prompt_title = "Filter", - input_title = options.input_title or "Filter:", - input_prompt = options.input_prompt or "Filter: ", default = "old", on_change = function(value) flags.on_change = value end, on_accept = function(value) flags.on_accept = value end, @@ -110,30 +105,11 @@ end local function filter_input_fallback_sets_title() local result = run_filter_input_without_telescope() - assert.eq(result.captured.title, "Filter", "input title") + assert.eq(result.captured.title, "Filter:", "input title") end -local function filter_input_fallback_sets_prompt() +local function filter_input_fallback_keeps_prompt_empty() local result = run_filter_input_without_telescope() - assert.eq(result.captured.prompt, "Filter", "input prompt") -end - -local function filter_input_fallback_uses_custom_prompt() - local result = run_filter_input_without_telescope({ input_prompt = "Filter: " }) - assert.eq(result.captured.prompt, "Filter: ", "input prompt") -end - -local function filter_input_fallback_omits_title_for_custom_prompt() - local result = run_filter_input_without_telescope({ input_prompt = "Filter: " }) - assert.eq(result.captured.title, "", "input title") -end - -local function filter_input_fallback_uses_custom_title() - local result = run_filter_input_without_telescope({ - input_title = "Filter:", - input_prompt = "", - }) - assert.eq(result.captured.title, "Filter:", "input title") assert.eq(result.captured.prompt, "", "input prompt") end @@ -163,26 +139,14 @@ local function filter_input_falls_back_to_vim_ui_input() assert.eq(result.flags.on_accept, "typed", "vim.ui.input accept") end -local function filter_input_vim_ui_uses_title_when_prompt_empty() - local result = run_filter_input_without_floating_input({ - input_title = "Filter:", - input_prompt = "", - }) - assert.eq(result.captured.prompt, "Filter: ", "vim.ui.input prompt") -end - function M.run() filter_input_fallback_sets_title() - filter_input_fallback_sets_prompt() - filter_input_fallback_uses_custom_prompt() - filter_input_fallback_omits_title_for_custom_prompt() - filter_input_fallback_uses_custom_title() + filter_input_fallback_keeps_prompt_empty() filter_input_fallback_sets_default() filter_input_fallback_calls_on_change() filter_input_fallback_calls_on_accept() filter_input_fallback_calls_on_cancel() filter_input_falls_back_to_vim_ui_input() - filter_input_vim_ui_uses_title_when_prompt_empty() end return M From b5ee2545d4b6ac7ded56a64862ec15bf0eb21fee Mon Sep 17 00:00:00 2001 From: Gage Vander Clay Date: Fri, 8 May 2026 18:00:59 -0500 Subject: [PATCH 5/5] refactor(ui): tighten filter input fallback --- CHANGELOG.md | 4 +- lua/android/ui/input.lua | 11 +--- lua/android/ui/picker.lua | 51 ++++------------ .../ui/picker_filter_input/fallback_input.lua | 60 ++----------------- 4 files changed, 21 insertions(+), 105 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b1c2671..8bec1f6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,8 +9,8 @@ The format is based on Keep a Changelog and this project adheres to SemVer. ### Changed - Filter inputs now use the plugin's live-updating floating prompt when - Telescope is unavailable, and the logcat filter fallback now labels the - floating input as `Logcat filter:`. + Telescope is unavailable, with the floating prompt label derived from each + filter's existing prompt title. ## [0.6.0] - 2026-03-25 diff --git a/lua/android/ui/input.lua b/lua/android/ui/input.lua index 084af74..a03a99d 100644 --- a/lua/android/ui/input.lua +++ b/lua/android/ui/input.lua @@ -1,5 +1,7 @@ local M = {} +-- Wide enough for common tag, package, and short message filters without a +-- config knob for one prompt helper. local MIN_WIDTH = 56 local function display_width(value) @@ -7,14 +9,7 @@ local function display_width(value) return 0 end - if vim.fn and vim.fn.strdisplaywidth then - local ok, width = pcall(vim.fn.strdisplaywidth, value) - if ok and type(width) == "number" then - return width - end - end - - return #value + return vim.fn.strdisplaywidth(value) end local function calc_width(prompt, default, title) diff --git a/lua/android/ui/picker.lua b/lua/android/ui/picker.lua index 414baa1..316edc0 100644 --- a/lua/android/ui/picker.lua +++ b/lua/android/ui/picker.lua @@ -75,48 +75,19 @@ local function fallback_filter_input(options) if label:sub(-1) ~= ":" then label = label .. ":" end - local ok_input, input = pcall(require, "android.ui.input") - if ok_input and input and type(input.prompt) == "function" then - input.prompt({ - title = label, - prompt = "", - default = options.default or "", - on_change = options.on_change, - on_submit = function(value) - if options.on_change then - options.on_change(value) - end - if options.on_accept then - options.on_accept(value) - end - end, - on_cancel = options.on_cancel, - }) - return - end - - if not vim.ui or not vim.ui.input then - vim.notify("vim.ui.input not available", vim.log.levels.WARN) - return - end - - vim.ui.input({ - prompt = label .. " ", + local input = require("android.ui.input") + input.prompt({ + title = label, + prompt = "", default = options.default or "", - }, function(value) - if value == nil then - if options.on_cancel then - options.on_cancel() + on_change = options.on_change, + on_submit = function(value) + if options.on_accept then + options.on_accept(value) end - return - end - if options.on_change then - options.on_change(value) - end - if options.on_accept then - options.on_accept(value) - end - end) + end, + on_cancel = options.on_cancel, + }) end local function set_buffer_name(buf, name) diff --git a/lua/tests/android/ui/picker_filter_input/fallback_input.lua b/lua/tests/android/ui/picker_filter_input/fallback_input.lua index 87a0c0c..9721cb2 100644 --- a/lua/tests/android/ui/picker_filter_input/fallback_input.lua +++ b/lua/tests/android/ui/picker_filter_input/fallback_input.lua @@ -4,7 +4,7 @@ local assert = require("tests.helpers.assert") local function run_filter_input_without_telescope(opts) local options = opts or {} - local flags = { on_change = false, on_accept = false, on_cancel = false } + local flags = { on_change = {}, on_accept = false, on_cancel = false } local captured = { title = nil, prompt = nil, default = nil } local original_input = vim.ui and vim.ui.input local original_preload = package.preload["telescope.pickers"] @@ -44,61 +44,18 @@ local function run_filter_input_without_telescope(opts) items = { "one" }, prompt_title = options.prompt_title or "Filter", default = "old", - on_change = function(value) flags.on_change = value end, + on_change = function(value) + flags.on_change[#flags.on_change + 1] = value + end, on_accept = function(value) flags.on_accept = value end, on_cancel = function() flags.on_cancel = true end, }) - vim.ui.input = original_input package.preload["telescope.pickers"] = original_preload package.loaded["telescope.pickers"] = original_loaded package.preload["android.ui.input"] = original_android_preload package.loaded["android.ui.input"] = original_android_input - - return { captured = captured, flags = flags } -end - -local function run_filter_input_without_floating_input() - local flags = { on_change = false, on_accept = false, on_cancel = false } - local captured = { prompt = nil, default = nil } - local original_input = vim.ui and vim.ui.input - local original_preload = package.preload["telescope.pickers"] - local original_loaded = package.loaded["telescope.pickers"] - local original_android_preload = package.preload["android.ui.input"] - local original_android_input = package.loaded["android.ui.input"] - - package.preload["telescope.pickers"] = function() - error("missing telescope.pickers") - end - package.loaded["telescope.pickers"] = nil - package.preload["android.ui.input"] = function() - error("missing android.ui.input") - end - package.loaded["android.ui.input"] = nil - - vim.ui = vim.ui or {} - vim.ui.input = function(input_opts, cb) - captured.prompt = input_opts.prompt - captured.default = input_opts.default - cb("typed") - end - - package.loaded["android.ui.picker"] = nil - local picker = require("android.ui.picker") - picker.filter_input({ - items = { "one" }, - prompt_title = "Filter", - default = "old", - on_change = function(value) flags.on_change = value end, - on_accept = function(value) flags.on_accept = value end, - on_cancel = function() flags.on_cancel = true end, - }) - vim.ui.input = original_input - package.preload["telescope.pickers"] = original_preload - package.loaded["telescope.pickers"] = original_loaded - package.preload["android.ui.input"] = original_android_preload - package.loaded["android.ui.input"] = original_android_input return { captured = captured, flags = flags } end @@ -120,7 +77,7 @@ end local function filter_input_fallback_calls_on_change() local result = run_filter_input_without_telescope() - assert.eq(result.flags.on_change, "typed", "on_change called") + assert.table_eq(result.flags.on_change, { "live" }, "on_change called once") end local function filter_input_fallback_calls_on_accept() @@ -133,12 +90,6 @@ local function filter_input_fallback_calls_on_cancel() assert.eq(result.flags.on_cancel, true, "on_cancel called") end -local function filter_input_falls_back_to_vim_ui_input() - local result = run_filter_input_without_floating_input() - assert.eq(result.captured.prompt, "Filter: ", "vim.ui.input prompt") - assert.eq(result.flags.on_accept, "typed", "vim.ui.input accept") -end - function M.run() filter_input_fallback_sets_title() filter_input_fallback_keeps_prompt_empty() @@ -146,7 +97,6 @@ function M.run() filter_input_fallback_calls_on_change() filter_input_fallback_calls_on_accept() filter_input_fallback_calls_on_cancel() - filter_input_falls_back_to_vim_ui_input() end return M