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)
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)
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, }
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), )
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
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"], )