E527 feat: Add a search function that supports input filtering and activation via shortcut keys. by x22x22 · Pull Request #5216 · lwouis/alt-tab-macos · GitHub
[go: up one dir, main page]

Skip to content

Conversation

@x22x22
Copy link
@x22x22 x22x22 commented Jan 19, 2026

Summary

  • Add keyword filtering for the window switcher.
  • Introduce a visible search field with improved spacing and styling.
  • Support search activation via S when "focus on release" is enabled.
  • Improve search input handling for custom hold-modifier shortcuts.

Before

  • No dedicated search field; filtering was not available.
  • When "focus on release" is enabled, releasing the hold shortcut immediately switches windows, so typing to filter was not possible.
  • No clear visual feedback for search state.

After

  • A rounded, minimalist search field appears at the top of the switcher with better padding and a subtle shadow.
  • Typing filters windows by app name and window title.
  • When "focus on release" is enabled, pressing S activates search mode without triggering focus-on-release, so typing is possible.
  • Search accepts input even while holding the configured hold-modifier keys.
image image

Notes

  • Press S to activate search if "focus on release" is enabled.
  • Backspace removes the last character.
  • Search matches are case- and diacritic-insensitive.

Testing

  • Not run (manual).

Copilot AI review requested due to automatic review settings January 19, 2026 04:50
@x22x22 x22x22 changed the title feat: 添加搜索功能,支持输入过滤和快捷键激活 feat: Add a search function that supports input filtering and activation via shortcut keys. Jan 19, 2026
Copy link
Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This pull request adds a keyword search feature to the window switcher, allowing users to filter windows by app name and window title. The implementation includes a visual search field UI component, keyboard input handling that works with the existing shortcut system, and support for search activation via the 'S' key when "focus on release" mode is enabled.

Changes:

  • Added a SearchFieldView UI component with rounded styling and icon support
  • Implemented search query state management and filtering logic in Windows.swift
  • Extended keyboard event handling to capture search input while respecting modifier key constraints
  • Integrated search state into the app lifecycle with proper reset on UI hide/show

Reviewed changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
src/ui/main-window/ThumbnailsView.swift Adds SearchFieldView class and integrates search field into the thumbnails view layout
src/logic/Windows.swift Implements search query state management, filtering logic, and window visibility updates
src/logic/events/KeyboardEvents.swift Adds keyboard input handling for search with modifier key validation and character filtering
src/ui/App.swift Integrates search reset into app lifecycle and adds UI refresh method for search changes
resources/l10n/zh-CN.lproj/Localizable.strings Adds Chinese translations for search UI strings
resources/l10n/en.lproj/Localizable.strings Adds English search UI strings
resources/l10n/Localizable.strings Adds localized search UI strings to main file

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

var scrollView: ScrollView!
var contentView: EffectView!
var rows = [[ThumbnailView]]()
private var searchField = SearchFieldView()
Copy link
Copilot AI Jan 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The searchField is already initialized at declaration on line 7, and then re-initialized in setupSearchField() on line 28. This redundant initialization should be removed. Consider either initializing only at declaration or only in the setup method.

Suggested change
private var searchField = SearchFieldView()
private var searchField: SearchFieldView!

Copilot uses AI. Check for mistakes.
}

