Clean up a bit

This commit is contained in:
Chris Breneman 2021-01-17 23:55:41 -05:00
parent ac88f76749
commit 15a9660b34
3 changed files with 22 additions and 29 deletions

7
LICENSE Normal file
View file

@ -0,0 +1,7 @@
Copyright 2021 Chris Breneman
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View file

@ -1,3 +1,6 @@
# Tmux Copy Toolkit
# (C) Chris Breneman 2021
import os
import os.path
import sys
@ -13,7 +16,8 @@ from datetime import datetime
import time
import platform
logdir = '/tmp/tmplog'
#logdir = '/tmp/copytklog'
logdir = None
python_command = 'python3'
# Find full path to tmux command so it can be invoked without a shell
@ -40,17 +44,6 @@ match_expr_presets = {
'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'])'
}
@ -181,7 +174,6 @@ def fetch_tmux_options(optmode='g'):
else:
rval += c
val = rval
log('Got option value: ' + val)
opts[name] = val
tmux_options_cache[optmode] = opts
return opts
@ -533,9 +525,8 @@ class PaneJumpAction:
aligninfo = align_capture_data(self.orig_pane['contents'], self.copy_data, self.orig_pane['pane_size'])
if aligninfo == None:
log('alignment failed')
# TODO: Remove exception, allow fallback
raise Exception('alignment failed')
# Fall back to just mapping the display data to itself
# raise Exception('alignment failed')
# Fall back to just mapping the display data to itself. Will break wrapped lines.
self.copy_data = self.orig_pane['contents']
self.disp_copy_map = get_data_xy_idx_map(self.copy_data, self.orig_pane['pane_size'])
self.copy_disp_map = get_data_xy_idx_rev_map(self.copy_data, self.orig_pane['pane_size'])
@ -609,7 +600,9 @@ class PaneJumpAction:
else:
self.stdscr.addstr(y, x, s, a)
except Exception as err:
log(f'Error writing str to screen. curses_size={self.curses_size} linelen={len(line)} i={i} err={str(err)}')
pass
# note: errors are expected in writes to bottom-right
#log(f'Error writing str to screen. curses_size={self.curses_size} linelen={len(line)} i={i} err={str(err)}')
def _redraw_contents(self):
line_width = min(self.curses_size[1], self.orig_pane['pane_size'][0])
@ -623,7 +616,6 @@ class PaneJumpAction:
line_width = min(self.curses_size[1], self.orig_pane['pane_size'][0])
hlattr = curses.color_pair(3)
for rng in self.highlight_ranges:
log('Drawing hl range: ' + str(rng))
for i in range(rng[0][1], rng[1][1] + 1):
line = self.display_content_lines[i]
if i < rng[0][1] or i > rng[1][1]: # whole line not hl
@ -638,7 +630,6 @@ class PaneJumpAction:
self.addstr(i, 0, line[0:rng[1][0]+1], hlattr)
else:
assert(False)
log('Done drawing hl range')
def _redraw_labels(self):
line_width = min(self.curses_size[1], self.orig_pane['pane_size'][0])
@ -650,12 +641,14 @@ class PaneJumpAction:
try:
self.stdscr.addstr(row, col, label[self.cur_label_pos], curses.color_pair(1))
except Exception as err:
log(f'Error writing str to screen. curses_size={self.curses_size} linelen={len(line)} i={i} err={str(err)}')
pass
#log(f'Error writing str to screen. curses_size={self.curses_size} linelen={len(line)} i={i} err={str(err)}')
if len(label) > self.cur_label_pos + 1:
try:
self.stdscr.addstr(row, col+1, label[self.cur_label_pos+1:], curses.color_pair(2))
except Exception as err:
log(f'Error writing str to screen. curses_size={self.curses_size} linelen={len(line)} i={i} err={str(err)}')
pass
#log(f'Error writing str to screen. curses_size={self.curses_size} linelen={len(line)} i={i} err={str(err)}')
def redraw(self):
self._redraw_contents()
@ -764,7 +757,6 @@ class EasyMotionAction(PaneJumpAction):
return search_str
def get_locations(self, action):
#pane_search_lines = process_pane_capture_lines(self.orig_pane['contents'], self.orig_pane['pane_size'][1])
pane_search_lines = self.display_content_lines
log('\n'.join(pane_search_lines), 'pane_search_lines')
@ -868,7 +860,6 @@ class QuickCopyAction(PaneJumpAction):
self.options_prefix = options_prefix
self._load_options(options_prefix)
self.em_label_chars = ''.join(( c for c in self.em_label_chars if c not in self.next_batch_char ))
log('constructor finished')
def _load_options(self, prefix='@copytk-quickcopy-'):
# Load in the tiers of match expressions.
@ -877,7 +868,6 @@ class QuickCopyAction(PaneJumpAction):
# The set of tiers is terminated by a missing 0 index for the tier.
tier_exprs = [] # list (of tiers) of lists of strings
tier_ctr = 0
log('getting options')
while True:
l = get_tmux_option(prefix + 'match-' + str(tier_ctr), aslist=True, userlist=True)
if l == None or len(l) == 0:
@ -964,8 +954,6 @@ class QuickCopyAction(PaneJumpAction):
batches = []
log('start arrange_matches')
while len(matches) > 0: # iterate over batches
#log('MATCHES:')
#log(str(matches))
last_added_tier = None
overlaps = []
virt = [ False ] * len(self.copy_data)
@ -1060,13 +1048,11 @@ class QuickCopyAction(PaneJumpAction):
log('quickcopy run')
# Get a list of all matches
matches = self.find_matches()
log('find_matches() result: ' + str(matches))
if len(matches) == 0: raise ActionCanceled()
log('got matches')
# Group them into display batches
batches = self.arrange_matches(matches, self.pack_tiers)
log('arrange_matches() result: ' + str(batches))
log('arranged matches')
swap_hidden_pane(True)
@ -1158,7 +1144,6 @@ def run_easycopy(stdscr):
EasyCopyAction(stdscr, nkeys).run()
def run_quickcopy(stdscr):
log('run_quickcopy')
QuickCopyAction(stdscr).run()
def run_quickopen(stdscr):

View file

@ -65,6 +65,7 @@ tmux set -g @copytk-quickcopy-match-3-2 ''\''([^'\'\\'n]*)'\'
# Match whole lines
tmux set -g @copytk-quickcopy-match-4-0 lines
# Matches for quickopen
tmux set -g @copytk-quickopen-match-0-0 urls
tmux set -g @copytk-quickopen-match-0-1 abspaths