ttdl

VladimirMarkelovMIT活跃维护

用 Rust 写的终端待办清单管理器,在命令行里就能快速记录、查看和管理 todo,不用离开终端。

TTDL - Terminal Todo List Manager

v6.2.5发布于 2026-11-08 01:45 UTC266star21fork同步于 2026-11-11 00:01 UTC

怎么上手

由 README 提炼,执行前请确认
cargo install ttdl
硬件要求按 README 说明

README 未说明;它是纯 CLI 工具,不依赖 GPU,能运行 Rust 1.31+ 编译产物的平台即可

下载:源码 zip·GitHub Releases(安装包)本站不镜像安装包

项目说明

由 README 自动翻译提炼

用 Rust 写的终端 todo.txt 管理器,在命令行里增删改查、筛选和归档任务

用 Rust 写的终端待办清单管理器,在命令行里就能快速记录、查看和管理 todo,不用离开终端。

核心能力

  • 按 todo.txt 格式管理任务,支持项目、上下文和标签
  • 支持日期表达式与循环任务,可设置截止和阈值日期
  • 可按任意字段过滤、分组,并支持归档已完成任务
  • 支持时间跟踪、统计、日历和议程视图
  • 可自定义输出列、颜色和 Markdown 渲染

容易踩的坑

需要 Rust 1.31 或更新版本才能从源码编译;默认不自动创建配置文件,需用 --init 或 --init-local 生成;新增任务若文本以 + 或 @ 开头会报 Subject is empty,需在前面加空格

内容由模型根据 README 生成,可能不完整或不准确,以官方文档为准。看原文 README

最近发版

5 个版本v6.2.5
  1. v6.2.5Fix duplicated tasks when custom filter is used最新正式版2026-11-08 · 2026-11-08 01:45 UTC
    • Fixed: when a task matched a few rules of a custom filter, the task was duplicated in the output
  2. v6.2.3Fix postpone command2026-10-18 · 2026-10-18 02:00 UTC
    查看更新说明

    Changes:

    • bump crate versions
    • now it is possible to postpone a threshold for a task that does not have a due date
  3. v6.2.2Shorthand for `--completed`2026-07-11 · 2026-07-11 18:55 UTC
    查看更新说明
    • Added a shorthand -C for the filter command-line option --completed
  4. v6.2.1Quality of life improvement2026-07-10 · 2026-07-10 02:08 UTC
    查看更新说明
    • fix some clippy warnings
    • QOL: the filter option --completed does not require -a any longer. If you do not pass either -a or -A, TTDL automatically enables -A along with --completed to avoid confusion for
  5. v6.1.1Cleanup debug print2026-04-26 · 2026-04-26 04:23 UTC
    查看更新说明
    • remove debug print when an updated file is saved
    • bump crate versions

用户点评

