This commit is contained in:
Chris Breneman 2021-01-17 20:17:09 -05:00
parent ccb3ec067e
commit 3ce1e9c602
3 changed files with 114 additions and 7 deletions

View file

@ -1,5 +1,69 @@
# Note: The regexes in this file are not intended to correctly validate
# all instances of the given type. They are designed to match the most
# common variants while specifically excluding less-common ones that could
# introduce false matches.
import re
def make_path_regexes():
# This is not intended to match all valid paths - that would result in too many matches.
# Note: Spaces, as a common delimiter, are handled specially. They are only allowed in
# paths with at least 3 elements, and never in the first or last element. There cannot
# be more than one consecutive space, and it cannot be at the beginning or end of the
# element.
edge_delimiters = r'[][\s:=,#$"{}<>()' + "'" + ']'
edge_delimiters_w_slash = r'[][\s:=,#$"{}<>()/' + "'" + ']'
leader = r'(?:^|' + edge_delimiters + ')'
leader_w_slash = r'(?:^|' + edge_delimiters_w_slash + ')'
follower = r'(?:$|' + edge_delimiters + ')'
path_el = r'[a-zA-Z0-9_-]{1,30}'
inner_path_el = r'[a-zA-Z0-9_-]{1,25}\\? [a-zA-Z0-9_-]{1,25}'
either_path_el = r'(?:' + path_el + '|' + inner_path_el + ')'
basic_filename = path_el + r'\.' + r'[a-zA-Z][a-zA-Z0-9]{0,5}'
path_filename = either_path_el + r'\.' + r'[a-zA-Z0-9]{1,6}'
sep = r'[\\/]'
root = r'(?:/|~/|[A-Z]:' + sep + ')'
abspath = root + r'(?:' + either_path_el + sep + ')*' + r'(?:' + path_filename + '|' + path_el + sep + '?)'
relpath2 = path_el + sep + r'(?:' + path_el + sep + '?|' + path_filename + ')'
relpath3 = path_el + sep + r'(?:' + either_path_el + sep + r')+' + r'(?:' + path_el + sep + '?|' + path_filename + ')'
anypath = r'(?:' + abspath + '|' + relpath2 + '|' + relpath3 + ')'
rabspath = leader + '(' + abspath + ')' + follower
ranypath = leader + '(' + anypath + ')' + follower
rfilename = leader_w_slash + '(' + basic_filename + ')' + follower
return rabspath, ranypath, rfilename
def test_path_regexes():
testpaths = r'''
foo
foo/bar
foo/bar/baz
baz.mp3
123.456
C:\fooo
C:\fooo.bar
C:/fooo.bar
/fooo
/fooo.bar
/abc/def/ghi
foo bar/baz
foo/bar baz/fiz
foo/bar baz/fiz.buz
'''.split('\n')
testpaths = [ p.strip() for p in testpaths if len(p.strip()) ]
abspath, anypath, filename = make_path_regexes()
print('abspath: ' + abspath)
print('anypath: ' + anypath)
print('filename: ' + filename)
for p in testpaths:
m1 = re.fullmatch(abspath, p)
m2 = re.fullmatch(anypath, p)
m3 = re.fullmatch(filename, p)
print(f'{p} - {"ABS" if m1 else ""} {"PATH" if m2 else ""} {"FILE" if m3 else ""}')
# Note: This url regex is not a validator, nor is it intended to be. It is intended
# to match the most common kinds of URLs that are used and avoid unintended matches.
# Notably, spaces in urls are not matched; with spaces there's too great a chance
@ -119,8 +183,25 @@ def test_url_regex():
else:
print(f'{u} - No Match')
test_url_regex()
def print_rex(name, r, comment=None):
rxstr = "r'" + r.replace("'", "'+\"'\"+r'") + "'"
if comment:
print(f"\t# {comment}")
print(f"\t'{name}': {rxstr}")
r = make_url_regex()
print("r'" + r.replace("'", "'+\"'\"+r'") + "'")
#test_url_regex()
#print('URL:')
print_rex('urls', make_url_regex(), 'matches common types of urls and things that look like urls')
print
#test_path_regexes()
absp, anyp, fn = make_path_regexes()
#print('ABS PATH:')
print_rex('abspaths', absp, 'Unix and window style absolute paths')
#print('PATH:')
print_rex('paths', anyp, 'Absolute or relative paths')
#print('FILE:')
print_rex('filenames', fn, 'Isolated filenames without paths')

