-
-
Notifications
You must be signed in to change notification settings - Fork 112
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Fuzzy finder #1393
Merged
Merged
Fuzzy finder #1393
Changes from 1 commit
Commits
Show all changes
45 commits
Select commit
Hold shift + click to select a range
50e3501
Added a fuzzy search modal
treagod dbd8692
Fixed indent
treagod 021ba91
Use accent color for selection
treagod b51d456
Listen to git change
treagod eb3827d
Use async fuzzy finder and ignore paths from .gitignore
treagod 830c45d
Resize dialog
treagod 88adb72
Added scrolling by up/down arrows
treagod b1be136
Check project content on each search to get all changes
treagod 4db8e57
Add basic debouncing and cancellation for each search query
colinkiama c717a35
Add cancellable checks to all loops during fuzzy search
colinkiama b9e419e
Increase fuzzy search results update speed
colinkiama b65057c
Fix long delay before fuzzy finder dialog opens
colinkiama aa9f61a
Fix vala-lint errors in fuzzy-search plugin directory
colinkiama 6fbf1c6
Prevent fuzzy search dialog from opening if there are no projects opened
colinkiama 6b89011
Clicking on fuzzy search results loads the clicked result
colinkiama 05a3214
Fuzzy search results across multiple projects are disambiguated using…
colinkiama 03686c4
Update fuzzy search debug logs
colinkiama a8c119a
Improve Fuzzy Search Accuracy
colinkiama a514b94
Ignore dot-prefixed directories in fuzzy search
colinkiama ea29d63
Update debug messaging for fuzzy search
colinkiama 949c927
Fix code style warnings in fuzzy search plugin
colinkiama 33be958
Update license and copyright info for Fuzzy Finder plugin
colinkiama b579d3a
Add end method to project path parsing async call
colinkiama e1989ca
Fix vala-lint errors
colinkiama 73a9b3e
Improve fuzzy matching
colinkiama 903505b
Change fuzzy search keyboard shortcut to 'ALT + F'
colinkiama 8610573
Remove unused GitManager signals
colinkiama 31936a0
Files that cannot be opened in Code are filtered out of fuzzy search
colinkiama 09df392
Render fuzzy search items with list boxes
colinkiama 7f82dc1
Add mouser hover styling to fuzzy search items
colinkiama 848e461
Handle Fuzzy Search key presses with EventControllerKey
colinkiama 243a3e5
Now using popover to show fuzzy search
colinkiama ad97f55
Add title label to fuzzy search popover
colinkiama bfd7a92
Remove unused window size and positining code
colinkiama dbfa10e
Prevent focus on fuzzy item results
colinkiama c836cbb
Fuzzy search results for current project are prioritised
colinkiama 0c1fba8
Expose fuzzy find function and accelerator in sidebar action menu (#1…
jeremypw 8c550d6
Associate popover with foldermanager (#1396)
jeremypw e973920
Add folder_item_change plugin manager hook in preparation for backgro…
colinkiama 868d188
Allow search project parsing to be cancellable
colinkiama 0fdcd77
Create indexer and add methods for adding and removing files and dire…
colinkiama a9980b2
Use fuzzy search indexer for fuzzy search results
colinkiama 1671c89
Update indexer debug logs
colinkiama 225b3ad
Update license info and comments in Fuzzy Search Project
colinkiama 8b90e64
Remove old fuzzy search indexer testing logs
colinkiama File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,332 @@ | ||
/* | ||
* SPDX-License-Identifier: GPL-3.0-or-later | ||
* SPDX-FileCopyrightText: 2023 elementary, Inc. <https://elementary.io> | ||
* | ||
* Authored by: Colin Kiama <[email protected]> | ||
*/ | ||
const long SECONDS_IN_MICROSECONDS = 1000000; // 1 Million microseconds = 1 second; | ||
|
||
public enum IndexerMessageType { | ||
INITIAL, | ||
PROJECT_UPDATE | ||
} | ||
|
||
public enum IndexerStatus { | ||
INITIALISING, | ||
INITIAL_PROCESSING, | ||
IDLE, | ||
PROCESSING | ||
} | ||
|
||
public enum ProjectUpdateType { | ||
ADDED, | ||
REMOVED, | ||
FILE_CREATED, | ||
DIRECTORY_CREATED, | ||
FILE_DELETED | ||
} | ||
|
||
public interface IndexerMessage : GLib.Object { | ||
public abstract IndexerMessageType message_type { get; construct; } | ||
} | ||
|
||
public class InitialIndexRequest : GLib.Object, IndexerMessage { | ||
public IndexerMessageType message_type { get; construct; } | ||
public string project_path { get; construct; } | ||
|
||
public InitialIndexRequest (string project_path) { | ||
Object ( | ||
message_type: IndexerMessageType.INITIAL, | ||
project_path: project_path | ||
); | ||
} | ||
} | ||
|
||
public class ProjectUpdate : GLib.Object, IndexerMessage { | ||
public IndexerMessageType message_type { get; construct; } | ||
public ProjectUpdateType update_type { get; construct; } | ||
public string source_path { get; construct; } | ||
public string? destination_path { get; construct; } | ||
public string? project_path { get; construct; } | ||
|
||
public ProjectUpdate (ProjectUpdateType update_type, string source_path, string? destination_path = null) { | ||
Object ( | ||
message_type: IndexerMessageType.PROJECT_UPDATE, | ||
update_type: update_type, | ||
source_path: source_path, | ||
destination_path: destination_path | ||
); | ||
} | ||
} | ||
|
||
public class Scratch.Services.FuzzySearchIndexer : GLib.Object { | ||
public Gee.HashMap<string, Services.SearchProject> project_paths { get; private set; } | ||
|
||
private Gee.ArrayList<InitialIndexRequest> initial_indexing_queue; | ||
private GLib.Settings folder_settings; | ||
private GLib.Cancellable cancellable; | ||
private Gee.ConcurrentList<IndexerMessage> processing_queue; | ||
private IndexerStatus status; | ||
|
||
public FuzzySearchIndexer (GLib.Cancellable cancellable) { | ||
this.cancellable = cancellable; | ||
status = IndexerStatus.INITIALISING; | ||
initial_indexing_queue = new Gee.ArrayList<InitialIndexRequest> (); | ||
processing_queue = new Gee.ConcurrentList<IndexerMessage> (); | ||
project_paths = new Gee.HashMap<string, Services.SearchProject> (); | ||
|
||
folder_settings = new GLib.Settings ("io.elementary.code.folder-manager"); | ||
folder_settings.changed["opened-folders"].connect (handle_opened_projects_change); | ||
} | ||
|
||
public void handle_folder_item_change (GLib.File source, GLib.File? dest, GLib.FileMonitorEvent event) { | ||
debug ("Folder Item Change:\nSource File: %s\ndDestination File: %s\nEvent Type: %s", | ||
source.get_path (), | ||
dest != null | ||
? dest.get_path () | ||
: "(None)", | ||
FuzzySearchIndexer.enum_to_nick (event, typeof (GLib.FileMonitorEvent)) | ||
); | ||
|
||
|
||
switch (event) { | ||
case GLib.FileMonitorEvent.CREATED: | ||
string path = source.get_path (); | ||
bool is_directory = FileUtils.test (path, GLib.FileTest.IS_DIR); | ||
|
||
var project_update = new ProjectUpdate ( | ||
is_directory ? ProjectUpdateType.DIRECTORY_CREATED : ProjectUpdateType.FILE_CREATED, | ||
path | ||
); | ||
|
||
processing_queue.add (project_update); | ||
break; | ||
case GLib.FileMonitorEvent.DELETED: | ||
string path = source.get_path (); | ||
|
||
var project_update = new ProjectUpdate (ProjectUpdateType.FILE_DELETED, path); | ||
processing_queue.add (project_update); | ||
break; | ||
default: | ||
break; | ||
} | ||
} | ||
|
||
public async void start_async () { | ||
string[] initial_projects = folder_settings.get_strv ("opened-folders"); | ||
if (initial_projects != null) { | ||
foreach (unowned string path in initial_projects) { | ||
initial_indexing_queue.add (new InitialIndexRequest (path)); | ||
} | ||
} | ||
|
||
new Thread<void> (null, () => { | ||
while (cancellable.is_cancelled () == false) { | ||
switch (status) { | ||
case IndexerStatus.INITIALISING: | ||
if (initial_indexing_queue.size < 1 ) { | ||
status = IndexerStatus.IDLE; | ||
debug ("Fuzzy Search - Indexer is now idle!\n"); | ||
break; | ||
} | ||
|
||
if (initial_indexing_queue.size > 0) { | ||
process_initial_indexing_requests_async | ||
.begin (initial_indexing_queue, | ||
project_paths, (obj, res) => { | ||
process_initial_indexing_requests_async.end (res); | ||
status = IndexerStatus.IDLE; | ||
} | ||
); | ||
status = IndexerStatus.INITIAL_PROCESSING; | ||
} | ||
|
||
break; | ||
// Indexer initialization is complete, now waiting for incoming messages to process. | ||
case IndexerStatus.IDLE: | ||
if (processing_queue.size > 0) { | ||
var first_item = processing_queue.get (0); | ||
process_next_message_async.begin (first_item, (obj, res) => { | ||
process_next_message_async.end (res); | ||
processing_queue.remove (first_item); | ||
status = IndexerStatus.IDLE; | ||
debug ("Fuzzy Search - Indexer is now idle!"); | ||
}); | ||
|
||
status = IndexerStatus.PROCESSING; | ||
debug ("Fuzzy Search - Indexer now processing!"); | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Blank line after if {} missing |
||
break; | ||
case IndexerStatus.INITIAL_PROCESSING: | ||
case IndexerStatus.PROCESSING: | ||
break; | ||
default: | ||
break; | ||
} | ||
|
||
Thread.usleep (1 * SECONDS_IN_MICROSECONDS); | ||
} | ||
|
||
folder_settings.changed["opened-folders"].disconnect (handle_opened_projects_change); | ||
Idle.add (start_async.callback); | ||
}); | ||
|
||
yield; | ||
} | ||
|
||
private async void process_next_message_async (IndexerMessage message) { | ||
switch (message.message_type) { | ||
case IndexerMessageType.PROJECT_UPDATE: | ||
process_project_update_async.begin ((ProjectUpdate) message, (obj, res) => { | ||
process_project_update_async.end (res); | ||
}); | ||
|
||
break; | ||
default: | ||
break; | ||
} | ||
} | ||
|
||
private async void process_project_update_async (ProjectUpdate message) { | ||
switch (message.update_type) { | ||
case ProjectUpdateType.ADDED: | ||
add_project_async.begin (message, (obj, res) => { | ||
add_project_async.end (res); | ||
}); | ||
|
||
break; | ||
case ProjectUpdateType.REMOVED: | ||
remove_project (message); | ||
break; | ||
case ProjectUpdateType.FILE_CREATED: | ||
add_file (message); | ||
break; | ||
case ProjectUpdateType.DIRECTORY_CREATED: | ||
add_directory_async.begin (message, (obj, res) => { | ||
add_directory_async.end (res); | ||
}); | ||
|
||
break; | ||
case ProjectUpdateType.FILE_DELETED: | ||
remove_file (message); | ||
break; | ||
} | ||
} | ||
|
||
private void remove_file (ProjectUpdate message) { | ||
string path = message.source_path; | ||
string project_key = get_project_path_of_file (path); | ||
if (project_key == null) { | ||
return; | ||
} | ||
|
||
Services.SearchProject project_search = project_paths[project_key]; | ||
project_search.remove_file (path, this.cancellable); | ||
processing_queue.remove (message); | ||
} | ||
|
||
private void add_file (ProjectUpdate message) { | ||
string path = message.source_path; | ||
string project_key = get_project_path_of_file (path); | ||
if (project_key == null) { | ||
return; | ||
} | ||
|
||
Services.SearchProject project_search = project_paths[project_key]; | ||
project_search.add_file (path, this.cancellable); | ||
processing_queue.remove (message); | ||
} | ||
|
||
private async void add_directory_async (ProjectUpdate message) { | ||
string path = message.source_path; | ||
string project_key = get_project_path_of_file (path); | ||
if (project_key == null) { | ||
return; | ||
} | ||
|
||
Services.SearchProject project_search = project_paths[project_key]; | ||
project_search.add_directory_async.begin (path, this.cancellable, (obj, res) => { | ||
project_search.add_directory_async.end (res); | ||
processing_queue.remove (message); | ||
}); | ||
} | ||
|
||
private async void add_project_async (ProjectUpdate message) { | ||
string path = message.source_path; | ||
var monitor = Services.GitManager.get_monitored_repository (path); | ||
var project_search = new Services.SearchProject (path, monitor); | ||
project_paths[path] = project_search; | ||
|
||
project_search.parse_async.begin (path, this.cancellable, (obj, res) => { | ||
project_search.parse_async.end (res); | ||
processing_queue.remove (message); | ||
}); | ||
} | ||
|
||
private void remove_project (ProjectUpdate message) { | ||
string path = message.source_path; | ||
project_paths.unset (path); | ||
} | ||
|
||
private void handle_opened_projects_change () { | ||
string[] opened_projects_array = folder_settings.get_strv ("opened-folders"); | ||
var opened_projects = new Gee.ArrayList<string>.wrap (opened_projects_array); | ||
// Handle project additions | ||
foreach (string project in opened_projects) { | ||
if (project_paths.keys.contains (project) == false) { | ||
processing_queue.add (new ProjectUpdate (ProjectUpdateType.ADDED, project)); | ||
} | ||
} | ||
|
||
// Handle project removals | ||
foreach (string project in project_paths.keys) { | ||
if (opened_projects.contains (project) == false) { | ||
processing_queue.add ( new ProjectUpdate (ProjectUpdateType.REMOVED, project)); | ||
} | ||
} | ||
} | ||
|
||
private async void process_initial_indexing_requests_async ( | ||
Gee.ArrayList<InitialIndexRequest> request_queue, | ||
Gee.HashMap<string, Services.SearchProject> project_paths) { | ||
for (int i = 0; i < request_queue.size; i++) { | ||
var request = request_queue[i]; | ||
var monitor = Services.GitManager.get_monitored_repository (request.project_path); | ||
var project_search = new Services.SearchProject (request.project_path, monitor); | ||
|
||
project_paths[request.project_path] = project_search; | ||
project_search.parse_async.begin (request.project_path, cancellable, (obj, res) => { | ||
project_search.parse_async.end (res); | ||
request_queue.remove (request); | ||
}); | ||
} | ||
} | ||
|
||
private string? get_project_path_of_file (string file_path) { | ||
var iter = project_paths.keys.iterator (); | ||
while (iter.next ()) { | ||
string project_path = iter.get (); | ||
if (file_path.has_prefix (project_path)) { | ||
return project_path; | ||
} | ||
} | ||
|
||
return null; | ||
} | ||
|
||
private static string enum_to_nick (int @value, Type enum_type) { | ||
var enum_class = (EnumClass) enum_type.class_ref (); | ||
|
||
if (enum_class == null) { | ||
return "%i".printf (@value); | ||
} | ||
|
||
unowned var enum_value = enum_class.get_value (@value); | ||
|
||
if (enum_value == null) { | ||
return "%i".printf (@value); | ||
} | ||
|
||
return enum_value.value_nick; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Extra tab present