Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
144 changes: 141 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,12 @@ The list uses the default `NSTableView` styles, so it'll look right at home on B

## Usage

This module strives for taking care of its components's memory wherever possible. This means you do not need to keep a reference to the window or its controller around. **Both will be freed when the action has been completed.**
This module strives for taking care of its components's memory wherever possible. This means you do not need to keep a reference to window or its controller around. **Both will be freed when action has been completed.**

### When all possible items are known

This is useful if you want to filter e.g. a limited collection of files, like "Recent Files", or to display a list of known macros.


```swift
import FloatingFilter

Expand All @@ -32,12 +31,151 @@ let items = [
Item(identifier: "custom-ID_123", title: "Show downloaded data")
]


FloatingFilterModule.showFilterWindow(items: items) { selectedItems in
print("Selected:", selectedItems.map { $0.title })
}
```

### Custom Filter Functions

Since version 0.8.0, you can provide a custom filter function instead of using the built-in fuzzy matching. The default implementation performs fuzzy string matching, scoring, filtering below a threshold, and sorting.

A custom filter function has the following type:

```swift
typealias ItemFilter = (_ needle: String, _ haystack: [Item]) -> [Item]
```

- `needle`: The search string typed by the user
- `haystack`: The complete list of items to filter
- Returns: A filtered and sorted array of matching items

#### Example 1: Exact Match Filter

```swift
import FloatingFilter

func exactMatchFilter(needle: String, haystack: [Item]) -> [Item] {
guard !needle.isEmpty else { return haystack }

return haystack.filter { item in
item.title.localizedCaseInsensitiveContains(needle)
}
}

let items = [
Item(identifier: 1, title: "Create new widget"),
Item(identifier: 2, title: "Open last document"),
Item(identifier: 3, title: "Show downloaded data")
]

FloatingFilterModule.showFilterWindow(
items: items,
filter: exactMatchFilter
) { selectedItems in
print("Selected:", selectedItems.map { $0.title })
}
```

#### Example 2: Prefix Match Filter

```swift
func prefixMatchFilter(needle: String, haystack: [Item]) -> [Item] {
guard !needle.isEmpty else { return haystack }

let lowercaseNeedle = needle.localizedLowercase
return haystack.filter { item in
item.title.localizedLowercase.hasPrefix(lowercaseNeedle)
}
}

FloatingFilterModule.showFilterWindow(
items: items,
filter: prefixMatchFilter
) { selectedItems in
print("Selected:", selectedItems.map { $0.title })
}
```

#### Example 3: Custom Scoring with FuzzyFind

If you want to use the [FuzzyFind](https://github.com/truizlop/FuzzyFind) library instead of the built-in fuzzy matcher:

```swift
import FloatingFilter
import FuzzyFind

func fuzzyFindFilter(needle: String, haystack: [Item]) -> [Item] {
guard !needle.isEmpty else { return haystack }

let matcher = FuzzyFind(matcher: FuzzyMatcher.init)
return matcher.match(needle, in: haystack)
.sorted(by: { $0.score > $1.score })
.map { $0.item }
}

FloatingFilterModule.showFilterWindow(
items: items,
filter: fuzzyFindFilter
) { selectedItems in
print("Selected:", selectedItems.map { $0.title })
}
```

#### Example 4: Filter with Custom Sorting

```swift
func filterByDate(needle: String, haystack: [Item]) -> [Item] {
// Combine your custom filter logic with any sorting you need
let filtered = haystack.filter { item in
item.title.localizedCaseInsensitiveContains(needle)
}

// Sort by identifier (assuming it's a timestamp)
return filtered.sorted { item1, item2 in
if let date1 = item1.identifier as? Int,
let date2 = item2.identifier as? Int {
return date1 > date2 // Newest first
}
return false
}
}

FloatingFilterModule.showFilterWindow(
items: items,
filter: filterByDate
) { selectedItems in
print("Selected:", selectedItems.map { $0.title })
}
```

#### Default Fuzzy Filter

If you want to use the built-in fuzzy matching with custom parameters:

```swift
let customFilter = FloatingFilterModule.defaultFuzzyFilter(
fuzziness: 0.2, // Lower = more strict matching
threshold: 0.5 // Higher = fewer matches shown
)

FloatingFilterModule.showFilterWindow(
items: items,
filter: customFilter
) { selectedItems in
print("Selected:", selectedItems.map { $0.title })
}
```

**Filter Parameters:**

- `fuzziness` (default: 0.3): How "fuzzy" the matching should be
- Lower values (0.0-0.2): More strict, exact-ish matches
- Higher values (0.4-0.5): More permissive, allow typos
- `threshold` (default: 0.4): Minimum score required to include a match
- Lower values (0.2-0.3): More results shown
- Higher values (0.5-0.6): Fewer, higher-quality results


## Installation

Expand Down