-
-
Notifications
You must be signed in to change notification settings - Fork 10
Flushed indentation for wrapped text on bullet point lists #124
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
Open
MysterieCatDev
wants to merge
13
commits into
elly-code:main
Choose a base branch
from
MysterieCatDev:list-wrapped-indentation
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
13 commits
Select commit
Hold shift + click to select a range
6494f61
feat: add flushed indentation to wrapped bullet list text
MysterieCatDev 9cd6c18
chore: remove debug print statement
MysterieCatDev e99072d
docs: add Fedora dev setup instructions
MysterieCatDev 4f4e98a
Merge branch 'main' into list-wrapped-indentation
MysterieCatDev 520e56b
fix: recalculate indentation on zoom level change
MysterieCatDev a7b8879
Merge branch 'main' into list-wrapped-indentation
teamcons 57716a1
Merge branch 'main' into list-wrapped-indentation
teamcons 4a0e344
fix: scan for, and reapply, indentations on load
MysterieCatDev 44ac1e2
feat: support changing list prefix during runtime
MysterieCatDev 682da9f
chore: updated list item start string
MysterieCatDev 10033b7
fix: observe buffer for undo/redo
MysterieCatDev 35688a6
fix: reduce sub-pixel rounding for hanging indents
MysterieCatDev 298c9bd
Merge branch 'main' into list-wrapped-indentation
teamcons 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 hidden or 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
This file contains hidden or 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
This file contains hidden or 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
This file contains hidden or 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 | ||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -12,7 +12,22 @@ | |||||||||||
| public class Jorts.TextView : Granite.HyperTextView { | ||||||||||||
|
|
||||||||||||
| private Gtk.EventControllerKey keyboard; | ||||||||||||
| public string list_item_start {get; set;} | ||||||||||||
| private Gtk.TextBuffer? observed_buffer; | ||||||||||||
| private ulong buffer_changed_handler_id = 0; | ||||||||||||
| private bool list_item_restore_queued = false; | ||||||||||||
| private string _list_item_start = ""; | ||||||||||||
| public string list_item_start { | ||||||||||||
| get { return _list_item_start; } | ||||||||||||
| set { | ||||||||||||
| if (_list_item_start == value) { | ||||||||||||
| return; | ||||||||||||
| } | ||||||||||||
|
|
||||||||||||
| var old_prefix = _list_item_start; | ||||||||||||
| _list_item_start = value; | ||||||||||||
| migrate_list_prefixes (old_prefix, value); | ||||||||||||
|
Comment on lines
+26
to
+28
Collaborator
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.
Suggested change
Id rather set the sauce to the new sauce after we migrated everything |
||||||||||||
| } | ||||||||||||
| } | ||||||||||||
| public bool on_list_item {public get; private set;} | ||||||||||||
|
|
||||||||||||
| public string text { | ||||||||||||
|
|
@@ -75,12 +90,118 @@ public class Jorts.TextView : Granite.HyperTextView { | |||||||||||
| /* CONNECTS AND BINDS */ | ||||||||||||
| /***************************************************/ | ||||||||||||
|
|
||||||||||||
| notify["buffer"].connect (() => { | ||||||||||||
| attach_buffer_observers (); | ||||||||||||
| queue_restore_list_item_indentation (); | ||||||||||||
| }); | ||||||||||||
| attach_buffer_observers (); | ||||||||||||
|
|
||||||||||||
| Application.gsettings.bind (KEY_LIST, | ||||||||||||
| this, "list-item-start", | ||||||||||||
| GLib.SettingsBindFlags.DEFAULT); | ||||||||||||
| } | ||||||||||||
|
|
||||||||||||
| private void attach_buffer_observers () { | ||||||||||||
| if (observed_buffer == buffer) { | ||||||||||||
| return; | ||||||||||||
| } | ||||||||||||
|
|
||||||||||||
| detach_buffer_observers (); | ||||||||||||
| observed_buffer = buffer; | ||||||||||||
|
|
||||||||||||
| buffer_changed_handler_id = observed_buffer.changed.connect_after (queue_restore_list_item_indentation); | ||||||||||||
| } | ||||||||||||
|
|
||||||||||||
| private void detach_buffer_observers () { | ||||||||||||
| if (observed_buffer == null) { | ||||||||||||
| return; | ||||||||||||
| } | ||||||||||||
|
|
||||||||||||
| if (buffer_changed_handler_id != 0) { | ||||||||||||
| SignalHandler.disconnect (observed_buffer, buffer_changed_handler_id); | ||||||||||||
| buffer_changed_handler_id = 0; | ||||||||||||
| } | ||||||||||||
|
|
||||||||||||
| observed_buffer = null; | ||||||||||||
| } | ||||||||||||
|
|
||||||||||||
| private void queue_restore_list_item_indentation () { | ||||||||||||
| if (list_item_restore_queued) { | ||||||||||||
| return; | ||||||||||||
| } | ||||||||||||
|
|
||||||||||||
| list_item_restore_queued = true; | ||||||||||||
|
|
||||||||||||
| Idle.add (() => { | ||||||||||||
| list_item_restore_queued = false; | ||||||||||||
| restore_list_item_indentation (); | ||||||||||||
| return false; | ||||||||||||
| }); | ||||||||||||
| } | ||||||||||||
|
|
||||||||||||
| private void ensure_tags () { | ||||||||||||
| if (list_item_start == "") { | ||||||||||||
| return; | ||||||||||||
| } | ||||||||||||
|
|
||||||||||||
| var measured_prefix = list_item_start.strip (); | ||||||||||||
|
|
||||||||||||
| if (measured_prefix == "") { | ||||||||||||
| measured_prefix = list_item_start; | ||||||||||||
| } | ||||||||||||
|
|
||||||||||||
| var layout = this.create_pango_layout (measured_prefix); | ||||||||||||
| int width, height; | ||||||||||||
| layout.get_pixel_size (out width, out height); | ||||||||||||
|
|
||||||||||||
| var indent_width = int.max (width + 4, 8); | ||||||||||||
|
|
||||||||||||
| var list_item_tag = buffer.tag_table.lookup ("list_item"); | ||||||||||||
|
|
||||||||||||
| if (list_item_tag == null) { | ||||||||||||
| buffer.create_tag ("list_item", | ||||||||||||
| "indent", -indent_width, | ||||||||||||
| "left-margin", SPACING_DOUBLE + indent_width | ||||||||||||
| ); | ||||||||||||
| return; | ||||||||||||
| } | ||||||||||||
|
|
||||||||||||
| list_item_tag.indent = -indent_width; | ||||||||||||
| list_item_tag.left_margin = SPACING_DOUBLE + indent_width; | ||||||||||||
| } | ||||||||||||
|
|
||||||||||||
| public void refresh_list_item_indentation () { | ||||||||||||
| ensure_tags (); | ||||||||||||
| } | ||||||||||||
|
|
||||||||||||
| public void restore_list_item_indentation () { | ||||||||||||
| Gtk.TextIter start, end; | ||||||||||||
| buffer.get_bounds (out start, out end); | ||||||||||||
| buffer.remove_tag_by_name ("list_item", start, end); | ||||||||||||
|
|
||||||||||||
| if (list_item_start == "") { | ||||||||||||
| return; | ||||||||||||
| } | ||||||||||||
|
|
||||||||||||
| ensure_tags (); | ||||||||||||
|
|
||||||||||||
| var line_count = buffer.get_line_count (); | ||||||||||||
|
|
||||||||||||
| for (int line_number = 0; line_number < line_count; line_number++) { | ||||||||||||
| if (!this.has_prefix (line_number)) { | ||||||||||||
| continue; | ||||||||||||
| } | ||||||||||||
|
|
||||||||||||
| Gtk.TextIter line_start, line_end; | ||||||||||||
| buffer.get_iter_at_line_offset (out line_start, line_number, 0); | ||||||||||||
| line_end = line_start.copy (); | ||||||||||||
| line_end.forward_to_line_end (); | ||||||||||||
| buffer.apply_tag_by_name ("list_item", line_start, line_end); | ||||||||||||
| } | ||||||||||||
| } | ||||||||||||
|
|
||||||||||||
| public void toggle_list () { | ||||||||||||
| ensure_tags (); | ||||||||||||
| Gtk.TextIter start, end; | ||||||||||||
| buffer.get_selection_bounds (out start, out end); | ||||||||||||
|
|
||||||||||||
|
|
@@ -105,8 +226,8 @@ public class Jorts.TextView : Granite.HyperTextView { | |||||||||||
| /** | ||||||||||||
| * Add the list prefix only to lines who hasnt it already | ||||||||||||
| */ | ||||||||||||
| private bool has_prefix (int line_number) { | ||||||||||||
| if (list_item_start == "") {return false;} | ||||||||||||
| private bool has_specific_prefix (int line_number, string prefix) { | ||||||||||||
| if (prefix == "") {return false;} | ||||||||||||
|
|
||||||||||||
| Gtk.TextIter start, end; | ||||||||||||
| buffer.get_iter_at_line_offset (out start, line_number, 0); | ||||||||||||
|
|
@@ -116,7 +237,54 @@ public class Jorts.TextView : Granite.HyperTextView { | |||||||||||
|
|
||||||||||||
| var text_in_line = buffer.get_slice (start, end, false); | ||||||||||||
|
|
||||||||||||
| return text_in_line.has_prefix (list_item_start); | ||||||||||||
| return text_in_line.has_prefix (prefix); | ||||||||||||
| } | ||||||||||||
|
|
||||||||||||
| private bool has_prefix (int line_number) { | ||||||||||||
| return has_specific_prefix (line_number, list_item_start); | ||||||||||||
| } | ||||||||||||
|
|
||||||||||||
| private void replace_prefix (int line_number, string old_prefix, string new_prefix) { | ||||||||||||
| Gtk.TextIter line_start, prefix_end; | ||||||||||||
|
|
||||||||||||
| buffer.get_iter_at_line_offset (out line_start, line_number, 0); | ||||||||||||
| buffer.get_iter_at_line_offset (out prefix_end, line_number, old_prefix.char_count ()); | ||||||||||||
| buffer.delete (ref line_start, ref prefix_end); | ||||||||||||
|
|
||||||||||||
| buffer.get_iter_at_line_offset (out line_start, line_number, 0); | ||||||||||||
| buffer.insert (ref line_start, new_prefix, -1); | ||||||||||||
| } | ||||||||||||
|
|
||||||||||||
| private void migrate_list_prefixes (string old_prefix, string new_prefix) { | ||||||||||||
| if (old_prefix == "") { | ||||||||||||
| if (new_prefix == "") { | ||||||||||||
| Gtk.TextIter start, end; | ||||||||||||
| buffer.get_bounds (out start, out end); | ||||||||||||
| buffer.remove_tag_by_name ("list_item", start, end); | ||||||||||||
| } | ||||||||||||
|
|
||||||||||||
| return; | ||||||||||||
| } | ||||||||||||
|
|
||||||||||||
| var line_count = buffer.get_line_count (); | ||||||||||||
| var did_change = false; | ||||||||||||
|
|
||||||||||||
| buffer.begin_user_action (); | ||||||||||||
|
|
||||||||||||
| for (int line_number = 0; line_number < line_count; line_number++) { | ||||||||||||
| if (!has_specific_prefix (line_number, old_prefix)) { | ||||||||||||
| continue; | ||||||||||||
| } | ||||||||||||
|
|
||||||||||||
| replace_prefix (line_number, old_prefix, new_prefix); | ||||||||||||
| did_change = true; | ||||||||||||
| } | ||||||||||||
|
|
||||||||||||
| buffer.end_user_action (); | ||||||||||||
|
|
||||||||||||
| if (did_change || new_prefix == "") { | ||||||||||||
| restore_list_item_indentation (); | ||||||||||||
| } | ||||||||||||
| } | ||||||||||||
|
|
||||||||||||
| /** | ||||||||||||
|
|
@@ -147,6 +315,13 @@ public class Jorts.TextView : Granite.HyperTextView { | |||||||||||
| buffer.get_iter_at_line_offset (out line_start, line_number, 0); | ||||||||||||
| buffer.insert (ref line_start, list_item_start, -1); | ||||||||||||
| } | ||||||||||||
|
|
||||||||||||
| // Apply hanging indent tag to the line | ||||||||||||
| Gtk.TextIter ls, le; | ||||||||||||
| buffer.get_iter_at_line_offset (out ls, line_number, 0); | ||||||||||||
| le = ls.copy (); | ||||||||||||
| le.forward_to_line_end (); | ||||||||||||
| buffer.apply_tag_by_name ("list_item", ls, le); | ||||||||||||
| } | ||||||||||||
| } | ||||||||||||
|
|
||||||||||||
|
|
@@ -163,24 +338,30 @@ public class Jorts.TextView : Granite.HyperTextView { | |||||||||||
| * Remove list prefix from line x to line y. Presuppose it is there | ||||||||||||
| */ | ||||||||||||
| private void remove_prefix (int line_number) { | ||||||||||||
| Gtk.TextIter line_start, prefix_end; | ||||||||||||
| Gtk.TextIter line_start, prefix_end, line_end; | ||||||||||||
| var remove_range = list_item_start.char_count (); | ||||||||||||
|
|
||||||||||||
| debug ("doing line " + line_number.to_string ()); | ||||||||||||
| buffer.get_iter_at_line_offset (out line_start, line_number, 0); | ||||||||||||
| buffer.get_iter_at_line_offset (out prefix_end, line_number, remove_range); | ||||||||||||
| buffer.delete (ref line_start, ref prefix_end); | ||||||||||||
|
|
||||||||||||
| // Remove hanging indent tag from the line | ||||||||||||
| buffer.get_iter_at_line_offset (out line_start, line_number, 0); | ||||||||||||
| line_end = line_start.copy (); | ||||||||||||
| line_end.forward_to_line_end (); | ||||||||||||
| buffer.remove_tag_by_name ("list_item", line_start, line_end); | ||||||||||||
| } | ||||||||||||
|
|
||||||||||||
| /** | ||||||||||||
| * Handler whenever a key is pressed, to see if user needs something and get ahead | ||||||||||||
| * Some local stuff is deduplicated in the Ifs, because i do not like the idea of getting computation done not needed 98% of the time | ||||||||||||
| */ | ||||||||||||
| private bool on_key_pressed (uint keyval, uint keycode, Gdk.ModifierType state) { | ||||||||||||
| ensure_tags (); | ||||||||||||
|
|
||||||||||||
| // If backspace on a prefix: Delete the prefix. | ||||||||||||
| if (keyval == Gdk.Key.BackSpace) { | ||||||||||||
| print ("backspace"); | ||||||||||||
|
|
||||||||||||
| Gtk.TextIter start, end; | ||||||||||||
| buffer.get_selection_bounds (out start, out end); | ||||||||||||
|
|
@@ -197,6 +378,13 @@ public class Jorts.TextView : Granite.HyperTextView { | |||||||||||
| buffer.begin_user_action (); | ||||||||||||
| buffer.delete (ref start, ref end); | ||||||||||||
| buffer.insert_at_cursor ("\n", -1); | ||||||||||||
|
|
||||||||||||
| // The line is now an empty normal line, so remove the hanging indent | ||||||||||||
| buffer.get_iter_at_line_offset (out start, line_number, 0); | ||||||||||||
| end = start.copy (); | ||||||||||||
| end.forward_to_line_end (); | ||||||||||||
| buffer.remove_tag_by_name ("list_item", start, end); | ||||||||||||
|
|
||||||||||||
| buffer.end_user_action (); | ||||||||||||
| } | ||||||||||||
| } | ||||||||||||
|
|
@@ -212,6 +400,13 @@ public class Jorts.TextView : Granite.HyperTextView { | |||||||||||
|
|
||||||||||||
| buffer.begin_user_action (); | ||||||||||||
| buffer.insert_at_cursor ("\n" + list_item_start, -1); | ||||||||||||
|
|
||||||||||||
| // Ensure new line has tag applied since it was just inserted | ||||||||||||
| buffer.get_iter_at_line_offset (out start, line_number + 1, 0); | ||||||||||||
| end = start.copy (); | ||||||||||||
| end.forward_to_line_end (); | ||||||||||||
| buffer.apply_tag_by_name ("list_item", start, end); | ||||||||||||
|
|
||||||||||||
| buffer.end_user_action (); | ||||||||||||
|
|
||||||||||||
| return true; | ||||||||||||
|
|
||||||||||||
This file contains hidden or 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
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.
i struggle with the whole observed_buffer thing. It feels very wrong, we end up with twice the amount of text per sticky note in memory (not even counting the gtk cruft), and compare them constantly at every change
i really need to sit down and work on this