diff --git a/src/pathpicker/command_mode.py b/src/pathpicker/command_mode.py new file mode 100644 index 0000000..042a189 --- /dev/null +++ b/src/pathpicker/command_mode.py @@ -0,0 +1,42 @@ +# Copyright (c) Facebook, Inc. and its affiliates. +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. +import os +import subprocess + + +def expand_tokens(cmd: str, file_path: str) -> str: + """ + Replace custom tokens in command string. + $FILE -> full file path + $DIR -> directory of file + $BASENAME -> file name without path + """ + return ( + cmd.replace("$FILE", file_path) + .replace("$DIR", os.path.dirname(file_path)) + .replace("$BASENAME", os.path.basename(file_path)) + ) + + +def run_command(cmd: str, file_path: str, auto_mode: bool = False) -> None: + """ + Runs a command safely with token expansion. + """ + if not file_path or not os.path.exists(file_path): + print(f"Error: file {file_path} does not exist!") + return + + cmd_to_run = expand_tokens(cmd, file_path) + + if not auto_mode: + confirm = input(f"Run command '{cmd_to_run}'? [y/N] ") + if confirm.lower() != "y": + print("Command skipped.") + return + + try: + subprocess.run(cmd_to_run, shell=True, check=True) + except subprocess.CalledProcessError as e: + print(f"Command failed: {e}") \ No newline at end of file diff --git a/src/pathpicker/parse.py b/src/pathpicker/parse.py index 2dcad77..881cffe 100644 --- a/src/pathpicker/parse.py +++ b/src/pathpicker/parse.py @@ -11,9 +11,11 @@ from pathpicker import logger from pathpicker.repos import REPOS +from pathpicker.regexes import MASTER_REGEX, JUST_FILE_WITH_SPACES # type: ignore + MatchResult = NewType("MatchResult", Tuple[str, int, Match]) -MASTER_REGEX = re.compile( +MASTER_RsEGEX = re.compile( r"(/?([a-z.A-Z0-9\-_]+/)+[@a-zA-Z0-9\-_+.]+\.[a-zA-Z0-9]{1,10})[:-]?(\d+)?" ) MASTER_REGEX_MORE_EXTENSIONS = re.compile( @@ -32,9 +34,10 @@ JUST_FILE = re.compile(r"([@%+a-z.A-Z0-9\-_]+\.[a-zA-Z]{1,10})(\s|$|:)+") JUST_EMACS_TEMP_FILE = re.compile(r"([@%+a-z.A-Z0-9\-_]+\.[a-zA-Z]{1,10}~)(\s|$|:)+") JUST_VIM_TEMP_FILE = re.compile(r"(#[@%+a-z.A-Z0-9\-_]+\.[a-zA-Z]{1,10}#)(\s|$|:)+") -# start with a normal char for ls -l -JUST_FILE_WITH_SPACES = re.compile( - r"([a-zA-Z][@+a-z. A-Z0-9\-_]+\.[a-zA-Z]{1,10})(\s|$|:)+" +# matches normal files including spaces, parentheses, and accented letters +JUST_FILE_WITH_SPACES_UNICODE = re.compile( + r"([\w\s\(\)@%+\-\.]+)\.[a-zA-Z0-9-]{1,30}(\s|$|:)+", + re.UNICODE ) FILE_NO_PERIODS = re.compile( ( @@ -231,6 +234,12 @@ class RegexConfig(NamedTuple): no_num=True, with_all_lines_matched=True, ), + RegexConfig( + "JUST_FILE_WITH_SPACES_UNICODE", + JUST_FILE_WITH_SPACES_UNICODE, + no_num=True, + only_with_file_inspection=True + ), ] diff --git a/src/pathpicker/screen.py b/src/pathpicker/screen.py index 79d3015..7169e3e 100644 --- a/src/pathpicker/screen.py +++ b/src/pathpicker/screen.py @@ -4,6 +4,7 @@ # LICENSE file in the root directory of this source tree. from abc import ABC, abstractmethod from typing import TYPE_CHECKING, Tuple +from .command_mode import run_command if TYPE_CHECKING: import curses @@ -75,3 +76,46 @@ def getstr(self, y_pos: int, x_pos: int, max_len: int) -> str: if isinstance(result, int): return str(result) return result.decode("utf-8") + + def clrtoeol(self) -> None: + """ + Clears from cursor to end of line using curses. + """ + if hasattr(self.screen, "clrtoeol"): + self.screen.clrtoeol() + else: + max_y, max_x = self.getmaxyx() + y, x = self.screen.getyx() + for _ in range(x, max_x): + self.delch(y, x) + self.refresh() + + def handle_command_mode(self, current_selection: str) -> None: + """ + Trigger command mode for the selected file/directory. + """ + self.addstr(0, 0, "Enter command: ", 0) + self.refresh() + cmd_input = self.getstr(0, 15, 100) # read input from user + + # Run the command with token expansion + run_command(cmd_input, current_selection, auto_mode=False) + + # Clear the input line after running + self.move(0, 0) + self.clrtoeol() + self.refresh() + + def handle_input(self, current_selection: str) -> None: + """ + Main input loop for the screen. + """ + while True: + key = self.getch() + + if key in (ord('q'), ord('Q')): + break # quit + elif key == ord(':'): + # Enter command mode + self.handle_command_mode(current_selection) + # Add other key handling here, e.g., navigation