Skip to content

Commit

Permalink
TextArea default CSS (Textualize#4074)
Browse files Browse the repository at this point in the history
* Starting CSS work for TextArea

* Remove xfail marker from test

* Adding component classes to TextArea, not using them yet

* Adding docstring for TextArea new component classes

* Passing all component styles to the theme so that they may be applied.

* Applying cursor component style

* Applying text-area--cursor-line component style

* Applying text-area--cursor-gutter component style

* Applying gutter cursor style correctly

* Default cursor styling

* CSS theming of the selection style

* default matching bracket theme in text area

* Support toggling dark and light mode

* Improve the theme on light mode for the cursor

* null check

* Snapshot for new default "css" theme of TextArea

* Hide cursor when TextArea doesnt have focus

* Some new docs for TextArea

* Add border to TextArea to fit more with Input

* Add note on how to remove the focus border effect

* Updating snapshots

* Updating snapshots

* Fixing tests to account for new TextArea border

* Fix a typo

* Updating CHANGELOG

* Update docs/widgets/text_area.md

Co-authored-by: Rodrigo Girão Serrão <[email protected]>

* Add missing docstring

---------

Co-authored-by: Rodrigo Girão Serrão <[email protected]>
  • Loading branch information
darrenburns and rodrigogiraoserrao authored Jan 31, 2024
1 parent 95e0592 commit f017604
Show file tree
Hide file tree
Showing 8 changed files with 1,884 additions and 1,424 deletions.
4 changes: 3 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
- Breaking change: Significant changes to `TextArea.__init__` default values/behaviour https://github.com/Textualize/textual/pull/3933
- `soft_wrap=True` - soft wrapping is now enabled by default.
- `show_line_numbers=False` - line numbers are now disabled by default.
- `tab_behaviour="focus"` - pressing the tab key now switches focus instead of indenting by default.
- `tab_behaviour="focus"` - pressing the tab key now switches focus instead of indenting by default.
- Breaking change: `TextArea` default theme changed to CSS, and default styling changed https://github.com/Textualize/textual/pull/4074
- Breaking change: `DOMNode.has_pseudo_class` now accepts a single name only https://github.com/Textualize/textual/pull/3970
- Made `textual.cache` (formerly `textual._cache`) public https://github.com/Textualize/textual/pull/3976
- `Tab.label` can now be used to change the label of a tab https://github.com/Textualize/textual/pull/3979
Expand All @@ -27,6 +28,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
- Added `TextArea.code_editor` classmethod/alternative constructor https://github.com/Textualize/textual/pull/3933
- Added `TextArea.wrapped_document` attribute which can convert between wrapped visual coordinates and locations https://github.com/Textualize/textual/pull/3933
- Added `show_line_numbers` to `TextArea.__init__` https://github.com/Textualize/textual/pull/3933
- Added component classes allowing `TextArea` to be styled using CSS https://github.com/Textualize/textual/pull/4074
- Added `Query.blur` and `Query.focus` https://github.com/Textualize/textual/pull/4012
- Added `MessagePump.message_queue_size` https://github.com/Textualize/textual/pull/4012
- Added `TabbedContent.active_pane` https://github.com/Textualize/textual/pull/4012
Expand Down
25 changes: 23 additions & 2 deletions docs/widgets/text_area.md
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,17 @@ There are some methods available which make common selections easier:
Themes give you control over the look and feel, including syntax highlighting,
the cursor, selection, gutter, and more.

#### Default theme

The default `TextArea` theme is called `css`.
This a theme which takes values entirely from CSS.
This means that the default appearance of the widget fits nicely into a standard Textual application,
and looks right on both dark and light mode.

More complex applications such as code editors will likely want to use pre-defined themes such as `monokai`.
This involves using a `TextAreaTheme` object, which we cover in detail below.
This allows full customization of the `TextArea`, including syntax highlighting, at the code level.

#### Using builtin themes

The initial theme of the `TextArea` is determined by the `theme` parameter.
Expand Down Expand Up @@ -232,6 +243,7 @@ my_theme = TextAreaTheme(

Attributes like `cursor_style` and `cursor_line_style` apply general language-agnostic
styling to the widget.
If you choose not to supply a value for one of these attributes, it will be taken from the CSS component styles.

The `syntax_styles` attribute of `TextAreaTheme` is used for syntax highlighting and
depends on the `language` currently in use.
Expand Down Expand Up @@ -485,9 +497,13 @@ The `TextArea` widget defines the following bindings:

## Component classes

The `TextArea` widget defines no component classes.
The `TextArea` defines component classes that can style various aspects of the widget.
Styles from the `theme` attribute take priority.

Styling should be done exclusively via [`TextAreaTheme`][textual.widgets.text_area.TextAreaTheme].
::: textual.widgets.TextArea.COMPONENT_CLASSES
options:
show_root_heading: false
show_root_toc_entry: false

## See also

Expand All @@ -499,6 +515,11 @@ Styling should be done exclusively via [`TextAreaTheme`][textual.widgets.text_ar
- The tree-sitter Python bindings [repository](https://github.com/tree-sitter/py-tree-sitter).
- `py-tree-sitter-languages` [repository](https://github.com/grantjenks/py-tree-sitter-languages) (provides binary wheels for a large variety of tree-sitter languages).


## Additional notes

- To remove the outline effect when the `TextArea` is focused, you can set `border: none; padding: 0;` in your CSS.

---

::: textual.widgets._text_area.TextArea
Expand Down
84 changes: 62 additions & 22 deletions src/textual/_text_area_theme.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
from __future__ import annotations

from dataclasses import dataclass, field
from typing import TYPE_CHECKING

from rich.style import Style

from textual.app import DEFAULT_COLORS
from textual.color import Color
from textual.design import DEFAULT_DARK_SURFACE

if TYPE_CHECKING:
from textual.widgets import TextArea


@dataclass
class TextAreaTheme:
Expand Down Expand Up @@ -63,10 +67,18 @@ class TextAreaTheme:
syntax_styles: dict[str, Style] = field(default_factory=dict)
"""The mapping of tree-sitter names from the `highlight_query` to Rich styles."""

def __post_init__(self) -> None:
"""Generate some styles if they haven't been supplied."""
if self.base_style is None:
self.base_style = Style()
def apply_css(self, text_area: TextArea) -> None:
"""Apply CSS rules from a TextArea to be used for fallback styling.
If any attributes in the theme aren't supplied, they'll be filled with the appropriate
base CSS (e.g. color, background, etc.) and component CSS (e.g. text-area--cursor) from
the supplied TextArea.
Args:
text_area: The TextArea instance to retrieve fallback styling from.
"""
self.base_style = text_area.rich_style or Style()
get_style = text_area.get_component_rich_style

if self.base_style.color is None:
self.base_style = Style(color="#f3f3f3", bgcolor=self.base_style.bgcolor)
Expand All @@ -81,33 +93,58 @@ def __post_init__(self) -> None:
assert self.base_style.bgcolor is not None

if self.gutter_style is None:
self.gutter_style = self.base_style.copy()
gutter_style = get_style("text-area--gutter")
if gutter_style:
self.gutter_style = gutter_style
else:
self.gutter_style = self.base_style.copy()

background_color = Color.from_rich_color(self.base_style.bgcolor)
if self.cursor_style is None:
self.cursor_style = Style(
color=background_color.rich_color,
bgcolor=background_color.inverse.rich_color,
)
# If the theme doesn't contain a cursor style, fallback to component styles.
cursor_style = get_style("text-area--cursor")
if cursor_style:
self.cursor_style = cursor_style
else:
# There's no component style either, fallback to a default.
self.cursor_style = Style(
color=background_color.rich_color,
bgcolor=background_color.inverse.rich_color,
)

# Apply fallbacks for the styles of the active line and active line gutter.
if self.cursor_line_style is None:
self.cursor_line_style = get_style("text-area--cursor-line")

if self.cursor_line_gutter_style is None:
self.cursor_line_gutter_style = get_style("text-area--cursor-gutter")

if self.cursor_line_gutter_style is None and self.cursor_line_style is not None:
self.cursor_line_gutter_style = self.cursor_line_style.copy()

if self.bracket_matching_style is None:
bracket_matching_background = background_color.blend(
background_color.inverse, factor=0.05
)
self.bracket_matching_style = Style(
bgcolor=bracket_matching_background.rich_color
)
matching_bracket_style = get_style("text-area--matching-bracket")
if matching_bracket_style:
self.bracket_matching_style = matching_bracket_style
else:
bracket_matching_background = background_color.blend(
background_color.inverse, factor=0.05
)
self.bracket_matching_style = Style(
bgcolor=bracket_matching_background.rich_color
)

if self.selection_style is None:
selection_background_color = background_color.blend(
DEFAULT_COLORS["dark"].primary, factor=0.75
)
self.selection_style = Style.from_color(
bgcolor=selection_background_color.rich_color
)
selection_style = get_style("text-area--selection")
if selection_style:
self.selection_style = selection_style
else:
selection_background_color = background_color.blend(
DEFAULT_COLORS["dark"].primary, factor=0.75
)
self.selection_style = Style.from_color(
bgcolor=selection_background_color.rich_color
)

@classmethod
def get_builtin_theme(cls, theme_name: str) -> TextAreaTheme | None:
Expand Down Expand Up @@ -342,12 +379,15 @@ def default(cls) -> TextAreaTheme:
},
)

_CSS_THEME = TextAreaTheme(name="css")

_BUILTIN_THEMES = {
"css": _CSS_THEME,
"monokai": _MONOKAI,
"dracula": _DRACULA,
"vscode_dark": _DARK_VS,
"github_light": _GITHUB_LIGHT,
}

DEFAULT_THEME = TextAreaTheme.get_builtin_theme("monokai")
DEFAULT_THEME = TextAreaTheme.get_builtin_theme("basic")
"""The default TextAreaTheme used by Textual."""
Loading

0 comments on commit f017604

Please sign in to comment.