コード例 #1
0
    def __init__(self, stdscr, lineObjs):
        curses.use_default_colors()
        self.stdscr = stdscr
        self.colorPrinter = ColorPrinter(self.stdscr)

        self.lineObjs = lineObjs
        self.hoverIndex = 0
        self.scrollOffset = 0
        self.scrollBar = ScrollBar(self.colorPrinter, lineObjs, self)
        self.helperChrome = HelperChrome(self.colorPrinter, self)
        (self.oldmaxy, self.oldmaxx) = self.getScreenDimensions()
        self.mode = SELECT_MODE

        self.simpleLines = []
        self.lineMatches = []
        # lets loop through and split
        for key, lineObj in self.lineObjs.items():
            lineObj.setController(self)
            if (lineObj.isSimple()):
                self.simpleLines.append(lineObj)
            else:
                self.lineMatches.append(lineObj)

        self.numLines = len(lineObjs.keys())
        self.numMatches = len(self.lineMatches)

        self.setHover(self.hoverIndex, True)

        # the scroll offset might not start off
        # at 0 if our first real match is WAY
        # down the screen -- so lets init it to
        # a valid value after we have all our line objects
        self.updateScrollOffset()
        logger.addEvent('init')
コード例 #2
0
ファイル: screenControl.py プロジェクト: ywy2090/PathPicker
    def __init__(self, flags, keyBindings, stdscr, lineObjs, cursesAPI):
        self.stdscr = stdscr
        self.cursesAPI = cursesAPI
        self.cursesAPI.useDefaultColors()
        self.colorPrinter = ColorPrinter(self.stdscr, cursesAPI)
        self.flags = flags
        self.keyBindings = keyBindings

        self.lineObjs = lineObjs
        self.hoverIndex = 0
        self.scrollOffset = 0
        self.scrollBar = ScrollBar(self.colorPrinter, lineObjs, self)
        self.helperChrome = HelperChrome(self.colorPrinter, self, flags)
        (self.oldmaxy, self.oldmaxx) = self.getScreenDimensions()
        self.mode = SELECT_MODE

        # lets loop through and split
        self.lineMatches = []

        for lineObj in self.lineObjs.values():
            lineObj.controller = self
            if not lineObj.isSimple():
                self.lineMatches.append(lineObj)

        # begin tracking dirty state
        self.resetDirty()

        if self.flags.args.all:
            self.toggleSelectAll()

        self.numLines = len(lineObjs.keys())
        self.numMatches = len(self.lineMatches)

        self.setHover(self.hoverIndex, True)

        # the scroll offset might not start off
        # at 0 if our first real match is WAY
        # down the screen -- so lets init it to
        # a valid value after we have all our line objects
        self.updateScrollOffset()

        logger.addEvent('init')
コード例 #3
0
ファイル: screenControl.py プロジェクト: facebook/PathPicker
    def __init__(self, flags, keyBindings, stdscr, lineObjs, cursesAPI):
        self.stdscr = stdscr
        self.cursesAPI = cursesAPI
        self.cursesAPI.useDefaultColors()
        self.colorPrinter = ColorPrinter(self.stdscr, cursesAPI)
        self.flags = flags
        self.keyBindings = keyBindings

        self.lineObjs = lineObjs
        self.hoverIndex = 0
        self.scrollOffset = 0
        self.scrollBar = ScrollBar(self.colorPrinter, lineObjs, self)
        self.helperChrome = HelperChrome(self.colorPrinter, self, flags)
        (self.oldmaxy, self.oldmaxx) = self.getScreenDimensions()
        self.mode = SELECT_MODE

        # lets loop through and split
        self.lineMatches = []

        for lineObj in self.lineObjs.values():
            lineObj.controller = self
            if not lineObj.isSimple():
                self.lineMatches.append(lineObj)

        # begin tracking dirty state
        self.resetDirty()

        if self.flags.args.all:
            self.toggleSelectAll()

        self.numLines = len(lineObjs.keys())
        self.numMatches = len(self.lineMatches)

        self.setHover(self.hoverIndex, True)

        # the scroll offset might not start off
        # at 0 if our first real match is WAY
        # down the screen -- so lets init it to
        # a valid value after we have all our line objects
        self.updateScrollOffset()

        logger.addEvent('init')
