Team Ai
Datasetpublic

codekingpro/portable-devtools

sourceHugging Faceupdated 5mo agoView on Hugging Face
1likes14kdownloads
_win32_console.py662 linesDownload Raw Back to rich
1"""Light wrapper around the Win32 Console API - this module should only be imported on Windows2 3The API that this module wraps is documented at https://docs.microsoft.com/en-us/windows/console/console-functions4"""5 6import ctypes7import sys8from typing import Any9 10windll: Any = None11if sys.platform == "win32":12    windll = ctypes.LibraryLoader(ctypes.WinDLL)13else:14    raise ImportError(f"{__name__} can only be imported on Windows")15 16import time17from ctypes import Structure, byref, wintypes18from typing import IO, NamedTuple, Type, cast19 20from rich.color import ColorSystem21from rich.style import Style22 23STDOUT = -1124ENABLE_VIRTUAL_TERMINAL_PROCESSING = 425 26COORD = wintypes._COORD27 28 29class LegacyWindowsError(Exception):30    pass31 32 33class WindowsCoordinates(NamedTuple):34    """Coordinates in the Windows Console API are (y, x), not (x, y).35    This class is intended to prevent that confusion.36    Rows and columns are indexed from 0.37    This class can be used in place of wintypes._COORD in arguments and argtypes.38    """39 40    row: int41    col: int42 43    @classmethod44    def from_param(cls, value: "WindowsCoordinates") -> COORD:45        """Converts a WindowsCoordinates into a wintypes _COORD structure.46        This classmethod is internally called by ctypes to perform the conversion.47 48        Args:49            value (WindowsCoordinates): The input coordinates to convert.50 51        Returns:52            wintypes._COORD: The converted coordinates struct.53        """54        return COORD(value.col, value.row)55 56 57class CONSOLE_SCREEN_BUFFER_INFO(Structure):58    _fields_ = [59        ("dwSize", COORD),60        ("dwCursorPosition", COORD),61        ("wAttributes", wintypes.WORD),62        ("srWindow", wintypes.SMALL_RECT),63        ("dwMaximumWindowSize", COORD),64    ]65 66 67class CONSOLE_CURSOR_INFO(ctypes.Structure):68    _fields_ = [("dwSize", wintypes.DWORD), ("bVisible", wintypes.BOOL)]69 70 71_GetStdHandle = windll.kernel32.GetStdHandle72_GetStdHandle.argtypes = [73    wintypes.DWORD,74]75_GetStdHandle.restype = wintypes.HANDLE76 77 78def GetStdHandle(handle: int = STDOUT) -> wintypes.HANDLE:79    """Retrieves a handle to the specified standard device (standard input, standard output, or standard error).80 81    Args:82        handle (int): Integer identifier for the handle. Defaults to -11 (stdout).83 84    Returns:85        wintypes.HANDLE: The handle86    """87    return cast(wintypes.HANDLE, _GetStdHandle(handle))88 89 90_GetConsoleMode = windll.kernel32.GetConsoleMode91_GetConsoleMode.argtypes = [wintypes.HANDLE, wintypes.LPDWORD]92_GetConsoleMode.restype = wintypes.BOOL93 94 95def GetConsoleMode(std_handle: wintypes.HANDLE) -> int:96    """Retrieves the current input mode of a console's input buffer97    or the current output mode of a console screen buffer.98 99    Args:100        std_handle (wintypes.HANDLE): A handle to the console input buffer or the console screen buffer.101 102    Raises:103        LegacyWindowsError: If any error occurs while calling the Windows console API.104 105    Returns:106        int: Value representing the current console mode as documented at107            https://docs.microsoft.com/en-us/windows/console/getconsolemode#parameters108    """109 110    console_mode = wintypes.DWORD()111    success = bool(_GetConsoleMode(std_handle, console_mode))112    if not success:113        raise LegacyWindowsError("Unable to get legacy Windows Console Mode")114    return console_mode.value115 116 117_FillConsoleOutputCharacterW = windll.kernel32.FillConsoleOutputCharacterW118_FillConsoleOutputCharacterW.argtypes = [119    wintypes.HANDLE,120    ctypes.c_char,121    wintypes.DWORD,122    cast(Type[COORD], WindowsCoordinates),123    ctypes.POINTER(wintypes.DWORD),124]125_FillConsoleOutputCharacterW.restype = wintypes.BOOL126 127 128def FillConsoleOutputCharacter(129    std_handle: wintypes.HANDLE,130    char: str,131    length: int,132    start: WindowsCoordinates,133) -> int:134    """Writes a character to the console screen buffer a specified number of times, beginning at the specified coordinates.135 136    Args:137        std_handle (wintypes.HANDLE): A handle to the console input buffer or the console screen buffer.138        char (str): The character to write. Must be a string of length 1.139        length (int): The number of times to write the character.140        start (WindowsCoordinates): The coordinates to start writing at.141 142    Returns:143        int: The number of characters written.144    """145    character = ctypes.c_char(char.encode())146    num_characters = wintypes.DWORD(length)147    num_written = wintypes.DWORD(0)148    _FillConsoleOutputCharacterW(149        std_handle,150        character,151        num_characters,152        start,153        byref(num_written),154    )155    return num_written.value156 157 158_FillConsoleOutputAttribute = windll.kernel32.FillConsoleOutputAttribute159_FillConsoleOutputAttribute.argtypes = [160    wintypes.HANDLE,161    wintypes.WORD,162    wintypes.DWORD,163    cast(Type[COORD], WindowsCoordinates),164    ctypes.POINTER(wintypes.DWORD),165]166_FillConsoleOutputAttribute.restype = wintypes.BOOL167 168 169def FillConsoleOutputAttribute(170    std_handle: wintypes.HANDLE,171    attributes: int,172    length: int,173    start: WindowsCoordinates,174) -> int:175    """Sets the character attributes for a specified number of character cells,176    beginning at the specified coordinates in a screen buffer.177 178    Args:179        std_handle (wintypes.HANDLE): A handle to the console input buffer or the console screen buffer.180        attributes (int): Integer value representing the foreground and background colours of the cells.181        length (int): The number of cells to set the output attribute of.182        start (WindowsCoordinates): The coordinates of the first cell whose attributes are to be set.183 184    Returns:185        int: The number of cells whose attributes were actually set.186    """187    num_cells = wintypes.DWORD(length)188    style_attrs = wintypes.WORD(attributes)189    num_written = wintypes.DWORD(0)190    _FillConsoleOutputAttribute(191        std_handle, style_attrs, num_cells, start, byref(num_written)192    )193    return num_written.value194 195 196_SetConsoleTextAttribute = windll.kernel32.SetConsoleTextAttribute197_SetConsoleTextAttribute.argtypes = [198    wintypes.HANDLE,199    wintypes.WORD,200]201_SetConsoleTextAttribute.restype = wintypes.BOOL202 203 204def SetConsoleTextAttribute(205    std_handle: wintypes.HANDLE, attributes: wintypes.WORD206) -> bool:207    """Set the colour attributes for all text written after this function is called.208 209    Args:210        std_handle (wintypes.HANDLE): A handle to the console input buffer or the console screen buffer.211        attributes (int): Integer value representing the foreground and background colours.212 213 214    Returns:215        bool: True if the attribute was set successfully, otherwise False.216    """217    return bool(_SetConsoleTextAttribute(std_handle, attributes))218 219 220_GetConsoleScreenBufferInfo = windll.kernel32.GetConsoleScreenBufferInfo221_GetConsoleScreenBufferInfo.argtypes = [222    wintypes.HANDLE,223    ctypes.POINTER(CONSOLE_SCREEN_BUFFER_INFO),224]225_GetConsoleScreenBufferInfo.restype = wintypes.BOOL226 227 228def GetConsoleScreenBufferInfo(229    std_handle: wintypes.HANDLE,230) -> CONSOLE_SCREEN_BUFFER_INFO:231    """Retrieves information about the specified console screen buffer.232 233    Args:234        std_handle (wintypes.HANDLE): A handle to the console input buffer or the console screen buffer.235 236    Returns:237        CONSOLE_SCREEN_BUFFER_INFO: A CONSOLE_SCREEN_BUFFER_INFO ctype struct contain information about238            screen size, cursor position, colour attributes, and more."""239    console_screen_buffer_info = CONSOLE_SCREEN_BUFFER_INFO()240    _GetConsoleScreenBufferInfo(std_handle, byref(console_screen_buffer_info))241    return console_screen_buffer_info242 243 244_SetConsoleCursorPosition = windll.kernel32.SetConsoleCursorPosition245_SetConsoleCursorPosition.argtypes = [246    wintypes.HANDLE,247    cast(Type[COORD], WindowsCoordinates),248]249_SetConsoleCursorPosition.restype = wintypes.BOOL250 251 252def SetConsoleCursorPosition(253    std_handle: wintypes.HANDLE, coords: WindowsCoordinates254) -> bool:255    """Set the position of the cursor in the console screen256 257    Args:258        std_handle (wintypes.HANDLE): A handle to the console input buffer or the console screen buffer.259        coords (WindowsCoordinates): The coordinates to move the cursor to.260 261    Returns:262        bool: True if the function succeeds, otherwise False.263    """264    return bool(_SetConsoleCursorPosition(std_handle, coords))265 266 267_GetConsoleCursorInfo = windll.kernel32.GetConsoleCursorInfo268_GetConsoleCursorInfo.argtypes = [269    wintypes.HANDLE,270    ctypes.POINTER(CONSOLE_CURSOR_INFO),271]272_GetConsoleCursorInfo.restype = wintypes.BOOL273 274 275def GetConsoleCursorInfo(276    std_handle: wintypes.HANDLE, cursor_info: CONSOLE_CURSOR_INFO277) -> bool:278    """Get the cursor info - used to get cursor visibility and width279 280    Args:281        std_handle (wintypes.HANDLE): A handle to the console input buffer or the console screen buffer.282        cursor_info (CONSOLE_CURSOR_INFO): CONSOLE_CURSOR_INFO ctype struct that receives information283            about the console's cursor.284 285    Returns:286          bool: True if the function succeeds, otherwise False.287    """288    return bool(_GetConsoleCursorInfo(std_handle, byref(cursor_info)))289 290 291_SetConsoleCursorInfo = windll.kernel32.SetConsoleCursorInfo292_SetConsoleCursorInfo.argtypes = [293    wintypes.HANDLE,294    ctypes.POINTER(CONSOLE_CURSOR_INFO),295]296_SetConsoleCursorInfo.restype = wintypes.BOOL297 298 299def SetConsoleCursorInfo(300    std_handle: wintypes.HANDLE, cursor_info: CONSOLE_CURSOR_INFO301) -> bool:302    """Set the cursor info - used for adjusting cursor visibility and width303 304    Args:305        std_handle (wintypes.HANDLE): A handle to the console input buffer or the console screen buffer.306        cursor_info (CONSOLE_CURSOR_INFO): CONSOLE_CURSOR_INFO ctype struct containing the new cursor info.307 308    Returns:309          bool: True if the function succeeds, otherwise False.310    """311    return bool(_SetConsoleCursorInfo(std_handle, byref(cursor_info)))312 313 314_SetConsoleTitle = windll.kernel32.SetConsoleTitleW315_SetConsoleTitle.argtypes = [wintypes.LPCWSTR]316_SetConsoleTitle.restype = wintypes.BOOL317 318 319def SetConsoleTitle(title: str) -> bool:320    """Sets the title of the current console window321 322    Args:323        title (str): The new title of the console window.324 325    Returns:326        bool: True if the function succeeds, otherwise False.327    """328    return bool(_SetConsoleTitle(title))329 330 331class LegacyWindowsTerm:332    """This class allows interaction with the legacy Windows Console API. It should only be used in the context333    of environments where virtual terminal processing is not available. However, if it is used in a Windows environment,334    the entire API should work.335 336    Args:337        file (IO[str]): The file which the Windows Console API HANDLE is retrieved from, defaults to sys.stdout.338    """339 340    BRIGHT_BIT = 8341 342    # Indices are ANSI color numbers, values are the corresponding Windows Console API color numbers343    ANSI_TO_WINDOWS = [344        0,  # black                      The Windows colours are defined in wincon.h as follows:345        4,  # red                         define FOREGROUND_BLUE            0x0001 -- 0000 0001346        2,  # green                       define FOREGROUND_GREEN           0x0002 -- 0000 0010347        6,  # yellow                      define FOREGROUND_RED             0x0004 -- 0000 0100348        1,  # blue                        define FOREGROUND_INTENSITY       0x0008 -- 0000 1000349        5,  # magenta                     define BACKGROUND_BLUE            0x0010 -- 0001 0000350        3,  # cyan                        define BACKGROUND_GREEN           0x0020 -- 0010 0000351        7,  # white                       define BACKGROUND_RED             0x0040 -- 0100 0000352        8,  # bright black (grey)         define BACKGROUND_INTENSITY       0x0080 -- 1000 0000353        12,  # bright red354        10,  # bright green355        14,  # bright yellow356        9,  # bright blue357        13,  # bright magenta358        11,  # bright cyan359        15,  # bright white360    ]361 362    def __init__(self, file: "IO[str]") -> None:363        handle = GetStdHandle(STDOUT)364        self._handle = handle365        default_text = GetConsoleScreenBufferInfo(handle).wAttributes366        self._default_text = default_text367 368        self._default_fore = default_text & 7369        self._default_back = (default_text >> 4) & 7370        self._default_attrs = self._default_fore | (self._default_back << 4)371 372        self._file = file373        self.write = file.write374        self.flush = file.flush375 376    @property377    def cursor_position(self) -> WindowsCoordinates:378        """Returns the current position of the cursor (0-based)379 380        Returns:381            WindowsCoordinates: The current cursor position.382        """383        coord: COORD = GetConsoleScreenBufferInfo(self._handle).dwCursorPosition384        return WindowsCoordinates(row=coord.Y, col=coord.X)385 386    @property387    def screen_size(self) -> WindowsCoordinates:388        """Returns the current size of the console screen buffer, in character columns and rows389 390        Returns:391            WindowsCoordinates: The width and height of the screen as WindowsCoordinates.392        """393        screen_size: COORD = GetConsoleScreenBufferInfo(self._handle).dwSize394        return WindowsCoordinates(row=screen_size.Y, col=screen_size.X)395 396    def write_text(self, text: str) -> None:397        """Write text directly to the terminal without any modification of styles398 399        Args:400            text (str): The text to write to the console401        """402        self.write(text)403        self.flush()404 405    def write_styled(self, text: str, style: Style) -> None:406        """Write styled text to the terminal.407 408        Args:409            text (str): The text to write410            style (Style): The style of the text411        """412        color = style.color413        bgcolor = style.bgcolor414        if style.reverse:415            color, bgcolor = bgcolor, color416 417        if color:418            fore = color.downgrade(ColorSystem.WINDOWS).number419            fore = fore if fore is not None else 7  # Default to ANSI 7: White420            if style.bold:421                fore = fore | self.BRIGHT_BIT422            if style.dim:423                fore = fore & ~self.BRIGHT_BIT424            fore = self.ANSI_TO_WINDOWS[fore]425        else:426            fore = self._default_fore427 428        if bgcolor:429            back = bgcolor.downgrade(ColorSystem.WINDOWS).number430            back = back if back is not None else 0  # Default to ANSI 0: Black431            back = self.ANSI_TO_WINDOWS[back]432        else:433            back = self._default_back434 435        assert fore is not None436        assert back is not None437 438        SetConsoleTextAttribute(439            self._handle, attributes=ctypes.c_ushort(fore | (back << 4))440        )441        self.write_text(text)442        SetConsoleTextAttribute(self._handle, attributes=self._default_text)443 444    def move_cursor_to(self, new_position: WindowsCoordinates) -> None:445        """Set the position of the cursor446 447        Args:448            new_position (WindowsCoordinates): The WindowsCoordinates representing the new position of the cursor.449        """450        if new_position.col < 0 or new_position.row < 0:451            return452        SetConsoleCursorPosition(self._handle, coords=new_position)453 454    def erase_line(self) -> None:455        """Erase all content on the line the cursor is currently located at"""456        screen_size = self.screen_size457        cursor_position = self.cursor_position458        cells_to_erase = screen_size.col459        start_coordinates = WindowsCoordinates(row=cursor_position.row, col=0)460        FillConsoleOutputCharacter(461            self._handle, " ", length=cells_to_erase, start=start_coordinates462        )463        FillConsoleOutputAttribute(464            self._handle,465            self._default_attrs,466            length=cells_to_erase,467            start=start_coordinates,468        )469 470    def erase_end_of_line(self) -> None:471        """Erase all content from the cursor position to the end of that line"""472        cursor_position = self.cursor_position473        cells_to_erase = self.screen_size.col - cursor_position.col474        FillConsoleOutputCharacter(475            self._handle, " ", length=cells_to_erase, start=cursor_position476        )477        FillConsoleOutputAttribute(478            self._handle,479            self._default_attrs,480            length=cells_to_erase,481            start=cursor_position,482        )483 484    def erase_start_of_line(self) -> None:485        """Erase all content from the cursor position to the start of that line"""486        row, col = self.cursor_position487        start = WindowsCoordinates(row, 0)488        FillConsoleOutputCharacter(self._handle, " ", length=col, start=start)489        FillConsoleOutputAttribute(490            self._handle, self._default_attrs, length=col, start=start491        )492 493    def move_cursor_up(self) -> None:494        """Move the cursor up a single cell"""495        cursor_position = self.cursor_position496        SetConsoleCursorPosition(497            self._handle,498            coords=WindowsCoordinates(499                row=cursor_position.row - 1, col=cursor_position.col500            ),501        )502 503    def move_cursor_down(self) -> None:504        """Move the cursor down a single cell"""505        cursor_position = self.cursor_position506        SetConsoleCursorPosition(507            self._handle,508            coords=WindowsCoordinates(509                row=cursor_position.row + 1,510                col=cursor_position.col,511            ),512        )513 514    def move_cursor_forward(self) -> None:515        """Move the cursor forward a single cell. Wrap to the next line if required."""516        row, col = self.cursor_position517        if col == self.screen_size.col - 1:518            row += 1519            col = 0520        else:521            col += 1522        SetConsoleCursorPosition(523            self._handle, coords=WindowsCoordinates(row=row, col=col)524        )525 526    def move_cursor_to_column(self, column: int) -> None:527        """Move cursor to the column specified by the zero-based column index, staying on the same row528 529        Args:530            column (int): The zero-based column index to move the cursor to.531        """532        row, _ = self.cursor_position533        SetConsoleCursorPosition(self._handle, coords=WindowsCoordinates(row, column))534 535    def move_cursor_backward(self) -> None:536        """Move the cursor backward a single cell. Wrap to the previous line if required."""537        row, col = self.cursor_position538        if col == 0:539            row -= 1540            col = self.screen_size.col - 1541        else:542            col -= 1543        SetConsoleCursorPosition(544            self._handle, coords=WindowsCoordinates(row=row, col=col)545        )546 547    def hide_cursor(self) -> None:548        """Hide the cursor"""549        current_cursor_size = self._get_cursor_size()550        invisible_cursor = CONSOLE_CURSOR_INFO(dwSize=current_cursor_size, bVisible=0)551        SetConsoleCursorInfo(self._handle, cursor_info=invisible_cursor)552 553    def show_cursor(self) -> None:554        """Show the cursor"""555        current_cursor_size = self._get_cursor_size()556        visible_cursor = CONSOLE_CURSOR_INFO(dwSize=current_cursor_size, bVisible=1)557        SetConsoleCursorInfo(self._handle, cursor_info=visible_cursor)558 559    def set_title(self, title: str) -> None:560        """Set the title of the terminal window561 562        Args:563            title (str): The new title of the console window564        """565        assert len(title) < 255, "Console title must be less than 255 characters"566        SetConsoleTitle(title)567 568    def _get_cursor_size(self) -> int:569        """Get the percentage of the character cell that is filled by the cursor"""570        cursor_info = CONSOLE_CURSOR_INFO()571        GetConsoleCursorInfo(self._handle, cursor_info=cursor_info)572        return int(cursor_info.dwSize)573 574 575if __name__ == "__main__":576    handle = GetStdHandle()577 578    from rich.console import Console579 580    console = Console()581 582    term = LegacyWindowsTerm(sys.stdout)583    term.set_title("Win32 Console Examples")584 585    style = Style(color="black", bgcolor="red")586 587    heading = Style.parse("black on green")588 589    # Check colour output590    console.rule("Checking colour output")591    console.print("[on red]on red!")592    console.print("[blue]blue!")593    console.print("[yellow]yellow!")594    console.print("[bold yellow]bold yellow!")595    console.print("[bright_yellow]bright_yellow!")596    console.print("[dim bright_yellow]dim bright_yellow!")597    console.print("[italic cyan]italic cyan!")598    console.print("[bold white on blue]bold white on blue!")599    console.print("[reverse bold white on blue]reverse bold white on blue!")600    console.print("[bold black on cyan]bold black on cyan!")601    console.print("[black on green]black on green!")602    console.print("[blue on green]blue on green!")603    console.print("[white on black]white on black!")604    console.print("[black on white]black on white!")605    console.print("[#1BB152 on #DA812D]#1BB152 on #DA812D!")606 607    # Check cursor movement608    console.rule("Checking cursor movement")609    console.print()610    term.move_cursor_backward()611    term.move_cursor_backward()612    term.write_text("went back and wrapped to prev line")613    time.sleep(1)614    term.move_cursor_up()615    term.write_text("we go up")616    time.sleep(1)617    term.move_cursor_down()618    term.write_text("and down")619    time.sleep(1)620    term.move_cursor_up()621    term.move_cursor_backward()622    term.move_cursor_backward()623    term.write_text("we went up and back 2")624    time.sleep(1)625    term.move_cursor_down()626    term.move_cursor_backward()627    term.move_cursor_backward()628    term.write_text("we went down and back 2")629    time.sleep(1)630 631    # Check erasing of lines632    term.hide_cursor()633    console.print()634    console.rule("Checking line erasing")635    console.print("\n...Deleting to the start of the line...")636    term.write_text("The red arrow shows the cursor location, and direction of erase")637    time.sleep(1)638    term.move_cursor_to_column(16)639    term.write_styled("<", Style.parse("black on red"))640    term.move_cursor_backward()641    time.sleep(1)642    term.erase_start_of_line()643    time.sleep(1)644 645    console.print("\n\n...And to the end of the line...")646    term.write_text("The red arrow shows the cursor location, and direction of erase")647    time.sleep(1)648 649    term.move_cursor_to_column(16)650    term.write_styled(">", Style.parse("black on red"))651    time.sleep(1)652    term.erase_end_of_line()653    time.sleep(1)654 655    console.print("\n\n...Now the whole line will be erased...")656    term.write_styled("I'm going to disappear!", style=Style.parse("black on cyan"))657    time.sleep(1)658    term.erase_line()659 660    term.show_cursor()661    print("\n")662 
codekingpro/portable-devtools · Team Ai