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
6 changes: 6 additions & 0 deletions docs/overview.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,12 @@ To make it easier to find entries later, `jrnl` includes support for inline tags
(the default tag symbol is `@`). You can find and filter entries by using tags
along with other search criteria.

`jrnl` also supports custom colors for individual tags, allowing you to create
visual organization systems. For example, you can assign different colors to
priority levels (`@high` in red, `@low` in green) or categories (`#work` in blue,
`#personal` in cyan). See the [configuration reference](./reference-config-file.md#tag_colors)
for details on setting up tag colors.

## Support for Multiple Journals

`jrnl` includes support for the creation of multiple journals, each of which
Expand Down
22 changes: 22 additions & 0 deletions docs/reference-config-file.md
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,28 @@ Current valid values are: `BLACK`, `RED`, `GREEN`, `YELLOW`, `BLUE`,

To disable colored output, set the value to `NONE`.

### tag_colors
A dictionary that allows you to specify custom colors for individual tags.
This provides more granular control over tag coloring than the general `colors.tags` setting.

Each key should be a tag (including the tag symbol), and each value should be a valid color name.
Tag matching is case-insensitive, so `@HIGH` and `@high` are treated the same.

Tags not specified in this dictionary will fall back to the color defined in `colors.tags`.

Example:
```yaml
tag_colors:
"@high": red
"@medium": yellow
"@low": green
"#urgent": magenta
"#work": blue
"#personal": cyan
```

Valid color values are the same as for the `colors` dictionary: `BLACK`, `RED`, `GREEN`, `YELLOW`, `BLUE`, `MAGENTA`, `CYAN`, `WHITE`, and `NONE`.

### display_format
Specifies formatter to use by default. See [formats](formats.md).

Expand Down
40 changes: 40 additions & 0 deletions docs/tips-and-tricks.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,46 @@ License: https://www.gnu.org/licenses/gpl-3.0.html
This page contains tips and tricks for using `jrnl`, often in conjunction
with other tools, including external editors.

## Color-coding tags for organization

You can use color-coded tags to create a visual system for organizing your entries. This is particularly useful for productivity systems, mood tracking, or project management.

Here's an example configuration for a productivity-focused journaling system:

```yaml
tag_colors:
# Priority levels
"@high": red
"@medium": yellow
"@low": green

# Categories
"#work": blue
"#personal": cyan
"#health": magenta

# Status indicators
"@done": green
"@todo": red
"@inprogress": yellow
```

With this setup, when you write entries like:

```sh
jrnl "Finished the quarterly report @done #work @high. Need to schedule team meeting @todo #work @medium."
```

The tags will be displayed in different colors, making it easy to scan your entries visually and quickly identify priorities, categories, and status at a glance.

You can then use tag filtering in combination with colors:

```sh
jrnl #work @high # Show high-priority work items
jrnl @todo # Show all todo items (in red)
jrnl #health -n 5 # Show recent health-related entries
```

## Co-occurrence of tags

If I want to find out how often I mentioned my flatmates Alberto and
Expand Down
21 changes: 20 additions & 1 deletion jrnl/color.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,23 @@
colorama.init()


def get_tag_color(tag: str, config: dict) -> str:
"""
Returns the configured color for a specific tag,
falling back to the general tags color.

:param tag: The tag string (e.g., "@high", "#work")
:param config: The journal configuration dict
:return: Color name to be used with colorize()
"""
# Check if tag_colors is configured and if this specific tag has a color
if "tag_colors" in config and tag.lower() in config["tag_colors"]:
return config["tag_colors"][tag.lower()]

# Fall back to the general tags color
return config.get("colors", {}).get("tags", "none")


def colorize(string: str, color: str, bold: bool = False) -> str:
"""Returns the string colored with colorama.Fore.color. If the color set by
the user is "NONE" or the color doesn't exist in the colorama.Fore attributes,
Expand Down Expand Up @@ -53,7 +70,9 @@ def colorized_text_generator(fragments):
if part and part[0] not in config["tagsymbols"]:
yield colorize(part, color, bold=is_title), part
elif part:
yield colorize(part, config["colors"]["tags"], bold=True), part
# Check if this tag has a specific color configured
tag_color = get_tag_color(part, config)
yield colorize(part, tag_color, bold=True), part

config = entry.journal.config
if config["highlight"]: # highlight tags
Expand Down
21 changes: 21 additions & 0 deletions jrnl/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ def get_default_config() -> dict[str, Any]:
"tags": "none",
"title": "none",
},
"tag_colors": {},
}


Expand Down Expand Up @@ -143,6 +144,26 @@ def verify_config_colors(config: dict) -> bool:
)
)
all_valid_colors = False

# Verify tag_colors if present
if "tag_colors" in config:
for tag, color in config["tag_colors"].items():
upper_color = color.upper()
if upper_color == "NONE":
continue
if not getattr(colorama.Fore, upper_color, None):
print_msg(
Message(
MsgText.InvalidColor,
MsgStyle.NORMAL,
{
"key": f"tag_colors.{tag}",
"color": color,
},
)
)
all_valid_colors = False

return all_valid_colors


Expand Down
61 changes: 61 additions & 0 deletions tests/unit/test_color.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from colorama import Style

from jrnl.color import colorize
from jrnl.color import get_tag_color


@pytest.fixture()
Expand All @@ -19,3 +20,63 @@ def test_colorize(data_fixture):
colorized_string = colorize(string, "BLUE", True)

assert colorized_string == Style.BRIGHT + Fore.BLUE + string + Style.RESET_ALL


@pytest.fixture()
def config_with_tag_colors():
return {
"tag_colors": {
"@high": "red",
"@low": "green",
"#urgent": "magenta",
},
"colors": {"tags": "yellow"},
}


def test_get_tag_color_with_specific_color(config_with_tag_colors):
"""Test that specific tag colors are returned when configured"""
config = config_with_tag_colors

assert get_tag_color("@high", config) == "red"
assert get_tag_color("#urgent", config) == "magenta"
assert get_tag_color("@low", config) == "green"


def test_get_tag_color_case_insensitive(config_with_tag_colors):
"""Test that tag color lookup is case insensitive"""
config = config_with_tag_colors

assert get_tag_color("@HIGH", config) == "red"
assert get_tag_color("#URGENT", config) == "magenta"


def test_get_tag_color_fallback_to_default(config_with_tag_colors):
"""Test that unspecified tags fall back to the default tags color"""
config = config_with_tag_colors

assert get_tag_color("@unknown", config) == "yellow"
assert get_tag_color("#work", config) == "yellow"


def test_get_tag_color_no_tag_colors_config():
"""Test that it falls back to default when no tag_colors section exists"""
config = {"colors": {"tags": "blue"}}

assert get_tag_color("@any", config) == "blue"
assert get_tag_color("#any", config) == "blue"


def test_get_tag_color_missing_colors_config():
"""Test that it falls back to 'none' when no colors section exists"""
config = {}

assert get_tag_color("@any", config) == "none"
assert get_tag_color("#any", config) == "none"


def test_get_tag_color_empty_tag_colors():
"""Test with empty tag_colors section"""
config = {"tag_colors": {}, "colors": {"tags": "cyan"}}

assert get_tag_color("@any", config) == "cyan"