<commit_before>import sublime
import sublime_plugin

import threading

from Vintageous.vi import actions
from Vintageous.vi import constants
from Vintageous.vi import motions
from Vintageous.vi import registers
from Vintageous.vi import utils
from Vintageous.vi.cmd_data import CmdData
from Vintageous.vi.constants import _MODE_INTERNAL_NORMAL
from Vintageous.vi.constants import ACTIONS_EXITING_TO_INSERT_MODE
from Vintageous.vi.constants import DIGRAPH_MOTION
from Vintageous.vi.constants import digraphs
from Vintageous.vi.constants import MODE_INSERT
from Vintageous.vi.constants import MODE_NORMAL
from Vintageous.vi.constants import MODE_NORMAL_INSERT
from Vintageous.vi.constants import MODE_REPLACE
from Vintageous.vi.constants import mode_to_str
from Vintageous.vi.constants import MODE_VISUAL
from Vintageous.vi.constants import MODE_VISUAL_LINE
from Vintageous.vi.contexts import KeyContext
from Vintageous.vi.registers import Registers
from Vintageous.vi.settings import SettingsManager
from Vintageous.vi.settings import SublimeSettings
from Vintageous.vi.settings import VintageSettings
from Vintageous.vi.marks import Marks


# Some commands use input panels to gather user input. When they call their .on_done() method,
# they most likely want the global state to remain untouched. However, an input panel is just
# a view, so when it closes, the previous view gets activated and Vintageous init code runs.
# This variable lets it know that is should exit early.
# XXX: Make this a class-level attribute of VintageState (had some trouble with it last time I tried).
# XXX Is there anything weird with ST and using class-level attributes from different modules?
_dont_reset_during_init = False


def _init_vintageous(view):
    global _dont_reset_during_init
    # Operate only on actual views.
    if (not getattr(view, 'settings') or
            view.settings().get('is_widget')):
        return

    if _dont_reset_during_init:
        # We are probably coming from an input panel, like when using '/'.
        _dont_reset_during_init = False
        return

    state = VintageState(view)

    if state.mode in (MODE_VISUAL, MODE_VISUAL_LINE):
        view.run_command('enter_normal_mode')
    elif state.mode in (MODE_INSERT, MODE_REPLACE):
        view.run_command('vi_enter_normal_mode_from_insert_mode')
    elif state.mode == MODE_NORMAL_INSERT:
        view.run_command('vi_run_normal_insert_mode_actions')
    else:
        # XXX: When is this run? Only at startup?
        state.enter_normal_mode()

    state.reset()


def plugin_loaded():
    view = sublime.active_window().active_view()
    _init_vintageous(view)


def unload_handler():
    for w in sublime.windows():
        for v in w.views():
            v.settings().set('command_mode', False)
            v.settings().set('inverse_caret_state', False)
            v.settings().set('vintage', {})


class VintageState(object):
    """ Stores per-view state using View.Settings() for storage.
    """

    # Makes yank data globally accesible.
    registers = Registers()
    context = KeyContext()
    marks = Marks()

    _latest_repeat_command = None

    def __init__(self, view):
        self.view = view
        # We have two types of settings: vi-specific (settings.vi) and regular
        # ST view settings (settings.view).
        self.settings = SettingsManager(self.view)

    def enter_normal_mode(self):
        self.settings.view['command_mode'] = True
        self.settings.view['inverse_caret_state'] = True
        # Make sure xpos is up to date when we return to normal mode. Insert mode does not affect
        # xpos.
        # XXX: Why is insert mode resetting xpos? xpos should never be reset?
        self.xpos = None if not self.view.sel() else self.view.rowcol(self.view.sel()[0].b)[1]
        self.mode = MODE_NORMAL

        if self.view.overwrite_status():
            self.view.set_overwrite_status(False)

        # Hide outlined regions created by searching.
        self.view.erase_regions('vi_search')

        self.view.run_command('glue_marked_undo_groups')

    def enter_visual_line_mode(self):
        self.mode = MODE_VISUAL_LINE

    def enter_insert_mode(self):
        self.settings.view['command_mode'] = False
        self.settings.view['inverse_caret_state'] = False
        self.mode = MODE_INSERT

    def enter_visual_mode(self):
        self.mode = MODE_VISUAL

    def enter_normal_insert_mode(self):
        self.mode = MODE_NORMAL_INSERT
        self.settings.view['command_mode'] = False
        self.settings.view['inverse_caret_state'] = False

    def enter_replace_mode(self):
        self.mode = MODE_REPLACE
        self.settings.view['command_mode'] = False
        self.settings.view['inverse_caret_state'] = False
        self.view.set_overwrite_status(True)

    def store_visual_selections(self):
        self.view.add_regions('vi_visual_selections', list(self.view.sel()))

    @property
    def mode(self):
        return self.settings.vi['mode']

    @mode.setter
    def mode(self, value):
        self.settings.vi['mode'] = value

    @property
    def cancel_action(self):
        # If we can't find a suitable action, we should cancel.
        return self.settings.vi['cancel_action']

    @cancel_action.setter
    def cancel_action(self, value):
        self.settings.vi['cancel_action'] = value

    @property
    def action(self):
        return self.settings.vi['action']

    @action.setter
    def action(self, name):
        action = self.settings.vi['action']
        target = 'action'

        # Check for digraphs like cc, dd, yy.
        if action and name:
            name, type_ = digraphs.get((action, name), ('', None))<commit_msg>#167 - visual mode should not add entries to the undo stack unless modifying commands have been issued<commit_after>import sublime
