예제 #1
0
    def load_from_layout(self, layout: str) -> None:
        """
        Load details about this window from a layout

        Parameters
        ----------
        layout : string
            The layout string to load details from
            Will be in a format of
                <num>: <name><status> (<N> panes) [<width>x<height>] [layout XXXX,...]
            May be zoomed, so this argument could be incomplete
        """
        match = re.match(r"^([0-9]+): ", layout)
        win_layout_num = match.groups()[0]
        debug("window", "loadLayout",
              "Parsing layout {0}".format(win_layout_num))
        layout = layout[len(win_layout_num) + 2:]
        self.name = re.split(self.WINDOW_TRAIL_REGEX, layout)[0]
        layout = layout[len(self.name):]
        chars = re.match(self.WINDOW_TRAIL_REGEX, layout)
        if chars:
            layout = layout[len(chars.groups()[0]):]
        layout = layout.strip()

        match = re.match(r".*\[([0-9]+x[0-9]+)\]", layout)
        win_size = parse_size(match.groups()[0])
        self.base_width = int(win_size["width"])
        self.base_height = int(win_size["height"])

        # Make sure layout is not zoomed
        select_pane(self.session_name, win_layout_num, 0)
        # Pull only the layout details
        win_layout = get_tmux_details(self.session_name, win_layout_num, None,
                                      WINDOW_LAYOUT_VARIABLE)
        self.parse_layout(win_layout)
예제 #2
0
    def add_simple_pane(self, win_id: str, parent_id: str = None) -> None:
        """
        Adds a pane to this window

        Parameters
        ----------
        win_id : str
            The window ID to prefix this pane with
        parent_id = None : str
            Optionally, the ID of this pane's parent
        """
        if parent_id:
            number = self.objects.get_pane_by_id(parent_id).number + 1
        else:
            number = 0

        debug("Window", "add_simple_pane", "Adding pane: " + str(number))
        # The layout is a single pane
        pane = Pane(win_id + str(number), self, None, True)
        self.panes_added += 1
        # All that's left after the comma is the pane number
        pane.set_number(number)
        self.panes.append(pane)
예제 #3
0
    def get_pane_details_from_layout(self, layout: str) -> dict:
        """
        Get details about a pane layout

        Parameters
        ----------
        layout : str
            The layout string

        Returns
        -------
        dict
            containing details about the pane
        """
        match = re.match(self.PANE_LAYOUT, layout)
        if not match:
            debug(
                "Window",
                "pane_details",
                "Layout did not match pane format! ({0})".format(layout),
            )
            return {}

        details = match.groups()[0]
        size, offset_left, offset_top = details.split(",")
        pane_number = layout.split(",")[3]

        layout = details + ",{0},".format(pane_number)

        return {
            "size": size,
            "left": offset_left,
            "top": offset_top,
            "number": pane_number,
            "selfLayout": layout,
        }
예제 #4
0
    def add_pane_by_layout(self,
                           layout: str,
                           pane_number: int,
                           direction: str,
                           parent_identity: str = None) -> None:
        """
        Adds a new pane, using the layout to fill in details

        Parameters
        ----------
        layout : string
            The layout string for the pane
            Will be in the following format:
              <size>,<offset_left>,<offset_top>
        pane_number : number
            The number of the pane, from the layout
        direction : string
            The direction this pane should be split from the previous one
        parent_identity : string
            The identity of the parent pane
            Used to re-order panes, if needed
        """
        size = parse_size(layout.split(",")[0])
        pane_id = "win{0}pane{1}".format(self.number, self.panes_added)
        pane = (Pane(pane_id, self, direction,
                     self.panes_added == 0).set_session_name(
                         self.session_name).set_size(
                             100 * size["width"] / self.base_width,
                             100 * size["height"] / self.base_height,
                         ))

        parent = None
        try:
            parent = self.objects.get_pane_by_id(parent_identity)
            parent_number = parent.number
        except NameError:
            parent_number = None

        pane_number = (parent_number +
                       1 if parent_number is not None else self.panes_added)

        if parent_number is not None and self.panes_added > pane_number:
            debug(
                "Window",
                "add_pane",
                "Incrementing pane numbers above {0}".format(pane_number),
            )
            for pane_to_check in self.panes:
                if pane_to_check.number >= pane_number:
                    pane_to_check.set_number(pane_to_check.number + 1)

        pane.set_number(pane_number)

        if self.last_pane is not None and parent_number is None:
            pane.set_target(self.last_pane)
        elif parent:
            pane.set_target(parent.identity)

        self.last_pane = pane.identity
        self.panes_added += 1
        self.panes.append(pane)
        debug(
            "Window",
            "add_pane",
            "Adding pane number {0} for parent {1} with size {2}".format(
                pane_number, parent_number, size),
        )
