Skip to content

Commit

Permalink
improve ctrl-u performance, i-search feedback is better, fixed subtle…
Browse files Browse the repository at this point in the history
… issues with selection
  • Loading branch information
canoeberry committed Sep 25, 2020
1 parent 32a33b9 commit 309ab2d
Show file tree
Hide file tree
Showing 5 changed files with 151 additions and 58 deletions.
112 changes: 71 additions & 41 deletions jove.py
Expand Up @@ -25,12 +25,12 @@ def __init__(self, *args, **kwargs):
def on_close(self, view):
ViewState.on_view_closed(view)

def on_modified(self, view):
CmdUtil(view).toggle_active_mark_mode(False)

def on_activated(self, view):
update_pinned_status(view)

def on_deactivated(self, view):
self.disable_empty_active_mark(view)

def on_activated_async(self, view):
info = isearch.info_for(view)
if info and not view.settings().get("is_widget"):
Expand Down Expand Up @@ -72,6 +72,25 @@ def doit():
dedup_views(sublime.active_window())
sublime.set_timeout(doit, 50)

#
# Turn off active mark mode in all the views related to this view.
#
# REMIND: Sadly this is called N times for the N views that are related to the specified view,
# and then we iterator through all N views. So this is N-squared sadness, for usually 2 or fewer
# views ...
#
def on_modified(self, view):
self.disable_empty_active_mark(view, False)

def disable_empty_active_mark(self, view, must_be_empty = True):
for related_view in ViewState.most_recent_related_view(view):
util = CmdUtil(related_view)
selection = related_view.sel()
regions = list(selection)
if not must_be_empty or util.all_empty_regions(regions):
util.toggle_active_mark_mode(False)
ViewState.get(related_view).this_cmd = None

#
# CmdWatcher watches all the commands and tries to correctly process the following situations:
#
Expand All @@ -83,7 +102,6 @@ def doit():
class CmdWatcher(sublime_plugin.EventListener):
def __init__(self, *args, **kwargs):
super(CmdWatcher, self).__init__(*args, **kwargs)
self.pinned_text = None

def on_post_window_command(self, window, cmd, args):
# update_pinned_status(window.active_view())
Expand All @@ -96,7 +114,7 @@ def on_post_window_command(self, window, cmd, args):
info.done()

#
# Override some commands to execute them N times if the numberic argument is supplied.
# Override some commands to execute them N times if the numeric argument is supplied.
#
def on_text_command(self, view, cmd, args):
# escape the current isearch if one is in progress, unless the command is already related to
Expand All @@ -121,16 +139,24 @@ def on_text_command(self, view, cmd, args):
# region.
#
if cmd == 'drag_select':
# NOTE: This is called only when you click, NOT when you drag. So if you triple click
# it's called three times.

# NOTE: remember the view that performed the drag_select because of the
# on_selection_modified bug of using the wrong view if the same view is displayed more
# than once
self.drag_select_view = view

# cancel isearch if necessary
info = isearch.info_for(view)
if info:
info.done()

# Set drag_count to 0 when drag_select command occurs. BUT, if the 'by' parameter is
# present, that means a double or triple click occurred. When that happens we have a
# selection we want to start using, so we set drag_count to 2. 2 is the number of
# drag_counts we need in the normal course of events before we turn on the active mark
# mode.
vs.drag_count = 2 if 'by' in args else 0
# Set drag_count to 0 when first drag_select command occurs.
if 'by' not in args:
vs.drag_count = 0
else:
self.drag_select_view = None

if cmd in ('move', 'move_to') and vs.active_mark and not args.get('extend', False):
# this is necessary or else the built-in commands (C-f, C-b) will not move when there is
Expand Down Expand Up @@ -172,36 +198,37 @@ def on_post_text_command(self, view, cmd, args):
vs.argument_supplied = False
vs.last_cmd = cmd

if vs.active_mark:
if vs.active_mark and cmd != 'drag_select':
util.set_cursors(util.get_regions())

# if cmd in built_in_ensure_visible_cmds and util.just_one_cursor():
# util.ensure_visible(util.get_last_cursor())

#
# Process the selection if it was created from a drag_select (mouse dragging) command.
#
def on_selection_modified(self, view):
vs = ViewState.get(view)
selection = view.sel()

if len(selection) == 1 and vs.this_cmd == 'drag_select':
cm = CmdUtil(view, vs);
if vs.drag_count == 2:
# second event - enable active mark
region = view.sel()[0]
cm.set_mark([sublime.Region(region.a)], and_selection=False)
cm.toggle_active_mark_mode(True)
elif vs.drag_count == 0:
cm.toggle_active_mark_mode(False)
vs.drag_count += 1


#
# At a minimum this is called when bytes are inserted into the buffer.
# REMIND: This iterates all related views because sublime notifies for the same view N times, if
# there are N separate views open on the same buffer.
#
def on_modified(self, view):
ViewState.get(view).this_cmd = None
def on_selection_modified(self, active_view):
for view in ViewState.most_recent_related_view(active_view):
vs = ViewState.get(view)
selection = view.sel()