登录用户的真实使用记录,可按版本和硬件筛选
共 0 条

    还没有人点评。第一个说出真实体验的人最有价值。

    README 原文

    未翻译

    Table of Contents

    TTDL (Terminal ToDo List)

    build crates.io Downloads

    A CLI tool to manage todo lists in todo.txt format. A short demo of TTDL in action:

    Installation

    The application can be compiled from source, or installed using cargo:

    cargo install ttdl
    

    TTDL includes Markdown rendering support by default. To build without it:

    cargo install ttdl --no-default-features
    

    When built without the markdown feature, the --markdown flag is accepted but has no effect.

    You need Rust compiler that supports Rust 2018 edition (Rust 1.31 or newer) to do it. If you want to upgrade existing ttdl execute the following command:

    cargo install ttdl --force
    

    Precompiled binaries

    For Windows and Ubuntu you can download precompiled binaries from Release page.

    • Windows binary works on Windows 7 or newer Windows.
    • Ubuntu binary tested on Ubuntu 16 but should work on Ubuntu 18 (and maybe on other deb-based Linux distributions)

    Scoop

    For Windows you can install using Scoop.

    First, you need to add scoop bucket Extras, then you can install TTDL:

    scoop bucket add extras
    
    scoop install ttdl
    

    Homebrew

    For macOS and Linux you can install TTDL using Homebrew:

    brew install ttdl
    

    Archlinux (AUR)

    For Archlinux you can install TTDL from the AUR package:

    paru -S ttdl
    

    Known issues

    Adding a new todo, append or prepend a text to existing todo results in error:

    It may happen if the text starts with(or contains only) a project or a context:

    $ ttdl add "+myproject"
    Subject is empty
    

    Workaround: add a space between quotation mark and '+' or '@' symbol. The todo will be added without leading space:

    $ ttdl add " +myproject"
    Added todo:
    # D P Created    Finished   Due        Threshold  Subject
    ----------------------------------------------------------
    8                                                 +myproject
    

    Configuration

    TTDL is a standalone binary and it does not create any files in user's directory. But at start, it checks for a configuration file - please see example configuration ttdl.toml in user's configuration directory and loads it. Local configuration files are supported as well. Locations where TTDL looks for a configuration file:

    • current working directory
    • Linux: ~/.config/ttdl/ttdl.toml
    • Windows: c:\Users\{username}\AppData\Roaming\ttdl\ttdl.toml
    • OSX: /Users/{username}/Library/Application Support/ttdl/ttdl.toml

    To check in which directory TTDL looks for its configuration file, please refer to the table Features, function name config_dir.

    First, TTDL looks for a configuration file in the current working directory. And only if it does not contain ttdl.toml, the application looks for its configuration file in user's directory. Automatic configuration path detection can be overridden with command line option -c or --config. If the option is set in command line TTDL disables automatic detection of the configuration file path.

    TTDL does not create its configuration automatically. You have to create it manually or copy existing one from some location, e.g. from the distribution archive. Another way to create a default configuration file is to run TTDL with one of command-line options:

    • --init - this option creates a default configuration file in the user's configuration directory. If TTDL fails to detect where the directory is, it does not create anything
    • --init-local - this option creates a default configuration file in the current working directory.

    None of the command-line options above overwrites TTDL configuration file if it exists. So if you want to reset all setting to default ones, you must delete or rename the existing configuration file and then run TTDL with any of init command-line options.

    The configuration file contains options that cannot be set in command line:

    • default foreground color(usually it is white color for dark-themed terminal). By default, the option is not in the configuration file that means None = terminal default color.
    • colors for special kinds of todos: overdue, due today, due soon, top priority, high priority, and completed
    • ranges for cases "due soon" and "high priority". By default both option are disabled. To enable "due soon", set it to the number of days, so todos that are due in equal to or less than that number(except overdue and due today todos) will be displayed with soon color. To enable high priority highlight, set important to a priority - all todos with this priority or higher(except top priority ones) will be displayed with important color.
    • filename - the path to global todo file (can point to directory, TTDL adds todo.txt automatically if filename is a directory). To override the option, you can set environment variable TTDL_FILENAME or use command line option --local if you need to load todo list from current working directory
    • creation_date_auto The option defines TTDL behavior when a new todo is added. By default, TTDL adds a todo as is - a user must set manually creation date in the subject. Setting creation_date_auto to true makes TTDL to set today as creation date for all new todos if their subject does not include creation date
    • editor - the path to the editor binary that is used to edit selected tasks in an editor of your choice.

    Extra ways to set filenames of active and archived todos

    Rules to choose a file which is loaded as todo list at startup (from lowest to highest priority):

    1. Default is a file "todo.txt" in the current working directory
    2. Configuration file option filename in section global
    3. Value of environment variable TTDL_FILENAME
    4. Command line option --todo-file
    5. If option --local is set it overrides all options above and loads "todo.txt" from the current working directory

    If any path at steps 2-5 points to a directory then "todo.txt" is added automatically.

    Rules to choose a file which is used to store archived todos (from lowest to highest priority):

    1. "done.txt" in the same directory with "todo.txt"
    2. Command line option --done-file. If any path set in command line points to a directory then "done.txt" is added automatically. If the value is only a filename without directory then the directory is inherited from todo list file

    Customizing the output

    NOTE: The only column that cannot be customized and cannot be hidden is the very first column ID. The ordinal number of a task always goes first.

    The default output shows the following columns: marker for done, priority, creation date, finish date, due date, and subject. All dates are displayed as absolute values in ISO format, e.g. "2023-05-19".

    There are a lot of way to change the way TTDL displays the task table. The simplest way is to use command-line option --short. The option hides all columns except marker for done and priority.

    If you prefer to see how many days left before something or how many days passed some date, use the command-line option --human. The option transforms absolute dates into in 1w(in 1 week) or 6d ago(6 days ago) depending on the current date.

    If you want to hide, show, or reorder some columns, use the command-line option --fields or modify configuration option global.fiels. The option is a command-separated list of column names. TTDL displays columns in the same order in which you put them to --fields. Usually the column name and a tag name are the same, but majority of standard columns are exceptions:

    Field nameColumn displays
    doneShows x if a task is done, R if a task is recurrent, and nothing otherwise
    priTask priority
    ctxTask contexts, comma-separated if the task has more than 1 context
    prjTask projects, comma-separated if the task has more than 1 project
    createdTask creation date
    finishedTask completion date
    dueTask due date
    thrThreshold date (the tag name is t:)
    untilThe date until which a recurrent task is active
    subjectThe subject(or the full text) of the task

    NOTE: the field subject is a special field. It is printed always. If you do not pass subject in the option --fields, the column Subject is always the last one. All other options such as --width, --wrap, --compact affects the output. When you pass a subject in the option --fields, it behaves a bit differently. In this case, you can put the subject column at any position, but the --wrap option is ignored, and the option --compact is always set. In other words, when you add subject to the --fields option, TTDL always prints a task per screen line and cuts the subject text if needed. Also note that in all cases, TTDL never wraps or cuts the subject text if the total length of the rest columns is greater than the width of screen minus the minimal length of the subject column (10).

    If you do not like empty columns, but you do not want to fiddle with --fields every time, you can use the option --auto-hide-cols or set the configuration option global.auto_hide_columns = true. When auto hiding columns is on, TTDL automatically hides the columns which have no values.

    The opposite case: you do not want to define --fields every time but you want to display all existing tags in separate columns. Use the option --auto-show-cols or set the global.auto_show_columns = true. When the option is on, first, TTDL traverse through the list of tasks and collects all non-empty tags. Second, TTDL shows all the tags in corresponding columns.

    NOTE: custom tags(tags which names start with !) are never shown in separate columns.

    When using --fields and auto-show-cols you can notice that some information is duplicated. The first occurrence is in the column, the second one is inside subject text. Sometimes it is helpful, e.g. if you pass --human option, columns with dates will display relative value(1d ago). The option --human does not affect custom tags that are not mentioned in the table above. At the same time, task subject contains the absolute value of the date(2023-05-17). Anyway, most of the time the duplication does not look good. To mitigate the issue, use the option --clean-subject or set the configuration option global.clean_subject. The option takes one of three values:

    1. none - default mode. Nothing is hidden.
    2. all - all duplicated info is removed from the subject (project, context, tags, etc)
    3. tags - only tags are removed from the subject(contexts and projects are always displayed in subject). Sometimes, context or project is a part of the text, so removing them can ruin the message. This mode helps to fix this trouble.

    The last option that affects the output, is --always-hide-cols or configuration option global.always_hide_columns. Its value is a comma-separated list of tags that are never shows in their own columns, they are shown only inside subject text.

    None of mentioned options are mutually exclusive. They allow you to customize the output in a flexible way. E.g, by setting --fields you define what fields you want to see first and in what order. Adding --auto-show-cols display the rest of columns in "random" order - in order of appearance in the task list.

    The options are applied to the output in the following order(in case of all options are enabled):

    1. First, --fields to create the beginning of the table
    2. Second, --auto-show-cols appends the rest of columns
    3. Third, --auto-hide-cols removes all empty columns
    4. Fourth, --always-hide-cols removes the columns that should not be visible
    5. The last, --clean-subject removes from the subject text all tags and values which are displayed in separate columns
    Output customization example

    Let's assume, todo.txt contains a task:

    buy milk @food +house due:2023-07-31 rec:1w count:2
    

    Show the list of tasks with various options (for brevity, I omit the tail of the output):

    > ttdl list
     # D P Created Finished Due        Subject
     -------------------------------------------
     1 R                    2023-07-31 buy milk @food +house due:2023-07-31 rec:1w count:2
    
    > ttdl list --human
     # D P Created Finished Due   Subject
     --------------------------------------
     1 R                    in 2d buy milk @food +house due:2023-07-31 rec:1w count:2
    
    > ttdl list --fields=ctx,created,due
     # Context Created Due        Subject
     --------------------------------------
     1 food            2023-07-31 buy milk @food +house due:2023-07-31 rec:1w count:2
    
    > ttdl list --fields=ctx,created,due --auto-hide-cols
     # Context Created Due        Subject
     --------------------------------------
     1 food    2023-07-31 buy milk @food +house due:2023-07-31 rec:1w count:2
    
    > ttdl list --fields=ctx,created,due --auto-hide-cols --auto-show-cols
     # Context Due        D Project Rec Count Subject
     --------------------------------------------------
     1 food    2023-07-31 R house   1w  2     buy milk @food +house due:2023-07-31 rec:1w count:2
    
    > ttdl list --fields=ctx,created,due --auto-hide-cols --auto-show-cols --clean-subject=all
     # Context Due        D Project Count Rec Subject
     --------------------------------------------------
     1 food    2023-07-31 R house   2     1w  buy milk
    
    > ttdl list --fields=ctx,created,due --auto-hide-cols --auto-show-cols --clean-subject=all --always-hide-cols=rec
     # Context Due        D Project Count Subject
     ----------------------------------------------
     1 food    2023-07-31 R house   2     buy milk rec:1w
    

    How to use

    Run TTDL with the command line:

    ttdl [command] [ID range] [subject] [filter options] [extra options]
    

    Options can be at any position. ID range and subject are optional but if you are going to use both, ID range must go first.

    If a non-option starts with + or @, the option is considered as a filter by project or context respectively. Command line can contain as many projects and contexts as needed. In this case, all items of the same group are combined with OR. Example: if you execute ttdl list +myproj +demo @docs, it displays all todos that belongs to either myproj or demo, and have docs context.

    If the first non-option word contains only digits and dash character, it is treated as a single ID(only digits) or ID range(digits with a dash) or ID list(numbers separated with comma). ID is 1-based: a number between 1 and the number of todos in the entire list. It is OK to use ID out of that range: IDs that are greater than the number of todos or lower than 1 are skipped. So, e.g., if you want to remove all todos starting from 10th todo, you can run the command ttdl remove 10-999999 -a - -a to delete both completed and incomplete todos.

    The second non-option(or the first one if ID range is not defined) is a subject. Subject's usage depends on command:

    • add - it is an entire text for a new todo (including priority, projects, contexts, due date, recurrence, and hashtags);
    • edit - it is an entire new subject for the first selected todo;
    • for the rest commands it can be either a substring(case-insensitive search) or a regular expression to search in the todo's subject, projects, and contexts - depends on the option --regex.

    NOTES:

    1. All dates are entered and displayed in format - YYYY-MM-DD(4 year digits - 2 month digits - 2 day digits, or ISO 8601 date format)
    2. Recurrence is defined in format 'the number of intervals' + 'interval type' without a space between them. Interval type is one of d - days, w - weeks, m - months, y - years. Example: to add a todo with your friend's birthday(let's assume today is 10th of April) use the following command ttdl add "best friend birthday due:2019-04-10 rec:1y". After the birthday passes, just execute ttdl done <todo-ID> and it will change its due date to 2020-04-10
    3. Recurrence special case: if you set due date to the last day of a month and interval is a month or longer then the next due date will always be the end of the months. Example: a todo pay credit due:2019-02-28 rec:1m after executing ttdl done ID turns into pay credit due:2019-03-31
    4. Priority must be the first item in the task description: an uppercase Latin letter inside parenthesis. Examples: a task with a highest priority (A) fix the fridge; with the lowest priority (Z) send a letter to John

    Date Expressions

    Sometimes date tags inside a task are depend on each other. E.g, a due date can be defined as "a week after a threshold date". In this case it is easier to define them with expressions instead of fixed dates.

    The expressions are very limited. They must follow the pattern:

    <base date>[<operation><duration>]
    

    Where base date can be any of:

    • exact date like 2024-11-05
    • any other tag name like t
    • special values like today or sunday. See Human-readable dates for list of special dates

    The operation can be only either + or -;

    The duration follows the same rules as recurrence: a number followed by a duration type(d - days, w - weeks, m - months, y - years; if type is missing, it defaults to d).

    The pair <operation><duration> can be repeated as many times as you need. E.g, to set the due date a month and a week from today, you can write due:today+1m+1w.

    Only two commands support expressions: adding a new task and edit an existing task. Making a tag value dependent on another tag adds flexibility to the feature. Be careful and do not create endless loops. In this case you probably get an error about recursion limit. Examples when you can get that error:

    • ttdl add "water plants due:t t:due" - neither due nor t is an exact date, so TTDL will stuck in endless loop trying to calculate both values until the recursion limit is exceeded
    • ttdl add "water plants due:due+1" - a similar situation: due is not exact date, so TTDL try to calculate due+1, then due+1+1 etc that eventually fails
    • tddl edit --set-due=due+1 - this command works only if the task had due date set before calling edit. In this case, the command just push the due date by 1 day. But if the task did not have due date before, the command fails with the recursion limit error.

    NOTE: when editing a task, all --set-* options are executed in the same pre-defined order. The due date is always goes before the threshold date. It results in that the command ttdl edit 1 --set-due=today --set-threshold=due-5d works as expected, but the command ttdl edit 1 --set-threshold=today --set-due=t+5d sets correctly only the threshold date. The new value of the due date will be calculated as old value of the threshold date plus 5 days - it is not what expected.

    How expressions affect task subject

    When task is added, all tags are calculated, but only new values of due and t tags are saved in the subject. All other tags will keep their values as expressions.Example:

    ttdl add due:t+1 t:start start:today+4d
    

    After the task is added, the resulting task subject will look like this(assuming that today is 2024-11-01):

    ttdl add due:2024-11-06 t:2024-11-05 start:today+1d
    

    It adds flexibility to TTDL when working with a few tasks at a time. Example: you have a few tasks for the same project. But the tasks have different due date: they should be done one after another in a certain order. You can create task with a custom tag that renders the order:

    ttdl add "first task +project due:when when:today"
    ttdl add "second task +project due:when when:today+2"
    

    When you need to define new due date (or push the current date to a few days), you can run:

    ttdl edit +project --set-due=when
    

    It sets due date of the first task to today, and two dates after today for the second one.

    Note that initially you can skip setting due tag if you do not know due date yet: ttdl add "first task +project when:today". The following edit command will create due tag correctly.

    Recurrent tasks

    Sometimes you need to create a task that you have to do periodically - a recurrent task. To make a task recurrent, set the task due date and define a recurrence interval using tag rec:. Please note that a recurrent task must contain a due date, otherwise the recurrence tag is ignored.

    A recurrence interval is an integer number followed by an optional interval type:

    Interval typeMeaning
    (nothing)the number of days
    dthe number of days
    bthe number of business days (Sundays and Saturdays are skipped when counting)
    wthe number of weeks
    mthe number of months
    ythe number of years

    Examples: rec:1y - repeat the task every year, rec:2w - repeat the task every two weeks.

    A bare recurrence defines a non-strict interval. You can make the recurrence strict by putting a symbol + right before the number: rec:1w - non-strict, rec:+1w - strict. Strictness affects on how TTDL calculates the next due date after completing the current task:

    • Strict - the next due date depends on the current due date: next due date = current due date + recurrence interval. This mode is useful to track birthdays or regular payments that must be paid by a certain day of month
    • Non-strict - the next due date depends on the current date: next due date = today + recurrence interval. This mode is useful to track things that have relaxed due date but the task should be done within certain limit of days. Example: you have to mow your lawn at least once a month. So you set non-strict interval rec:1m. Then, even if you mow your lawn a few days before the due date, completing the task moves the due date to exactly 1 month ahead of today. That helps you to keep you lawn nice and never mow too late.

    More about recurrent task completion in the section "Completion of recurrent tasks".

    Example

    Let's assume today is 2023-11-20. In the todo list there are two tasks:

    pay credit card due:2023-11-29 rec:+1m
    mow lawn due:2023-11-29 rec:1m
    

    Both tasks have the same due date, but different strictness. Let's complete both tasks. The todo list now contains the following lines:

    pay credit card due:2023-12-29 rec:+1m
    mow lawn due:2023-12-20 rec:1m
    

    The strict task moved its due date to a month ahead of its previous due date. White the non-strict task now have the due date exactly in a month from the current date.

    Marking task completed and uncompleted

    When you complete a task and you do not want to see it in the default output any longer, mark the task 'done' with the command ttdl done ,ID>. If you have completed an incorrect task by accident, you can get it back with the command ttdl undone <ID>. Where ID is the same that you used for ttdo done command, because marking a task completed does not change its ID.

    Task completion command done supports a command-line option --resolution (-r for short). The value of the option is a text that is added to all completed tasks. If the value does not start with a space character, the space is added automatically. The option is a smart equivalent for two consecutive commands: ttdl append <task IDs> "resolution" and ttdl done <task IDs>. It is smarter because, when using a command-line option, the tasks that are already completed do nto get extra text at the end. At the same time, ttdl append and ttdl done, especially with an extra option -a, could add the resolution to task that have been completed a long time ago.

    Hint: to remove an invalid task use ttdl rm. This command may change IDs of all or a few tasks. The command ttdl rm is irreversible.

    Hint: after some time, your todo.txt can grow long and can be full of completed tasks. You can use ttdl archive to move all completed tasks to done.txt. It may make TTDL faster.

    For majority of cases it is all any user should know about completing and uncompleting tasks. But there are two special cases when completion can work differently: for tasks with priority set, and for recurrent tasks.

    Completion of recurrent tasks

    When you complete a recurrent task, it marks the old one as done and creates its clone with new due date. So, if you uncomplete the task, you will end with two similar tasks in your todo list. They differ only by their due dates. You should manually remove a duplicated task with ttdl rm.

    There is a special case for a recurrent task when the task's due date(for strict recurrence) or today's date(for non-strict recurrence) is the last day of a month and the recurrence interval type is either m(month) or y(year). In this case, the next due date always is the last day of a new due date's month.

    Example for strict recurrence: when completing a task pay credit card due:2023-02-28 rec:+1m, the new due date is due:2023-03-31 because 2023-02-28 is the last day of February.

    Example for non-strict recurrence: if today is 2023-02-28 and you complete a task pay credit card due:2023-02-25 rec:1m, the next due date of the task is due:2023-03-28 because today is the last day of a month - of February.

    Every time you mark a recurrent task done, a new recurrent task is created with a new due date. It goes on and on forever until you make the task non-recurrent or delete the task. There is another way to deal with it. You can set a special tag until. The value is the date after which the task stops creating new tasks automatically. When setting a value for until it is possible to use relative dates. E.g, until:2m sets until to a date that is 2 months from today. Example: let's assume your friend is going to travel for a month, and he asked you to come to his house once a week to water plants. In this case, you can create a task: water friend's plants due:1w rec:1w until:1m. The task means: starting from the next week I need to water the plants every week until 1 month passes.

    Tasks with priority

    Default TTDL behavior is just prepend x to the task subject when the task is done. Completed tasks in default mode keep displaying their priorities in the priority column as regular tasks do. If you do not want to see priority column filled with anything(and do not want to colorize done tasks by priority) for completed tasks, tune up the TTDL configuration.

    Either change ttdl.toml configuration file: section global, the option priority_on_done or pass command-line option priority-on-done. Available modes:

    • keep - use the default mode, when a completed tasks keeps showing its priority
    • move - on completion the priority moves after task completion date. So, the priority becomes part of task subject but is now shown in the priority column
    • tag - on completion the priority is set to none, but a new tag with priority value is appended to the subject. Example: (A) buy milk becomes x buy milk pri:A in this mode
    • erase - just set priority to None

    NOTE: do not forget to pass the same command-line option value when you want to uncomplete a completed task. All modes above, except erase, are reversible and correctly restore the task priority when you uncomplete the task.

    Output example

    # D P Created    Finished   Due        Threshold  Subject
    ----------------------------------------------------
    1 x A 2016-04-30 2016-05-20                       measure space for +chapelShelving @chapel
    2   C 2016-05-20                                  paint +chapelShelving @shelve
    3 R                         2018-11-11            pay credit card rec:1m
    ----------------------------------------------------
    3 todos (of 3 total)
    

    Columns:

    • # - order number of a todo
    • D - 'Done', it is empty for an incomplete regular todo, 'x' for a completed todo, and 'R' for recurrent todo
    • P - priority (from A to Z, empty value means no priority)
    • T - marks an active todo - a todo which has its timer running to track time spent on it

    Filtering

    TTDL allows operations on a range of todos. A range is defined either by todo IDs or by todo attribute values. The filters can be used to limit displayed todos with some condition, or to edit or done a group of command. E.g., you can mark done all todos of a project using a single command.

    All examples below are for list command, but filter can be used for more commands, like done and edit.

    Filter by IDs

    If the first argument after a command is a number-like, TTDL treats it as todo ID or todo ID list. Supported ID formats:

    • ttdl list 5 - a single todo ID, lists a single todo with ID = 5(if it is incomplete)
    • ttdl list 2-4 - a range, show all todos from 2nd to 4th(inclusive)
    • ttdl list 2,4 - a list of IDs, show two todos(if they are incomplete): only 2nd and 4th ones
    Filter by priority

    Priority filter is set with --pri argument. Available filter kinds:

    • ttdl list --pri=none - show all incomplete todos that does not have priority set
    • ttdl list --pri=- - the same as above
    • ttdl list --pri=any - show all incomplete todos that have non-empty priority
    • ttdl list --pri=+ - as above
    • ttdl list --pri=# - where # is a low case Latin letter between a and z, show all incomplete todos that have the same priority
    • ttdl list --pri=#+ - where # is a low case Latin letter between a and z, show all incomplete todos that have priority # or higher
    Filter by recurrence
    • ttdl list --rec=none - show all non-recurrent todos
    • ttdl list --rec=- - the same as above
    • ttdl list --rec=any - show all recurrent todos
    • ttdl list --rec=+ - as above
    Filter by project, context, and tag
    Free CLI arguments

    Any free command-line argument starting with + is a project name, and starting with @ is a context. A command can contain any number of project names(the same true for contexts), in this case the filter includes all todos that contains any of listed projects names. Please note, that while passing a few contexts means "select todos with any of provided contexts", passing a few project names and contexts at the same time means "select todos with any of provided project names and any of provided contexts".

    Project names and contexts support basic matching. Append or prepend * to project name to match projects which names ends or starts with the word.

    • ttdl list +proj - show all incomplete todos that belong to project proj
    • ttdl list @ctxA @ctxB - show all incomplete todos that have either context ctxA or context ctxB
    • ttdl list +projA +projB @ctx - show all incomplete todos that belong either to project projA or to project projB, and have context ctx ((projA ∨ projB) ∧ ctx)
    • ttdl list +*clientA - show all incomplete todos which project names ends with clientA
    • ttdl list @car* - show all incomplete todos which have a context starting with car

    To exclude todos containing certain project or context, prepend -(minus sign) before + or @:

    ttdl list +projA -@ctx - show all incomplete todos that belongs to project projA and do not contains context ctx. (projA ∧ ¬(ctx))

    Classic CLI options

    Besides using + and @ to indicate project and context titles, the application supports classic CLI options to set the list of values for a filter:

    • --project - filter by project title
    • --context - filter by context name
    • --tag - filter by tag name

    All options accepts a comma-separated list of names (and/or patterns). If any option value matches any record value, the record is displayed.

    Special values:

    • none - select records that do not have any values (--context none - records without any context)
    • any - select records that have at least one value (--project any - records that belong any project)

    Any item can be a full name or a pattern to match as it is described in the previous section.

    It is possible to exclude todos that contain given projects or contexts from the output. Minus sign - marks items to exclude:

    ttdl list --project projA --context "any,-ctxB" - list all todos that belong to project projA and have any context except ctxB. ({∀x | (x ∈ projA) ∧ ¬(x ∈ ctxB) })

    Filter by date

    All date-like fields(due, threshold, creation, finish) support filtering with a date range as well as a single date. Ranges are always inclusive. Start and end of a range are separated with either .. or :. A range can be "open": in this case it is a single value with appended or prepended range separator. For a closed range, the order of the range end is arbitrary: TTDL automatically exchanges beginning and end if needed. Ranges support only relative dates (positive for dates in the future and negative for dates in the past) or one of special dates.

    Relative dates is a list of numbers followed by suffixes (d - days, w - weeks, m - months, y- years) without separator. E.g., 1y in a year; 2m1w in two months and a week; -10d - 10 days ago.

    Special dates are:

    • none - a date is unset; the value can be used as a single value or as only one end of a range. In latter case the filter works as two filters combined: an open range with a real date, and all todos that have the date field empty
    • today, tomorrow, yesterday - their meaning is clear
    • first - first day of the next month
    • -first - first day of this month or the previous month(in case of today is the 1st)
    • last - last day of this month or next month(in case of today is the last day)
    • -last - last day of the previous month
    • day-of-week - a full name of a day of week or abbreviated to 2 or 3 first letters, the nearest day-of-week in the future(it is never today)
    • -day-of-week - a full name of a day of week or abbreviated to 2 or 3 first letters, the nearest day-of-week in the past(it is never today)

    Use the following command-line options to filter by:

    • due date --due
    • threshold date --thr
    • date of creation --created
    • completion date --completed. If you do not pass -a or -A in the command line, this option automatically enables -A

    Field threshold is the only field with a specific default value. All other fields have empty default value which means the filter is off. The default value of threshold is inclusive range [none..today]. TTDL by default hides tasks that has threshold date and the threshold date is tomorrow and later.

    Examples (note the difference between ranges and single values):

    • ttdl list --due today - show all todos that are due today
    • ttdl list --due ..today - show all todos that are due today or overdue
    • ttdl list --due today.. - show all todos that are due today or any day after today
    • ttdl list --due today..tomorrow - show todos that are due only today or tomorrow
    • ttdl list --due tomorrow..today - the same as above
    • ttdl list --completed -1w.. -a - show todos that were done within the last 7 days(a week from today)
    • ttdl list --completed -first.. - show todos that were done this month(or the previous month if today is the 1st), -a and -A are optional and can be omitted
    • ttdl list --due -first..last -a - show todos that are due this month(including overdue ones); in corner cases(today is the 1st or the last day of month) it shows todos for 2 months range instead of a month range
    • ttdl list --created -mon.. - show todos that were created this week(or the previous week if today is Monday)
    • ttdl list --created -mon - show todos that were created last Monday
    • ttdl list --due -2d..2d - show todos that are either slightly overdue(1-2 days overdue) or must be done within 2 next days
    • ttdl list --due none - show todos with undefined due date
    • ttdl list --due none..tomorrow - show todos that are overdue, due today or tomorrow, and with empty due date
    • ttdl list --due tomorrow..none - show todos that are due tomorrow or further in the future, and with empty due date
    Global filter by a substring

    All filters above works only for a certain todo attribute, but it is possible to filter with a simple substring or regular expression. In this case, TTDL looks for the substring everywhere: in subject(description), contexts, and projects. To do this, just pass a bare substring as the first argument after a command. Note the difference between project/context filter and a simple substring one: in the latter case * means a character *, not anything before or after substring. Use command-line option -e to enable fuzzy regular expression based filter.

    • ttdl list car* - show all incomplete todos that have substring car* in subject, project or context
    • ttdl list car.* -e show all incomplete todos which project, context or subject matches regular expression car.*

    Prepend the character ! to negate the filter. The command ttdl list !car shows all tasks that do not contain car in their subjects.

    Filter by anything

    If you have a lot of tasks, you may need more sophisticated filter. TTDL command-line option --filter allows you to use complex filter expressions that work with any part of a task: tags, hashtags, fields, and the whole subject. In this mode, TTDL also treats some tag values as strictly-typed ones, that allows filtering with ranges.

    This filter can replace a bunch of command-line options described above. The option --use-regex(-e) affects the filter. Regular expressions in the range filters are not supported.

    Every filter expression follows these rules:

    • The string contains all filter rules separated by tilde ~ or pipe | character. In case of using regular expression, you can use only ~. But in the regular case, any of two characters can be used. The rule: if the expression contains ~ it is used as the separator. Otherwise, the character | is used
    • Every rule is a list of conditions separated by character ;
    • A condition's format is TAG_NAME=<tagValue|tagList|tagRange, ..>
    • In other words, after the character = there is a list of values and ranges of values separated by character ,
    • As in the other places, you can use ranges in a way start_value..end_value. Both ends of the range are inclusive. Open-ended ranges are available as well, e.g, ..end_value works as <= end_value
    • And there are two special values none - means a task does not have the tag, and any means - a task must have the tag with any value
    • If a filter does not include a tag value, it means "show all tasks that include the tag". E.g, the filter type is the same as type=any

    The only field that has a few exceptions from the rules above is the field done - a marker that a task is completed. This field should be used without tag values. In other words, this fields should be used only in two ways: --filter="done" or --filter="-done". Though, some value can work: done=any means the same as bare done, and done=none is the same as -done.

    The TTDL treats all the mentioned parts of the passed value in the following way:

    • All rules separated by ~(or |) are independent rules. A task must match any of the rules
    • A task must match all conditions separated by ;
    • A tag value must match any of values or ranges separated by ,

    Examples of the pitfalls when using ~ and |:

    • --filter subj=val1|subj=val2 - it works as expected: shows tasks that contain either val1 or val2 in their subjects
    • --filter subj=val1|subj=val2~subj=val3 - as the character ~ is used, it becomes the separator. So, the filter means: show tasks that contains either val3 or val1|subj=val2 in their subjects
      • -e --filter subj=val1|subj=val2 - regular expressions are enabled, it means the character ~ must be the separator. So, the filter shows tasks that contain val1|subj=val2

    Note that in the last example you can rewrite the filter using regex rules: -e -f subj=val1|val2 to show tasks that contain either val1 or val2 in their subjects

    Example of reading a filter due=..yesterday|due=..today;severity=2..3,999:

    • The string contains two independent rules: due=..yesterday and due=..today;severity=2..3,999
    • The first rule is simple and contains only one condition
    • The second rule contains two conditions: due=..today and severity=2..3,999
    • The last condition is a list of allowed values: 2..3 and 999

    Combining everything together, we can declare the whole rule: filter tasks that matches any of two rules (or both):

    • due date before today
    • due date today AND (severity is either 999 OR in a range from 2 to 3 inclusive)

    For date fields you can use some predefined variables and expressions:

    ValueMeaning
    todayToday's date
    tomorrowTomorrow's date
    yesterdayYesterday's date
    relative date expression like 1wA week after today
    negative date expression like -1yA year ago
    On tag values

    When defining a tag values for a filter, you can use this types of values:

    • special value any - a task must have the field with any value
    • special value none or - - a task must have the field undefined
    • arbitrary single value - a task must have a field with the value. Example: due=today - choose only tasks with due date equals today date
    • a list of values - a task must have at least one of the values. Example: priority=A,C - choose only tasks that have priority either A or C
    • an open range of values - a task must have a field value within the range. Examples: 1) priority=C.. - tasks with priority C or lower; 2) priority=..B - tasks with priority B or higher
    • an open range of value with none value - a task must have a field value within the range or the field is undefined. Examples: priority=C..none - tasks with priority C or lower, and tasks that do not have priority set
    • a range with values at both ends - a task must have a field value within the range (inclusive). Example: priority=B..D - tasks with priority from B through D (or in other words with priority B, C, or D)

    Note that for some fields(for string-typed fields) ranges are not supported and filtering with a range always results in an empty list. Example: project or context. Such fields can be filtered only with special values, a single value or with a list of values. On the other hand, only string fields support fuzzy search.

    Built-in field names and shortcuts

    There are a few predefined field names that TTDL treats in a special way. If you pass a field name that TTDL does not know, it always treated as a task tag.

    Full nameShort namesDescription
    IDThe task ID that is printed in the first column when you list tasks
    prioritypriTask priority
    doneTask is marked completed
    subjectsubjThe whole task subject. Useful for full-text search
    projectprj or +Task project
    contextctx or @Task context
    hashtag#Task hashtag
    completedTask completion date
    createdcreateTask creation date

    The rest names are treated as-is: as task tags.

    Negative conditions

    Conditions can be negative. To make a condition negative, prepend a character - or ! to a tag name. Instead prepending the character to a tag name, you can prepend it to a tag value. But it works only in case of single value is used, for ranges you must prepend - only to the tag name. In other words, "id=-10 and -id=10 mean almost the same, but id=-10..-10 and -id=10..10 are different things.

    Note that the position of the negation sign - affects the result. Choose carefully where you put it: before a tag name or before a tag value:

    • If - is in front of a tag name, TTDL first determines what tasks match the condition and then selects the rest
    • If - is in front of a tag value, TTDL selects tasks that have the tag and ignore ones that has the tag with this value

    Examples of the importance of the position of the - mark:

    • no negation: priority=A,C - selects all tasks that have either A or C priority
    • negate the tag name: -priority=A,C - selects all tasks that either have no priority or their priority is not A or C
    • negate a single tag value: priority=-A,C - selects all tasks that have priority set and the priority is either C or not equal A. As you see, C is redundant and simpler expression priority=-A works the same
    • negate all tag values: priority=-A,-C - selects all tasks that have priority set and it is not equal to A or it is not equal to C. In other words, it the same as just priority=any because no task can have both A and C priority at the same time

    The default behavior of negation can be inconvenient in some case as it can include tasks with empty values. If you want to select only tasks that have the field set, you can make the expression a bit longer:

    • simple negation: -priority=A,C - selects all tasks that either have no priority or their priority is not A or C
    • extra condition: priority=any;-priority=A,C - selects all tasks that both have a priority set and it is not equal A and C
    Fuzzy search

    If a field is a string type or a field is a tag that can be coerced to a string, you can use simple fuzzy search. A character * can be put to a start, an end, or to both sides of a value. The character * inside a value is treated as *, not fuzzy search marker. Obviously, fuzzy search is unsupported in ranges.

    The field subject gets a special rule. The filter value for the subject is enclosed in * if you do not add this character manually. In other words, while for any field value means looking for a task that has the field value with the exact value, for the subject value means searching a task that contain value in its subject anywhere.

    In regex mode, you can use regular expression syntax to create more complex filters. By default, the regular expression looks for inclusion. If you need exact equality, use regular expression anchors like $ or ^.

    In all modes, the search is case-insensitive.

    Examples (context was chosen arbitraty, you can replace it with any field, e.g, subj):

    • context="My" - selects tasks that has context that equals My
    • context="My*" - selects tasks that include a context that starts with My
    • context="*money" - selects tasks that include a context that ends with money
    • context="*important*" - selects tasks which context contains important
    • -e -f context="My|Yours" - selects tasks which context contains either my or yours
    • -e -f context="value[12]" - selects task which context contains either value1 or value2

    Note about subject: when you search for a task subject containing some value, you do not have to add character *. Searching subject is always fuzzy, case-insensitive, and it looks for a substring at any position. In other words, subject=look is the same as subject=*look*.

    Data types

    To compare tags correctly, TTDL tries to detect the tag type beforehand. If it fails to auto-detect a tag type, it compares values as strings. If the type is detected, but the filter value cannot be converted to this data type, the matching always fails. E.g, --filter="due=abc" does not show any tasks because abc cannot be converted to a date.

    If you use * to do fuzzy search, tag type is automatically considered a string type. This automatic coercion works only for tag fields, i.e, it does not work for priority, creation and completion dates.

    The difference between default and coerced data type:

    • due=2025-12-02 - due is a date field, so 2025-12-02 is converted to a date and then filter does its job
    • due=2025-12* - now due is treated as a string, so the filter selects tasks that have due date in December, 2025

    Rules TTDL follows when detecting a tag type:

    1. By a tag name (the rest are treated as Strings):
    TagType
    dueDate
    tDate
    createdDate
    completedDate
    spentTime duration
    priorityPriority. It works like integer that is displayed as a character
    doneBoolean. Ranges are not supported
    name ends with _timeTime of a day
    name ends with _dur or _durationTime duration
    name ends with _dateDate
    names ends with _sz or _sizeDate size in bytes
    1. If it fails to auto-detect by a tag name, it tries to detect the type by the tag value (the fallback type is String):

    In the table # means any digit. Character N means any integer number. Character F means any floating point number. Values in square brackets can be omitted.

    FormatType
    ####-##-##Date
    [Nw][Nd][Nh][Nm][Ns]Time interval, e.g 1h5s = one hour and 5 seconds
    An integer number that ends with data size suffix like GiBData size
    N[am] or N[pm]Time of the day. If the string does not contain either am or pm, the values is treated as a time in 24-hours format, otherwise it is treated as time in AM/PM-format
    NAn integer
    FA floating point number

    If TTDL fails to parse a value as a date or as a time, it defaults to String type. E.g, 1080 cannot be parse as time as it means 10:80 - an hour cannot have 80 minutes, or 1310pm - if AM/PM is used the value of hours must be within 1..12 interval.

    Examples

    Examples:

    • "-tagname" - matches only tasks that do not include tag tagname. Another ways to define the same condition are: "tagname=none" and "tagname=-"
    • "-tagname=2..4" - matches tasks that do not have the tag tagname and or their tag value is out of range 2..4
    • "tagname=-done" - matches only tasks that include tag tagname and its value is not done
    • "id=-10..-20" - in this case, the character - means "minus", not negation, because a range is used, so it matches tasks that include tag id with values between -10 and -20.
    • "!id=10,11,12;id=5..15" - matches only tasks that have tag value between 5 and 15 (inclusive), and the value is not one of 10, 11, and 12.
    • due=today;show_time=..1200 - show all tasks that are due today and their show_time is before noon
    • type=car,house;weight=1,6..10 - show all tasks that have a tag type with values car or house and have tag weight with values 1 or from 6 to 10 inclusive
    • spent=1h.. - show all tasks which took more than 1 hour of time to complete

    Grouping

    It can be useful to display the list of tasks grouped by certain condition. Sometimes it can be done by using sorting. But it does not work well always. E.g, if a few tasks have more than one context or hashtag, sorting does not help. In this case, you can use a command-line option --group.

    The option supports only one-level grouping. In other words, you can group by project or by context but you cannot group by project and context at the same time. Another limitation is that some fields cannot be used for grouping: creation date, finish date, and priority. As these fields never have multiple values, you can mitigate the limitation by sorting with the command-line option --sort.

    The command-line option accepted arguments:

    ValueAction
    prj or projectGroup by project name
    ctx or contextGroup by context name
    hash or hashtagGroup by hashtags
    any other valueGroup by a tag with that name. Note that this way allows you to group by due (due dates) and rec (recurrence)

    In group mode the list can be much longer than without it. Because if a task has more than one item, the task will be printed in all groups. E.g, if a task has 3 hashtags, grouping by hashtag displays the task three times.

    The tasks that do not have any values are printed in the very last group with the header [Empty].

    NOTE: if TTDL detects that there is only one value for all tasks or no task contains the tag, the output won't contain any group headers. In this case, the output looks the same as if you displayed the list without --group option.

    Examples

    The list:

    $ cat todo.txt
    
    Pay rent +house #money due:2024-10-12 rec:1m type:important
    Pay credit card +bank #money #credit due:2024-10-10 rec:1m
    

    Group by context displays the list without any grouping as no task has a context:

    $ ttdl list --group ctx
    
     # D P Created Finished Due        Subject
    -------------------------------------------
     1 R                    2024-10-12 Pay rent +house #money due:2024-10-12 rec:1m type:important
     2 R                    2024-10-10 Pay credit card +bank #money #credit due:2024-10-10 rec:1m
    

    Group by hashtag displays some tasks more than once:

    $ ttdl list --group hashtag
     # D P Created Finished Due        Subject
    -------------------------------------------
    #credit
     2 R                    2024-10-10 Pay credit card +bank #money #credit due:2024-10-10 rec:1m
    #money
     1 R                    2024-10-12 Pay rent +house #money due:2024-10-12 rec:1m type:important
     2 R                    2024-10-10 Pay credit card +bank #money #credit due:2024-10-10 rec:1m
    

    Group by custom tag type shows two groups including one that contains tasks without the tag:

    $ ttdl list --group type
     # D P Created Finished Due        Subject
    -------------------------------------------
    important
     1 R                    2024-10-12 Pay rent +house #money due:2024-10-12 rec:1m type:important
    [Empty]
     2 R                    2024-10-10 Pay credit card +bank #money #credit due:2024-10-10 rec:1m
    

    Archive

    In the long run a todo list gets full of completed tasks. They may slow down the todo list management. If you do not need to keep completed stuff, you can delete them using command remove. But if completed tasks must be kept for a while, you can archive them. Execute clean(or archive) command and completed tasks will be moved from the actual todo list to its archive. Cleaning up automatically removes also all empty todos. To keep empty todos, pass the command-line option --keep-empty.

    Archiving completed todos makes the actual todo list loading faster. Though it has a few drawbacks:

    • archived todos cannot be modified (e.g, if you want to delete some archived todos, you have to do it manually in any text editor)
    • there is no way to show actual and archived todos at the same time
    How to show archived todos

    To display archived todos, use option --done. The option enables "archive" mode: the only available command in this mode is list and TTDL loads done.txt instead of todo.txt. On entering this mode, the option -A is enabled automatically if neither -a nor -A is defined. Read the 1st paragraph of Command line examples to understand -[A|a]

    Supported commands

    The list of available command is short but the commands are powerful. All commands support group operations and dry run mode, except add command that adds a new todo one at a time. Please, refer to section "Examples", it provides a handful of useful examples of how to filter and modify todo list.

    The application determines which command to execute with the following rules:

    1. If the command does not contain any free arguments, TTDL executes list command
    2. If the first free argument is a command, TTDL executes it
    3. In all other cases TTDL executes add command

    The rules above allow a user to omit the most used commands add and list. At the same time, the way is error-prone because every typo in command results in adding a new undesirable todo. E.g., if you type ttdl del 15, TTDL, instead of removing todo at 15 position, adds a new todo with message del 15. To avoid such cases and make TTDL command-line check stricter, either add strict_mode = true to section global or pass --strict in command-line. It disables the rule number 3 and TTDL will always require a valid command as the first argument, if there are at least one exists. In strict mode, ttdl del 15 returns the error "first argument must be a command".

    Commands:

    • add - add a new todo;
    • list - show list of todo items. By default it displays all incomplete todos;
    • done - mark selected todos completed. If a todo is recurrent and contains due or threshold date(or both) the todo is marked completed and new one is created with due and threshold dates moved to the future;
    • undone - remove finished mark from completed todos;
    • remove - deletes the selected todos;
    • clean - moves completed todos from main file to done.txt. The file done.txt is created(if it does not exist) in the same directory where main todo list file is located. By default, the command also removes all empty todos;
    • edit - modify one or few properties for the selected todos. One exception: modifying todo's subject changes only the first selected todo, others are skipped;
    • append - adds a text to the end of the selected todos (space between old text and new one is added automatically);
    • prepend - inserts a new text at the beginning of the selected todos (space between old text and new one is