Source code for visidata.movement

import itertools

from visidata import vd, BaseSheet, Sheet, Column, Progress, ALT, asyncthread


def rotateRange(n, idx, reverse=False):
    'Wraps an iter starting from idx. Yields indices from idx+1 to n and then 0 to idx, or from idx-1 to 0 and n-1 to idx.'
    if n == 0: return []
    if reverse:
        rng = range(idx-1, -1, -1)
        rng2 = range(n-1, idx-1, -1)
    else:
        rng = range(idx+1, n)
        rng2 = range(0, idx+1)

    wrapped = False
    with Progress(total=n) as prog:
        for r in itertools.chain(rng, rng2):
            prog.addProgress(1)
            if not wrapped and r in rng2:
                vd.status('search wrapped')
                wrapped = True
            yield r

@Sheet.api
def pageLeft(self):
    '''Redraw page one screen to the left.

    Note: keep the column cursor in the same general relative position:

     - if it is on the furthest right column, then it should stay on the
       furthest right column if possible

     - likewise on the left or in the middle

    So really both the `leftIndex` and the `cursorIndex` should move in
    tandem until things are correct.'''

    # a hidden column shows alone when the cursor is on it (index past the
    # visible cols); step back into the visible columns instead of stalling  #3134
    if self.cursorVisibleColIndex >= self.nVisibleCols:
        self.cursorVisibleColIndex = self.nVisibleCols-1
        self.leftVisibleColIndex = self.cursorVisibleColIndex
        return

    targetIdx = self.leftVisibleColIndex  # for rightmost column
    firstNonKeyVisibleColIndex = self.visibleCols.index(self.nonKeyVisibleCols[0])
    while self.rightVisibleColIndex != targetIdx and self.leftVisibleColIndex > firstNonKeyVisibleColIndex:
        self.cursorVisibleColIndex -= 1
        self.leftVisibleColIndex -= 1
        self.calcColLayout()  # recompute rightVisibleColIndex

    # in case that rightmost column is last column, try to squeeze maximum real estate from screen
    if self.rightVisibleColIndex == self.nVisibleCols-1:
        # try to move further left while right column is still full width
        while self.leftVisibleColIndex > 0:
            rightcol = self.visibleCols[self.rightVisibleColIndex]
            if (rightcol.width or 0) > self._visibleColLayout[self.rightVisibleColIndex][1]:
                # went too far
                self.cursorVisibleColIndex += 1
                self.leftVisibleColIndex += 1
                break
            else:
                self.cursorVisibleColIndex -= 1
                self.leftVisibleColIndex -= 1
                self.calcColLayout()  # recompute rightVisibleColIndex


