Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 1 addition & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ A plugin to name your tmux windows smartly, like IDE's.
## Dependencies

* tmux (Tested on 3.0a)
* Python 3.6.8+ (Maybe can be lower, tested on 3.6.8)
* Python 3.7+
* pip
* [libtmux](https://github.com/tmux-python/libtmux) >0.16

Expand Down Expand Up @@ -129,11 +129,6 @@ _**Note**_: Make sure you are using the `user` python and not `sudo` python or `
python3 -m pip install --user libtmux
```

### Install dataclasses (for Python 3.6.X only)
```sh
python3 -m pip install dataclasses --user
```

### Installation with [Tmux Plugin Manager](https://github.com/tmux-plugins/tpm) (recommended)

Add plugin to the list of TPM plugins:
Expand Down
28 changes: 15 additions & 13 deletions scripts/path_utils.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,15 @@
#!/usr/bin/env python3
from __future__ import annotations

from dataclasses import dataclass
from pathlib import Path
from typing import List, Optional, Tuple

from libtmux.pane import Pane as TmuxPane


@dataclass
class Pane:
info: TmuxPane
program: Optional[str] # None when no program is running
program: str | None # None when no program is running


@dataclass
Expand All @@ -25,18 +24,20 @@ def from_pane(pane: Pane):
return DisplayedPath(pane, path, Path(path.name))


def get_uncommon_path(a: Path, b: Path) -> Tuple[Path, Path]:
"""Get 2 uncommon path between paths
def get_uncommon_path(a: Path, b: Path) -> tuple[Path, Path]:
"""
Get 2 uncommon path between paths.

Args:
a (Path): first path
b (Path): second path
a: first path
b: second path

Returns:
Tuple of uncommon paths

E.g:
a = 'a/dir1/c', b = 'b/dir2/c' -> ('dir1/c', 'dir2/c')
Examples:
>>> get_uncommon_path(Path('a/dir1/c'), Path('b/dir2/c'))
('dir1/c', 'dir2/c')
"""
# Go from -1 to -Maximum length to check each part from the end
# E.g: 'a/dir1/c', 'b/dir1/c' will go [:-1], [:-2] and stop
Expand All @@ -51,14 +52,15 @@ def get_uncommon_path(a: Path, b: Path) -> Tuple[Path, Path]:
return Path(*a.parts[x:]), Path(*b.parts[x:])


def get_exclusive_paths(panes: List[Pane]) -> List[Tuple[Pane, Path]]:
"""Get exclusive path for each pane (better explaining in the README)
def get_exclusive_paths(panes: list[Pane]) -> list[tuple[Pane, Path]]:
"""
Get exclusive path for each pane (better explaining in the README).

Args:
panes (List[Pane]): list of the panes
panes: list of the panes

Returns:
List of tuples with the original pane and display path
List of tuples with the original pane and display path.
"""
# Start all displays as the last dir (.name)
exc_paths = [DisplayedPath.from_pane(pane) for pane in panes]
Expand Down
73 changes: 46 additions & 27 deletions scripts/rename_session_windows.py
Original file line number Diff line number Diff line change
@@ -1,22 +1,22 @@
#!/usr/bin/env python3
from __future__ import annotations

import logging
import logging.config
import tempfile
import subprocess
import os
import re
from dataclasses import dataclass, field
from pathlib import Path
from typing import Any, Iterator, List, Optional, Tuple
import subprocess
import tempfile
from argparse import ArgumentParser
from contextlib import contextmanager
from dataclasses import dataclass, field
from pathlib import Path
from typing import Any, Iterator

from libtmux.pane import Pane as TmuxPane
from libtmux.server import Server
from libtmux.session import Session
from libtmux.pane import Pane as TmuxPane

from path_utils import get_exclusive_paths, Pane
from path_utils import Pane, get_exclusive_paths

OPTIONS_PREFIX = '@tmux_window_name_'
HOOK_INDEX = 8921
Expand All @@ -37,12 +37,17 @@ def set_option(server: Server, option: str, val: str):
server.cmd('set-option', '-g', f'{OPTIONS_PREFIX}{option}', val)


def get_window_option(server: Server, window_id: Optional[str], option: str, default: Any) -> Any:
def get_window_option(server: Server, window_id: str | None, option: str, default: Any) -> Any:
return get_window_tmux_option(server, window_id, f'{OPTIONS_PREFIX}{option}', default, do_eval=True)


def get_window_tmux_option(
server: Server, window_id: Optional[str], option: str, default: Any, do_eval: bool = False
server: Server,
window_id: str | None,
option: str,
default: Any,
*,
do_eval: bool = False,
) -> Any:
arguments = ['show-option', '-wqv']

Expand All @@ -62,7 +67,7 @@ def get_window_tmux_option(
return out[0]


def set_window_tmux_option(server: Server, window_id: Optional[str], option: str, value: str) -> Any:
def set_window_tmux_option(server: Server, window_id: str | None, option: str, value: str) -> Any:
arguments = ['set-option', '-wq']
if window_id is not None:
arguments.append('-t')
Expand Down Expand Up @@ -128,28 +133,28 @@ def tmux_guard(server: Server) -> Iterator[bool]:

@dataclass
class Options:
shells: List[str] = field(default_factory=lambda: ['bash', 'fish', 'sh', 'zsh'])
dir_programs: List[str] = field(default_factory=lambda: ['nvim', 'vim', 'vi', 'git'])
ignored_programs: List[str] = field(default_factory=lambda: [])
shells: list[str] = field(default_factory=lambda: ['bash', 'fish', 'sh', 'zsh'])
dir_programs: list[str] = field(default_factory=lambda: ['nvim', 'vim', 'vi', 'git'])
ignored_programs: list[str] = field(default_factory=list)
max_name_len: int = 20
use_tilde: bool = False
substitute_sets: List[Tuple] = field(
substitute_sets: list[tuple] = field(
default_factory=lambda: [
(r'.+ipython([32])', r'ipython\g<1>'),
USR_BIN_REMOVER,
(r'(bash) (.+)/(.+[ $])(.+)', r'\g<3>\g<4>'),
(r'.+poetry shell', 'poetry'),
]
)
dir_substitute_sets: List[Tuple] = field(default_factory=lambda: [])
dir_substitute_sets: list[tuple] = field(default_factory=list)
show_program_args: bool = True
log_level: str = 'WARNING'

@staticmethod
def from_options(server: Server):
fields = Options.__dataclass_fields__

def default_field_value(f: field):
def default_field_value(f):
if callable(f.default_factory):
return f.default_factory()
return f.default
Expand All @@ -161,7 +166,7 @@ def default_field_value(f: field):
return Options(**fields_values)


def parse_shell_command(shell_cmd: List[bytes]) -> Optional[str]:
def parse_shell_command(shell_cmd: list[bytes]) -> str | None:
# Only shell
if len(shell_cmd) == 1:
return None
Expand All @@ -172,7 +177,7 @@ def parse_shell_command(shell_cmd: List[bytes]) -> Optional[str]:
return ' '.join(shell_cmd_str[1:])


def get_current_program(running_programs: List[bytes], pane: TmuxPane, options: Options) -> Optional[str]:
def get_current_program(running_programs: list[bytes], pane: TmuxPane, options: Options) -> str | None:
if pane.pane_pid is None:
raise ValueError(f'Pane id is none, pane: {pane}')

Expand Down Expand Up @@ -210,7 +215,7 @@ def get_current_program(running_programs: List[bytes], pane: TmuxPane, options:
return None


def get_program_if_dir(program_line: str, dir_programs: List[str]) -> Optional[str]:
def get_program_if_dir(program_line: str, dir_programs: list[str]) -> str | None:
program = program_line.split()

for p in dir_programs:
Expand Down Expand Up @@ -242,7 +247,7 @@ def rename_window(server: Server, window_id: str, window_name: str, max_name_len
) # Turn on automatic-rename to make resurrect remeber the option


def get_panes_programs(session: Session, options: Options) -> List[Pane]:
def get_panes_programs(session: Session, options: Options) -> list[Pane]:
session_active_panes = get_session_active_panes(session)
try:
running_programs = subprocess.check_output(['ps', '-a', '-oppid,command']).splitlines()[1:]
Expand Down Expand Up @@ -324,7 +329,7 @@ def get_current_session(server: Server) -> Session:
return Session(server, session_id=session_id)


def substitute_name(name: str, substitute_sets: List[Tuple]) -> str:
def substitute_name(name: str, substitute_sets: list[tuple]) -> str:
logging.debug(f'substituting {name}')
for pattern, replacement in substitute_sets:
name = re.sub(pattern, replacement, name)
Expand All @@ -347,9 +352,21 @@ def main():
server = Server()

parser = ArgumentParser('Renames tmux session windows')
parser.add_argument('--print_programs', action='store_true', help='Prints full name of the programs in the session')
parser.add_argument('--enable_rename_hook', action='store_true', help='Enables rename hook, for internal use')
parser.add_argument('--disable_rename_hook', action='store_true', help='Enables rename hook, for internal use')
parser.add_argument(
'--print_programs',
action='store_true',
help='Prints full name of the programs in the session',
)
parser.add_argument(
'--enable_rename_hook',
action='store_true',
help='Enables rename hook, for internal use',
)
parser.add_argument(
'--disable_rename_hook',
action='store_true',
help='Enables rename hook, for internal use',
)
parser.add_argument(
'--post_restore',
action='store_true',
Expand All @@ -368,9 +385,11 @@ def main():
)

log_level = logging._nameToLevel.get(options.log_level, logging.WARNING)
log_file = os.path.join(tempfile.gettempdir(), 'tmux-window-name')
log_file = Path(tempfile.gettempdir()) / 'tmux-window-name'
logging.basicConfig(
level=log_level, filename=log_file, format='%(levelname)s - %(filename)s:%(lineno)d %(funcName)s() %(message)s'
level=log_level,
filename=log_file,
format='%(levelname)s - %(filename)s:%(lineno)d %(funcName)s() %(message)s',
)
logging.debug(f'Args: {args}')
logging.debug(f'Options: {options}')
Expand Down
17 changes: 9 additions & 8 deletions tests/test_exclusive_paths.py
Original file line number Diff line number Diff line change
@@ -1,29 +1,30 @@
#!/usr/bin/env python3
from __future__ import annotations

# I hate this but i don't want to make it a pip package, its a script.
import sys
from typing import List, Optional, Tuple
from dataclasses import dataclass

sys.path.append('scripts/')
from dataclasses import dataclass

from path_utils import get_exclusive_paths, Pane
from path_utils import Pane, get_exclusive_paths


@dataclass
class FakePane:
pane_current_path: str | None


def _fake_pane(path: str, program: Optional[str]):
def _fake_pane(path: str, program: str | None):
return Pane(FakePane(path), program)


def _check(expected: List[Tuple[str, Optional[str], str]]):
"""check expected displayed paths
def _check(expected: list[tuple[str, str | None, str]]):
"""
Check expected displayed paths.

Args:
expected (List[Tuple[str, Optional[str], str]]): list of (full_path, program, expected_display)
expected: list of (full_path, program, expected_display)

E.g:
_check([
('a/dir', 'p1', 'dir'), # Program p1 in a/dir will display dir (will be formated to p1:dir)
Expand Down
3 changes: 1 addition & 2 deletions tests/test_get_uncommon_path.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
#!/usr/bin/env python3

# I hate this but i don't want to make it a pip package, its a script.
import sys

sys.path.append('scripts/')

from pathlib import Path

from path_utils import get_uncommon_path


Expand Down