if len(selection) == 1 and vs.this_cmd == 'drag_select':
cm = CmdUtil(view, vs)
# # REMIND: we cannot rely on drag_count unfortunately because if you have the same
# # buffer in multiple views, they each get notified.
# if vs.drag_count >= 2 and not vs.active_mark:
# # wait until selection is at least 1 character long before activating
# region = view.sel()[0]
# if region.size() >= 1:
# cm.set_mark([sublime.Region(region.a, region.b)], and_selection=False)
# vs.active_mark = True
# elif vs.drag_count == 0:
# cm.toggle_active_mark_mode(False)
# vs.drag_count += 1

# update the mark ring
sel = selection[0]
vs.mark_ring.set([sublime.Region(sel.a, sel.a)], True)


class WindowCmdWatcher(sublime_plugin.EventListener):
Expand Down Expand Up @@ -781,18 +808,15 @@ def run_cmd(self, util):
cursors = state.mark_ring.pop()
if cursors:
util.set_cursors(cursors)
else:
util.set_status("No mark to pop!")
state.this_cmd = "sbp_pop_mark"
state.this_cmd = 'sbp_pop_mark'
elif state.this_cmd == state.last_cmd:
# at least two set mark commands in a row: turn ON the highlight
util.toggle_active_mark_mode()
else:
# set the mark
util.set_mark()

if settings_helper.get("sbp_active_mark_mode", False):
util.set_active_mark_mode()
if settings_helper.get("sbp_active_mark_mode", False):
util.set_active_mark_mode()

class SbpCancelMarkCommand(SbpTextCommand):
def run_cmd(self, util):
Expand Down Expand Up @@ -1286,6 +1310,7 @@ def run_cmd(self, util, indent_on_repeat=False):
point = util.get_point()
indent,cursor = util.get_line_indent(point)
tab_size = util.get_tab_size()

if util.state.active_mark or cursor > indent:
util.run_command("reindent", {})
else:
Expand All @@ -1298,7 +1323,12 @@ def run_cmd(self, util, indent_on_repeat=False):
self.view.run_command("insert", {"characters": " " * delta})
if cursor < indent:
util.run_command("move_to", {"to": "bol", "extend": False})

# re-indent and then if we're in the same place, indent another level
util.run_command("reindent", {})
indent2, cursor2 = util.get_line_indent(point)
if indent2 == indent:
util.run_command("indent", {})

#
# A quit command which is basically a no-op unless there are multiple cursors or a selection, in
Expand Down
48 changes: 34 additions & 14 deletions lib/isearch.py
Expand Up @@ -114,7 +114,6 @@ def __init__(self, view, forward, regex):
self.util = CmdUtil(view)
self.window = view.window()
self.point = self.util.get_cursors(True)
self.update()
self.input_view = None
self.in_changes = 0
self.forward = forward
Expand Down Expand Up @@ -145,18 +144,23 @@ def restart(self, text=""):
self.util.set_cursors(self.point)
self.current = item
if True or self.test_string(text):
# REMIND: always recreate the search one character at a time for now.
# REMIND: recreate the search one character at a time unless it's > 500, in which case
# that can be quite slow
self.in_append_from_cursor = True
self.append_group_id += 1
for index in range(0, len(text)):
self.on_change(text[0:index])
if len(text) < 1000:
for index in range(0, len(text)):
self.on_change(text[0:index])
else:
self.on_change(text)
self.in_append_from_cursor = False
self.set_text(text, False)
self.update()