[docs] @Sheet.api @asyncthread def moveToNextRow(vs, func, reverse=False, msg='no different value up this column'): 'Move cursor to next (prev if reverse) row for which func returns True.' rng = range(vs.cursorRowIndex-1, -1, -1) if reverse else range(vs.cursorRowIndex+1, vs.nRows) found = False with Progress(total=len(vs.rows)) as prog: for i in rng: prog.addProgress(1) try: if func(vs.rows[i]): vs.cursorRowIndex = i found = True break except Exception: pass if not found: vd.status(msg)
@Column.property def visibleWidth(self): 'Width of column as is displayed in terminal' vcolidx = self.sheet.availCols.index(self) if vcolidx in self.sheet._visibleColLayout: w = self.sheet._visibleColLayout[vcolidx][1] else: #this case should never happen in normal use #the width can be inaccurate if the column is not at x=0 w = self.sheet.calcSingleColLayout(vcolidx) return w @Sheet.api def go_row_number(sheet, number): index = number if number >= 0 else sheet.nRows + number sheet.cursorRowIndex = max(0, min(index, sheet.nRows-1)) if not sheet.nRows: vd.warning('no rows') elif index != sheet.cursorRowIndex: vd.warning(f'row number `{number}` out of bounds; moved to row {sheet.cursorRowIndex}') @BaseSheet.api def jump_sheet(sheet, name): matches = [s for s in vd.allSheets if s.name == name] if not matches: vd.fail(f'no sheet named `{name}`') vd.push(matches[0]) Sheet.init('stoppedAtHiddenEdge', bool) @Sheet.api def go_right(sheet): atHiddenEdge = sheet.cursorVisibleColIndex == sheet.nVisibleCols-1 and sheet.nVisibleCols < len(sheet.availCols) if atHiddenEdge and not sheet.stoppedAtHiddenEdge: sheet.stoppedAtHiddenEdge = True vd.status('press again to enter hidden columns') return sheet.stoppedAtHiddenEdge = False sheet.cursorRight(+1) Sheet.addCommand(None, 'go-left', 'cursorRight(-1)', 'go left', replay=False) Sheet.addCommand(None, 'go-down', 'cursorDown(+1)', 'go down', replay=False) Sheet.addCommand(None, 'go-up', 'cursorDown(-1)', 'go up', replay=False) Sheet.addCommand(None, 'go-right', 'go_right()', 'go right; press twice to cross into hidden columns', replay=False) Sheet.addCommand(None, 'go-pagedown', 'cursorDown(nScreenRows-1); sheet.topRowIndex = bottomRowIndex', 'scroll one page forward', replay=False) Sheet.addCommand(None, 'go-pageup', 'cursorDown(-nScreenRows+1); sheet.bottomRowIndex = topRowIndex', 'scroll one page backward', replay=False) Sheet.addCommand(None, 'go-pagedown-half', 'n=nScreenRows//2; cursorDown(n); sheet.topRowIndex += n', 'scroll half page forward', replay=False) Sheet.addCommand(None, 'go-pageup-half', 'n=-nScreenRows//2; cursorDown(n); sheet.topRowIndex += n', 'scroll half page backward', replay=False) Sheet.addCommand(None, 'go-leftmost', 'sheet.cursorVisibleColIndex = sheet.leftVisibleColIndex = 0', 'go all the way to the left of sheet') Sheet.addCommand(None, 'go-top', 'sheet.cursorRowIndex = sheet.topRowIndex = 0', 'go all the way to the top of sheet') Sheet.addCommand(None, 'go-bottom', 'sheet.cursorRowIndex = sheet.bottomRowIndex = len(rows)-1', 'go all the way to the bottom of sheet') Sheet.addCommand(None, 'go-rightmost', 'sheet.leftVisibleColIndex = len(visibleCols)-1; pageLeft(); sheet.cursorVisibleColIndex = len(visibleCols)-1', 'go all the way to the right of sheet') Sheet.addCommand(None, 'go-screen-top', 'sheet.cursorRowIndex = sheet.topRowIndex', 'go to the first row visible on screen') Sheet.addCommand(None, 'go-screen-middle', 'sheet.cursorRowIndex = (sheet.topRowIndex+sheet.bottomRowIndex)//2', 'go to the middle row visible on screen') Sheet.addCommand(None, 'go-screen-bottom', 'sheet.cursorRowIndex = sheet.bottomRowIndex', 'go to the last row visible on screen') Sheet.addCommand('zr', 'go-row-number', 'go_row_number(int(input("move to row number: ")))', 'go to the given row number (0-based, negative from end); warn if clamped') Sheet.addCommand('<', 'go-prev-value', 'moveToNextRow(lambda row,sheet=sheet,col=cursorCol,val=cursorTypedValue: col.getTypedValue(row) != val, reverse=True, msg="no different value up this column")', 'go up current column to next value') Sheet.addCommand('>', 'go-next-value', 'moveToNextRow(lambda row,sheet=sheet,col=cursorCol,val=cursorTypedValue: col.getTypedValue(row) != val, msg="no different value down this column")', 'go down current column to next value') Sheet.addCommand('{', 'go-prev-selected', 'moveToNextRow(lambda row,sheet=sheet: sheet.isSelected(row), reverse=True, msg="no previous selected row")', 'go up current column to previous selected row') Sheet.addCommand('}', 'go-next-selected', 'moveToNextRow(lambda row,sheet=sheet: sheet.isSelected(row), msg="no next selected row") ', 'go down current column to next selected row') Sheet.addCommand('z<', 'go-prev-null', 'moveToNextRow(lambda row,col=cursorCol,isnull=isNullFunc(): isnull(col.getValue(row)), reverse=True, msg="no null up this column")', 'go up current column to next null value') Sheet.addCommand('z>', 'go-next-null', 'moveToNextRow(lambda row,col=cursorCol,isnull=isNullFunc(): isnull(col.getValue(row)), msg="no null down this column")', 'go down current column to next null value') for i in range(1, 11): BaseSheet.addCommand(f'Alt+{str(i)[-1]}', f'jump-sheet-{i}', f'vd.push(*(list(s for s in allSheets if s.shortcut==str({i})) or fail("no sheet")))', f'jump to sheet {i}') for i in range(11, 21): BaseSheet.addCommand('', f'jump-sheet-{i}', f'vd.push(*(list(s for s in allSheets if s.shortcut==str({i})) or fail("no sheet")))', f'jump to sheet {i}') BaseSheet.addCommand('', 'jump-sheet', 'jump_sheet(input("jump to sheet: ", completer=CompleteKey(s.name for s in allSheets)))', 'jump to sheet by name') BaseSheet.bindkey('Left', 'go-left') BaseSheet.bindkey('Down', 'go-down') BaseSheet.bindkey('Up', 'go-up') BaseSheet.bindkey('Right', 'go-right') BaseSheet.bindkey('PgDn', 'go-pagedown') BaseSheet.bindkey('PgUp', 'go-pageup') BaseSheet.bindkey('gLeft', 'go-leftmost') BaseSheet.bindkey('gRight', 'go-rightmost') BaseSheet.bindkey('gUp', 'go-top') BaseSheet.bindkey('gDown', 'go-bottom') BaseSheet.bindkey('Home', 'go-top') BaseSheet.bindkey('End', 'go-bottom') # vim-style scrolling with the 'z' prefix Sheet.addCommand('zz', 'scroll-middle', 'sheet.topRowIndex = cursorRowIndex-int(nScreenRows/2)', 'scroll current row to center of screen') Sheet.addCommand('Ctrl+Right', 'go-right-page', 'sheet.cursorVisibleColIndex = sheet.leftVisibleColIndex = rightVisibleColIndex', 'scroll cursor one page right', replay=False) Sheet.addCommand('Ctrl+Left', 'go-left-page', 'pageLeft()', 'scroll cursor one page left', replay=False) Sheet.addCommand(None, 'scroll-left', 'sheet.cursorVisibleColIndex -= options.scroll_incr', 'scroll left by one column increment') Sheet.addCommand(None, 'scroll-right', 'sheet.cursorVisibleColIndex += options.scroll_incr', 'scroll right by one column increment') Sheet.addCommand(None, 'scroll-leftmost', 'sheet.leftVisibleColIndex = cursorVisibleColIndex', 'scroll sheet to leftmost column') Sheet.addCommand(None, 'scroll-rightmost', 'tmp = cursorVisibleColIndex; pageLeft(); sheet.cursorVisibleColIndex = tmp', 'scroll sheet to rightmost column') Sheet.addCommand('zl', 'scroll-cells-right', 'cursorCol.hoffset += cursorCol.visibleWidth-2', 'scroll display of current column to the right') Sheet.addCommand('zh', 'scroll-cells-left', 'cursorCol.hoffset -= cursorCol.visibleWidth-2', 'scroll display of current column to the left') Sheet.addCommand('gzl', 'scroll-cells-rightmost', 'cursorCol.hoffset = -cursorCol.visibleWidth+2', 'scroll display of current column to the end') Sheet.addCommand('gzh', 'scroll-cells-leftmost', 'cursorCol.hoffset = 0', 'scroll display of current column to the beginning') Sheet.addCommand('zj', 'scroll-cells-down', 'cursorCol.voffset += 1 if cursorCol.height > 1 else fail("multiline column needed for scrolling")', 'scroll display of current column down one line') Sheet.addCommand('zk', 'scroll-cells-up', 'cursorCol.voffset -= 1 if cursorCol.height > 1 else fail("multiline column needed for scrolling")', 'scroll display of current column up one line') Sheet.addCommand('gzj', 'scroll-cells-bottom', 'cursorCol.voffset = -1', 'scroll display of current column to the bottom') Sheet.addCommand('gzk', 'scroll-cells-top', 'cursorCol.voffset = 0', 'scroll display of current column to the top') Sheet.addCommand(None, 'go-end', 'sheet.cursorRowIndex = len(rows)-1; sheet.cursorVisibleColIndex = len(visibleCols)-1', 'go to last row and last column') Sheet.addCommand(None, 'go-home', 'sheet.topRowIndex = sheet.cursorRowIndex = 0; sheet.leftVisibleColIndex = sheet.cursorVisibleColIndex = 0', 'go to first row and first column') BaseSheet.bindkey('Ctrl+ScrollUp', 'scroll-left') BaseSheet.bindkey('Ctrl+ScrollDown', 'scroll-right') BaseSheet.bindkey('zUp', 'scroll-up') BaseSheet.bindkey('zDown', 'scroll-down') BaseSheet.bindkey('zLeft', 'scroll-left') BaseSheet.bindkey('zRight', 'scroll-right') # vim-like keybindings BaseSheet.bindkey('h', 'go-left') BaseSheet.bindkey('j', 'go-down') BaseSheet.bindkey('k', 'go-up') BaseSheet.bindkey('l', 'go-right') BaseSheet.bindkey('Ctrl+F', 'go-pagedown') BaseSheet.bindkey('Ctrl+B', 'go-pageup') BaseSheet.bindkey('gg', 'go-top') BaseSheet.bindkey('G', 'go-bottom') BaseSheet.bindkey('gj', 'go-bottom') BaseSheet.bindkey('gk', 'go-top') BaseSheet.bindkey('gh', 'go-leftmost') BaseSheet.bindkey('gl', 'go-rightmost') BaseSheet.addCommand('Ctrl+^', 'jump-prev', 'vd.activeStack[1:] or fail("no previous sheet"); vd.push(vd.activeStack[1])', 'jump to previous sheet in this pane') BaseSheet.addCommand('gCtrl+^', 'jump-first', 'vd.push(vd.activeStack[-1])', 'jump to first sheet') BaseSheet.addCommand('BUTTON1_RELEASED', 'no-op', 'pass', 'do nothing') BaseSheet.addCommand(None, 'mouse-enable', 'b = vd.enableMouse(True); status("mouse "+("ON" if b else "OFF"))', 'enable mouse events') BaseSheet.addCommand(None, 'mouse-disable', 'b = vd.enableMouse(False); status("mouse "+("ON" if b else "OFF"))', 'disable mouse events') vd.addGlobals({'rotateRange': rotateRange}) vd.addMenuItems(''' View > Other sheet > previous sheet > jump-prev View > Other sheet > first sheet > jump-first Row > Goto > top > go-top Row > Goto > bottom > go-bottom Row > Goto > screen top > go-screen-top Row > Goto > screen bottom > go-screen-bottom Row > Goto > screen middle > go-screen-middle Row > Goto > previous > page > go-pageup Row > Goto > previous > half page > go-pageup-half Row > Goto > previous > null > go-prev-null Row > Goto > previous > value > go-prev-value Row > Goto > previous > selected > go-prev-selected Row > Goto > next > page > go-pagedown Row > Goto > next > half page > go-pagedown-half Row > Goto > next > null > go-next-null Row > Goto > next > value > go-next-value Row > Goto > next > selected > go-next-selected Row > Goto > by number > go-row-number ''')