Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
6a99b15
FormatBar: don't subclass MenuButton (#1774)
danirabbit Jul 22, 2026
0405b6f
MainWindow: inline namespace
danirabbit Jul 21, 2026
997774b
MainWindow: reduce variable scope and organize
danirabbit Jul 21, 2026
80f1bba
Translated using Weblate (Georgian)
NorwayFun Jul 22, 2026
f7eccf0
Replace Gtk.events_pending and main_iteration (#1741)
jeremypw Jul 24, 2026
650ba30
Translated using Weblate (Greek)
asongofghostandfire Jul 24, 2026
3b910ea
Translated using Weblate (Greek)
asongofghostandfire Jul 24, 2026
c8d7a7f
Metainfo: add 8.3.2 release notes (#1780)
danirabbit Aug 3, 2026
ab1795c
Update translation template
elementaryBot Aug 3, 2026
620658f
Translated using Weblate (Chinese (Traditional Han script) (zh_TW))
kisaragi-hiu Aug 2, 2026
0117b7b
Translated using Weblate (Chinese (Traditional Han script) (zh_TW))
kisaragi-hiu Aug 2, 2026
aea48c5
Update translation files
weblate Aug 3, 2026
4b4e1e9
Update translation files
weblate Aug 3, 2026
5b05ce3
Update translation files
weblate Aug 3, 2026
3f75045
Release 8.3.2 (#1781)
danirabbit Aug 5, 2026
1c0df31
Translated using Weblate (Ukrainian)
IhorHordiichuk Aug 6, 2026
55fcbf4
Gtk4Prep: Fuzzy search popover - use ListStore instead of Items array…
jeremypw Aug 10, 2026
085b158
Replace pack_start in pastebin dialog (#1784)
jeremypw Aug 10, 2026
8001527
FuzzySearch: ellipsize middle instead of horizontal scroll (#1790)
danirabbit Aug 11, 2026
c5d7197
FuzzySearch/FileItem: use arrow, account for RTL (#1791)
danirabbit Aug 11, 2026
110b86a
FuzzySearchPopover: use native keynav features (#1789)
danirabbit Aug 11, 2026
38d0e5c
FuzzySearch: adjust label styles (#1792)
danirabbit Aug 11, 2026
9f3c315
FuzzySearch: fix file names (#1793)
danirabbit Aug 11, 2026
ad0b305
Merge branch 'master' into danirabbit/formatbar-langaction
jeremypw Aug 13, 2026
904313b
Change a confusing name
jeremypw Aug 13, 2026
067356f
Fix set document on start up
jeremypw Aug 13, 2026
6e6dbb0
Remove unnecessary, duplicate handler
jeremypw Aug 13, 2026
6a880eb
Reorder public before private functions
jeremypw Aug 13, 2026
7bf3ca0
Fix long lines
jeremypw Aug 13, 2026
bf8c02a
Cleanup update current lang
jeremypw Aug 13, 2026
7efa64f
Use CheckButton
jeremypw Aug 13, 2026
54e26f7
Popdown list after choice
jeremypw Aug 13, 2026
498db2c
Do not popdown - like master
jeremypw Aug 13, 2026
8a0685d
Do not set document language to same language
jeremypw Aug 13, 2026
0a0d2c4
Do not call format_tab_header_from_global_settings too early
jeremypw Aug 13, 2026
4b0beb7
Ensure signal handlers connect and disconnect once per doc
jeremypw Aug 13, 2026
b75732d
Move handler ids to start
jeremypw Aug 13, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
13 changes: 3 additions & 10 deletions data/Application.css
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ textview.scrubber {
background-color: transparent;
}

.fuzzy-item.preselect-fuzzy,
.fuzzy-item:focused,
.fuzzy-item:hover {
border-radius: 0.5rem;
}
Expand All @@ -54,22 +54,15 @@ textview.scrubber {
background-color: @theme_unfocused_selected_bg_color;
}

.fuzzy-item.preselect-fuzzy {
.fuzzy-item:focused {
background-color: @selected_bg_color;
color: @selected_fg_color;
}

.fuzzy-item .fuzzy-file-icon {
margin-right: 0.5rem;
}

.fuzzy-item label:nth-child(1) {
font-weight: 700;
}

.fuzzy-item.preselect-fuzzy label {
opacity: 0.7;
}

.symbol-outline > box.horizontal {
margin: 1em;
}
Expand Down
15 changes: 15 additions & 0 deletions data/code.metainfo.xml.in
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,21 @@
<update_contact>contact_AT_elementary.io</update_contact>

<releases>
<release version="8.3.2" date="2026-08-04" urgency="medium">
<description>
<p>Minor updates:</p>
<ul>
<li>Updated translations</li>
</ul>
</description>
<issues>
<issue url="https://github.com/elementary/code/issues/851">Word completion retains deleted words</issue>
<issue url="https://github.com/elementary/code/issues/1527">Word completion omits first word in document until refreshed</issue>
<issue url="https://github.com/elementary/code/issues/1737">Changing pane visibility in one window affects all windows</issue>
<issue url="https://github.com/elementary/code/issues/1750">Code uses system document font instead of monospace font by default</issue>
</issues>
</release>

<release version="8.3.1" date="2026-06-19" urgency="medium">
<description>
<p>Improvements:</p>
Expand Down
2 changes: 1 addition & 1 deletion meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ project(
'io.elementary.code',
'vala', 'c',
meson_version: '>= 0.58.0',
version: '8.3.1'
version: '8.3.2'
)

add_project_arguments([
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,12 @@ public class FileItem : Gtk.ListBoxRow {
var path_box = new Gtk.Box (Gtk.Orientation.VERTICAL, 1);
path_box.valign = Gtk.Align.CENTER;

var path_label = new Gtk.Label (
@"$(should_distinguish_project ? result.project + " • " : "")$(result.relative_path)"
);

path_label.halign = Gtk.Align.START;
var path_label = new Gtk.Label (get_path_label (should_distinguish_project)) {
halign = START,
ellipsize = MIDDLE
};
path_label.get_style_context ().add_class (Gtk.STYLE_CLASS_DIM_LABEL);
path_label.get_style_context ().add_class (Granite.STYLE_CLASS_SMALL_LABEL);

var filename_label = new Gtk.Label (Path.get_basename (result.relative_path));
filename_label.halign = Gtk.Align.START;
Expand Down Expand Up @@ -56,4 +57,16 @@ public class FileItem : Gtk.ListBoxRow {

this.child = container_box;
}

private string get_path_label (bool show_project) {
if (!show_project) {
return result.relative_path;
}

if (Gtk.StateFlags.DIR_RTL in get_state_flags ()) {
return "%s ← %s".printf (result.relative_path, result.project);
}

return "%s → %s".printf (result.project, result.relative_path);
}
}
277 changes: 277 additions & 0 deletions plugins/FuzzySearch/FuzzySearchPopover.vala
Original file line number Diff line number Diff line change
@@ -0,0 +1,277 @@
/*
* SPDX-License-Identifier: GPL-3.0-or-later
* SPDX-FileCopyrightText: 2023-2026 elementary, Inc. <https://elementary.io>
*
* Authored by: Marvin Ahlgrimm
* Colin Kiama <colinkiama@gmail.com>
*/

public class Scratch.FuzzySearchPopover : Gtk.Popover {
public signal void open_file (string filepath);
public signal void close_search ();

public Scratch.MainWindow current_window { get; construct; }
public Scratch.Services.FuzzySearchIndexer search_indexer { get; construct; }
public bool sidebar_is_visible { get; set; }

private Gtk.SearchEntry search_term_entry;
private Services.FuzzyFinder fuzzy_finder;
private Gtk.ListBox search_result_container;
private ListStore search_list_store;
private int preselected_index;
private Gtk.ScrolledWindow scrolled;
private Scratch.Services.FuzzySearchIndexer indexer;
private int max_items;
private Gee.LinkedList<GLib.Cancellable> cancellables;
private Gtk.EventControllerKey search_term_entry_key_controller;
private Gtk.Label title_label;
private string current_doc_project;

public FuzzySearchPopover (Scratch.Services.FuzzySearchIndexer search_indexer, Scratch.MainWindow window) {
Object (
current_window: window,
search_indexer: search_indexer
);
}

construct {
modal = true;
relative_to = current_window.document_view;
width_request = 500;
pointing_to = { 0, 32, 1, 1 };
get_style_context ().add_class ("fuzzy-popover");

title_label = new Granite.HeaderLabel (_("Find project files"));

search_term_entry = new Gtk.SearchEntry () {
hexpand = true,
valign = START
};

search_list_store = new ListStore (typeof (FileItem));
search_result_container = new Gtk.ListBox () {
selection_mode = BROWSE,
activate_on_single_click = true
};
search_result_container.get_style_context ().add_class ("fuzzy-list");
search_result_container.bind_model (search_list_store, (obj) => (FileItem)obj);

var entry_layout = new Gtk.Box (VERTICAL, 0) {
valign = START
};
entry_layout.add (title_label);
entry_layout.add (search_term_entry);

scrolled = new Gtk.ScrolledWindow (null, null) {
propagate_natural_height = true,
hexpand = true,
vexpand = true,
child = search_result_container,
hscrollbar_policy = NEVER
};

var box = new Gtk.Box (VERTICAL, 0);
box.add (entry_layout);
box.add (scrolled);
box.show_all ();

scrolled.hide ();

add (box);

fuzzy_finder = new Services.FuzzyFinder (search_indexer.project_paths);
indexer = search_indexer;
cancellables = new Gee.LinkedList<GLib.Cancellable> ();

search_result_container.row_activated.connect ((row) => {
var file_item = row as FileItem;
if (file_item == null) {
return;
}

handle_item_selection (file_item);
});

search_term_entry_key_controller = new Gtk.EventControllerKey (search_term_entry);
search_term_entry_key_controller.key_pressed.connect ((keyval, keycode, state) => {
switch (keyval) {
// Handle seperately, otherwise it takes 2 escape hits to close the modal
case Gdk.Key.Escape:
close_search ();
return Gdk.EVENT_STOP;
default:
break;
}

return Gdk.EVENT_PROPAGATE;
});

search_term_entry.activate.connect (() => {
if (search_list_store.n_items > 0) {
handle_item_selection ((FileItem) search_list_store.get_item (preselected_index));
}
});

search_term_entry.changed.connect ((e) => {
if (search_term_entry.text.length >= 1) {
var previous_text = search_term_entry.text;
if (cancellables.size > 0) {
var last_cancellable = cancellables.last ();
last_cancellable.cancel ();
}

Timeout.add (1, () => {
// If the entry is empty or the text has changed
// since searching, do nothing
if (previous_text.length == 0 || previous_text != search_term_entry.text) {
return Source.REMOVE;
}

var next_cancellable = new GLib.Cancellable ();
cancellables.add (next_cancellable);

var dir_length = 0, term = search_term_entry.text;
var parts = term.split (Path.DIR_SEPARATOR_S, 0);
var rev_parts = term.reverse ().split (Path.DIR_SEPARATOR_S, 2);
if (rev_parts.length == 2) {
dir_length = rev_parts[0].length + 1;
}

fuzzy_finder.fuzzy_find_async.begin (
term,
dir_length,
current_doc_project,
next_cancellable,
(obj, res) => {
if (next_cancellable.is_cancelled ()) {
cancellables.remove (next_cancellable);
return;
}

var results = fuzzy_finder.fuzzy_find_async.end (res);
if (results == null) {
return;
}

bool first = true;

search_list_store.remove_all ();

foreach (var result in results) {
var file_item = new FileItem (result, indexer.project_paths.size > 1);

if (first) {
first = false;
preselected_index = 0;
}

search_list_store.insert_sorted (file_item, sort_func);
}

scrolled.hide ();
scrolled.show_all ();

// Reset scrolling
scrolled.vadjustment.value = 0;
}
);

return Source.REMOVE;
});
} else {
search_list_store.remove_all ();

scrolled.hide ();
}
});

search_term_entry.realize.connect_after (() => {
int height;
current_window.get_size (null, out height);

// Limit the shown results if the window height is too small
if (height > 400) {
max_items = height / 80;
} else {
max_items = 3;
}

scrolled.set_max_content_height (45 /* height */ * max_items);

current_doc_project = get_current_project (); // This will not change while popover is showing
});

search_result_container.move_cursor.connect (move_cursor);
search_result_container.move_cursor.connect_after (move_cursor_after);
}

private int sort_func (Object a, Object b) {
var result_a = ((FileItem)a).result;
var result_b = ((FileItem)b).result;
var project_a_is_current = result_a.project == current_doc_project;
var project_b_is_current = result_b.project == current_doc_project;
if (project_a_is_current && !project_b_is_current) {
return 1;
} else if (project_b_is_current && !project_a_is_current) {
return -1;
} else if (result_a.score > result_b.score) {
return -1;
} else if (result_b.score > result_a.score) {
return 1;
} else {
return strcmp (((FileItem)a).result.full_path, ((FileItem)b).result.full_path);
}
}

/* focus_child not selected_row because selected_row sometimes lags behind focus
* wrap in connect_after otherwise cursor will move after refocus
*/
private bool wrap_cursor = false;
private void move_cursor (Gtk.ListBox list_box, Gtk.MovementStep step, int count) requires (step == DISPLAY_LINES) {
// Move up to the searchbar
if (list_box.get_focus_child () == list_box.get_row_at_index (0) && count == -1) {
move_focus (TAB_BACKWARD);
return;
}

if (list_box.get_focus_child () == list_box.get_row_at_index ((int) search_list_store.n_items - 1) && count == 1) {
wrap_cursor = true;
}
}

private void move_cursor_after (Gtk.ListBox list_box, Gtk.MovementStep step, int count) {
if (!wrap_cursor) {
return;
}

list_box.get_row_at_index (0).grab_focus ();
wrap_cursor = false;
}

private void handle_item_selection (FileItem item) {
open_file (item.filepath.strip ());
}

private string get_current_project () {
Scratch.Services.Document current_document = current_window.document_view.current_document;
if (current_document == null) {
return "";
}

if (current_document.is_file_temporary) {
return "";
}

string file_path = current_document.file.get_path ();

var iter = indexer.project_paths.keys.iterator ();
while (iter.next ()) {
string project_path = iter.get ();
if (file_path.has_prefix (project_path)) {
return project_path;
}
}

return "";
}
}
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
module_name = 'fuzzy-search'

module_files = [
'file-item.vala',
'fuzzy-search.vala',
'fuzzy-finder.vala',
'fuzzy-search-indexer.vala',
'fuzzy-search-popover.vala',
'fuzzy-search-project.vala',
'search-result.vala',
'FileItem.vala',
'FuzzySearch.vala',
'FuzzyFinder.vala',
'FuzzySearchIndexer.vala',
'FuzzySearchPopover.vala',
'FuzzySearchProject.vala',
'SearchResult.vala',
]

module_deps = [
Expand Down
Loading
Loading