On branch main
Your branch is up to date with 'origin/main'. Accounts: the account panel now sorts from most free to least free. Fewer Busy tasks come first, and ties go alphabetically. Tasks: they now sort by status, then account A–Z, then wait time, then task name. Status order: Limit reset, Testing, Prompt, Wait, Uploading, Frozen, Done. Account order: it ignores case. Tasks with no account come last in their status group. Wait time: it only matters among tasks that have the same status and the same account. Changes to be committed: modified: README.md modified: include/Status.h modified: include/StringUtil.h modified: src/Display.cpp modified: src/Status.cpp modified: src/StringUtil.cpp
This commit is contained in:
1 parent
4c69c164e4
commit
04fa2b1b20
6 files changed
+69
-53
No files matched your search
@@ -72,13 +72,15 @@ goes back to `$VISUAL` / `$EDITOR` / nano / vim / vi. The choice is stored in
|
||||
to save if there are unsaved changes.
|
||||
* The stored status is never shown directly: a `wait` task displays as
|
||||
`Wait HH:MM` until that time passes, then as `Limit reset`. Tasks are sorted
|
||||
by the computed status. The panels are cleared and redrawn after changes.
|
||||
* Account panel: one row per account. `Busy` while any of its tasks is
|
||||
Testing or Wait (those tasks are listed), otherwise `Free`
|
||||
by computed status (Limit reset, Testing, Prompt, Wait, Uploading, Frozen,
|
||||
Done), then account A-Z (unassigned last), then wait time, then name. The panels are cleared and redrawn after changes.
|
||||
* Account panel: one row per account, sorted from most free to least free.
|
||||
`Busy` while any of its tasks is Testing or Wait (those tasks are listed),
|
||||
otherwise `Free`; fewer busy tasks sorts first, ties by name
|
||||
(see `Display::renderAccounts` / `StatusUtil::isBusy` to change the rules).
|
||||
* Deleting an account leaves its tasks unassigned (`-`).
|
||||
* Names are case-insensitive; the original spelling is kept.
|
||||
* Notes live in the database (SQLite TEXT, binary-safe, SQLite's default limit is ~1 GB). Multi-line notes show on one line in the table, in full.
|
||||
* Notes live in the database (SQLite TEXT, binary-safe, SQLite's default limit is ~1 GB). The task table shows `-` when a task has no notes, otherwise the number of lines (e.g. `3 lines`); open them with `tsk notes <name>`.
|
||||
|
||||
## Layout
|
||||
|
||||
|
||||
+1
-2
@@ -7,7 +7,6 @@
|
||||
|
||||
// Prompt: free to accept prompts.
|
||||
// LimitReset: computed - a Wait whose time has passed (can also be set by hand).
|
||||
// The enum order is the sort order of the panels.
|
||||
enum class StatusKind { Prompt, LimitReset, Testing, Wait, Uploading, Frozen, Done };
|
||||
|
||||
struct Status {
|
||||
@@ -36,7 +35,7 @@ std::vector<StatusKind> allKinds();
|
||||
// Accepts "limit-reset", "limit_reset", "limitreset", "reset"; "available" is an
|
||||
// old name for "prompt".
|
||||
std::optional<StatusKind> kindFromName(const std::string& name);
|
||||
int rank(StatusKind kind); // sort order
|
||||
int rank(StatusKind kind); // task sort priority, lowest first
|
||||
bool isBusy(StatusKind kind); // does this status hold the account?
|
||||
|
||||
} // namespace StatusUtil
|
||||
@@ -15,8 +15,7 @@ std::string join(const std::vector<std::string>& parts, std::size_t from, const
|
||||
// Number of characters (UTF-8 code points) in the text.
|
||||
std::size_t displayWidth(const std::string& text);
|
||||
|
||||
// Collapses line breaks into " / ". If `maxChars` > 0 the text is cut to that
|
||||
// many characters; 0 means no limit.
|
||||
std::string singleLine(const std::string& text, std::size_t maxChars);
|
||||
// Number of lines in the text, ignoring trailing blank lines (0 for empty text).
|
||||
std::size_t lineCount(const std::string& text);
|
||||
|
||||
} // namespace StringUtil
|
||||
+44
-9
@@ -30,18 +30,37 @@ std::vector<TaskView> loadViews(const Database& db, std::time_t now) {
|
||||
return views;
|
||||
}
|
||||
|
||||
// Task order: status priority (see StatusUtil::rank), then account A-Z
|
||||
// (tasks without an account last), then earliest wait time, then task name.
|
||||
bool taskLess(const TaskView& a, const TaskView& b) {
|
||||
int rankA = StatusUtil::rank(a.status.kind);
|
||||
int rankB = StatusUtil::rank(b.status.kind);
|
||||
const int rankA = StatusUtil::rank(a.status.kind);
|
||||
const int rankB = StatusUtil::rank(b.status.kind);
|
||||
if (rankA != rankB) {
|
||||
return rankA < rankB;
|
||||
}
|
||||
if (a.account.empty() != b.account.empty()) {
|
||||
return !a.account.empty();
|
||||
}
|
||||
const std::string accountA = StringUtil::lower(a.account);
|
||||
const std::string accountB = StringUtil::lower(b.account);
|
||||
if (accountA != accountB) {
|
||||
return accountA < accountB;
|
||||
}
|
||||
if (a.status.kind == StatusKind::Wait && a.status.waitUntil != b.status.waitUntil) {
|
||||
return a.status.waitUntil < b.status.waitUntil;
|
||||
}
|
||||
return StringUtil::lower(a.name) < StringUtil::lower(b.name);
|
||||
}
|
||||
|
||||
// "-" when there are no notes, otherwise how many lines they have.
|
||||
std::string notesSummary(const std::string& notes) {
|
||||
const std::size_t lines = StringUtil::lineCount(notes);
|
||||
if (lines == 0) {
|
||||
return "-";
|
||||
}
|
||||
return std::to_string(lines) + (lines == 1 ? " line" : " lines");
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
Display::Display(const Database& db) : db_(db) {}
|
||||
@@ -66,7 +85,7 @@ std::string Display::renderTasks(std::time_t now) const {
|
||||
table.addRow({view.name,
|
||||
view.account.empty() ? "-" : view.account,
|
||||
StatusUtil::label(view.status),
|
||||
view.notes.empty() ? "-" : StringUtil::singleLine(view.notes, 0)});
|
||||
notesSummary(view.notes)});
|
||||
}
|
||||
return table.render();
|
||||
}
|
||||
@@ -77,22 +96,38 @@ std::string Display::renderAccounts(std::time_t now) const {
|
||||
|
||||
// One row per account. It is Busy while any of its tasks is Testing or
|
||||
// Wait (those tasks are listed); otherwise it is Free.
|
||||
TextTable table({"ACCOUNT", "STATUS", "TASKS"});
|
||||
for (const std::string& account : db_.listAccounts()) {
|
||||
struct AccountView {
|
||||
std::string name;
|
||||
std::vector<std::string> busyTasks;
|
||||
};
|
||||
|
||||
std::vector<AccountView> accounts;
|
||||
for (const std::string& account : db_.listAccounts()) {
|
||||
AccountView entry;
|
||||
entry.name = account;
|
||||
for (const TaskView& view : views) {
|
||||
if (view.account == account && StatusUtil::isBusy(view.status.kind)) {
|
||||
busyTasks.push_back(view.name);
|
||||
entry.busyTasks.push_back(view.name);
|
||||
}
|
||||
}
|
||||
accounts.push_back(entry);
|
||||
}
|
||||
|
||||
const AccountLevel level = busyTasks.empty() ? AccountLevel::Free : AccountLevel::Busy;
|
||||
// Most free first: fewer busy tasks first, then by name (listAccounts is
|
||||
// already alphabetical and stable_sort keeps that order for ties).
|
||||
std::stable_sort(accounts.begin(), accounts.end(), [](const AccountView& a, const AccountView& b) {
|
||||
return a.busyTasks.size() < b.busyTasks.size();
|
||||
});
|
||||
|
||||
TextTable table({"ACCOUNT", "STATUS", "TASKS"});
|
||||
for (const AccountView& entry : accounts) {
|
||||
const AccountLevel level = entry.busyTasks.empty() ? AccountLevel::Free : AccountLevel::Busy;
|
||||
if (settings_.hiddenAccountLevels.count(level) > 0) {
|
||||
continue;
|
||||
}
|
||||
table.addRow({account,
|
||||
table.addRow({entry.name,
|
||||
level == AccountLevel::Busy ? "Busy" : "Free",
|
||||
busyTasks.empty() ? "-" : StringUtil::join(busyTasks, 0, ", ")});
|
||||
entry.busyTasks.empty() ? "-" : StringUtil::join(entry.busyTasks, 0, ", ")});
|
||||
}
|
||||
return table.render();
|
||||
}
|
||||
+11
-1
@@ -138,8 +138,18 @@ std::optional<StatusKind> kindFromName(const std::string& name) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
// Priority in the task table: Limit reset, Testing, Prompt, Wait, Uploading, Frozen, Done.
|
||||
int rank(StatusKind kind) {
|
||||
return static_cast<int>(kind);
|
||||
switch (kind) {
|
||||
case StatusKind::LimitReset: return 0;
|
||||
case StatusKind::Testing: return 1;
|
||||
case StatusKind::Prompt: return 2;
|
||||
case StatusKind::Wait: return 3;
|
||||
case StatusKind::Uploading: return 4;
|
||||
case StatusKind::Frozen: return 5;
|
||||
case StatusKind::Done: return 6;
|
||||
}
|
||||
return 6;
|
||||
}
|
||||
|
||||
bool isBusy(StatusKind kind) {
|
||||
|
||||
+5
-34
@@ -53,41 +53,12 @@ std::size_t displayWidth(const std::string& text) {
|
||||
return width;
|
||||
}
|
||||
|
||||
std::string singleLine(const std::string& text, std::size_t maxChars) {
|
||||
std::string out;
|
||||
bool pendingBreak = false;
|
||||
for (char c : text) {
|
||||
if (c == '\n' || c == '\r') {
|
||||
pendingBreak = true;
|
||||
continue;
|
||||
std::size_t lineCount(const std::string& text) {
|
||||
const std::string body = trim(text);
|
||||
if (body.empty()) {
|
||||
return 0;
|
||||
}
|
||||
if (pendingBreak) {
|
||||
if (!out.empty()) {
|
||||
out += " / ";
|
||||
}
|
||||
pendingBreak = false;
|
||||
}
|
||||
out.push_back(c == '\t' ? ' ' : c);
|
||||
}
|
||||
|
||||
if (maxChars == 0) {
|
||||
return out;
|
||||
}
|
||||
|
||||
std::size_t chars = 0;
|
||||
std::size_t cut = 0;
|
||||
for (; cut < out.size(); ++cut) {
|
||||
if ((static_cast<unsigned char>(out[cut]) & 0xC0) != 0x80) {
|
||||
if (chars == maxChars) {
|
||||
break;
|
||||
}
|
||||
++chars;
|
||||
}
|
||||
}
|
||||
if (cut < out.size()) {
|
||||
out = out.substr(0, cut) + "...";
|
||||
}
|
||||
return out;
|
||||
return static_cast<std::size_t>(std::count(body.begin(), body.end(), '\n')) + 1;
|
||||
}
|
||||
|
||||
} // namespace StringUtil
|
||||
Reference in new issue
Block a user