From df88f78eabfceb86de202ad54a8109355276752e Mon Sep 17 00:00:00 2001 From: Will McGugan Date: Sun, 2 Feb 2020 14:13:31 +0000 Subject: [PATCH] windows color support --- CHANGELOG.md | 6 ++++++ rich/color.py | 25 ++++++++++++++++++++++++- rich/console.py | 15 +++++++++++---- rich/markdown.py | 46 +++++++++++++++++++++++++++++++++++++++------- 4 files changed, 80 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 37ed4b49..71188343 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.3.3] - Unreleased + +### Fixed + +- Fixed Windows color support + ## [0.3.2] - 2020-01-26 ### Added diff --git a/rich/color.py b/rich/color.py index 14fb53f2..cd7fd144 100644 --- a/rich/color.py +++ b/rich/color.py @@ -4,6 +4,7 @@ from dataclasses import dataclass from enum import IntEnum from functools import lru_cache from math import sqrt +import platform from typing import Iterable, List, NamedTuple, Optional, Sequence, Tuple, TYPE_CHECKING from ._palettes import STANDARD_PALETTE, EIGHT_BIT_PALETTE @@ -13,12 +14,16 @@ if TYPE_CHECKING: # pragma: no cover from .theme import Theme +WINDOWS = platform.system() == "Windows" + + class ColorSystem(IntEnum): """One of the 3 color system supported by terminals.""" STANDARD = 1 EIGHT_BIT = 2 TRUECOLOR = 3 + WINDOWS = 4 class ColorType(IntEnum): @@ -28,6 +33,7 @@ class ColorType(IntEnum): STANDARD = 1 EIGHT_BIT = 2 TRUECOLOR = 3 + WINDOWS = 4 ANSI_COLOR_NAMES = { @@ -291,6 +297,8 @@ class Color(NamedTuple): elif self.type == ColorType.STANDARD: assert self.number is not None return theme.ansi_colors[self.number] + elif self.type == ColorType.WINDOWS: + return STANDARD_PALETTE[self.number] else: # self.type == ColorType.DEFAULT: assert self.number is None return theme.foreground_color if foreground else theme.background_color @@ -371,7 +379,7 @@ class Color(NamedTuple): if _type == ColorType.DEFAULT: return ["39" if foreground else "49"] - elif _type == ColorType.STANDARD: + elif _type in (ColorType.STANDARD, ColorType.WINDOWS): number = self.number assert number is not None return [str(30 + number if foreground else 40 + number)] @@ -423,6 +431,21 @@ class Color(NamedTuple): color_number = STANDARD_PALETTE.match(triplet) return Color(self.name, ColorType.STANDARD, number=color_number) + elif system == ColorSystem.WINDOWS: + if self.system == ColorSystem.TRUECOLOR: + assert self.triplet is not None + triplet = self.triplet + else: # self.system == ColorSystem.EIGHT_BIT + assert self.number is not None + if self.number < 8: + return Color(self.name, ColorType.WINDOWS, number=self.number) + elif self.number < 16: + return Color(self.name, ColorType.WINDOWS, number=self.number - 8) + triplet = ColorTriplet(*EIGHT_BIT_PALETTE[self.number]) + + color_number = STANDARD_PALETTE.match(triplet) + return Color(self.name, ColorType.WINDOWS, number=color_number) + return self diff --git a/rich/console.py b/rich/console.py index 774f1bcf..e37386f0 100644 --- a/rich/console.py +++ b/rich/console.py @@ -7,6 +7,7 @@ import inspect from itertools import chain import os from operator import itemgetter +import platform import re import shutil import sys @@ -48,8 +49,9 @@ from .segment import Segment if TYPE_CHECKING: # pragma: no cover from .text import Text -HighlighterType = Callable[[Union[str, "Text"]], "Text"] +WINDOWS = platform.system() == "Windows" +HighlighterType = Callable[[Union[str, "Text"]], "Text"] JustifyValues = Optional[Literal["left", "center", "right", "full"]] @@ -151,6 +153,7 @@ COLOR_SYSTEMS = { "standard": ColorSystem.STANDARD, "256": ColorSystem.EIGHT_BIT, "truecolor": ColorSystem.TRUECOLOR, + "windows": ColorSystem.WINDOWS, } @@ -179,7 +182,7 @@ class Console: def __init__( self, color_system: Optional[ - Literal["auto", "standard", "256", "truecolor"] + Literal["auto", "standard", "256", "truecolor", "windows"] ] = "auto", styles: Dict[str, Style] = None, file: IO = None, @@ -226,6 +229,8 @@ class Console: """Detect color system from env vars.""" if not self.is_terminal: return None + if WINDOWS: + return ColorSystem.WINDOWS if os.environ.get("COLORTERM", "").strip().lower() == "truecolor": return ColorSystem.TRUECOLOR # 256 can be considered standard nowadays @@ -311,6 +316,8 @@ class Console: return ConsoleDimensions(self._width, self._height) width, height = shutil.get_terminal_size() + if WINDOWS: + width -= 1 return ConsoleDimensions( width if self._width is None else self._width, height if self._height is None else self._height, @@ -796,7 +803,7 @@ class Console: """ text = self.export_text(clear=clear, styles=styles) - with open(path, "wt") as write_file: + with open(path, "wt", encoding="utf-8") as write_file: write_file.write(text) def export_html( @@ -899,7 +906,7 @@ class Console: code_format=code_format, inline_styles=inline_styles, ) - with open(path, "wt") as write_file: + with open(path, "wt", encoding="utf-8") as write_file: write_file.write(html) diff --git a/rich/markdown.py b/rich/markdown.py index 906271f9..205eca27 100644 --- a/rich/markdown.py +++ b/rich/markdown.py @@ -368,7 +368,7 @@ class Markdown: inlines = self.inlines new_line = False for current, entering in nodes: - print(current, current.literal) + # print(current, current.literal) node_type = current.t if node_type in ("html_inline", "html_block", "text"): context.on_text(current.literal) @@ -433,12 +433,44 @@ if __name__ == "__main__": # pragma: no cover markup = """ An h1 header ============ - -| Syntax | Description | -| --- | ----------- | -| Header | Title | -| Paragraph | Text | - +Paragraphs are separated by a blank line. +2nd paragraph. *Italic*, **bold**, and `monospace`. Itemized lists look like: +* this one +* that one +* the other one +Note that --- not considering the asterisk --- the actual text content starts at 4-columns in. +> Block quotes are +> written like so. +> +> They can span multiple paragraphs, +> if you like. +Use 3 dashes for an em-dash. Use 2 dashes for ranges (ex., "it's all in chapters 12--14"). Three dots ... will be converted to an ellipsis. Unicode is supported. ☺ +An h2 header +------------ +```python + @classmethod + def adjust_line_length( + cls, line: List[Segment], length: int, style: Style = None + ) -> List[Segment]: + line_length = sum(len(text) for text, _style in line) + if line_length < length: + return line[:] + [Segment(" " * (length - line_length), style)] + elif line_length > length: + line_length = 0 + new_line: List[Segment] = [] + append = new_line.append + for segment in line: + segment_length = len(segment.text) + if line_length + segment_length < length: + append(segment) + line_length += segment_length + else: + text, style = segment + append(Segment(text[: length - line_length], style)) + break + return new_line + return line +``` """ import commonmark