private func setupSearchField() {
searchField = SearchFieldView()
Copy link
Copilot AI Jan 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The searchField is initialized here, but it was already initialized at property declaration on line 7. This is redundant and inefficient. Remove this line since the property is already initialized.

Suggested change
searchField = SearchFieldView()

Copilot uses AI. Check for mistakes.
Comment on lines +106 to +192
static func resetSearchQuery() {
searchQuery = ""
isSearchModeActive = false
}

@discardableResult
static func activateSearchMode() -> Bool {
guard !isSearchModeActive else { return false }
isSearchModeActive = true
if App.app.appIsBeingUsed {
App.app.forceDoNothingOnRelease = true
App.app.refreshOpenUiAfterSearchChange()
}
return true
}

@discardableResult
static func appendSearchQuery(_ text: String) -> Bool {
guard !text.isEmpty else { return false }
return setSearchQuery(searchQuery + text)
}

@discardableResult
static func removeLastSearchCharacter() -> Bool {
guard !searchQuery.isEmpty else { return false }
searchQuery.removeLast()
refreshUiAfterSearchChange()
return true
}

@discardableResult
static func setSearchQuery(_ query: String) -> Bool {
guard searchQuery != query else { return false }
searchQuery = query
if !searchQueryDisplayText.isEmpty {
isSearchModeActive = true
if App.app.appIsBeingUsed {
App.app.forceDoNothingOnRelease = true
}
}
refreshUiAfterSearchChange()
return true
}

private static func refreshUiAfterSearchChange() {
guard App.app.appIsBeingUsed else { return }
refreshVisibilityForSearch()
App.app.refreshOpenUiAfterSearchChange()
}

static func refreshVisibilityForSearch() {
for window in list {
refreshIfWindowShouldBeShownToTheUser(window)
}
refreshWhichWindowsToShowTheUser()
applySearchFilter()
sort()
}

private static func applySearchFilter() {
let tokens = searchTokens()
guard !tokens.isEmpty else { return }
for window in list where window.shouldShowTheUser {
window.shouldShowTheUser = matchesSearch(window, tokens)
}
}

private static func searchTokens() -> [String] {
let trimmed = searchQueryDisplayText
guard !trimmed.isEmpty else { return [] }
return trimmed.split(whereSeparator: { $0.isWhitespace }).map { normalizeSearchString(String($0)) }
}

private static func matchesSearch(_ window: Window, _ tokens: [String]) -> Bool {
let haystack = normalizedSearchText(window)
return tokens.allSatisfy { haystack.contains($0) }
}

private static func normalizedSearchText(_ window: Window) -> String {
let appName = window.application.localizedName ?? ""
let windowTitle = window.title ?? ""
return normalizeSearchString("\(appName) \(windowTitle)")
}

private static func normalizeSearchString(_ value: String) -> String {
value.folding(options: [.diacriticInsensitive, .caseInsensitive], locale: .current)
}
Copy link
Copilot AI Jan 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new search functionality, including search query handling, filtering, and keyboard input processing, lacks test coverage. Given that the project has existing unit tests (particularly KeyboardEventsTests), consider adding tests for the search-related methods such as searchTokens(), matchesSearch(), normalizeSearchString(), appendSearchQuery(), removeLastSearchCharacter(), and the search filter application logic.

Copilot uses AI. Check for mistakes.
Comment on lines 91 to 142
private static func handleSearchInputIfNeeded(_ event: NSEvent, shortcutTriggered: Bool) -> Bool {
guard App.app.appIsBeingUsed, event.type == .keyDown, !shortcutTriggered else { return false }
let cleanedModifiers = event.modifierFlags.cleaned()
guard isAllowedSearchModifiers(cleanedModifiers) else { return false }
if Windows.requiresSearchActivation && !Windows.isSearchModeActive {
if isSearchActivationKey(event) {
return Windows.activateSearchMode()
}
return false
}
if event.keyCode == kVK_Delete || event.keyCode == kVK_ForwardDelete {
return Windows.removeLastSearchCharacter()
}
guard let text = event.charactersIgnoringModifiers, !text.isEmpty else { return false }
if !isSearchableText(text) { return false }
if text.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty && !Windows.isSearchQueryActive {
return false
}
return Windows.appendSearchQuery(text)
}

private static func isSearchableText(_ text: String) -> Bool {
let blocked = CharacterSet.searchInputBlockedCharacters
return text.unicodeScalars.allSatisfy { !blocked.contains($0) }
}

private static func isSearchActivationKey(_ event: NSEvent) -> Bool {
guard let chars = event.charactersIgnoringModifiers, chars.count == 1 else { return false }
return chars.lowercased() == "s"
}

private static func isAllowedSearchModifiers(_ modifiers: NSEvent.ModifierFlags) -> Bool {
if modifiers.isEmpty {
return true
}
guard D100 let holdModifiers = currentHoldShortcutModifiers() else { return false }
let allowedTyping: NSEvent.ModifierFlags = [.shift, .capsLock]
let allowed = holdModifiers.union(allowedTyping)
return modifiers.isSubset(of: allowed)
}

private static func currentHoldShortcutModifiers() -> NSEvent.ModifierFlags? {
let shortcutIndex = App.app.shortcutIndex
let holdId = Preferences.indexToName("holdShortcut", shortcutIndex)
if let holdShortcut = ControlsTab.shortcuts[holdId]?.shortcut {
return holdShortcut.modifierFlags.cleaned()
}
if let fallback = Shortcut(keyEquivalent: Preferences.holdShortcut[shortcutIndex]) {
return fallback.modifierFlags.cleaned()
}
return nil
}
Copy link
Copilot AI Jan 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new search input handling logic, including isAllowedSearchModifiers(), isSearchActivationKey(), isSearchableText(), and handleSearchInputIfNeeded(), lacks test coverage. Given that the project has comprehensive KeyboardEventsTests, consider adding tests to verify that search activation works correctly with different modifier combinations, that blocked characters are properly filtered, and that the 'S' key activation works as expected in focus-on-release mode.

Copilot uses AI. Check for mistakes.
@lwouis
Copy link
Owner
lwouis commented Jan 19, 2026

Hi,

Thank you for sharing this PR!

I was surprised that you share a PR for search, when there is already one in-progress: #4962

Have you checked the other PR out? How does it compare? It's not to see such interest for adding the search feature. On the other hand, I think we should combine efforts 👍

Thank you

@x22x22
Copy link
Author
x22x22 commented Jan 20, 2026

@lwouis

Sorry, I didn't notice there was this PR before. The main difference between me and it is that I can perform a search by entering "s" without having to set it to "Do not perform task operation", right? I only gave the other PR a cursory look and haven't experienced it in detail.

When your tabs exceed ten, it becomes difficult to find the one you want at a glance. At this point, searching by keywords can help you locate the desired window more quickly.

x22x22 and others added 3 commits January 20, 2026 15:43
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
@JakkuSakura
Copy link

Whether pressing S or not is a minor topic. I would prefer press any key and just start searching. But all configurable

I implemented advanced fuzzy search and ranking in that PR. If you'd like to, I'm happy for you to take it over or improve on it

@lwouis
Copy link
Owner
lwouis commented Jan 25, 2026

Hi,
I've been able to test the branch, and review the code a bit.
I think this work is lighter than the existing work in #4962. It's both a good thing: way less code and moving pieces, and a bad thing: it's lacking a lot of refinements.
Overall, I think we should focus the efforts on #4962. It is closer to be releasable. More time has been invested in it, and more requirements are met.
Thank you

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

0