diff --git a/src/core/widgets/code/command_system/commands/line_down.py b/src/core/widgets/code/command_system/commands/line_down.py index e976f1a..8053d4b 100644 --- a/src/core/widgets/code/command_system/commands/line_down.py +++ b/src/core/widgets/code/command_system/commands/line_down.py @@ -8,6 +8,7 @@ gi.require_version('GtkSource', '4') from gi.repository import GtkSource # Application imports +from libs.dto.states import SourceViewStates @@ -17,4 +18,6 @@ def execute( **kwargs ): logger.debug("Command: Line Down") + if not view.state == SourceViewStates.INSERT: return + view.emit("move-lines", True) diff --git a/src/core/widgets/code/command_system/commands/line_up.py b/src/core/widgets/code/command_system/commands/line_up.py index bc62c78..da27d27 100644 --- a/src/core/widgets/code/command_system/commands/line_up.py +++ b/src/core/widgets/code/command_system/commands/line_up.py @@ -8,6 +8,7 @@ gi.require_version('GtkSource', '4') from gi.repository import GtkSource # Application imports +from libs.dto.states import SourceViewStates @@ -17,4 +18,6 @@ def execute( **kwargs ): logger.debug("Command: Line Up") + if not view.state == SourceViewStates.INSERT: return + view.emit("move-lines", False) diff --git a/src/core/widgets/code/controllers/views/marker_manager.py b/src/core/widgets/code/controllers/views/marker_manager.py index a11cac3..3b81b68 100644 --- a/src/core/widgets/code/controllers/views/marker_manager.py +++ b/src/core/widgets/code/controllers/views/marker_manager.py @@ -56,10 +56,28 @@ class MarkerManager(MarkSupportMixin): continue if has_selection: + caret_itr = buffer.get_iter_at_mark(end_mark) + start_itr = buffer.get_iter_at_mark(start_mark) + is_left_edge = caret_itr.compare(start_itr) <= 0 + is_right_edge = not is_left_edge + can_move = ( + (is_forward and is_right_edge) or + (not is_forward and is_left_edge) + ) + self.collapse_selection(buffer, mark_hash, start_mark, end_mark, is_forward) + if mode == "word": + if not can_move: continue + + itr = caret_itr + self._move_iter(buffer, itr, mode, is_forward) + buffer.move_mark(start_mark, itr) + buffer.move_mark(end_mark, itr) + continue - # No selection — move both anchor and caret together + + # No selection - move both anchor and caret together self._move_iter(buffer, end_itr, mode, is_forward) buffer.move_mark(start_mark, end_itr) @@ -81,17 +99,50 @@ class MarkerManager(MarkSupportMixin): left = end_itr right = start_itr - # If moving forward → collapse to right edge + # If moving forward -> collapse to right edge collapse_itr = right if is_forward else left buffer.move_mark(start_mark, collapse_itr) buffer.move_mark(end_mark, collapse_itr) + def move_word_snake_case(self, itr: Gtk.TextIter, count: int): + def is_word(ch): + return ch and (ch.isalnum() or ch == "_") + + def step(fwd): + return itr.forward_cursor_position() if fwd else itr.backward_cursor_position() + + def peek(fwd): + if fwd: return itr.get_char() + tmp = itr.copy() + return tmp.backward_cursor_position() and tmp.get_char() + + def walk(fwd, cond): + while True: + ch = peek(fwd) + if not cond(ch): break + if not step(fwd): return False + + return True + + fwd = count > 0 + + for _ in range(abs(count)): + ch = itr.get_char() if fwd else peek(False) + + if is_word(ch): + # inside word + if not walk(fwd, is_word): return + else: + # in separators -> skip them, then the word + if not walk(fwd, lambda c: not is_word(c)): return + if not walk(fwd, is_word): return + def _move_iter(self, buffer, itr_, mode: str, is_forward: bool): if mode == "char": itr_.forward_char() if is_forward else itr_.backward_char() elif mode == "word": - itr_.forward_word_end() if is_forward else itr_.backward_word_start() + self.move_word_snake_case(itr_, 1 if is_forward else -1) elif mode == "line": line = itr_.get_line() offset = itr_.get_line_offset() diff --git a/src/core/widgets/code/controllers/views/signal_mapper.py b/src/core/widgets/code/controllers/views/signal_mapper.py index da702a4..5a30405 100644 --- a/src/core/widgets/code/controllers/views/signal_mapper.py +++ b/src/core/widgets/code/controllers/views/signal_mapper.py @@ -57,6 +57,7 @@ class SourceViewSignalMapper: "key-release-event": self._key_release_event, "button-press-event": self._button_press_event, "button-release-event": self._button_release_event, + "scroll-event": self._scroll_event, "populate-popup": self._populate_popup } @@ -81,5 +82,8 @@ class SourceViewSignalMapper: def _button_release_event(self, source_view: SourceView, eve): return self.state_manager.handle_button_release_event(source_view, eve) - def _populate_popup(self, source_view, menu): + def _scroll_event(self, source_view: SourceView, eve): + return self.state_manager.handle_scroll_event(source_view, eve) + + def _populate_popup(self, source_view: SourceView, menu): return self.state_manager.handle_populate_popup(source_view, menu, self.emit) diff --git a/src/core/widgets/code/controllers/views/state_manager.py b/src/core/widgets/code/controllers/views/state_manager.py index 104428a..6087cca 100644 --- a/src/core/widgets/code/controllers/views/state_manager.py +++ b/src/core/widgets/code/controllers/views/state_manager.py @@ -53,8 +53,15 @@ class SourceViewStateManager: def handle_button_release_event(self, source_view, eve): return self.states[source_view.state].button_release_event(source_view, eve) + def handle_scroll_event(self, source_view, eve): + return self.states[source_view.state].scroll_event( + source_view, eve, self.key_mapper + ) + def handle_populate_popup(self, source_view, menu, emit): - return self.states[source_view.state].populate_popup(source_view, menu, emit) + return self.states[source_view.state].populate_popup( + source_view, menu, emit + ) def _handle_multi_insert_toggle(self, source_view, eve): is_control = self.key_mapper.is_control(eve) diff --git a/src/core/widgets/code/controllers/views/states/source_view_base_state.py b/src/core/widgets/code/controllers/views/states/source_view_base_state.py index 6b7a77d..4d877e7 100644 --- a/src/core/widgets/code/controllers/views/states/source_view_base_state.py +++ b/src/core/widgets/code/controllers/views/states/source_view_base_state.py @@ -1,6 +1,9 @@ # Python imports # Lib imports +import gi +gi.require_version('Gdk', '3.0') +from gi.repository import Gdk # Application imports from libs.event_factory import Event_Factory, Code_Event_Types @@ -79,6 +82,29 @@ class SourceViewsBaseState: return True if not response else response + def scroll_event(self, source_view, eve, key_mapper): + is_control = key_mapper.is_control(eve) + + if not is_control: return + + if eve.direction == Gdk.ScrollDirection.SMOOTH: + has_deltas, dx, dy = eve.get_scroll_deltas() + if not has_deltas: return False + + if dy < 0: + source_view.command.exec("zoom_in") + elif dy > 0: + source_view.command.exec("zoom_out") + + return True + + if eve.direction == Gdk.ScrollDirection.UP: + source_view.command.exec("zoom_in") + elif eve.direction == Gdk.ScrollDirection.DOWN: + source_view.command.exec("zoom_out") + + return True + def populate_popup(self, source_view, menu, emit): buffer = source_view.get_buffer() event = Event_Factory.create_event( diff --git a/src/core/widgets/code/controllers/views/states/source_view_multi_insert_state.py b/src/core/widgets/code/controllers/views/states/source_view_multi_insert_state.py index 5460b38..94673a6 100644 --- a/src/core/widgets/code/controllers/views/states/source_view_multi_insert_state.py +++ b/src/core/widgets/code/controllers/views/states/source_view_multi_insert_state.py @@ -61,7 +61,6 @@ class SourceViewsMultiInsertState(SourceViewsBaseState): self.marker_manager.apply_to_marks(buffer, replace_word) return True - def move_cursor(self, source_view, step, count, is_selection, emit): is_forward = count > 0 buffer = source_view.get_buffer() @@ -78,6 +77,8 @@ class SourceViewsMultiInsertState(SourceViewsBaseState): self._signal_cursor_moved(source_view, emit) + return False + def key_press_event(self, source_view, event, key_mapper): char = key_mapper.get_raw_keyname(event).upper() self.is_control = key_mapper.is_control(event) diff --git a/src/core/widgets/code/mixins/mark_support_mixin.py b/src/core/widgets/code/mixins/mark_support_mixin.py index b6f9b50..430e013 100644 --- a/src/core/widgets/code/mixins/mark_support_mixin.py +++ b/src/core/widgets/code/mixins/mark_support_mixin.py @@ -106,7 +106,6 @@ class MarkSupportMixin: name = f"multi-insert-end-{hash}", left_gravity = False ) -# left_gravity = True buffer.add_mark(start_mark, target_iter) buffer.add_mark(end_mark, target_iter)