コード例 #4
0
class Controller(object):
    def __init__(self, flags, stdscr, lineObjs, cursesAPI):
        self.stdscr = stdscr
        self.cursesAPI = cursesAPI
        self.cursesAPI.useDefaultColors()
        self.colorPrinter = ColorPrinter(self.stdscr, cursesAPI)
        self.flags = flags

        self.lineObjs = lineObjs
        self.hoverIndex = 0
        self.scrollOffset = 0
        self.scrollBar = ScrollBar(self.colorPrinter, lineObjs, self)
        self.helperChrome = HelperChrome(self.colorPrinter, self, flags)
        (self.oldmaxy, self.oldmaxx) = self.getScreenDimensions()
        self.mode = SELECT_MODE

        # lets loop through and split
        self.lineMatches = []

        for lineObj in self.lineObjs.values():
            lineObj.controller = self
            if not lineObj.isSimple():
                self.lineMatches.append(lineObj)

        self.numLines = len(lineObjs.keys())
        self.numMatches = len(self.lineMatches)

        # begin tracking dirty state
        self.resetDirty()

        self.setHover(self.hoverIndex, True)

        # the scroll offset might not start off
        # at 0 if our first real match is WAY
        # down the screen -- so lets init it to
        # a valid value after we have all our line objects
        self.updateScrollOffset()

        logger.addEvent('init')

    def getScrollOffset(self):
        return self.scrollOffset

    def getScreenDimensions(self):
        return self.stdscr.getmaxyx()

    def getChromeBoundaries(self):
        (maxy, maxx) = self.stdscr.getmaxyx()
        minx = CHROME_MIN_X if self.scrollBar.getIsActivated(
        ) or self.mode == X_MODE else 0
        maxy = self.helperChrome.reduceMaxY(maxy)
        maxx = self.helperChrome.reduceMaxX(maxx)
        # format of (MINX, MINY, MAXX, MAXY)
        return (minx, CHROME_MIN_Y, maxx, maxy)

    def getViewportHeight(self):
        (minx, miny, maxx, maxy) = self.getChromeBoundaries()
        return maxy - miny

    def setHover(self, index, val):
        self.lineMatches[index].setHover(val)

    def toggleSelect(self):
        self.lineMatches[self.hoverIndex].toggleSelect()

    def toggleSelectAll(self):
        paths = set()
        for line in self.lineMatches:
            if line.getPath() not in paths:
                paths.add(line.getPath())
                line.toggleSelect()

    def setSelect(self, val):
        self.lineMatches[self.hoverIndex].setSelect(val)

    def describeFile(self):
        self.helperChrome.outputDescription(self.lineMatches[self.hoverIndex])

    def control(self):
        # we start out by printing everything we need to
        self.printAll()
        self.resetDirty()
        self.moveCursor()
        while True:
            inKey = self.getKey()
            self.checkResize()
            self.processInput(inKey)
            self.processDirty()
            self.resetDirty()
            self.moveCursor()
            self.stdscr.refresh()

    def checkResize(self):
        (maxy, maxx) = self.getScreenDimensions()
        if (maxy is not self.oldmaxy or maxx is not self.oldmaxx):
            # we resized so print all!
            self.printAll()
            self.resetDirty()
            self.stdscr.refresh()
            logger.addEvent('resize')
        (self.oldmaxy, self.oldmaxx) = self.getScreenDimensions()

    def updateScrollOffset(self):
        """
          yay scrolling logic! we will start simple here
          and basically just center the viewport to current
          matched line
      """
        windowHeight = self.getViewportHeight()
        halfHeight = int(round(windowHeight / 2.0))

        # important, we need to get the real SCREEN position
        # of the hover index, not its index within our matches
        hovered = self.lineMatches[self.hoverIndex]
        desiredTopRow = hovered.getScreenIndex() - halfHeight

        oldOffset = self.scrollOffset
        desiredTopRow = max(desiredTopRow, 0)
        newOffset = -desiredTopRow
        # lets add in some leeway -- dont bother repositioning
        # if the old offset is within 1/2 of the window height
        # of our desired (unless we absolutely have to)
        if abs(newOffset -
               oldOffset) > halfHeight / 2 or self.hoverIndex + oldOffset < 0:
            # need to reassign now we have gone too far
            self.scrollOffset = newOffset
        if oldOffset is not self.scrollOffset:
            self.dirtyAll()

        # also update our scroll bar
        self.scrollBar.calcBoxFractions()

    def pageDown(self):
        pageHeight = (int)(self.getViewportHeight() * 0.5)
        self.moveIndex(pageHeight)

    def pageUp(self):
        pageHeight = (int)(self.getViewportHeight() * 0.5)
        self.moveIndex(-pageHeight)

    def moveIndex(self, delta):
        newIndex = (self.hoverIndex + delta) % self.numMatches
        self.jumpToIndex(newIndex)
        # also clear the description pane if necessary
        self.helperChrome.clearDescriptionPane()

    def jumpToIndex(self, newIndex):
        self.setHover(self.hoverIndex, False)
        self.hoverIndex = newIndex
        self.setHover(self.hoverIndex, True)
        self.updateScrollOffset()

    def processInput(self, key):
        if key == 'UP' or key == 'k':
            self.moveIndex(-1)
        elif key == 'DOWN' or key == 'j':
            self.moveIndex(1)
        elif key == 'x':
            self.toggleXMode()
        elif key == 'c':
            self.beginEnterCommand()
        elif key == ' ' or key == 'NPAGE':
            self.pageDown()
        elif key == 'b' or key == 'PPAGE':
            self.pageUp()
        elif key == 'g' or key == 'HOME':
            self.jumpToIndex(0)
        elif (key == 'G' and not self.mode == X_MODE) or key == 'END':
            self.jumpToIndex(self.numMatches - 1)
        elif key == 'd':
            self.describeFile()
        elif key == 'f':
            self.toggleSelect()
        elif key == 'F':
            self.toggleSelect()
            self.moveIndex(1)
        elif key == 'A' and not self.mode == X_MODE:
            self.toggleSelectAll()
        elif key == 'ENTER' and (not self.flags.getAllInput()
                                 or len(self.flags.getPresetCommand())):
            # it does not make sense to process an 'ENTER' keypress if we're in the allInput
            # mode and there is not a preset command.
            self.onEnter()
        elif key == 'q':
            output.outputNothing()
            # this will get the appropriate selection and save it to a file for reuse
            # before exiting the program
            self.getPathsToUse()
            self.cursesAPI.exit()
        elif self.mode == X_MODE and key in lbls:
            self.selectXMode(key)
        pass

    def getPathsToUse(self):
        # if we have selected paths, those, otherwise hovered
        toUse = self.getSelectedPaths()
        if not toUse:
            toUse = self.getHoveredPaths()

        # save the selection we are using
        if self.cursesAPI.allowFileOutput():
            output.outputSelection(toUse)
        return toUse

    def getSelectedPaths(self):
        return [
            lineObj for (index, lineObj) in enumerate(self.lineMatches)
            if lineObj.getSelected()
        ]

    def getHoveredPaths(self):
        return [
            lineObj for (index, lineObj) in enumerate(self.lineMatches)
            if index == self.hoverIndex
        ]

    def showAndGetCommand(self):
        pathObjs = self.getPathsToUse()
        paths = [pathObj.getPath() for pathObj in pathObjs]
        (maxy, maxx) = self.getScreenDimensions()

        # Alright this is a bit tricy -- for tall screens, we try to aim
        # the command prompt right at the middle of the screen so you dont
        # have to shift your eyes down or up a bunch
        beginHeight = int(round(maxy / 2) - len(paths) / 2.0)
        # but if you have a TON of paths, we are going to start printing
        # way off screen. in this case lets just slap the prompt
        # at the bottom so we can fit as much as possible.
        #
        # There could better option here to slowly increase the prompt
        # height to the bottom, but this is good enough for now...
        if beginHeight <= 1:
            beginHeight = maxy - 6

        borderLine = '=' * len(SHORT_COMMAND_PROMPT)
        promptLine = '.' * len(SHORT_COMMAND_PROMPT)
        # from helper chrome code
        maxPathLength = maxx - 5
        if self.helperChrome.getIsSidebarMode():
            # need to be shorter to not go into side bar
            maxPathLength = len(SHORT_COMMAND_PROMPT) + 18

        # first lets print all the paths
        startHeight = beginHeight - 1 - len(paths)
        try:
            self.colorPrinter.addstr(startHeight - 3, 0, borderLine)
            self.colorPrinter.addstr(startHeight - 2, 0, SHORT_PATHS_HEADER)
            self.colorPrinter.addstr(startHeight - 1, 0, borderLine)
        except curses.error:
            pass

        for index, path in enumerate(paths):
            try:
                self.colorPrinter.addstr(startHeight + index, 0,
                                         path[0:maxPathLength])
            except curses.error:
                pass

        # first print prompt
        try:
            self.colorPrinter.addstr(beginHeight, 0, SHORT_COMMAND_PROMPT)
            self.colorPrinter.addstr(beginHeight + 1, 0, SHORT_COMMAND_PROMPT2)
        except curses.error:
            pass
        # then line to distinguish and prompt line
        try:
            self.colorPrinter.addstr(beginHeight - 1, 0, borderLine)
            self.colorPrinter.addstr(beginHeight + 2, 0, borderLine)
            self.colorPrinter.addstr(beginHeight + 3, 0, promptLine)
        except curses.error:
            pass

        self.stdscr.refresh()
        self.cursesAPI.echo()
        maxX = int(round(maxx - 1))

        command = self.stdscr.getstr(beginHeight + 3, 0, maxX)
        return command

    def beginEnterCommand(self):
        self.stdscr.erase()
        # first check if they are trying to enter command mode
        # but already have a command...
        if len(self.flags.getPresetCommand()):
            self.helperChrome.output(self.mode)
            (minX, minY, _, maxY) = self.getChromeBoundaries()
            yStart = (maxY + minY) / 2 - 3
            self.printProvidedCommandWarning(yStart, minX)
            self.stdscr.refresh()
            self.getKey()
            self.mode = SELECT_MODE
            self.dirtyAll()
            return

        self.mode = COMMAND_MODE
        self.helperChrome.output(self.mode)
        logger.addEvent('enter_command_mode')

        command = self.showAndGetCommand()
        if len(command) == 0:
            # go back to selection mode and repaint
            self.mode = SELECT_MODE
            self.cursesAPI.noecho()
            self.dirtyAll()
            logger.addEvent('exit_command_mode')
            return
        lineObjs = self.getPathsToUse()
        output.execComposedCommand(command, lineObjs)
        sys.exit(0)

    def onEnter(self):
        lineObjs = self.getPathsToUse()
        if not lineObjs:
            # nothing selected, assume we want hovered
            lineObjs = self.getHoveredPaths()
        logger.addEvent('selected_num_files', len(lineObjs))

        # commands passed from the command line get used immediately
        presetCommand = self.flags.getPresetCommand()
        if len(presetCommand) > 0:
            output.execComposedCommand(presetCommand, lineObjs)
        else:
            output.editFiles(lineObjs)

        sys.exit(0)

    def resetDirty(self):
        # reset all dirty state for our components
        self.dirty = False
        self.dirtyIndexes = []

    def dirtyLine(self, index):
        self.dirtyIndexes.append(index)

    def dirtyAll(self):
        self.dirty = True

    def processDirty(self):
        if self.dirty:
            self.printAll()
            return
        (minx, miny, maxx, maxy) = self.getChromeBoundaries()
        didClearLine = False
        for index in self.dirtyIndexes:
            y = miny + index + self.getScrollOffset()
            if y >= miny and y < maxy:
                didClearLine = True
                self.clearLine(y)
                self.lineObjs[index].output(self.colorPrinter)
        if didClearLine and self.helperChrome.getIsSidebarMode():
            # now we need to output the chrome again since on wide
            # monitors we will have cleared out a line of the chrome
            self.helperChrome.output(self.mode)

    def clearLine(self, y):
        '''Clear a line of content, excluding the chrome'''
        (minx, _, _, _) = self.getChromeBoundaries()
        (_, maxx) = self.stdscr.getmaxyx()
        charsToDelete = range(minx, maxx)
        # we go in the **reverse** order since the original documentation
        # of delchar (http://dell9.ma.utexas.edu/cgi-bin/man-cgi?delch+3)
        # mentions that delchar actually moves all the characters to the right
        # of the cursor
        for x in reversed(charsToDelete):
            self.stdscr.delch(y, x)

    def printAll(self):
        self.stdscr.erase()
        self.printLines()
        self.printScroll()
        self.printXMode()
        self.printChrome()

    def printLines(self):
        for lineObj in self.lineObjs.values():
            lineObj.output(self.colorPrinter)

    def printScroll(self):
        self.scrollBar.output()

    def printProvidedCommandWarning(self, yStart, xStart):
        self.colorPrinter.addstr(
            yStart, xStart, 'Oh no! You already provided a command so ' +
            'you cannot enter command mode.',
            self.colorPrinter.getAttributes(curses.COLOR_WHITE,
                                            curses.COLOR_RED, 0))

        self.colorPrinter.addstr(
            yStart + 1, xStart, 'The command you provided was "%s" ' %
            self.flags.getPresetCommand())
        self.colorPrinter.addstr(
            yStart + 2, xStart, 'Press any key to go back to selecting paths.')

    def printChrome(self):
        self.helperChrome.output(self.mode)

    def moveCursor(self):
        x = CHROME_MIN_X if self.scrollBar.getIsActivated() else 0
        y = self.lineMatches[
            self.hoverIndex].getScreenIndex() + self.scrollOffset
        self.stdscr.move(y, x)

    def getKey(self):
        charCode = self.stdscr.getch()
        return CODE_TO_CHAR.get(charCode, '')

    def toggleXMode(self):
        self.mode = X_MODE if self.mode != X_MODE else SELECT_MODE
        self.printAll()

    def printXMode(self):
        if self.mode == X_MODE:
            (maxy, _) = self.scrollBar.screenControl.getScreenDimensions()
            topY = maxy - 2
            minY = self.scrollBar.getMinY() - 1
            for i in range(minY, topY + 1):
                idx = i - minY
                if idx < len(lbls):
                    self.colorPrinter.addstr(i, 1, lbls[idx])

    def selectXMode(self, key):
        lineObj = self.lineObjs[lbls.index(key) - self.scrollOffset]
        if type(lineObj) == format.LineMatch:
            lineMatchIndex = self.lineMatches.index(lineObj)
            self.hoverIndex = lineMatchIndex
            self.toggleSelect()
