diff --git a/data/Application.css b/data/Application.css
index f607b3d3ea..7bab5b8cab 100644
--- a/data/Application.css
+++ b/data/Application.css
@@ -69,3 +69,11 @@ textview.scrubber {
.fuzzy-item.preselect-fuzzy label {
opacity: 0.7;
}
+
+.symbol-outline > box.horizontal {
+ margin: 1em;
+}
+
+.symbol-outline .sidebar {
+ background: inherit;
+}
diff --git a/data/icons/filter-symbolic.svg b/data/icons/filter-symbolic.svg
new file mode 100644
index 0000000000..8be0c63741
--- /dev/null
+++ b/data/icons/filter-symbolic.svg
@@ -0,0 +1,282 @@
+
+
diff --git a/data/io.elementary.code.gresource.xml b/data/io.elementary.code.gresource.xml
index ff90ecb696..0167807d9f 100644
--- a/data/io.elementary.code.gresource.xml
+++ b/data/io.elementary.code.gresource.xml
@@ -29,4 +29,7 @@
icons/panel-right-rtl-symbolic.svg
icons/panel-right-symbolic.svg
+
+ icons/filter-symbolic.svg
+
diff --git a/src/SymbolPane/C/CtagsSymbol.vala b/src/SymbolPane/C/CtagsSymbol.vala
index 1b9530f01f..97f0d4228c 100644
--- a/src/SymbolPane/C/CtagsSymbol.vala
+++ b/src/SymbolPane/C/CtagsSymbol.vala
@@ -18,10 +18,25 @@
public class Scratch.Services.CtagsSymbol : Code.Widgets.SourceList.ExpandableItem {
public Scratch.Services.Document doc { get; construct set; }
+ public SymbolType symbol_type { get; set; default = SymbolType.OTHER; }
public int line { get; construct set; }
- public CtagsSymbol (Scratch.Services.Document doc, string name, int line, Icon? _icon) {
- Object (doc: doc, name: name, line: line);
+ public CtagsSymbol (
+ Scratch.Services.Document doc,
+ string name,
+ int line,
+ Icon? _icon,
+ SymbolType? s_type = null) {
+
+ Object (
+ doc: doc,
+ name: name,
+ line: line
+ );
+
icon = _icon;
+ if (s_type != null) {
+ symbol_type = s_type;
+ }
}
}
diff --git a/src/SymbolPane/C/CtagsSymbolIter.vala b/src/SymbolPane/C/CtagsSymbolIter.vala
index 829a9abeac..91960fd24d 100644
--- a/src/SymbolPane/C/CtagsSymbolIter.vala
+++ b/src/SymbolPane/C/CtagsSymbolIter.vala
@@ -21,8 +21,22 @@ public class Scratch.Services.CtagsSymbolIter : Object {
public string parent { get; construct set; }
public int line { get; construct set; }
public Icon? icon { get; construct set; }
+ public SymbolType? symbol_type;
- public CtagsSymbolIter (string name, string parent, int line, Icon? icon) {
- Object (name: name, parent: parent, line: line, icon: icon);
+ public CtagsSymbolIter (
+ string name,
+ string parent,
+ int line,
+ Icon? icon,
+ SymbolType? s_type = SymbolType.OTHER) {
+
+ Object (
+ name: name,
+ parent: parent,
+ line: line,
+ icon: icon
+ );
+
+ symbol_type = s_type;
}
}
diff --git a/src/SymbolPane/C/CtagsSymbolOutline.vala b/src/SymbolPane/C/CtagsSymbolOutline.vala
index 8efb71666f..bcaf86e00a 100644
--- a/src/SymbolPane/C/CtagsSymbolOutline.vala
+++ b/src/SymbolPane/C/CtagsSymbolOutline.vala
@@ -21,10 +21,25 @@ public class Scratch.Services.CtagsSymbolOutline : Scratch.Services.SymbolOutlin
public CtagsSymbolOutline (Scratch.Services.Document _doc) {
Object (
+ orientation: Gtk.Orientation.VERTICAL,
+ hexpand: true,
doc: _doc
);
}
+ static construct {
+ // Array of symbol types that could be assigned to a CtagsSymbol
+ // by parse output ()
+ filters = {
+ SymbolType.CLASS,
+ SymbolType.CONSTRUCTOR,
+ SymbolType.PROPERTY,
+ SymbolType.METHOD,
+ SymbolType.STRUCT,
+ SymbolType.ENUM,
+ SymbolType.CONSTANT
+ };
+ }
construct {
store.item_selected.connect ((selected) => {
doc.goto (((CtagsSymbol)selected).line);
@@ -74,66 +89,92 @@ public class Scratch.Services.CtagsSymbolOutline : Scratch.Services.SymbolOutlin
int line = int.parse (parts[4].offset ("line:".length));
string? parent = null;
GLib.Icon? parent_icon = null;
+ SymbolType? parent_s_type = null;
if (parts.length > 5 && parts[5] != null) {
if ("typeref:" in parts[5]) {
parent = parts[5].offset ("typeref:".length);
} else if ("class:" in parts[5]) {
parent = parts[5].offset ("class:".length);
parent_icon = new ThemedIcon ("lang-class");
+ parent_s_type = SymbolType.CLASS;
} else if ("struct:" in parts[5]) {
parent = parts[5].offset ("struct:".length);
parent_icon = new ThemedIcon ("lang-struct");
+ parent_s_type = SymbolType.STRUCT;
} else if ("enum:" in parts[5]) {
parent = parts[5].offset ("enum:".length);
parent_icon = new ThemedIcon ("lang-enum");
+ parent_s_type = SymbolType.ENUM;
}
}
Icon? icon = null;
+ SymbolType? s_type = null;
switch (type) {
case "class":
icon = new ThemedIcon ("lang-class");
+ s_type = SymbolType.CLASS;
break;
case "struct":
icon = new ThemedIcon ("lang-struct");
+ s_type = SymbolType.STRUCT;
break;
case "field":
case "member":
case "variable":
icon = new ThemedIcon ("lang-property");
+ s_type = SymbolType.PROPERTY;
break;
case "enum":
case "enumerator":
icon = new ThemedIcon ("lang-enum");
+ s_type = SymbolType.ENUM;
break;
case "macro":
case "constant":
case "typedef":
icon = new ThemedIcon ("lang-constant");
+ s_type = SymbolType.CONSTANT;
break;
case "constructor":
icon = new ThemedIcon ("lang-constructor");
+ s_type = SymbolType.CONSTRUCTOR;
break;
case "destructor":
case "method":
case "function":
icon = new ThemedIcon ("lang-method");
+ s_type = SymbolType.METHOD;
break;
case "namespace":
icon = new ThemedIcon ("lang-namespace");
+ s_type = SymbolType.NAMESPACE;
break;
case "package":
break;
case "property":
icon = new ThemedIcon ("lang-property");
+ s_type = SymbolType.PROPERTY;
break;
}
if (parent == null) {
- var s = new CtagsSymbol (doc, name, line, icon);
+ var s = new CtagsSymbol (
+ doc,
+ name,
+ line,
+ icon,
+ s_type
+ );
new_root.add (s);
} else {
- parent_dependent.add (new CtagsSymbolIter (name, parent, line, parent_icon));
+ parent_dependent.add (new CtagsSymbolIter (
+ name,
+ parent,
+ line,
+ parent_icon,
+ parent_s_type
+ ));
}
}
} catch (Error e) {
@@ -152,7 +193,13 @@ public class Scratch.Services.CtagsSymbolOutline : Scratch.Services.SymbolOutlin
var parent = find_existing (i.parent, new_root);
if (parent != null) {
found_something = true;
- parent.add (new CtagsSymbol (doc, i.name, i.line, i.icon));
+ parent.add (new CtagsSymbol (
+ doc,
+ i.name,
+ i.line,
+ i.icon,
+ i.symbol_type
+ ));
iter.remove ();
} else {
if (":" in i.parent) {
@@ -177,10 +224,22 @@ public class Scratch.Services.CtagsSymbolOutline : Scratch.Services.SymbolOutlin
}
// anonymous enum
if (i.parent.has_prefix ("__anon")) {
- var e = new CtagsSymbol (doc, i.parent, i.line - 1, new ThemedIcon ("lang-enum"));
+ var e = new CtagsSymbol (
+ doc,
+ i.parent,
+ i.line - 1,
+ new ThemedIcon ("lang-enum"),
+ SymbolType.ENUM
+ );
new_root.add (e);
- e.add (new CtagsSymbol (doc, i.name, i.line, i.icon));
+ e.add (new CtagsSymbol (
+ doc,
+ i.name,
+ i.line,
+ i.icon,
+ i.symbol_type
+ ));
iter.remove ();
}
}
@@ -189,7 +248,13 @@ public class Scratch.Services.CtagsSymbolOutline : Scratch.Services.SymbolOutlin
// just add the rest
foreach (var symbol in parent_dependent) {
- new_root.add (new CtagsSymbol (doc, symbol.name, symbol.line, symbol.icon));
+ new_root.add (new CtagsSymbol (
+ doc,
+ symbol.name,
+ symbol.line,
+ symbol.icon,
+ symbol.symbol_type
+ ));
}
Idle.add (() => {
diff --git a/src/SymbolPane/SymbolOutline.vala b/src/SymbolPane/SymbolOutline.vala
index 74b2a221e9..a42a79f289 100644
--- a/src/SymbolPane/SymbolOutline.vala
+++ b/src/SymbolPane/SymbolOutline.vala
@@ -16,21 +16,216 @@
* along with this program. If not, see .
*/
-public abstract class Scratch.Services.SymbolOutline : Object {
+public enum Scratch.Services.SymbolType {
+ CLASS,
+ PROPERTY,
+ SIGNAL,
+ METHOD,
+ STRUCT,
+ ENUM,
+ CONSTANT,
+ CONSTRUCTOR,
+ INTERFACE,
+ NAMESPACE,
+ OTHER;
+
+ public unowned string to_string () {
+ switch (this) {
+ case SymbolType.CLASS:
+ return _("Class");
+ case SymbolType.PROPERTY:
+ return _("Property");
+ case SymbolType.SIGNAL:
+ return _("Signal");
+ case SymbolType.METHOD:
+ return _("Method");
+ case SymbolType.STRUCT:
+ return _("Struct");
+ case SymbolType.ENUM:
+ return _("Enum");
+ case SymbolType.CONSTANT:
+ return _("Constant");
+ case SymbolType.CONSTRUCTOR:
+ return _("Constructor");
+ case SymbolType.INTERFACE:
+ return _("Interface");
+ case SymbolType.NAMESPACE:
+ return _("Namespace");
+ case SymbolType.OTHER:
+ return _("Other");
+ default:
+ assert_not_reached ();
+ }
+ }
+}
+
+public interface Scratch.Services.SymbolItem : Code.Widgets.SourceList.ExpandableItem {
+ public abstract SymbolType symbol_type { get; set; default = SymbolType.OTHER;}
+}
+
+public class Scratch.Services.SymbolOutline : Gtk.Box {
+ protected static SymbolType[] filters; //Initialized by derived classes
+ const string ACTION_GROUP = "symbol";
+ const string ACTION_PREFIX = ACTION_GROUP + ".";
+ const string ACTION_SELECT = "action-select";
+ const string ACTION_TOGGLE = "toggle-";
+ SimpleActionGroup symbol_action_group;
+
public Scratch.Services.Document doc { get; construct; }
+ protected Gee.HashMap checks;
+ protected Gtk.SearchEntry search_entry;
protected Code.Widgets.SourceList store;
protected Code.Widgets.SourceList.ExpandableItem root;
protected Gtk.CssProvider source_list_style_provider;
- public Gtk.Widget get_widget () { return store; }
- public abstract void parse_symbols ();
+ public Gtk.Widget get_widget () { return this; }
+ public virtual void parse_symbols () {}
+
+ Gtk.MenuButton filter_button;
construct {
+ symbol_action_group = new SimpleActionGroup ();
+ insert_action_group (ACTION_GROUP, symbol_action_group);
+
+ checks = new Gee.HashMap ();
store = new Code.Widgets.SourceList ();
root = new Code.Widgets.SourceList.ExpandableItem (_("Symbols"));
store.root.add (root);
+ search_entry = new Gtk.SearchEntry () {
+ placeholder_text = _("Find Symbol"),
+ hexpand = true
+ };
+
+ filter_button = new Gtk.MenuButton () {
+ image = new Gtk.Image.from_icon_name (
+ "filter-symbolic",
+ Gtk.IconSize.SMALL_TOOLBAR
+ ),
+ tooltip_text = _("Filter symbol type"),
+ };
+
+ var select_section = new Menu ();
+ var top_model = new Menu ();
+ foreach (var filter in filters) {
+ add_filter_menuitem (top_model, filter);
+ }
+
+ // Derived classes must not add SymbolType.OTHER
+ add_filter_menuitem (top_model, SymbolType.OTHER);
+
+ var select_action = new SimpleAction (
+ ACTION_SELECT,
+ new VariantType ("b")
+ );
+ select_action.activate.connect (action_select_filters);
+ symbol_action_group.add_action (select_action);
+
+ select_section.append (
+ _("Select All"),
+ Action.print_detailed_name (
+ ACTION_PREFIX + ACTION_SELECT, new Variant ("b", true)
+ )
+ );
+ select_section.append (
+ _("Deselect All"),
+ Action.print_detailed_name (
+ ACTION_PREFIX + ACTION_SELECT, new Variant ("b", false)
+ )
+ );
+ top_model.append_section ("", select_section);
+
+ filter_button.menu_model = top_model;
+
+ var tool_box = new Gtk.Box (HORIZONTAL, 3);
+ tool_box.add (search_entry);
+ tool_box.add (filter_button);
+ add (tool_box);
+ add (store);
set_up_css ();
+ show_all ();
+
+ realize.connect (() => {
+ store.set_filter_func (filter_func, false);
+ search_entry.changed.connect (schedule_refilter);
+ });
+ }
+
+ private void add_filter_menuitem (Menu menu, SymbolType filter) {
+ var filter_action = new SimpleAction.stateful (
+ ACTION_TOGGLE + ((uint)filter).to_string (),
+ null,
+ new Variant.boolean (true)
+ );
+
+ checks[filter] = filter_action;
+ filter_action.activate.connect (action_toggle_filter);
+ symbol_action_group.add_action (filter_action);
+
+ var filter_item = new MenuItem (
+ filter.to_string (),
+ ACTION_PREFIX + filter_action.get_name ()
+ );
+
+ menu.append_item (filter_item);
+ }
+
+ protected bool filter_func (Object item) {
+ if (!(item is SymbolItem)) {
+ return true;
+ }
+
+ var symbol_type = ((SymbolItem)item).symbol_type;
+ if (symbol_type == SymbolType.NAMESPACE) {
+ return true;
+ }
+
+ if (checks[symbol_type] == null) {
+ symbol_type = SymbolType.OTHER;
+ }
+
+ var filter_action = checks[symbol_type];
+
+ if (!filter_action.get_state ().get_boolean ()) {
+ return false;
+ }
+
+ // Do not exclude text search misses on Item with children as may
+ // hide hits on its children
+ if (item is Code.Widgets.SourceList.ExpandableItem) {
+ var expandable = (Code.Widgets.SourceList.ExpandableItem)item;
+ if (expandable.n_children > 0) {
+ return true;
+ }
+
+ return ((SymbolItem)item).name.contains (search_entry.text);
+ }
+
+ return true;
+ }
+
+ uint refilter_timeout_id = 0;
+ bool delay_refilter = false;
+ protected void schedule_refilter () {
+ // Ensure a refilter happens at least 500mS later if not already
+ // delayed.
+ if (refilter_timeout_id > 0) {
+ delay_refilter = true;
+ return;
+ }
+
+ refilter_timeout_id = Timeout.add (500, () => {
+ if (delay_refilter) {
+ delay_refilter = false;
+ return Source.CONTINUE;
+ } else {
+ refilter_timeout_id = 0;
+ store.refilter ();
+ // Ensure new visible items shown when filter removed
+ store.root.expand_all (true, true);
+ return Source.REMOVE;
+ }
+ });
}
protected void set_up_css () {
@@ -41,8 +236,7 @@ public abstract class Scratch.Services.SymbolOutline : Object {
Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION
);
// Add a class to distinguish from foldermanager sourcelist
- store.get_style_context ().add_class ("symbol-outline");
-
+ get_style_context ().add_class ("symbol-outline");
update_style_scheme (((Gtk.SourceBuffer)(doc.source_view.buffer)).style_scheme);
doc.source_view.style_changed.connect (update_style_scheme);
}
@@ -56,7 +250,7 @@ public abstract class Scratch.Services.SymbolOutline : Object {
color = text_color_data.background;
}
- var define = ".symbol-outline .sidebar {background-color: %s;}".printf (color);
+ var define = ".symbol-outline {background-color: %s;}".printf (color);
try {
source_list_style_provider.load_from_data (define);
@@ -64,4 +258,22 @@ public abstract class Scratch.Services.SymbolOutline : Object {
critical ("Unable to sourcelist styling, going back to classic styling");
}
}
+
+ private void action_select_filters (SimpleAction action, Variant? param) {
+ foreach (var filter_action in checks.values) {
+ filter_action.set_state (new Variant ("b", param.get_boolean ()));
+ }
+ schedule_refilter ();
+ // Keep menu open
+ Idle.add (() => {
+ filter_button.set_active (true);
+ return Source.REMOVE;
+ });
+ }
+
+ private void action_toggle_filter (SimpleAction action, Variant? param) {
+ var state = action.get_state ().get_boolean ();
+ action.set_state (new Variant ("b", !state));
+ schedule_refilter ();
+ }
}
diff --git a/src/SymbolPane/Vala/ValaSymbolItem.vala b/src/SymbolPane/Vala/ValaSymbolItem.vala
index f3d407c762..96b5613a7f 100644
--- a/src/SymbolPane/Vala/ValaSymbolItem.vala
+++ b/src/SymbolPane/Vala/ValaSymbolItem.vala
@@ -16,9 +16,9 @@
*
*/
-public class Scratch.Services.ValaSymbolItem : Code.Widgets.SourceList.ExpandableItem, Code.Widgets.SourceListSortable {
+public class Scratch.Services.ValaSymbolItem : Code.Widgets.SourceList.ExpandableItem, Code.Widgets.SourceListSortable, Scratch.Services.SymbolItem {
public Vala.Symbol symbol { get; set; }
-
+ public SymbolType symbol_type { get; set; default = SymbolType.OTHER; }
public ValaSymbolItem (Vala.Symbol symbol) {
this.symbol = symbol;
this.name = symbol.name;
diff --git a/src/SymbolPane/Vala/ValaSymbolOutline.vala b/src/SymbolPane/Vala/ValaSymbolOutline.vala
index c2f36f2ee9..f33520a509 100644
--- a/src/SymbolPane/Vala/ValaSymbolOutline.vala
+++ b/src/SymbolPane/Vala/ValaSymbolOutline.vala
@@ -22,10 +22,27 @@ public class Scratch.Services.ValaSymbolOutline : Scratch.Services.SymbolOutline
private GLib.Cancellable cancellable;
public ValaSymbolOutline (Scratch.Services.Document _doc) {
Object (
+ orientation: Gtk.Orientation.VERTICAL,
+ hexpand: true,
doc: _doc
);
}
+ static construct {
+ // Array of symbol types that could be assigned to a ValaSymbolItem
+ // by construct_child output ()
+ filters = {
+ SymbolType.CLASS,
+ SymbolType.CONSTRUCTOR,
+ SymbolType.PROPERTY,
+ SymbolType.METHOD,
+ SymbolType.STRUCT,
+ SymbolType.ENUM,
+ SymbolType.CONSTANT,
+ SymbolType.INTERFACE
+ };
+ }
+
construct {
parser = new Vala.Parser ();
resolver = new Code.Plugins.ValaSymbolResolver ();
@@ -144,20 +161,27 @@ public class Scratch.Services.ValaSymbolOutline : Scratch.Services.SymbolOutline
var tree_child = new ValaSymbolItem (symbol);
if (symbol is Vala.Struct) {
tree_child.icon = new ThemedIcon ("lang-struct");
+ tree_child.symbol_type = SymbolType.STRUCT;
} else if (symbol is Vala.Class) {
if (((Vala.Class) symbol).is_abstract) {
tree_child.icon = new ThemedIcon ("lang-class-abstract");
} else {
tree_child.icon = new ThemedIcon ("lang-class");
}
+
+ tree_child.symbol_type = SymbolType.CLASS;
} else if (symbol is Vala.Constant) {
tree_child.icon = new ThemedIcon ("lang-constant");
+ tree_child.symbol_type = SymbolType.CONSTANT;
} else if (symbol is Vala.Enum) {
tree_child.icon = new ThemedIcon ("lang-enum");
+ tree_child.symbol_type = SymbolType.ENUM;
} else if (symbol is Vala.Field) {
tree_child.icon = new ThemedIcon ("lang-property");
+ tree_child.symbol_type = SymbolType.PROPERTY;
} else if (symbol is Vala.Interface) {
tree_child.icon = new ThemedIcon ("lang-interface");
+ tree_child.symbol_type = SymbolType.INTERFACE;
} else if (symbol is Vala.Property) {
if (((Vala.Property) symbol).is_abstract) {
tree_child.icon = new ThemedIcon ("lang-property-abstract");
@@ -166,10 +190,14 @@ public class Scratch.Services.ValaSymbolOutline : Scratch.Services.SymbolOutline
} else {
tree_child.icon = new ThemedIcon ("lang-property");
}
+
+ tree_child.symbol_type = SymbolType.PROPERTY;
} else if (symbol is Vala.Signal) {
tree_child.icon = new ThemedIcon ("lang-signal");
+ tree_child.symbol_type = SymbolType.SIGNAL;
} else if (symbol is Vala.CreationMethod) {
tree_child.icon = new ThemedIcon ("lang-constructor");
+ tree_child.symbol_type = SymbolType.CONSTRUCTOR;
} else if (symbol is Vala.Method) {
if (((Vala.Method) symbol).is_abstract) {
tree_child.icon = new ThemedIcon ("lang-method-abstract");
@@ -180,8 +208,11 @@ public class Scratch.Services.ValaSymbolOutline : Scratch.Services.SymbolOutline
} else {
tree_child.icon = new ThemedIcon ("lang-method");
}
+
+ tree_child.symbol_type = SymbolType.METHOD;
} else if (symbol is Vala.Namespace) {
tree_child.icon = new ThemedIcon ("lang-namespace");
+ tree_child.symbol_type = SymbolType.NAMESPACE;
} else if (symbol is Vala.ErrorDomain) {
tree_child.icon = new ThemedIcon ("lang-errordomain");
} else if (symbol is Vala.Delegate) {