예제 #5
0
    def identify_deferred_layouts(
        self,
        layout: str,
        direction: str,
        parent_id: str = None,
        prevailing_split: str = None,
    ) -> list:
        """
        Identifies any layouts needing to be resolved later
        This algorithm requires breadth-first parsing, to have
        correct numbering/re-creation behaviors

        Parameters
        ----------
        layout : str
            The layout to analyze
        direction : str
            The direction of this layout
        parent_id = None : str
            The original parent this layout should be created for
        prevailing_split = None : str
            The direction of the layout prior to this one

        Returns
        -------
        list
            A list of layouts which need to be processed later
        """
        first_found = True
        deferred_layouts = []

        while True:
            match = re.match(self.PANE_LAYOUT, layout)
            if not match:
                break

            pane_layout = match.groups()[0]
            debug("Window", "identify_deferred", "Pane details:")
            debug("Window", "identify_deferred", pane_layout)
            layout = layout[len(pane_layout):]
            debug("Window", "identify_deferred", "Remaining layout:")
            debug("Window", "identify_deferred", layout)
            if layout[0] == ",":
                debug("Window", "identify_deferred", "Got pane number:")
                match = re.match(r",([0-9]+)", layout)
                pane_number = match.groups()[0]
                debug("Window", "identify_deferred", pane_number)
                match = re.match(r"(,[0-9]+,?)", layout)
                to_remove = match.groups()[0]
                layout = layout[len(to_remove):]
                debug("Window", "identify_deferred",
                      "Stripping number, leaves:")
                debug("Window", "identify_deferred", layout)
                debug(
                    "Window",
                    "identify_deferred",
                    "Adding pane with split direction: " +
                    (direction if not first_found or not prevailing_split else
                     prevailing_split),
                )
                self.add_pane_by_layout(
                    pane_layout,
                    pane_number,
                    direction if not first_found or not prevailing_split else
                    prevailing_split,
                    parent_id if parent_id else self.last_pane,
                )
                first_found = False

            else:
                debug("Window", "identify_deferred",
                      "Resolving layout recursively")
                # Find the end of the nested layout
                # Tracks the levels of layouts
                # e.g. for [ { [ ] } ] will have [, {, [
                nested_layouts = [layout[0]]
                length = 0
                for char in layout[1:]:
                    if char in self.LAYOUT_PAIRS.keys():
                        nested_layouts.append(char)
                    elif char == self.LAYOUT_PAIRS[nested_layouts[-1]]:
                        nested_layouts.pop()
                        # If this was the last one, then stop searching the string

                    length += 1
                    if len(nested_layouts) == 0:
                        break

                debug("Window", "identify_deferred", "Found deferred layout:")
                debug("Window", "identify_deferred",
                      "Layout: " + layout[:length + 1])
                debug(
                    "Window",
                    "identify_deferred",
                    "Parent: " + str(self.panes_added - 1),
                )
                debug("Window", "identify_deferred",
                      "Split direction: " + direction)

                pane_details = self.get_pane_details_from_layout(
                    layout[1:length])
                self.add_pane_by_layout(
                    pane_details["selfLayout"],
                    pane_details["number"],
                    direction,
                    parent_id,
                )

                deferred_layouts.append({
                    # Keep the first character of the layout - [ or {
                    "layout":
                    layout[0]
                    # Deferred layout is everything after the first pane through
                    # the end of the matched (nested) layout
                    # This will be added later, since
                    # we need to parse layouts breadth-first
                    + layout[len(pane_details["selfLayout"]) + 1:length + 1],
                    "parent":
                    self.last_pane,
                    # Deferred layouts will contain the next direction,
                    # not the current direction
                    "split":
                    self.DIRECTION_MAP[layout[0]],
                })

                # Strip the part that was nested out of the layout
                # and remove a left-leading comma, if there is one
                layout = layout[length + 1:].lstrip(",")
                debug("Window", "identify_deferred",
                      "Removing deferred layout leaves:")
                debug("Window", "identify_deferred", layout)

            # For deferred layouts where multiple panes will be added,
            # chain parent to this pane
            # Otherwise, you end up with 0 - 2 - 1 for pane numbering,
            # when you really should have 0 - 1 - 2
            # This is because the "add_pane" bit will
            # shift numbers 1 and greater up one, but that doesn't match
            # the order of creation/insertion that is expected
            if parent_id:
                parent_id = self.last_pane

        return deferred_layouts
예제 #6
0
    def add_pane_row(self,
                     layout: str,
                     parent_id: str = None,
                     prevailing_split: str = None) -> None:
        """
        Add a row of panes, either horizontally or vertically

        Parameters
        ----------
        layout : string
            The layout for the row, should start with either a [ or {,
            depending on whether the split is vertical or horizontal
        parent_id : string
            The identity of the pane to base these off of
        prevailing_split: string
            The current split direction, for the first pane to be based off of

            The following data will be a comma-separated array of pane data
            See add_pane_by_layout
        """
        win_id = "win{0}pane".format(self.number)
        debug("Window", "add_pane_row", "Adding pane row")
        debug("Window", "add_pane_row", layout)
        if layout[0] == ",":
            self.add_simple_pane(win_id, parent_id)
        else:
            direction = self.DIRECTION_MAP[layout[0]]
            direction = SPLIT_HORIZONTAL if layout[0] == "{" else SPLIT_VERTICAL
            debug("Window", "add_pane_row",
                  "Layout split direction: " + direction)
            # Strip outermost direction indicators
            layout = layout[1:-1]
            debug("Window", "add_pane_row", "Resolved layout: " + layout)

            deferred_layouts = self.identify_deferred_layouts(
                layout, direction, parent_id, prevailing_split)

            for deferred_layout in deferred_layouts:
                debug("Window", "add_pane_row", "Processing deferred layout:")
                debug("Window", "add_pane_row",
                      "Layout: " + deferred_layout["layout"])
                debug(
                    "Window",
                    "add_pane_row",
                    "Parent: " + str(deferred_layout["parent"]) +
                    ", number: " + str(
                        self.objects.get_pane_by_id(
                            deferred_layout["parent"]).number),
                )
                debug(
                    "Window",
                    "add_pane_row",
                    "Split direction: " + deferred_layout["split"],
                )
                self.add_pane_row(
                    deferred_layout["layout"],
                    deferred_layout["parent"],
                    deferred_layout["split"],
                )