コード例 #5
0
ファイル: screenControl.py プロジェクト: sr105/PathPicker
class Controller(object):

    def __init__(self, flags, stdscr, lineObjs, cursesAPI):
        self.stdscr = stdscr
        self.cursesAPI = cursesAPI
        self.cursesAPI.useDefaultColors()
        self.colorPrinter = ColorPrinter(self.stdscr, cursesAPI)
        self.flags = flags

        self.lineObjs = lineObjs
        self.hoverIndex = 0
        self.scrollOffset = 0
        self.scrollBar = ScrollBar(self.colorPrinter, lineObjs, self)
        self.helperChrome = HelperChrome(self.colorPrinter, self)
        (self.oldmaxy, self.oldmaxx) = self.getScreenDimensions()
        self.mode = SELECT_MODE

        # lets loop through and split
        self.lineMatches = []

        for lineObj in self.lineObjs.values():
            lineObj.controller = self
            if not lineObj.isSimple():
                self.lineMatches.append(lineObj)

        self.numLines = len(lineObjs.keys())
        self.numMatches = len(self.lineMatches)

        # begin tracking dirty state
        self.resetDirty()

        self.setHover(self.hoverIndex, True)

        # the scroll offset might not start off
        # at 0 if our first real match is WAY
        # down the screen -- so lets init it to
        # a valid value after we have all our line objects
        self.updateScrollOffset()

        logger.addEvent('init')

    def getScrollOffset(self):
        return self.scrollOffset

    def getScreenDimensions(self):
        return self.stdscr.getmaxyx()

    def getChromeBoundaries(self):
        (maxy, maxx) = self.stdscr.getmaxyx()
        minx = CHROME_MIN_X if self.scrollBar.getIsActivated() else 0
        maxy = self.helperChrome.reduceMaxY(maxy)
        maxx = self.helperChrome.reduceMaxX(maxx)
        # format of (MINX, MINY, MAXX, MAXY)
        return (minx, CHROME_MIN_Y, maxx, maxy)

    def getViewportHeight(self):
        (minx, miny, maxx, maxy) = self.getChromeBoundaries()
        return maxy - miny

    def setHover(self, index, val):
        self.lineMatches[index].setHover(val)

    def toggleSelect(self):
        self.lineMatches[self.hoverIndex].toggleSelect()

    def toggleSelectAll(self):
        files = set()
        for line in self.lineMatches:
            if line.getFile() not in files:
                files.add(line.getFile())
                line.toggleSelect()

    def setSelect(self, val):
        self.lineMatches[self.hoverIndex].setSelect(val)

    def control(self):
        # we start out by printing everything we need to
        self.printAll()
        self.resetDirty()
        self.moveCursor()
        while True:
            inKey = self.getKey()
            self.checkResize()
            self.processInput(inKey)
            self.processDirty()
            self.resetDirty()
            self.moveCursor()
            self.stdscr.refresh()

    def checkResize(self):
        (maxy, maxx) = self.getScreenDimensions()
        if (maxy is not self.oldmaxy or maxx is not self.oldmaxx):
            # we resized so print all!
            self.printAll()
            self.resetDirty()
            self.stdscr.refresh()
            logger.addEvent('resize')
        (self.oldmaxy, self.oldmaxx) = self.getScreenDimensions()

    def updateScrollOffset(self):
        """
          yay scrolling logic! we will start simple here
          and basically just center the viewport to current
          matched line
      """
        windowHeight = self.getViewportHeight()
        halfHeight = int(round(windowHeight / 2.0))

        # important, we need to get the real SCREEN position
        # of the hover index, not its index within our matches
        hovered = self.lineMatches[self.hoverIndex]
        desiredTopRow = hovered.getScreenIndex() - halfHeight

        oldOffset = self.scrollOffset
        desiredTopRow = max(desiredTopRow, 0)
        newOffset = -desiredTopRow
        # lets add in some leeway -- dont bother repositioning
        # if the old offset is within 1/2 of the window height
        # of our desired (unless we absolutely have to)
        if abs(newOffset -
               oldOffset) > halfHeight / 2 or self.hoverIndex + oldOffset < 0:
            # need to reassign now we have gone too far
            self.scrollOffset = newOffset
        if oldOffset is not self.scrollOffset:
            self.dirtyAll()

        # also update our scroll bar
        self.scrollBar.calcBoxFractions()

    def pageDown(self):
        pageHeight = (int)(self.getViewportHeight() * 0.5)
        self.moveIndex(pageHeight)

    def pageUp(self):
        pageHeight = (int)(self.getViewportHeight() * 0.5)
        self.moveIndex(-pageHeight)

    def moveIndex(self, delta):
        newIndex = (self.hoverIndex + delta) % self.numMatches
        self.jumpToIndex(newIndex)

    def jumpToIndex(self, newIndex):
        self.setHover(self.hoverIndex, False)
        self.hoverIndex = newIndex
        self.setHover(self.hoverIndex, True)
        self.updateScrollOffset()

    def processInput(self, key):
        if key == 'UP' or key == 'k':
            self.moveIndex(-1)
        elif key == 'DOWN' or key == 'j':
            self.moveIndex(1)
        elif key == 'c':
            self.beginEnterCommand()
        elif key == ' ' or key == 'PAGE_DOWN':
            self.pageDown()
        elif key == 'b' or key == 'PAGE_UP':
            self.pageUp()
        elif key == 'g':
            self.jumpToIndex(0)
        elif key == 'G':
            self.jumpToIndex(self.numMatches - 1)
        elif key == 'f':
            self.toggleSelect()
        elif key == 'A':
            self.toggleSelectAll()
        elif key == 'ENTER':
            self.onEnter()
        elif key == 'q':
            output.outputNothing()
            # this will get the appropriate selection and save it to a file for reuse
            # before exiting the program
            self.getFilesToUse()
            self.cursesAPI.exit()
        pass

    def getFilesToUse(self):
        # if we have select files, those, otherwise hovered
        toUse = self.getSelectedFiles()
        if not toUse:
            toUse = self.getHoveredFiles()

        # save the selection we are using
        if self.cursesAPI.allowFileOutput():
            output.outputSelection(toUse)
        return toUse

    def getSelectedFiles(self):
        return [lineObj for (index, lineObj) in enumerate(self.lineMatches)
                if lineObj.getSelected()]

    def getHoveredFiles(self):
        return [lineObj for (index, lineObj) in enumerate(self.lineMatches)
                if index == self.hoverIndex]

    def showAndGetCommand(self):
        fileObjs = self.getFilesToUse()
        files = [fileObj.getFile() for fileObj in fileObjs]
        (maxy, maxx) = self.getScreenDimensions()
        halfHeight = int(round(maxy / 2) - len(files) / 2.0)

        borderLine = '=' * len(SHORT_COMMAND_PROMPT)
        promptLine = '.' * len(SHORT_COMMAND_PROMPT)
        # from helper chrome code
        maxFileLength = maxx - 5
        if self.helperChrome.getIsSidebarMode():
            # need to be shorter to not go into side bar
            maxFileLength = len(SHORT_COMMAND_PROMPT) + 18

        # first lets print all the files
        startHeight = halfHeight - 1 - len(files)
        try:
            self.stdscr.addstr(startHeight - 3, 0, borderLine)
            self.stdscr.addstr(startHeight - 2, 0, SHORT_FILES_HEADER)
            self.stdscr.addstr(startHeight - 1, 0, borderLine)
            for index, file in enumerate(files):
                self.stdscr.addstr(startHeight + index, 0,
                                   file[0:maxFileLength])
        except curses.error:
            pass

        # first print prompt
        try:
            self.stdscr.addstr(halfHeight, 0, SHORT_COMMAND_PROMPT)
            self.stdscr.addstr(halfHeight + 1, 0, SHORT_COMMAND_PROMPT2)
        except curses.error:
            pass
        # then line to distinguish and prompt line
        try:
            self.stdscr.addstr(halfHeight - 1, 0, borderLine)
            self.stdscr.addstr(halfHeight + 2, 0, borderLine)
            self.stdscr.addstr(halfHeight + 3, 0, promptLine)
        except curses.error:
            pass

        self.stdscr.refresh()
        self.cursesAPI.echo()
        maxX = int(round(maxx - 1))

        command = self.stdscr.getstr(halfHeight + 3, 0, maxX)
        return command

    def beginEnterCommand(self):
        self.stdscr.erase()
        # first check if they are trying to enter command mode
        # but already have a command...
        if len(self.flags.getPresetCommand()):
            self.helperChrome.output(self.mode)
            (_, minY, _, maxY) = self.getChromeBoundaries()
            yStart = (maxY + minY) / 2 - 3
            self.printProvidedCommandWarning(yStart)
            self.stdscr.refresh()
            self.getKey()
            self.mode = SELECT_MODE
            self.dirtyAll()
            return

        self.mode = COMMAND_MODE
        self.helperChrome.output(self.mode)
        logger.addEvent('enter_command_mode')

        command = self.showAndGetCommand()
        if len(command) == 0:
            # go back to selection mode and repaint
            self.mode = SELECT_MODE
            self.cursesAPI.noecho()
            self.dirtyAll()
            logger.addEvent('exit_command_mode')
            return
        lineObjs = self.getFilesToUse()
        output.execComposedCommand(command, lineObjs)
        sys.exit(0)

    def onEnter(self):
        lineObjs = self.getFilesToUse()
        if not lineObjs:
            # nothing selected, assume we want hovered
            lineObjs = self.getHoveredFiles()
        logger.addEvent('selected_num_files', len(lineObjs))

        # commands passed from the command line get used immediately
        presetCommand = self.flags.getPresetCommand()
        if len(presetCommand) > 0:
            output.execComposedCommand(presetCommand, lineObjs)
        else:
            output.editFiles(lineObjs)

        sys.exit(0)

    def resetDirty(self):
        # reset all dirty state for our components
        self.dirty = False
        self.dirtyIndexes = []

    def dirtyLine(self, index):
        self.dirtyIndexes.append(index)

    def dirtyAll(self):
        self.dirty = True

    def processDirty(self):
        if self.dirty:
            self.printAll()
            return
        (minx, miny, maxx, maxy) = self.getChromeBoundaries()
        didClearLine = False
        for index in self.dirtyIndexes:
            y = miny + index + self.getScrollOffset()
            if y >= miny or y < maxy:
                didClearLine = True
                self.clearLine(y)
                self.lineObjs[index].output(self.colorPrinter)
        if didClearLine and self.helperChrome.getIsSidebarMode():
            # now we need to output the chrome again since on wide
            # monitors we will have cleared out a line of the chrome
            self.helperChrome.output(self.mode)

    def clearLine(self, y):
        '''Clear a line of content, excluding the chrome'''
        (minx, _, _, _) = self.getChromeBoundaries()
        (_, maxx) = self.stdscr.getmaxyx()
        charsToDelete = range(minx, maxx)
        # we go in the **reverse** order since the original documentation
        # of delchar (http://dell9.ma.utexas.edu/cgi-bin/man-cgi?delch+3)
        # mentions that delchar actually moves all the characters to the right
        # of the cursor
        for x in reversed(charsToDelete):
            self.stdscr.delch(y, x)

    def printAll(self):
        self.stdscr.erase()
        self.printLines()
        self.printScroll()
        self.printChrome()

    def printLines(self):
        for lineObj in self.lineObjs.values():
            lineObj.output(self.colorPrinter)

    def printScroll(self):
        self.scrollBar.output()

    def printProvidedCommandWarning(self, yStart):
        self.colorPrinter.setAttributes(
            curses.COLOR_WHITE, curses.COLOR_RED, 0)
        self.stdscr.addstr(yStart, 0, 'Oh no! You already provided a command so ' +
                           'you cannot enter command mode.')
        self.stdscr.attrset(0)
        self.stdscr.addstr(
            yStart + 1, 0, 'The command you provided was "%s" ' % self.flags.getPresetCommand())
        self.stdscr.addstr(
            yStart + 2, 0, 'Press any key to go back to selecting files.')

    def printChrome(self):
        self.helperChrome.output(self.mode)

    def moveCursor(self):
        x = CHROME_MIN_X if self.scrollBar.getIsActivated() else 0
        y = self.lineMatches[
            self.hoverIndex].getScreenIndex() + self.scrollOffset
        self.stdscr.move(y, x)

    def getKey(self):
        charCode = self.stdscr.getch()
        return CODE_TO_CHAR.get(charCode, '')
