Files
tinycast/.swiftlint.yml
Michael Aristarcoandabue-ammar 91ba10af3d Search files through the Spotlight index (#222)
* Add Spotlight-backed file search

* Document file search architecture

* Add file search performance baseline

* Serialize Spotlight file searches

* Cancel file search on click-away

* Correct file search documentation counts

* Stabilize file search scroll origin

* Keep file search disabled by default

* Configure file search scopes, ignore patterns and its command

File search resolved its policy from hardcoded rules: home plus the cloud
roots, minus six baked-in directory names. All three are now the user's.

Search Scopes is an editable folder list seeded with home. A configured
home root still expands into its visible children plus the cloud roots,
since ~/Library can never be an MDQuery scope; every other root is handed
to Spotlight verbatim. A cleared list searches nothing rather than
falling back.

Ignore Patterns are gitignore-flavoured globs run through fnmatch with
FNM_CASEFOLD and without FNM_PATHNAME, so **/[Cc]ache/** behaves as
written. FileSearchIgnoreList compiles each pattern once into a literal
name set, name globs and path globs, so matching stays cheap enough for
every candidate. The six shipped rules are compiled in and never
persisted, which is what makes them non-removable and lets the list
change without losing to a stale copy on disk.

Bare * name globs also join the Spotlight expression as exclusions, so
ignored files cannot consume the 1,000-candidate cap. Only that shape is
pushed: ? and [ are literals to Spotlight and kMDItemPath is not
queryable, so path globs stay local. Pushed patterns are escaped and any
still carrying a quote is dropped, since an unescaped one would nil
MDQueryCreate and break every search until it was deleted.

Search Files gains a HotKeyAction of its own, the first built-in command
to have one, so its launcher row prints the chord and both settings panes
carry the same recorder. Its launcher-visibility checkbox binds to
VisibilityStore on the CommandCatalog entry rather than a new setting, so
it is the same switch as the one in Settings > Commands. LegacyHotKeyRecords
now returns nil for an action that postdates the old scheme instead of
inventing a migration key.

Editing either list cancels the session: a result found under the old
rules must not publish under the new ones.

* Configure file search scopes, ignore patterns and its command

File search resolved its policy from hardcoded rules: home plus the cloud
roots, minus six baked-in directory names. All three are now the user's.

Search Scopes is an editable folder list seeded with home. A configured
home root still expands into its visible children plus the cloud roots,
since ~/Library can never be an MDQuery scope; every other root is handed
to Spotlight verbatim. A cleared list searches nothing rather than
falling back.

Ignore Patterns are gitignore-flavoured globs run through fnmatch with
FNM_CASEFOLD and without FNM_PATHNAME, so **/[Cc]ache/** behaves as
written. FileSearchIgnoreList compiles each pattern once into a literal
name set, name globs and path globs, so matching stays cheap enough for
every candidate. The six shipped rules are compiled in and never
persisted, which is what makes them non-removable and lets the list
change without losing to a stale copy on disk.

Bare * name globs also join the Spotlight expression as exclusions, so
ignored files cannot consume the 1,000-candidate cap. Only that shape is
pushed: ? and [ are literals to Spotlight and kMDItemPath is not
queryable, so path globs stay local. Pushed patterns are escaped and any
still carrying a quote is dropped, since an unescaped one would nil
MDQueryCreate and break every search until it was deleted.

Search Files gains a HotKeyAction of its own, the first built-in command
to have one, so its launcher row prints the chord and both settings panes
carry the same recorder. Its launcher-visibility checkbox binds to
VisibilityStore on the CommandCatalog entry rather than a new setting, so
it is the same switch as the one in Settings > Commands. LegacyHotKeyRecords
now returns nil for an action that postdates the old scheme instead of
inventing a migration key.

Editing either list cancels the session: a result found under the old
rules must not publish under the new ones.

---------

Co-authored-by: abue-ammar <iabueammar@gmail.com>
2026-08-11 03:24:23 +06:00

61 lines
2.5 KiB
YAML

# SwiftLint is the only code-quality tool here — there is no formatter, deliberately.
# So this config sticks to rules that catch defects, and stays quiet
# about style a formatter would otherwise own. See docs/standards.md.
included:
- Tinycast
- Tests
excluded:
- Tinycast/DesignSystem/Scrolling/EdgeDissolve.swift
- Tinycast/DesignSystem/Scrolling/ThinScrollbar.swift
- Tinycast/Features/Emoji/Model/EmojiData.generated.swift
- Tinycast/Features/Calculator/Model/CurrencyData.generated.swift
# Noisy here, or actively wrong for this codebase.
disabled_rules:
- todo # tracked in issues, not by the linter
- trailing_whitespace # cosmetic, and nothing auto-fixes it on save
- identifier_name # rejects `id`, `dx`, `x` — all correct here
- type_body_length # a SwiftUI view is legitimately long
- function_body_length
- file_length # RootPaletteView is 662 lines by design
- cyclomatic_complexity # a switch over AppEntry.Kind is not complex
- nesting # namespace enums are the house style
- large_tuple
- function_parameter_count # SwiftUI initialisers
- multiple_closures_with_trailing_closure # fights SwiftUI
- opening_brace # ─┐ a formatter's job, and there is no formatter
- trailing_comma # ─┘ here, so these would just be standing noise
# `count` is a hit count on LauncherRankingRecord and PaletteRowIndex, not a collection count.
# This rule rewrites `count == 0` to `isEmpty` on both, which does not compile.
- empty_count
# Each one prevents a real class of defect, not a style preference.
opt_in_rules:
- first_where
- last_where
- contains_over_filter_count
- contains_over_first_not_nil
- optional_data_string_conversion
- implicitly_unwrapped_optional
- redundant_nil_coalescing
- toggle_bool
- static_over_final_class
- unneeded_parentheses_in_closure_argument
- weak_delegate
# A `try!` is never acceptable. A `force_cast` sometimes is — the AX and AppKit bridges have four —
# so it warns rather than blocks.
force_try: error
force_cast: warning
type_name:
min_length: 2 # `ID` is the correct name for an Identifiable's ID
# Warning-only: length is a judgement call, and a harness header comment is not a defect.
line_length:
warning: 130
ignores_urls: true
ignores_comments: false