import sublime_plugin

import threading

from Vintageous.vi import actions
from Vintageous.vi import constants
from Vintageous.vi import motions
from Vintageous.vi import registers
from Vintageous.vi import utils
from Vintageous.vi.cmd_data import CmdData
from Vintageous.vi.constants import _MODE_INTERNAL_NORMAL
from Vintageous.vi.constants import ACTIONS_EXITING_TO_INSERT_MODE
from Vintageous.vi.constants import DIGRAPH_MOTION
from Vintageous.vi.constants import digraphs
from Vintageous.vi.constants import MODE_INSERT
from Vintageous.vi.constants import MODE_NORMAL
from Vintageous.vi.constants import MODE_NORMAL_INSERT
from Vintageous.vi.constants import MODE_REPLACE
from Vintageous.vi.constants import mode_to_str
from Vintageous.vi.constants import MODE_VISUAL
from Vintageous.vi.constants import MODE_VISUAL_LINE
from Vintageous.vi.contexts import KeyContext
from Vintageous.vi.registers import Registers
from Vintageous.vi.settings import SettingsManager
from Vintageous.vi.settings import SublimeSettings
from Vintageous.vi.settings import VintageSettings
from Vintageous.vi.marks import Marks


# Some commands use input panels to gather user input. When they call their .on_done() method,
# they most likely want the global state to remain untouched. However, an input panel is just
# a view, so when it closes, the previous view gets activated and Vintageous init code runs.
# This variable lets it know that is should exit early.
# XXX: Make this a class-level attribute of VintageState (had some trouble with it last time I tried).
# XXX Is there anything weird with ST and using class-level attributes from different modules?
_dont_reset_during_init = False


def _init_vintageous(view):
    global _dont_reset_during_init
    # Operate only on actual views.
    if (not getattr(view, 'settings') or
            view.settings().get('is_widget')):
        return

    if _dont_reset_during_init:
        # We are probably coming from an input panel, like when using '/'.
        _dont_reset_during_init = False
        return

    state = VintageState(view)

    if state.mode in (MODE_VISUAL, MODE_VISUAL_LINE):
        view.run_command('enter_normal_mode')
    elif state.mode in (MODE_INSERT, MODE_REPLACE):
        view.run_command('vi_enter_normal_mode_from_insert_mode')
    elif state.mode == MODE_NORMAL_INSERT:
        view.run_command('vi_run_normal_insert_mode_actions')
    else:
        # XXX: When is this run? Only at startup?
        state.enter_normal_mode()

    state.reset()


def plugin_loaded():
    view = sublime.active_window().active_view()
    _init_vintageous(view)


def unload_handler():
    for w in sublime.windows():
        for v in w.views():
            v.settings().set('command_mode', False)
            v.settings().set('inverse_caret_state', False)
            v.settings().set('vintage', {})