View file

@ -31,8 +31,25 @@ tmux_command = find_command_path('tmux')
# strings that can be used to map to regexes in the quickcopy matches
match_expr_presets = {
# generated by _regex_builds.py
'urls': r'(?:^|[][\s:=,#"{}()'+"'"+r'])([a-zA-Z][a-zA-Z0-9]{1,5}://(?:[a-zA-Z0-9_]+(?::[a-zA-Z0-9_-]+)?@)?(?:(?:[a-zA-Z0-9][\w-]*\.)*[a-zA-Z][\w-]*|(?:[0-2]?[0-9]{1,2}\.){3}[0-2]?[0-9]{1,2})(?::[0-9]{1,5})?(?:/(?:[\w.~%/&-]+|(?:[\w.~%/&-]*\([\w.~%/&-]*\)[\w.~%/&-]*)+)?/?)?(?:\?(?:(?:[\w.~%/&-]+|(?:[\w.~%/&-]*\([\w.~%/&-]*\)[\w.~%/&-]*)+)+(?:=(?:[\w.~%/&-]+|(?:[\w.~%/&-]*\([\w.~%/&-]*\)[\w.~%/&-]*)+)?)?&)*(?:(?:[\w.~%/&-]+|(?:[\w.~%/&-]*\([\w.~%/&-]*\)[\w.~%/&-]*)+)+(?:=(?:[\w.~%/&-]+|(?:[\w.~%/&-]*\([\w.~%/&-]*\)[\w.~%/&-]*)+)?)?)?)?(?:#(?:(?:[\w.~%/&-]+|(?:[\w.~%/&-]*\([\w.~%/&-]*\)[\w.~%/&-]*)+)+(?:=(?:[\w.~%/&-]+|(?:[\w.~%/&-]*\([\w.~%/&-]*\)[\w.~%/&-]*)+)?)?&)*(?:(?:[\w.~%/&-]+|(?:[\w.~%/&-]*\([\w.~%/&-]*\)[\w.~%/&-]*)+)+(?:=(?:[\w.~%/&-]+|(?:[\w.~%/&-]*\([\w.~%/&-]*\)[\w.~%/&-]*)+)?)?)?)?)(?:$|[][\s:=,#"{}()'+"'"+r'])'
# matches common types of urls and things that look like urls
'urls': r'(?:^|[][\s:=,#"{}()'+"'"+r'])([a-zA-Z][a-zA-Z0-9]{1,5}://(?:[a-zA-Z0-9_]+(?::[a-zA-Z0-9_-]+)?@)?(?:(?:[a-zA-Z0-9][\w-]*\.)*[a-zA-Z][\w-]*|(?:[0-2]?[0-9]{1,2}\.){3}[0-2]?[0-9]{1,2})(?::[0-9]{1,5})?(?:/(?:[\w.~%/&-]+|(?:[\w.~%/&-]*\([\w.~%/&-]*\)[\w.~%/&-]*)+)?/?)?(?:\?(?:(?:[\w.~%/&-]+|(?:[\w.~%/&-]*\([\w.~%/&-]*\)[\w.~%/&-]*)+)+(?:=(?:[\w.~%/&-]+|(?:[\w.~%/&-]*\([\w.~%/&-]*\)[\w.~%/&-]*)+)?)?&)*(?:(?:[\w.~%/&-]+|(?:[\w.~%/&-]*\([\w.~%/&-]*\)[\w.~%/&-]*)+)+(?:=(?:[\w.~%/&-]+|(?:[\w.~%/&-]*\([\w.~%/&-]*\)[\w.~%/&-]*)+)?)?)?)?(?:#(?:(?:[\w.~%/&-]+|(?:[\w.~%/&-]*\([\w.~%/&-]*\)[\w.~%/&-]*)+)+(?:=(?:[\w.~%/&-]+|(?:[\w.~%/&-]*\([\w.~%/&-]*\)[\w.~%/&-]*)+)?)?&)*(?:(?:[\w.~%/&-]+|(?:[\w.~%/&-]*\([\w.~%/&-]*\)[\w.~%/&-]*)+)+(?:=(?:[\w.~%/&-]+|(?:[\w.~%/&-]*\([\w.~%/&-]*\)[\w.~%/&-]*)+)?)?)?)?)(?:$|[][\s:=,#"{}()'+"'"+r'])',
# Unix and window style absolute paths
'abspaths': r'(?:^|[][\s:=,#$"{}<>()'+"'"+r'])((?:/|~/|[A-Z]:[\\/])(?:(?:[a-zA-Z0-9_-]{1,30}|[a-zA-Z0-9_-]{1,25}\\? [a-zA-Z0-9_-]{1,25})[\\/])*(?:(?:[a-zA-Z0-9_-]{1,30}|[a-zA-Z0-9_-]{1,25}\\? [a-zA-Z0-9_-]{1,25})\.[a-zA-Z0-9]{1,6}|[a-zA-Z0-9_-]{1,30}[\\/]?))(?:$|[][\s:=,#$"{}<>()'+"'"+r'])',
# Absolute or relative paths
'paths': r'(?:^|[][\s:=,#$"{}<>()'+"'"+r'])((?:(?:/|~/|[A-Z]:[\\/])(?:(?:[a-zA-Z0-9_-]{1,30}|[a-zA-Z0-9_-]{1,25}\\? [a-zA-Z0-9_-]{1,25})[\\/])*(?:(?:[a-zA-Z0-9_-]{1,30}|[a-zA-Z0-9_-]{1,25}\\? [a-zA-Z0-9_-]{1,25})\.[a-zA-Z0-9]{1,6}|[a-zA-Z0-9_-]{1,30}[\\/]?)|[a-zA-Z0-9_-]{1,30}[\\/](?:[a-zA-Z0-9_-]{1,30}[\\/]?|(?:[a-zA-Z0-9_-]{1,30}|[a-zA-Z0-9_-]{1,25}\\? [a-zA-Z0-9_-]{1,25})\.[a-zA-Z0-9]{1,6})|[a-zA-Z0-9_-]{1,30}[\\/](?:(?:[a-zA-Z0-9_-]{1,30}|[a-zA-Z0-9_-]{1,25}\\? [a-zA-Z0-9_-]{1,25})[\\/])+(?:[a-zA-Z0-9_-]{1,30}[\\/]?|(?:[a-zA-Z0-9_-]{1,30}|[a-zA-Z0-9_-]{1,25}\\? [a-zA-Z0-9_-]{1,25})\.[a-zA-Z0-9]{1,6})))(?:$|[][\s:=,#$"{}<>()'+"'"+r'])',
# Isolated filenames without paths
'filenames': r'(?:^|[][\s:=,#$"{}<>()/'+"'"+r'])([a-zA-Z0-9_-]{1,30}\.[a-zA-Z][a-zA-Z0-9]{0,5})(?:$|[][\s:=,#$"{}<>()'+"'"+r'])'
# long regexes generated by _regex_builds.py
# matches things that look like common URLs. does not match esoteric URLs.
# 'urls': r'(?:^|[][\s:=,#"{}()'+"'"+r'])([a-zA-Z][a-zA-Z0-9]{1,5}://(?:[a-zA-Z0-9_]+(?::[a-zA-Z0-9_-]+)?@)?(?:(?:[a-zA-Z0-9][\w-]*\.)*[a-zA-Z][\w-]*|(?:[0-2]?[0-9]{1,2}\.){3}[0-2]?[0-9]{1,2})(?::[0-9]{1,5})?(?:/(?:[\w.~%/&-]+|(?:[\w.~%/&-]*\([\w.~%/&-]*\)[\w.~%/&-]*)+)?/?)?(?:\?(?:(?:[\w.~%/&-]+|(?:[\w.~%/&-]*\([\w.~%/&-]*\)[\w.~%/&-]*)+)+(?:=(?:[\w.~%/&-]+|(?:[\w.~%/&-]*\([\w.~%/&-]*\)[\w.~%/&-]*)+)?)?&)*(?:(?:[\w.~%/&-]+|(?:[\w.~%/&-]*\([\w.~%/&-]*\)[\w.~%/&-]*)+)+(?:=(?:[\w.~%/&-]+|(?:[\w.~%/&-]*\([\w.~%/&-]*\)[\w.~%/&-]*)+)?)?)?)?(?:#(?:(?:[\w.~%/&-]+|(?:[\w.~%/&-]*\([\w.~%/&-]*\)[\w.~%/&-]*)+)+(?:=(?:[\w.~%/&-]+|(?:[\w.~%/&-]*\([\w.~%/&-]*\)[\w.~%/&-]*)+)?)?&)*(?:(?:[\w.~%/&-]+|(?:[\w.~%/&-]*\([\w.~%/&-]*\)[\w.~%/&-]*)+)+(?:=(?:[\w.~%/&-]+|(?:[\w.~%/&-]*\([\w.~%/&-]*\)[\w.~%/&-]*)+)?)?)?)?)(?:$|[][\s:=,#"{}()'+"'"+r'])',
# matches things that look like UNIX or Windows style absolute paths
# 'abspaths': r'(?:^|[][\s:=,#$"{}()'+"'"+r'])((?:/|~/|[A-Z]:[\\/])(?:(?:[a-zA-Z0-9_-]{1,30}|[a-zA-Z0-9_-]{1,25}\\? [a-zA-Z0-9_-]{1,25})[\\/])*(?:(?:[a-zA-Z0-9_-]{1,30}|[a-zA-Z0-9_-]{1,25}\\? [a-zA-Z0-9_-]{1,25})\.[a-zA-Z0-9]{1,6}|[a-zA-Z0-9_-]{1,30}[\\/]?))(?:$|[][\s:=,#$"{}()'+"'"+r'])',
# matches both things that look like absolute paths, and relative paths with at least one separator
# may also match spaces in path components, but never at the beginning or end, and only in an inner component
# 'paths': r'(?:^|[][\s:=,#$"{}()'+"'"+r'])((?:(?:/|~/|[A-Z]:[\\/])(?:(?:[a-zA-Z0-9_-]{1,30}|[a-zA-Z0-9_-]{1,25}\\? [a-zA-Z0-9_-]{1,25})[\\/])*(?:(?:[a-zA-Z0-9_-]{1,30}|[a-zA-Z0-9_-]{1,25}\\? [a-zA-Z0-9_-]{1,25})\.[a-zA-Z0-9]{1,6}|[a-zA-Z0-9_-]{1,30}[\\/]?)|[a-zA-Z0-9_-]{1,30}[\\/](?:[a-zA-Z0-9_-]{1,30}[\\/]?|(?:[a-zA-Z0-9_-]{1,30}|[a-zA-Z0-9_-]{1,25}\\? [a-zA-Z0-9_-]{1,25})\.[a-zA-Z0-9]{1,6})|[a-zA-Z0-9_-]{1,30}[\\/](?:(?:[a-zA-Z0-9_-]{1,30}|[a-zA-Z0-9_-]{1,25}\\? [a-zA-Z0-9_-]{1,25})[\\/])+(?:[a-zA-Z0-9_-]{1,30}[\\/]?|(?:[a-zA-Z0-9_-]{1,30}|[a-zA-Z0-9_-]{1,25}\\? [a-zA-Z0-9_-]{1,25})\.[a-zA-Z0-9]{1,6})))(?:$|[][\s:=,#$"{}()'+"'"+r'])',
# matches things that look like an isolated filename (no path)
# 'filenames': r'(?:^|[][\s:=,#$"{}()'+"'"+r'])([a-zA-Z0-9_-]{1,30}\.[a-zA-Z][a-zA-Z0-9]{0,5})(?:$|[][\s:=,#$"{}()'+"'"+r'])'
}
@ -764,6 +781,8 @@ class EasyMotionAction(PaneJumpAction):
locs = self._em_filter_locs(locs)
if filter_locs:
locs = [ l for l in locs if filter_locs(l) ]
if len(locs) == 0:
raise ActionCanceled()
self._em_sort_locs_cursor_proximity(locs, sort_close_to)
# Assign each match a label
@ -1024,6 +1043,7 @@ class QuickCopyAction(PaneJumpAction):
log('quickcopy run')
# Get a list of all matches
matches = self.find_matches()
if len(matches) == 0: return
log('got matches')
# Group them into display batches

View file

@ -29,7 +29,13 @@ tmux bind-key -T copy-mode-vi S switch-client -T copytk
tmux bind-key -T copy-mode S switch-client -T copytk
# Match URLs
tmux set -g @copytk-quickcopy-match-0-0 urls
tmux set -g @copytk-quickcopy-match-1-0 '[0-9]{4,}'
tmux set -g @copytk-quickcopy-match-2-0 lines
tmux set -g @copytk-quickcopy-match-0-1 abspaths
tmux set -g @copytk-quickcopy-match-1-0 paths
tmux set -g @copytk-quickcopy-match-1-0 filenames
# Match numbers
tmux set -g @copytk-quickcopy-match-2-0 '-?[0-9]+(?:\.[0-9]+)?(?:[eE]-?[0-9]+)?'
# Match whole lines
tmux set -g @copytk-quickcopy-match-3-0 lines