def calculate_buffer_search_flags(view, pattern): flags = 0 if get_option(view, 'ignorecase'): flags |= IGNORECASE if not get_option(view, 'magic'): flags |= LITERAL elif pattern: # In magic mode some characters in the pattern are taken literally. They # match with the same character in the text e.g. ], '], "[ are taken # literally and patterns like [0-9], .+, ^, are regular expressions. # XXX Note that this is a very rough hacky implementation to support # some obvious patterns that should be taken literally like [ and "[. if re.match('^[a-zA-Z0-9_\'"\\[\\]]+$', pattern): if '[' not in pattern or ']' not in pattern: flags |= LITERAL elif re.match('^[a-zA-Z0-9_\'"\\(\\)]+$', pattern): if '(' not in pattern or ')' not in pattern: flags |= LITERAL return flags
def add_search_highlighting(view, occurrences, incremental=None): if get_option(view, 'incsearch') and incremental: view.add_regions('_nv_search_inc', incremental, scope='support.function neovintageous_search_inc', flags=ui_region_flags( get_setting_neo(view, 'search_inc_style'))) if get_option(view, 'hlsearch'): sels = view.sel() # TODO Optimise current = [] for region in occurrences: for sel in sels: if region.contains(sel): current.append(region) view.add_regions('_nv_search_occ', occurrences, scope='string neovintageous_search_occ', flags=ui_region_flags( get_setting_neo(view, 'search_occ_style'))) view.add_regions('_nv_search_cur', current, scope='support.function neovintageous_search_cur', flags=ui_region_flags( get_setting_neo(view, 'search_cur_style')))
def find_wrapping(view, term: str, start: int, end: int, flags: int = 0, times: int = 1): try: current_sel = view.sel()[0] except IndexError: return for x in range(times): match = find_in_range(view, term, start, end, flags) # make sure we wrap around the end of the buffer if not match: if not get_option(view, 'wrapscan'): return start = 0 # Extend the end of search to the end of current word, because # otherwise the current word would be excluded and not found. # See https://github.com/NeoVintageous/NeoVintageous/issues/223. end = current_sel.a end = view.word(current_sel.a).b match = find_in_range(view, term, start, end, flags) if not match: return start = match.b return match
def calculate_word_search_flags(view, pattern): flags = 0 if get_option(view, 'ignorecase'): flags |= IGNORECASE return flags
def reverse_find_wrapping(view, term: str, start: int, end: int, flags: int = 0, times: int = 1): try: current_sel = view.sel()[0] except IndexError: return # Search wrapping around the end of the buffer. for x in range(times): match = reverse_search(view, term, start, end, flags) # Start searching in the lower half of the buffer if we aren't doing it yet. if not match and start <= current_sel.b: if not get_option(view, 'wrapscan'): return # Extend the start of search to start of current word, because # otherwise the current word would be excluded and not found. # See https://github.com/NeoVintageous/NeoVintageous/issues/223. start = view.word(current_sel.b).a end = view.size() match = reverse_search(view, term, start, end, flags) if not match: return # No luck in the whole buffer. elif not match: return end = match.a return match
def _ui_bell(*msg: str) -> None: window = active_window() if not window: return view = window.active_view() if not view: return if msg: status_message(*msg) if get_option(view, 'belloff') == 'all': return color_scheme = get_setting(view, 'bell_color_scheme') if color_scheme in ('dark', 'light'): color_scheme = 'Packages/NeoVintageous/res/Bell-%s.hidden-color-scheme' % color_scheme duration = int(0.3 * 1000) times = 4 delay = 55 style = get_setting(view, 'bell') settings = view.settings() if style == 'view': settings.set('color_scheme', color_scheme) def remove_bell() -> None: settings.erase('color_scheme') set_timeout(remove_bell, duration) elif style == 'views': views = [] for group in range(window.num_groups()): view = window.active_view_in_group(group) if view: view.settings().set('color_scheme', color_scheme) views.append(view) def remove_bell() -> None: for view in views: view.settings().erase('color_scheme') set_timeout(remove_bell, duration) elif style == 'blink': # Ensure we leave the setting as we found it. times = times if (times % 2) == 0 else times + 1 def do_blink() -> None: nonlocal times if times > 0: settings.set('highlight_line', not settings.get('highlight_line')) times -= 1 set_timeout(do_blink, delay) do_blink()
def on_post_save(self, view): if get_option(view, 'modeline'): do_modeline(view) # Ensure the carets are within valid bounds. For instance, this is a # concern when 'trim_trailing_white_space_on_save' is set to true. # TODO Kill State dependency fix_eol_cursor(view, State(view).mode)
def _get_search_flags(view, search: str) -> int: flags = LITERAL if search and get_setting(view, 'sneak_use_ic_scs') == 1: if get_option(view, 'ignorecase') and not is_smartcase_pattern(view, search): flags |= IGNORECASE return flags
def process_word_search_pattern(view, pattern: str) -> tuple: flags = 0 if get_option(view, 'ignorecase'): flags |= IGNORECASE pattern = r'\b{0}\b'.format(re.escape(pattern)) return pattern, flags
def filter_region(view, text: str, cmd: str) -> str: # Redirect STDERR to STDOUT to capture both. # This seems to be the behavior of vim as well. p = subprocess.Popen([get_option(view, 'shell'), '-c', cmd], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) # Pass in text as input: saves having to deal with quoting stuff. out, _ = p.communicate(text.encode('utf-8')) return out.decode('utf-8', errors='backslashreplace')
def add_search_highlighting(view, occurrences: list, incremental: list = None) -> None: # Incremental search match string highlighting: while typing a search # command, where the pattern, as it was typed so far, matches. if incremental and get_option(view, 'incsearch'): view.add_regions('_nv_search_inc', incremental, scope='support.function neovintageous_search_inc', flags=ui_region_flags( get_setting_neo(view, 'search_inc_style'))) # Occurrences and current search match string highlighting: when there are # search matches, highlight all the matches and the current active one too. if occurrences and get_option(view, 'hlsearch'): view.add_regions('_nv_search_occ', occurrences, scope='string neovintageous_search_occ', flags=ui_region_flags( get_setting_neo(view, 'search_occ_style'))) sels = [] for sel in view.sel(): if sel.empty(): sel.b += 1 sels.append(sel) current = [] for region in occurrences: for sel in sels: if region.contains(sel): current.append(region) if current: view.add_regions('_nv_search_cur', current, scope='support.function neovintageous_search_cur', flags=ui_region_flags( get_setting_neo(view, 'search_cur_style')))
def read(view, cmd: str) -> str: p = subprocess.Popen([get_option(view, 'shell'), '-c', cmd], stdout=subprocess.PIPE, stderr=subprocess.PIPE) out, err = p.communicate() if out: return out.decode('utf-8') if err: return err.decode('utf-8') return ''
def wrapscan(view, forward=True): start = list(view.sel()) yield if not get_option(view, 'wrapscan'): for before, after in zip(start, list(view.sel())): if forward: if after.a < before.a: set_selection(view, start) break else: if after.a > before.a: set_selection(view, start) break
def read(view, cmd: str) -> str: p = subprocess.Popen([get_option(view, 'shell'), '/c', cmd], stdout=subprocess.PIPE, stderr=subprocess.PIPE, startupinfo=_get_startup_info()) out, err = p.communicate() if out: return _translate_newlines(out.decode(_get_encoding())) if err: return _translate_newlines(err.decode(_get_encoding())) return ''
def __init__(self, window, type: str, on_done=None, on_change=None, on_cancel=None): self._window = window if type not in self._TYPES: raise ValueError('invalid cmdline type') self._type = type # TODO Make view a contructor dependency? window.active_view() is a race-condition whereas view.window() isn't if type in (self.SEARCH_FORWARD, self.SEARCH_BACKWARD) and not get_option(window.active_view(), 'incsearch'): on_change = None self._callbacks = { 'on_done': on_done, 'on_change': on_change, 'on_cancel': on_cancel, }
def wrapscan(view, forward: bool = True): # This works by comparing the postion of the cursor after the enclosed # operation. If wrapscan is disabled and the cursor position has "wrapped # around" then it's reset to the previous postion before it was wrapped. start = list(view.sel()) yield if not get_option(view, 'wrapscan'): for before, after in zip(start, list(view.sel())): if forward: if after.a < before.a: set_selection(view, start) break else: if after.a > before.a: set_selection(view, start) break
def _is_alt_key_enabled(view, operator, operand, match_all): # Some GUI versions allow the access to menu entries by using the ALT # key in combination with a character that appears underlined in the # menu. This conflicts with the use of the ALT key for mappings and # entering special characters. This option tells what to do: # no Don't use ALT keys for menus. ALT key combinations can be # mapped, but there is no automatic handling. # yes ALT key handling is done by the windowing system. ALT key # combinations cannot be mapped. # menu Using ALT in combination with a character that is a menu # shortcut key, will be handled by the windowing system. Other # keys can be mapped. # If the menu is disabled by excluding 'm' from 'guioptions', the ALT # key is never used for the menu. winaltkeys = get_option(view, 'winaltkeys') if winaltkeys == 'menu': return (operand not in tuple('efghinpstv') or not view.window().is_menu_visible()) and _is_command_mode(view) return False if winaltkeys == 'yes' else _is_command_mode(view)
def do_modeline(view) -> None: # A feature similar to vim modeline. A number of lines at the beginning and # end of the file are checked for modelines. The number of lines checked is # controlled by the 'modelines' option, the default is 5. # # Examples: # vim: number # vim: nonumber # vim: tabstop=4 # vim: ts=4 noet window = view.window() # If the view is "transient" (for example when opened in in preview via the # CTRL-p overlay) then the view won't have a window object. Some ST events # like on_load() may open transient views. if not window: window = active_window() if window: modelines = get_option(view, 'modelines') line_count = view.rowcol(view.size())[0] + 1 head_lines = range(0, min(modelines, line_count)) tail_lines = range(max(0, line_count - modelines), line_count) lines = list(set(list(head_lines) + list(tail_lines))) for i in lines: line = view.line(view.text_point(i, 0)) if line.size() > 0: options = _parse_line(view.substr(line)) if options: for option in options: if option.strip().startswith('shell'): message('Error detected while processing modelines:') message('line %s:', str(i + 1)) message('E520: Not allowed in a modeline: %s', option) else: do_ex_cmdline(window, ':setlocal ' + option)
def _gen_modeline_options(view): modelines = _gen_modelines(view, get_option(view, 'modelines')) for opt in _gen_raw_options(modelines): name, sep, value = opt.partition(' ') yield view.settings().set, name.rstrip(':'), value.rstrip(';')
def on_load(self, view): if is_view(view) and get_option(view, 'modeline'): do_modeline(view)
def _is_alt_key_enabled(view, operator, operand, match_all): winaltkeys = get_option(view, 'winaltkeys') if winaltkeys == 'menu': return (operand not in tuple('efghinpstv') or not view.window().is_menu_visible()) and _is_command_mode(view) return False if winaltkeys == 'yes' else _is_command_mode(view)
def on_load(self, view): if get_option(view, 'modeline'): do_modeline(view)
def process_search_pattern(view, pattern: str) -> tuple: flags = 0 if get_option(view, 'ignorecase') and not is_smartcase_pattern(view, pattern): flags |= IGNORECASE # Changes the special characters that can be used in search patterns. is_magic = get_option(view, 'magic') # Pattern modes can be specified anywhere within the pattern itself and the # effect of the mode applies to the entire pattern: # # \c ignore case, do not use the 'ignorecase' option # \C match case, do not use the 'ignorecase' option pattern_modes = set() def _add_pattern_mode(match) -> str: pattern_modes.add(match.group(1)) return '' pattern = re.sub('\\\\([cC])', _add_pattern_mode, pattern) for m in pattern_modes: if m == 'c': flags |= IGNORECASE elif m == 'C': flags &= ~IGNORECASE # Some characters in the pattern are taken literally. They match with the # same character in the text. When preceded with a backslash however, these # characters get a special meaning. See :help magic for more details. # # Patterns can be prefixed by a "mode" that overrides the 'magic' option: # # \m 'magic' on for the following chars in the pattern. # \M 'magic' off for the following chars in the pattern. # \v the following chars in the pattern are "very magic". # \V the following chars int the pattern are "very nomagic". mode = None match = re.match('^\\\\(m|M|v|V)(.*)$', pattern) if match: mode = match.group(1) pattern = match.group(2) def _process_magic(pattern, flags): # When magic is on some characters in a pattern are interpreted # literally depending on context. For example [0-9 is interpreted # literally and [0-9] is interpreted as a regular expression. # XXX The following is a quick and dirty implementation to support some # very basic 'magic' literal interpretations. if pattern: if re.match('^[a-zA-Z0-9_\'"\\[\\]]+$', pattern): if '[' not in pattern or ']' not in pattern: flags |= LITERAL elif re.match('^[a-zA-Z0-9_\'"\\(\\)]+$', pattern): if '(' not in pattern or ')' not in pattern: flags |= LITERAL return pattern, flags # magic if mode == 'm' or (is_magic and not mode): pattern, flags = _process_magic(pattern, flags) # very magic elif mode == 'v': pattern, flags = _process_magic(pattern, flags) # nomagic elif mode == 'M' or (not is_magic and not mode): flags |= LITERAL # very nomagic elif mode == 'V': flags |= LITERAL return pattern, flags
def is_smartcase_pattern(view, pattern: str) -> bool: return get_option(view, 'smartcase') and any(p.isupper() for p in pattern)