class VintageState(object):
    """ Stores per-view state using View.Settings() for storage.
    """

    # Makes yank data globally accesible.
    registers = Registers()
    context = KeyContext()
    marks = Marks()

    _latest_repeat_command = None

    def __init__(self, view):
        self.view = view
        # We have two types of settings: vi-specific (settings.vi) and regular
        # ST view settings (settings.view).
        self.settings = SettingsManager(self.view)

    def enter_normal_mode(self):
        self.settings.view['command_mode'] = True
        self.settings.view['inverse_caret_state'] = True
        # Make sure xpos is up to date when we return to normal mode. Insert mode does not affect
        # xpos.
        # XXX: Why is insert mode resetting xpos? xpos should never be reset?
        self.xpos = None if not self.view.sel() else self.view.rowcol(self.view.sel()[0].b)[1]

        if self.view.overwrite_status():
            self.view.set_overwrite_status(False)

        # Hide outlined regions created by searching.
        self.view.erase_regions('vi_search')

        if not self.buffer_was_changed_in_visual_mode():
            # We've been in some visual mode, but we haven't modified the buffer at all.
            self.view.run_command('unmark_undo_groups_for_gluing')
        else:
            # Either we haven't been in any visual mode or we've modified the buffer while in
            # any visual mode.
            self.view.run_command('glue_marked_undo_groups')

        self.mode = MODE_NORMAL

    def enter_visual_line_mode(self):
        self.mode = MODE_VISUAL_LINE

    def enter_insert_mode(self):
        self.settings.view['command_mode'] = False
        self.settings.view['inverse_caret_state'] = False
        self.mode = MODE_INSERT

    def enter_visual_mode(self):
        self.mode = MODE_VISUAL

    def enter_normal_insert_mode(self):
        self.mode = MODE_NORMAL_INSERT
        self.settings.view['command_mode'] = False
        self.settings.view['inverse_caret_state'] = False

    def enter_replace_mode(self):
        self.mode = MODE_REPLACE
        self.settings.view['command_mode'] = False
        self.settings.view['inverse_caret_state'] = False
        self.view.set_overwrite_status(True)

    def store_visual_selections(self):
        self.view.add_regions('vi_visual_selections', list(self.view.sel()))

    def buffer_was_changed_in_visual_mode(self):
        """Returns `True` if we've changed the buffer while in visual mode.
        """
        # XXX: What if we used view.is_dirty() instead? That should be simpler?
        # XXX: If we can be sure that any modifying command will leave the buffer in a dirty
        # state, we could go for this solution.

        # 'maybe_mark_undo_groups_for_gluing' and 'glue_marked_undo_groups' seem to add an entry
        # to the undo stack regardless of whether intervening modifying-commands have been
        # issued.
        #
        # Example:
        #   1) We enter visual mode by pressing 'v'.
        #   2) We exit visual mode by pressing 'v' again.
        #
        # Since before the first 'v' and after the second we've called the aforementioned commands,
        # we'd now have a new (useless) entry in the undo stack, and the redo stack would be
        # empty. This would be undesirable, so we need to find out whether marked groups in
        # visual mode actually need to be glued or not.

        # FIXME: Design issue. This method won't always work. We have actions like yy that
        # will make this method return true, while it should return False (since it isn't a
        # modifying command). However, yy signals in its own way that it's a non-modifying command.
        # I don't think it will cause any bug, but we need to unify.

        if self.mode == MODE_VISUAL:
            visual_cmd = 'enter_visual_mode'
        elif self.mode == MODE_VISUAL_LINE:
            visual_cmd = 'enter_visual_line_mode'
        else:
            return True

        unmark = False
        i = 0
        cmds = []
        while True:
            cmd_name, args, _ = self.view.command_history(i)
            if (cmd_name == 'vi_run' and args['action'] and
                args['action']['command'] == visual_cmd):
                    break

            if not cmd_name or i < -250:
                break

            cmds.append((cmd_name, args))
            i -= 1

        # If we have an action between v..v calls, we have modified the buffer (most of the
        # time, anyway).
        unmark = not [name for (name, data) in cmds
                                        if data and data.get('action')]

        return unmark

    @property
    def mode(self):
        return self.settings.vi['mode']

    @mode.setter
    def mode(self, value):
        self.settings.vi['mode'] = value

    @property
    def cancel_action(self):
        # If we can't find a suitable action, we should cancel.
        return self.settings.vi['cancel_action']

    @cancel_action.setter
    def cancel_action(self, value):
        self.settings.vi['cancel_action'] = value

    @property
    def action(self):
        return self.settings.vi['action']

    @action.setter
    def action(self, name):
        action = self.settings.vi['action']
        target = 'action'

        # Check for digraphs like cc, dd, yy.
        if action and name:
            name, type_ = digraphs.get((action, name), ('', None))
{'fraction_non_alphanumeric': 0.08266798558609482, 'fraction_numerical': 0.0009891895711156644, 'mean_word_length': 2.965816755393668, 'pattern_counts': {'":': 0, '<': 4, '<?xml version=': 0, '>': 3, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 2, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6656122', 'n_tokens_mistral': 4210, 'n_tokens_neox': 4086, 'n_words': 1258}