コード例 #6
0
ファイル: screenControl.py プロジェクト: facebook/PathPicker
class Controller(object):

    def __init__(self, flags, keyBindings, stdscr, lineObjs, cursesAPI):
        self.stdscr = stdscr
        self.cursesAPI = cursesAPI
        self.cursesAPI.useDefaultColors()
        self.colorPrinter = ColorPrinter(self.stdscr, cursesAPI)
        self.flags = flags
        self.keyBindings = keyBindings

        self.lineObjs = lineObjs
        self.hoverIndex = 0
        self.scrollOffset = 0
        self.scrollBar = ScrollBar(self.colorPrinter, lineObjs, self)
        self.helperChrome = HelperChrome(self.colorPrinter, self, flags)
        (self.oldmaxy, self.oldmaxx) = self.getScreenDimensions()
        self.mode = SELECT_MODE

        # lets loop through and split
        self.lineMatches = []

        for lineObj in self.lineObjs.values():
            lineObj.controller = self
            if not lineObj.isSimple():
                self.lineMatches.append(lineObj)

        # begin tracking dirty state
        self.resetDirty()

        if self.flags.args.all:
            self.toggleSelectAll()

        self.numLines = len(lineObjs.keys())
        self.numMatches = len(self.lineMatches)

        self.setHover(self.hoverIndex, True)

        # the scroll offset might not start off
        # at 0 if our first real match is WAY
        # down the screen -- so lets init it to
        # a valid value after we have all our line objects
        self.updateScrollOffset()

        logger.addEvent('init')

    def getScrollOffset(self):
        return self.scrollOffset

    def getScreenDimensions(self):
        return self.stdscr.getmaxyx()

    def getChromeBoundaries(self):
        (maxy, maxx) = self.stdscr.getmaxyx()
        minx = CHROME_MIN_X if self.scrollBar.getIsActivated(
        ) or self.mode == X_MODE else 0
        maxy = self.helperChrome.reduceMaxY(maxy)
        maxx = self.helperChrome.reduceMaxX(maxx)
        # format of (MINX, MINY, MAXX, MAXY)
        return (minx, CHROME_MIN_Y, maxx, maxy)

    def getViewportHeight(self):
        (minx, miny, maxx, maxy) = self.getChromeBoundaries()
        return maxy - miny

    def setHover(self, index, val):
        self.lineMatches[index].setHover(val)

    def toggleSelect(self):
        self.lineMatches[self.hoverIndex].toggleSelect()

    def toggleSelectAll(self):
        paths = set()
        for line in self.lineMatches:
            if line.getPath() not in paths:
                paths.add(line.getPath())
                line.toggleSelect()

    def setSelect(self, val):
        self.lineMatches[self.hoverIndex].setSelect(val)

    def describeFile(self):
        self.helperChrome.outputDescription(self.lineMatches[self.hoverIndex])

    def control(self):
        executeKeys = self.flags.getExecuteKeys()

        # we start out by printing everything we need to
        self.printAll()
        self.resetDirty()
        self.moveCursor()
        while True:
            if len(executeKeys) > 0:
                inKey = executeKeys.pop(0)
            else:
                inKey = self.getKey()
            self.checkResize()
            self.processInput(inKey)
            self.processDirty()
            self.resetDirty()
            self.moveCursor()
            self.stdscr.refresh()

    def checkResize(self):
        (maxy, maxx) = self.getScreenDimensions()
        if (maxy is not self.oldmaxy or maxx is not self.oldmaxx):
            # we resized so print all!
            self.printAll()
            self.resetDirty()
            self.updateScrollOffset()
            self.stdscr.refresh()
            logger.addEvent('resize')
        (self.oldmaxy, self.oldmaxx) = self.getScreenDimensions()

    def updateScrollOffset(self):
        """
          yay scrolling logic! we will start simple here
          and basically just center the viewport to current
          matched line
      """
        windowHeight = self.getViewportHeight()
        halfHeight = int(round(windowHeight / 2.0))

        # important, we need to get the real SCREEN position
        # of the hover index, not its index within our matches
        hovered = self.lineMatches[self.hoverIndex]
        desiredTopRow = hovered.getScreenIndex() - halfHeight

        oldOffset = self.scrollOffset
        desiredTopRow = max(desiredTopRow, 0)
        newOffset = -desiredTopRow
        # lets add in some leeway -- dont bother repositioning
        # if the old offset is within 1/2 of the window height
        # of our desired (unless we absolutely have to)
        if abs(newOffset -
               oldOffset) > halfHeight / 2 or self.hoverIndex + oldOffset < 0:
            # need to reassign now we have gone too far
            self.scrollOffset = newOffset
        if oldOffset is not self.scrollOffset:
            self.dirtyAll()

        # also update our scroll bar
        self.scrollBar.calcBoxFractions()

    def pageDown(self):
        pageHeight = (int)(self.getViewportHeight() * 0.5)
        self.moveIndex(pageHeight)

    def pageUp(self):
        pageHeight = (int)(self.getViewportHeight() * 0.5)
        self.moveIndex(-pageHeight)

    def moveIndex(self, delta):
        newIndex = (self.hoverIndex + delta) % self.numMatches
        self.jumpToIndex(newIndex)
        # also clear the description pane if necessary
        self.helperChrome.clearDescriptionPane()

    def jumpToIndex(self, newIndex):
        self.setHover(self.hoverIndex, False)
        self.hoverIndex = newIndex
        self.setHover(self.hoverIndex, True)
        self.updateScrollOffset()

    def processInput(self, key):
        if key == 'UP' or key == 'k':
            self.moveIndex(-1)
        elif key == 'DOWN' or key == 'j':
            self.moveIndex(1)
        elif key == 'x':
            self.toggleXMode()
        elif key == 'c':
            self.beginEnterCommand()
        elif key == ' ' or key == 'NPAGE':
            self.pageDown()
        elif key == 'b' or key == 'PPAGE':
            self.pageUp()
        elif key == 'g' or key == 'HOME':
            self.jumpToIndex(0)
        elif (key == 'G' and not self.mode == X_MODE) or key == 'END':
            self.jumpToIndex(self.numMatches - 1)
        elif key == 'd':
            self.describeFile()
        elif key == 'f':
            self.toggleSelect()
        elif key == 'F':
            self.toggleSelect()
            self.moveIndex(1)
        elif key == 'A' and not self.mode == X_MODE:
            self.toggleSelectAll()
        elif key == 'ENTER' and (not self.flags.getAllInput() or len(self.flags.getPresetCommand())):
            # it does not make sense to process an 'ENTER' keypress if we're in the allInput
            # mode and there is not a preset command.
            self.onEnter()
        elif key == 'q':
            output.outputNothing()
            # this will get the appropriate selection and save it to a file for reuse
            # before exiting the program
            self.getPathsToUse()
            self.cursesAPI.exit()
        elif self.mode == X_MODE and key in lbls:
            self.selectXMode(key)

        for boundKey, command in self.keyBindings.bindings:
            if key == boundKey:
                self.executePreconfiguredCommand(command)

    def getPathsToUse(self):
        # if we have selected paths, those, otherwise hovered
        toUse = self.getSelectedPaths()
        if not toUse:
            toUse = self.getHoveredPaths()

        # save the selection we are using
        if self.cursesAPI.allowFileOutput():
            output.outputSelection(toUse)
        return toUse

    def getSelectedPaths(self):
        return [lineObj for (index, lineObj) in enumerate(self.lineMatches)
                if lineObj.getSelected()]

    def getHoveredPaths(self):
        return [lineObj for (index, lineObj) in enumerate(self.lineMatches)
                if index == self.hoverIndex]

    def showAndGetCommand(self):
        pathObjs = self.getPathsToUse()
        paths = [pathObj.getPath() for pathObj in pathObjs]
        (maxy, maxx) = self.getScreenDimensions()

        # Alright this is a bit tricy -- for tall screens, we try to aim
        # the command prompt right at the middle of the screen so you dont
        # have to shift your eyes down or up a bunch
        beginHeight = int(round(maxy / 2) - len(paths) / 2.0)
        # but if you have a TON of paths, we are going to start printing
        # way off screen. in this case lets just slap the prompt
        # at the bottom so we can fit as much as possible.
        #
        # There could better option here to slowly increase the prompt
        # height to the bottom, but this is good enough for now...
        if beginHeight <= 1:
            beginHeight = maxy - 6

        borderLine = '=' * len(SHORT_COMMAND_PROMPT)
        promptLine = '.' * len(SHORT_COMMAND_PROMPT)
        # from helper chrome code
        maxPathLength = maxx - 5
        if self.helperChrome.getIsSidebarMode():
            # need to be shorter to not go into side bar
            maxPathLength = len(SHORT_COMMAND_PROMPT) + 18

        # first lets print all the paths
        startHeight = beginHeight - 1 - len(paths)
        try:
            self.colorPrinter.addstr(startHeight - 3, 0, borderLine)
            self.colorPrinter.addstr(startHeight - 2, 0, SHORT_PATHS_HEADER)
            self.colorPrinter.addstr(startHeight - 1, 0, borderLine)
        except curses.error:
            pass

        for index, path in enumerate(paths):
            try:
                self.colorPrinter.addstr(
                    startHeight + index,
                    0,
                    path[0:maxPathLength]
                )
            except curses.error:
                pass

        # first print prompt
        try:
            self.colorPrinter.addstr(beginHeight, 0, SHORT_COMMAND_PROMPT)
            self.colorPrinter.addstr(beginHeight + 1, 0, SHORT_COMMAND_PROMPT2)
        except curses.error:
            pass
        # then line to distinguish and prompt line
        try:
            self.colorPrinter.addstr(beginHeight - 1, 0, borderLine)
            self.colorPrinter.addstr(beginHeight + 2, 0, borderLine)
            self.colorPrinter.addstr(beginHeight + 3, 0, promptLine)
        except curses.error:
            pass

        self.stdscr.refresh()
        self.cursesAPI.echo()
        maxX = int(round(maxx - 1))

        command = self.stdscr.getstr(beginHeight + 3, 0, maxX)
        return command

    def beginEnterCommand(self):
        self.stdscr.erase()
        # first check if they are trying to enter command mode
        # but already have a command...
        if len(self.flags.getPresetCommand()):
            self.helperChrome.output(self.mode)
            (minX, minY, _, maxY) = self.getChromeBoundaries()
            yStart = (maxY + minY) / 2 - 3
            self.printProvidedCommandWarning(yStart, minX)
            self.stdscr.refresh()
            self.getKey()
            self.mode = SELECT_MODE
            self.dirtyAll()
            return

        self.mode = COMMAND_MODE
        self.helperChrome.output(self.mode)
        logger.addEvent('enter_command_mode')

        command = self.showAndGetCommand()
        if len(command) == 0:
            # go back to selection mode and repaint
            self.mode = SELECT_MODE
            self.cursesAPI.noecho()
            self.dirtyAll()
            logger.addEvent('exit_command_mode')
            return
        lineObjs = self.getPathsToUse()
        output.execComposedCommand(command, lineObjs)
        sys.exit(0)

    def executePreconfiguredCommand(self, command):
        lineObjs = self.getPathsToUse()
        output.execComposedCommand(command, lineObjs)
        sys.exit(0)

    def onEnter(self):
        lineObjs = self.getPathsToUse()
        if not lineObjs:
            # nothing selected, assume we want hovered
            lineObjs = self.getHoveredPaths()
        logger.addEvent('selected_num_files', len(lineObjs))

        # commands passed from the command line get used immediately
        presetCommand = self.flags.getPresetCommand()
        if len(presetCommand) > 0:
            output.execComposedCommand(presetCommand, lineObjs)
        else:
            output.editFiles(lineObjs)

        sys.exit(0)

    def resetDirty(self):
        # reset all dirty state for our components
        self.dirty = False
        self.dirtyIndexes = []

    def dirtyLine(self, index):
        self.dirtyIndexes.append(index)

    def dirtyAll(self):
        self.dirty = True

    def processDirty(self):
        if self.dirty:
            self.printAll()
            return
        (minx, miny, maxx, maxy) = self.getChromeBoundaries()
        didClearLine = False
        for index in self.dirtyIndexes:
            y = miny + index + self.getScrollOffset()
            if y >= miny and y < maxy:
                didClearLine = True
                self.clearLine(y)
                self.lineObjs[index].output(self.colorPrinter)
        if didClearLine and self.helperChrome.getIsSidebarMode():
            # now we need to output the chrome again since on wide
            # monitors we will have cleared out a line of the chrome
            self.helperChrome.output(self.mode)

    def clearLine(self, y):
        '''Clear a line of content, excluding the chrome'''
        (minx, _, _, _) = self.getChromeBoundaries()
        (_, maxx) = self.stdscr.getmaxyx()
        charsToDelete = range(minx, maxx)
        # we go in the **reverse** order since the original documentation
        # of delchar (http://dell9.ma.utexas.edu/cgi-bin/man-cgi?delch+3)
        # mentions that delchar actually moves all the characters to the right
        # of the cursor
        for x in reversed(charsToDelete):
            self.stdscr.delch(y, x)

    def printAll(self):
        self.stdscr.erase()
        self.printLines()
        self.printScroll()
        self.printXMode()
        self.printChrome()

    def printLines(self):
        for lineObj in self.lineObjs.values():
            lineObj.output(self.colorPrinter)

    def printScroll(self):
        self.scrollBar.output()

    def printProvidedCommandWarning(self, yStart, xStart):
        self.colorPrinter.addstr(yStart, xStart, 'Oh no! You already provided a command so ' +
                                 'you cannot enter command mode.',
                                 self.colorPrinter.getAttributes(curses.COLOR_WHITE,
                                                                 curses.COLOR_RED,
                                                                 0))

        self.colorPrinter.addstr(
            yStart + 1, xStart, 'The command you provided was "%s" ' % self.flags.getPresetCommand())
        self.colorPrinter.addstr(
            yStart + 2, xStart, 'Press any key to go back to selecting paths.')

    def printChrome(self):
        self.helperChrome.output(self.mode)

    def moveCursor(self):
        x = CHROME_MIN_X if self.scrollBar.getIsActivated() else 0
        y = self.lineMatches[
            self.hoverIndex].getScreenIndex() + self.scrollOffset
        self.stdscr.move(y, x)

    def getKey(self):
        charCode = self.stdscr.getch()
        return CODE_TO_CHAR.get(charCode, '')

    def toggleXMode(self):
        self.mode = X_MODE if self.mode != X_MODE else SELECT_MODE
        self.printAll()

    def printXMode(self):
        if self.mode == X_MODE:
            (maxy, _) = self.scrollBar.screenControl.getScreenDimensions()
            topY = maxy - 2
            minY = self.scrollBar.getMinY() - 1
            for i in range(minY, topY + 1):
                idx = i - minY
                if idx < len(lbls):
                    self.colorPrinter.addstr(i, 1, lbls[idx])

    def selectXMode(self, key):
        if (lbls.index(key) >= len(self.lineObjs)):
            return
        lineObj = self.lineObjs[
            lbls.index(key) - self.scrollOffset]
        if type(lineObj) == format.LineMatch:
            lineMatchIndex = self.lineMatches.index(lineObj)
            self.hoverIndex = lineMatchIndex
            self.toggleSelect()