mirror of
https://github.com/oh-my-fish/oh-my-fish
synced 2024-11-19 09:25:28 +00:00
199 lines
5.8 KiB
Python
199 lines
5.8 KiB
Python
|
# This script is part of the vi-mode plugin for fish
|
||
|
# author: Ian Munsie
|
||
|
# Adapted for oh-my-fish by Sylvain Benner
|
||
|
|
||
|
import sys
|
||
|
from functools import reduce
|
||
|
|
||
|
command = sys.argv[1]
|
||
|
direction = sys.argv[2]
|
||
|
new_pos = pos = int(sys.argv[3])
|
||
|
lineno = int(sys.argv[4]) - 1
|
||
|
cmdline_list = sys.argv[5:]
|
||
|
cmdline = '\n'.join(cmdline_list)
|
||
|
|
||
|
def start():
|
||
|
return (0, 0)
|
||
|
def end():
|
||
|
return (len(cmdline), -1)
|
||
|
class not_found(Exception): pass
|
||
|
|
||
|
def dir_0():
|
||
|
return (reduce(lambda a,b: a + len(b) + 1, cmdline_list[:lineno], 0), 0)
|
||
|
|
||
|
def dir_eol(): # end of line
|
||
|
before_len = reduce(lambda a,b: a + len(b) + 1, cmdline_list[:lineno], 0)
|
||
|
line_len = len(cmdline_list[lineno]) or 1
|
||
|
return (before_len + line_len, -1)
|
||
|
|
||
|
# These routines are all similar, they can probably be combined into one, but
|
||
|
# I'll make sure I get all working and understand the differences first
|
||
|
def dir_fnw(): # First Non-Whitespace
|
||
|
import re
|
||
|
len_before = reduce(lambda a,b: a + len(b) + 1, cmdline_list[:lineno], 0)
|
||
|
match = re.search('^\s*[^\s]', cmdline_list[lineno])
|
||
|
if not match:
|
||
|
return start()
|
||
|
return (len_before + match.end()-1, 0)
|
||
|
dir__ = dir_fnw # XXX: I always used _ as this, but turns out that might not be quite right
|
||
|
|
||
|
def _dir_w(regexp):
|
||
|
import re
|
||
|
|
||
|
searchpart = cmdline[pos:]
|
||
|
match = re.search(regexp, searchpart)
|
||
|
if not match:
|
||
|
return end()
|
||
|
return (pos + match.end()-1, 0)
|
||
|
|
||
|
def _dir_e(regexp):
|
||
|
import re
|
||
|
|
||
|
searchpart = cmdline[pos+1:]
|
||
|
match = re.search(regexp, searchpart)
|
||
|
if not match:
|
||
|
return end()
|
||
|
return (pos+2 + match.start(), -1)
|
||
|
|
||
|
def _dir_b(regexp):
|
||
|
import re
|
||
|
|
||
|
if pos == 0:
|
||
|
return start()
|
||
|
|
||
|
# Reverse the string instead of matching to right:
|
||
|
searchpart = cmdline[pos-1::-1]
|
||
|
match = re.search(regexp, searchpart)
|
||
|
if not match:
|
||
|
return start()
|
||
|
return (len(searchpart) - (match.start()+1), 0)
|
||
|
|
||
|
def _dir_ge(regexp):
|
||
|
import re
|
||
|
|
||
|
if pos == 0:
|
||
|
return start()
|
||
|
|
||
|
# Reverse the string instead of matching to right:
|
||
|
searchpart = cmdline[pos::-1]
|
||
|
match = re.search(regexp, searchpart)
|
||
|
if not match:
|
||
|
return start()
|
||
|
return (len(searchpart) - (match.end()), 0)
|
||
|
|
||
|
# Simple, but not inclusive enough:
|
||
|
# def dir_w(): return _dir_w(r'[^\w]\w')
|
||
|
# def dir_e(): return _dir_e(r'\w[^\w]')
|
||
|
|
||
|
# Slightly too inclusive, e.g. fi--sh matches both '-' characters, but should only match one:
|
||
|
_dir_w_regexp = r'[^\w][^\s]|\w[^\w\s]'
|
||
|
_dir_e_regexp = r'[^\s][^\w]|[^\w\s]\w'
|
||
|
_dir_W_regexp = r'\s[^\s]'
|
||
|
_dir_E_regexp = r'[^\s]\s'
|
||
|
|
||
|
def dir_w(): return _dir_w(_dir_w_regexp)
|
||
|
def dir_W(): return _dir_w(_dir_W_regexp)
|
||
|
def dir_e(): return _dir_e(_dir_e_regexp)
|
||
|
def dir_E(): return _dir_e(_dir_E_regexp)
|
||
|
def dir_b(): return _dir_b(_dir_e_regexp)
|
||
|
def dir_B(): return _dir_b(_dir_E_regexp)
|
||
|
def dir_ge(): return _dir_ge(_dir_w_regexp)
|
||
|
def dir_gE(): return _dir_ge(_dir_W_regexp)
|
||
|
def dir_cw(): return _dir_w(_dir_e_regexp)
|
||
|
def dir_cW(): return _dir_w(_dir_E_regexp)
|
||
|
|
||
|
def dir_h():
|
||
|
if pos: return (pos-1, 0)
|
||
|
return start()
|
||
|
|
||
|
def dir_l():
|
||
|
return (pos+1, 0)
|
||
|
|
||
|
def dir_t(char):
|
||
|
new_pos = cmdline.find(char, pos+1)
|
||
|
if new_pos < 0:
|
||
|
raise not_found
|
||
|
return (new_pos, -1)
|
||
|
|
||
|
def dir_T(char):
|
||
|
new_pos = cmdline.rfind(char, 0, pos)
|
||
|
if new_pos < 0:
|
||
|
raise not_found
|
||
|
return (new_pos+1, 0)
|
||
|
|
||
|
def dir_f(char): return (dir_t(char)[0]+1, -1)
|
||
|
def dir_F(char): return (dir_T(char)[0]-1, 0)
|
||
|
|
||
|
def cmd_delete():
|
||
|
dst_pos = dir(direction)
|
||
|
if dst_pos >= pos:
|
||
|
new_cmdline = cmdline[:pos] + cmdline[dst_pos:]
|
||
|
return (new_cmdline, pos)
|
||
|
new_cmdline = cmdline[:dst_pos] + cmdline[pos:]
|
||
|
return (new_cmdline, dst_pos)
|
||
|
|
||
|
def cmd_change():
|
||
|
# 'Special case: 'cw' and 'cW' are treated like 'ce' and 'cE' if the cursor
|
||
|
# is on a non-blank. This is because 'cw' is interpreted as change-word,
|
||
|
# and a word does not include the following white space.'
|
||
|
#
|
||
|
# Note: Even with this special case the behaviour does not quite match what
|
||
|
# vim actually does in practice - try with the cursor on punctuation, or on
|
||
|
# the last character in a word. Specifically, the behaviour of cw differs
|
||
|
# from ce when the cursor is already on the last character of a 'word', for
|
||
|
# vim's definition of word.
|
||
|
#
|
||
|
# Because of this, we use a special direction to handle this case.
|
||
|
global direction
|
||
|
if direction in 'wW' and not cmdline[pos].isspace():
|
||
|
direction = direction.replace('w', 'cw').replace('W', 'cW')
|
||
|
return cmd_delete()
|
||
|
|
||
|
def cmd_o():
|
||
|
above = '\n'.join(cmdline_list[:lineno + 1])
|
||
|
below = '\n'.join(cmdline_list[lineno + 1:])
|
||
|
return (above + '\n\n' + below, len(above)+1)
|
||
|
|
||
|
def cmd_O():
|
||
|
above = '\n'.join(cmdline_list[:lineno])
|
||
|
below = '\n'.join(cmdline_list[lineno:])
|
||
|
return (above + '\n\n' + below, len(above)+1)
|
||
|
|
||
|
def _dir_cmd_func(func):
|
||
|
(pos1, pos2) = (pos, dir(direction))
|
||
|
if pos2 < pos:
|
||
|
(pos1, pos2) = (pos2, pos1)
|
||
|
new_cmdline = cmdline[:pos1] + func(cmdline[pos1:pos2]) + cmdline[pos2:]
|
||
|
return (new_cmdline, pos1)
|
||
|
|
||
|
# XXX: automagic completion sometimes hides the results of changing the case in these commands:
|
||
|
def cmd_upper(): return _dir_cmd_func(str.upper)
|
||
|
def cmd_lower(): return _dir_cmd_func(str.lower)
|
||
|
def cmd_swapcase(): return _dir_cmd_func(str.swapcase)
|
||
|
|
||
|
def dir(d, cursor = False):
|
||
|
def validate(pos):
|
||
|
if pos < 0: return 0
|
||
|
if pos > len(cmdline): return len(cmdline)
|
||
|
return pos
|
||
|
a = ()
|
||
|
if ':' in d:
|
||
|
(d, a) = d.split(':', 1)
|
||
|
(new_pos, cursor_off) = globals()['dir_%s' % d](*a)
|
||
|
if cursor:
|
||
|
return validate(new_pos + cursor_off)
|
||
|
return validate(new_pos)
|
||
|
|
||
|
def cmd(c): return globals()['cmd_%s' % c]()
|
||
|
|
||
|
def cmd_normal():
|
||
|
return (None, dir(direction, True))
|
||
|
|
||
|
try:
|
||
|
(cmdline, new_pos) = cmd(command)
|
||
|
if cmdline is not None:
|
||
|
print ( cmdline )
|
||
|
except not_found:
|
||
|
new_pos = pos
|
||
|
print ( new_pos )
|