def open(self):
window = self.view.window()
self.input_view = window.show_input_panel("%sI-Search:" % ("Regexp " if self.regex else "", ),
in_error_str = "" if self.not_in_error() else "Failing "
self.input_view = window.show_input_panel("%s%sI-Search:" % (in_error_str, "Regexp " if self.regex else ""),
"", self.on_done, self.on_change, self.on_cancel)
self.view_change_count = self.input_view.change_count()

Expand Down Expand Up @@ -311,6 +315,7 @@ def finish(self, abort=False, input_panel_hack=False):
if self.current and self.current.search:
save_search(self.current.search)
util.set_status("", False)
util.toggle_active_mark_mode(False)

point_set = False
if not abort:
Expand Down Expand Up @@ -344,29 +349,44 @@ def finish(self, abort=False, input_panel_hack=False):
self.hide_panel()

def update(self):
si = self.current
if si is None:
current = self.current
if current is None:
return
not_in_error = self.not_in_error()

self.view.add_regions(REGION_FIND, si.regions, "text", "", sublime.DRAW_NO_FILL)
selected = si.selected or (not_in_error.selected and [not_in_error.selected[-1]]) or []
self.view.add_regions(REGION_SELECTED, selected, "string", "", sublime.DRAW_NO_OUTLINE)
# figure out which of the input is NOT in error
if not_in_error != current:
scope = "markup.deleted.git_gutter"
else:
scope = "string"

if not_in_error != current:
# highlight the part of the search that is in error
self.input_view.add_regions(REGION_FIND,
[sublime.Region(len(not_in_error.search), len(current.search))],
"text", "", sublime.DRAW_NO_OUTLINE)
elif self.input_view is not None:
# erase the error indicator
self.input_view.add_regions(REGION_FIND, [], "text", "", sublime.DRAW_NO_OUTLINE)

self.view.add_regions(REGION_FIND, current.regions, "text", "", sublime.DRAW_NO_FILL)
selected = current.selected or (not_in_error.selected and [not_in_error.selected[-1]]) or []
self.view.add_regions(REGION_SELECTED, selected, scope, "", sublime.DRAW_NO_OUTLINE)
if selected:
self.view.show(selected[-1])

status = ""
if si != not_in_error or si.try_wrapped:
if current != not_in_error or current.try_wrapped:
status += "Failing "
if self.current.wrapped:
status += "Wrapped "
status += "I-Search " + ("Forward" if self.current.forward else "Reverse")
if si != not_in_error:
if current != not_in_error:
if len(self.current.regions) > 0:
status += " %s %s" % (pluralize("match", len(self.current.regions), "es"), ("above" if self.forward else "below"))
else:
n_cursors = min(len(si.selected), len(si.regions))
status += " %s, %s" % (pluralize("match", len(si.regions), "es"), pluralize("cursor", n_cursors))
n_cursors = min(len(current.selected), len(current.regions))
status += " %s, %s" % (pluralize("match", len(current.regions), "es"), pluralize("cursor", n_cursors))

self.util.set_status(status, False)

Expand Down
3 changes: 2 additions & 1 deletion lib/misc.py
Expand Up @@ -127,7 +127,7 @@ def get_cmd_name(cls):
kill_cmds.add(name)

#
# The baseclass for JOVE/SBP commands. This sets up state, creates a helper, processes the universal
# The base class for JOVE/SBP commands. This sets up state, creates a helper, processes the universal
# argument, and then calls the run_cmd method, which subclasses should override.
#
class SbpTextCommand(sublime_plugin.TextCommand):
Expand Down Expand Up @@ -367,6 +367,7 @@ def toggle_active_mark_mode(self, value=None):
return

self.state.active_mark = value if value is not None else (not self.state.active_mark)

if self.state.active_mark:
self.set_cursors(self.get_regions(), ensure_visible=False)
else:
Expand Down
17 changes: 17 additions & 0 deletions lib/viewstate.py
Expand Up @@ -69,6 +69,23 @@ def sorted_views(cls, window, group=None):
sorted_states = sorted(states, key=lambda state: state.touched, reverse=True)
return [state.view for state in sorted_states]

#
# Find all the existing views that are views into the same buffer as the specified view,
# and yield the most recently touched one to the caller.
#
@classmethod
def most_recent_related_view(cls, view):
view_states = cls.view_state_dict
buffer_id = view.buffer_id()
best = None
for view_id in view_states:
state = view_states[view_id]
if state.view.buffer_id() == buffer_id:
if best is None or best.touched < state.touched:
best = state
if best is not None:
yield best.view

#
# Reset the state for this view.
#
Expand Down
29 changes: 27 additions & 2 deletions messages/v3.1.5.txt
@@ -1,11 +1,32 @@
Emacs Pro Essentials v3.1.5 Update
==================================

- Added default emacs binding for UNDO.
This is an extremely long overdue release. I was nervous about the changes so I needed to run with
them for a long time to make sure I didn't screw anything up, and then I lost track of time. My
apologies.

Here's an overview of changes since my last release ... which was apparently 4 years ago!

- I've improved the performance of ctrl+u repeated commands. If you do ctrl+u 64 ctrl+n to go down
64 lines it's instantaneous now, as it should be.

- I've improved the way i-search works. When you are failing your search the selected text turns
red'ish according to the git delete coloring (assuming your theme supports git-gutter styling).
Also the text input itself is highlighted when the string becomes not found. You'll see what I
mean.

- There were some subtle problems with the selection sometimes going whacky when you switch back to
the sublime text window and click for the first time. I've tried to mitigate those issues and I
have been happy with the changes.

- The Tab indentation command is slightly smarter but I can't remember exactly how. I think it
requires fewer Tab key presses to get to where you are supposed to be.

- Added the default EMACS binding for UNDO.

- Changed the kill ring behavior with respect to the clipboard. Now when multiple regions are killed
or copied, the clipboard takes on all those regions joined together with Newlines. This is the
default Sublime behavior now supported by this plugin.
default Sublime behaviour now supported by this plugin.

This plugin still stores them as separate regions, and yank still works the way it used to, with
one exception: if you yank multiple regions into a single cursor, the regions are joined together
Expand All @@ -14,3 +35,7 @@ Emacs Pro Essentials v3.1.5 Update

- Fixed an issue with setting the target column for next and previous line commands after running
the s-expression commands. This was interfering with the ctrl+u command.

I hope you notice the differences and enjoy this update.

Jonathan Payne

0 comments on commit 309ab2d

Please sign in to comment.