Compare commits
4 Commits
6e60698526
...
dev
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6b1418fc71 | ||
|
|
b46aced2c7 | ||
|
|
3ab26f658d | ||
|
|
1a9fd9b4b0 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -2,3 +2,4 @@
|
|||||||
test/taskchampion.sqlite3
|
test/taskchampion.sqlite3
|
||||||
tasksquire
|
tasksquire
|
||||||
test/*.sqlite3*
|
test/*.sqlite3*
|
||||||
|
result
|
||||||
|
|||||||
@@ -106,13 +106,13 @@ func NewKeymap() *Keymap {
|
|||||||
),
|
),
|
||||||
|
|
||||||
NextPage: key.NewBinding(
|
NextPage: key.NewBinding(
|
||||||
key.WithKeys("]"),
|
key.WithKeys("]", "L"),
|
||||||
key.WithHelp("[", "Next page"),
|
key.WithHelp("]/L", "Next page"),
|
||||||
),
|
),
|
||||||
|
|
||||||
PrevPage: key.NewBinding(
|
PrevPage: key.NewBinding(
|
||||||
key.WithKeys("["),
|
key.WithKeys("[", "H"),
|
||||||
key.WithHelp("]", "Previous page"),
|
key.WithHelp("[/H", "Previous page"),
|
||||||
),
|
),
|
||||||
|
|
||||||
SetReport: key.NewBinding(
|
SetReport: key.NewBinding(
|
||||||
|
|||||||
@@ -19,8 +19,19 @@ type TableStyle struct {
|
|||||||
Selected lipgloss.Style
|
Selected lipgloss.Style
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type Palette struct {
|
||||||
|
Primary lipgloss.Style
|
||||||
|
Secondary lipgloss.Style
|
||||||
|
Accent lipgloss.Style
|
||||||
|
Muted lipgloss.Style
|
||||||
|
Border lipgloss.Style
|
||||||
|
Background lipgloss.Style
|
||||||
|
Text lipgloss.Style
|
||||||
|
}
|
||||||
|
|
||||||
type Styles struct {
|
type Styles struct {
|
||||||
Colors map[string]*lipgloss.Style
|
Colors map[string]*lipgloss.Style
|
||||||
|
Palette Palette
|
||||||
|
|
||||||
Base lipgloss.Style
|
Base lipgloss.Style
|
||||||
|
|
||||||
@@ -50,23 +61,43 @@ func NewStyles(config *taskwarrior.TWConfig) *Styles {
|
|||||||
|
|
||||||
styles.Colors = colors
|
styles.Colors = colors
|
||||||
|
|
||||||
styles.Base = lipgloss.NewStyle()
|
// Initialize Palette (Iceberg Light)
|
||||||
|
styles.Palette.Primary = lipgloss.NewStyle().Foreground(lipgloss.Color("#2d539e")) // Blue
|
||||||
|
styles.Palette.Secondary = lipgloss.NewStyle().Foreground(lipgloss.Color("#7759b4")) // Purple
|
||||||
|
styles.Palette.Accent = lipgloss.NewStyle().Foreground(lipgloss.Color("#c57339")) // Orange
|
||||||
|
styles.Palette.Muted = lipgloss.NewStyle().Foreground(lipgloss.Color("#8389a3")) // Grey
|
||||||
|
styles.Palette.Border = lipgloss.NewStyle().Foreground(lipgloss.Color("#cad0de")) // Light Grey Border
|
||||||
|
styles.Palette.Background = lipgloss.NewStyle().Background(lipgloss.Color("#e8e9ec")) // Light Background
|
||||||
|
styles.Palette.Text = lipgloss.NewStyle().Foreground(lipgloss.Color("#33374c")) // Dark Text
|
||||||
|
|
||||||
|
// Override from config if available (example mapping)
|
||||||
|
if s, ok := styles.Colors["primary"]; ok {
|
||||||
|
styles.Palette.Primary = *s
|
||||||
|
}
|
||||||
|
if s, ok := styles.Colors["secondary"]; ok {
|
||||||
|
styles.Palette.Secondary = *s
|
||||||
|
}
|
||||||
|
if s, ok := styles.Colors["active"]; ok {
|
||||||
|
styles.Palette.Accent = *s
|
||||||
|
}
|
||||||
|
|
||||||
|
styles.Base = lipgloss.NewStyle().Foreground(styles.Palette.Text.GetForeground())
|
||||||
|
|
||||||
styles.TableStyle = TableStyle{
|
styles.TableStyle = TableStyle{
|
||||||
// Header: lipgloss.NewStyle().Bold(true).Padding(0, 1).BorderBottom(true),
|
// Header: lipgloss.NewStyle().Bold(true).Padding(0, 1).BorderBottom(true),
|
||||||
Cell: lipgloss.NewStyle().Padding(0, 1, 0, 0),
|
Cell: lipgloss.NewStyle().Padding(0, 1, 0, 0),
|
||||||
Header: lipgloss.NewStyle().Bold(true).Padding(0, 1, 0, 0).Underline(true),
|
Header: lipgloss.NewStyle().Bold(true).Padding(0, 1, 0, 0).Underline(true).Foreground(styles.Palette.Primary.GetForeground()),
|
||||||
Selected: lipgloss.NewStyle().Bold(true).Reverse(true),
|
Selected: lipgloss.NewStyle().Bold(true).Reverse(true).Foreground(styles.Palette.Accent.GetForeground()),
|
||||||
}
|
}
|
||||||
|
|
||||||
formTheme := huh.ThemeBase()
|
formTheme := huh.ThemeBase()
|
||||||
formTheme.Focused.Title = formTheme.Focused.Title.Bold(true)
|
formTheme.Focused.Title = formTheme.Focused.Title.Bold(true).Foreground(styles.Palette.Primary.GetForeground())
|
||||||
formTheme.Focused.SelectSelector = formTheme.Focused.SelectSelector.SetString("→ ")
|
formTheme.Focused.SelectSelector = formTheme.Focused.SelectSelector.SetString("→ ").Foreground(styles.Palette.Accent.GetForeground())
|
||||||
formTheme.Focused.SelectedOption = formTheme.Focused.SelectedOption.Bold(true)
|
formTheme.Focused.SelectedOption = formTheme.Focused.SelectedOption.Bold(true).Foreground(styles.Palette.Accent.GetForeground())
|
||||||
formTheme.Focused.MultiSelectSelector = formTheme.Focused.MultiSelectSelector.SetString("→ ")
|
formTheme.Focused.MultiSelectSelector = formTheme.Focused.MultiSelectSelector.SetString("→ ").Foreground(styles.Palette.Accent.GetForeground())
|
||||||
formTheme.Focused.SelectedPrefix = formTheme.Focused.SelectedPrefix.SetString("✓ ")
|
formTheme.Focused.SelectedPrefix = formTheme.Focused.SelectedPrefix.SetString("✓ ").Foreground(styles.Palette.Accent.GetForeground())
|
||||||
formTheme.Focused.UnselectedPrefix = formTheme.Focused.SelectedPrefix.SetString("• ")
|
formTheme.Focused.UnselectedPrefix = formTheme.Focused.SelectedPrefix.SetString("• ")
|
||||||
formTheme.Blurred.Title = formTheme.Blurred.Title.Bold(true)
|
formTheme.Blurred.Title = formTheme.Blurred.Title.Bold(true).Foreground(styles.Palette.Muted.GetForeground())
|
||||||
formTheme.Blurred.SelectSelector = formTheme.Blurred.SelectSelector.SetString(" ")
|
formTheme.Blurred.SelectSelector = formTheme.Blurred.SelectSelector.SetString(" ")
|
||||||
formTheme.Blurred.SelectedOption = formTheme.Blurred.SelectedOption.Bold(true)
|
formTheme.Blurred.SelectedOption = formTheme.Blurred.SelectedOption.Bold(true)
|
||||||
formTheme.Blurred.MultiSelectSelector = formTheme.Blurred.MultiSelectSelector.SetString(" ")
|
formTheme.Blurred.MultiSelectSelector = formTheme.Blurred.MultiSelectSelector.SetString(" ")
|
||||||
@@ -77,27 +108,38 @@ func NewStyles(config *taskwarrior.TWConfig) *Styles {
|
|||||||
|
|
||||||
styles.Tab = lipgloss.NewStyle().
|
styles.Tab = lipgloss.NewStyle().
|
||||||
Padding(0, 1).
|
Padding(0, 1).
|
||||||
Foreground(lipgloss.Color("240"))
|
Foreground(styles.Palette.Muted.GetForeground())
|
||||||
|
|
||||||
styles.ActiveTab = styles.Tab.
|
styles.ActiveTab = styles.Tab.
|
||||||
Foreground(lipgloss.Color("252")).
|
Foreground(styles.Palette.Primary.GetForeground()).
|
||||||
Bold(true)
|
Bold(true)
|
||||||
|
|
||||||
styles.TabBar = lipgloss.NewStyle().
|
styles.TabBar = lipgloss.NewStyle().
|
||||||
Border(lipgloss.NormalBorder(), false, false, true, false).
|
Border(lipgloss.NormalBorder(), false, false, true, false).
|
||||||
BorderForeground(lipgloss.Color("240")).
|
BorderForeground(styles.Palette.Border.GetForeground()).
|
||||||
MarginBottom(1)
|
MarginBottom(1)
|
||||||
|
|
||||||
styles.ColumnFocused = lipgloss.NewStyle().Border(lipgloss.RoundedBorder(), true).Padding(1)
|
styles.ColumnFocused = lipgloss.NewStyle().Border(lipgloss.RoundedBorder(), true).Padding(1).BorderForeground(styles.Palette.Primary.GetForeground())
|
||||||
styles.ColumnBlurred = lipgloss.NewStyle().Border(lipgloss.HiddenBorder(), true).Padding(1)
|
styles.ColumnBlurred = lipgloss.NewStyle().Border(lipgloss.HiddenBorder(), true).Padding(1).BorderForeground(styles.Palette.Border.GetForeground())
|
||||||
styles.ColumnInsert = lipgloss.NewStyle().Border(lipgloss.RoundedBorder(), true).Padding(1)
|
styles.ColumnInsert = lipgloss.NewStyle().Border(lipgloss.RoundedBorder(), true).Padding(1).BorderForeground(styles.Palette.Accent.GetForeground())
|
||||||
if styles.Colors["active"] != nil {
|
|
||||||
styles.ColumnInsert = styles.ColumnInsert.BorderForeground(styles.Colors["active"].GetForeground())
|
|
||||||
}
|
|
||||||
|
|
||||||
return &styles
|
return &styles
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *Styles) GetModalSize(width, height int) (int, int) {
|
||||||
|
modalWidth := 60
|
||||||
|
if width < 64 {
|
||||||
|
modalWidth = width - 4
|
||||||
|
}
|
||||||
|
|
||||||
|
modalHeight := 20
|
||||||
|
if height < 24 {
|
||||||
|
modalHeight = height - 4
|
||||||
|
}
|
||||||
|
|
||||||
|
return modalWidth, modalHeight
|
||||||
|
}
|
||||||
|
|
||||||
func parseColorString(color string) *lipgloss.Style {
|
func parseColorString(color string) *lipgloss.Style {
|
||||||
if color == "" {
|
if color == "" {
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -178,7 +178,7 @@ func (t *Task) GetString(fieldWFormat string) string {
|
|||||||
return t.Recur
|
return t.Recur
|
||||||
|
|
||||||
default:
|
default:
|
||||||
slog.Error(fmt.Sprintf("Field not implemented: %s", field))
|
slog.Error("Field not implemented", "field", field)
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -214,7 +214,7 @@ func formatDate(date string, format string) string {
|
|||||||
dtformat := "20060102T150405Z"
|
dtformat := "20060102T150405Z"
|
||||||
dt, err := time.Parse(dtformat, date)
|
dt, err := time.Parse(dtformat, date)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
slog.Error("Failed to parse time:", err)
|
slog.Error("Failed to parse time", "error", err)
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -238,7 +238,7 @@ func formatDate(date string, format string) string {
|
|||||||
case "countdown":
|
case "countdown":
|
||||||
return parseCountdown(time.Since(dt))
|
return parseCountdown(time.Since(dt))
|
||||||
default:
|
default:
|
||||||
slog.Error(fmt.Sprintf("Date format not implemented: %s", format))
|
slog.Error("Date format not implemented", "format", format)
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -81,10 +81,10 @@ func (d *DetailsViewer) View() string {
|
|||||||
// Title bar
|
// Title bar
|
||||||
titleStyle := lipgloss.NewStyle().
|
titleStyle := lipgloss.NewStyle().
|
||||||
Bold(true).
|
Bold(true).
|
||||||
Foreground(lipgloss.Color("252"))
|
Foreground(d.common.Styles.Palette.Text.GetForeground())
|
||||||
|
|
||||||
helpStyle := lipgloss.NewStyle().
|
helpStyle := lipgloss.NewStyle().
|
||||||
Foreground(lipgloss.Color("240"))
|
Foreground(d.common.Styles.Palette.Muted.GetForeground())
|
||||||
|
|
||||||
header := lipgloss.JoinHorizontal(
|
header := lipgloss.JoinHorizontal(
|
||||||
lipgloss.Left,
|
lipgloss.Left,
|
||||||
@@ -96,7 +96,7 @@ func (d *DetailsViewer) View() string {
|
|||||||
// Container style
|
// Container style
|
||||||
containerStyle := lipgloss.NewStyle().
|
containerStyle := lipgloss.NewStyle().
|
||||||
Border(lipgloss.RoundedBorder()).
|
Border(lipgloss.RoundedBorder()).
|
||||||
BorderForeground(lipgloss.Color("240")).
|
BorderForeground(d.common.Styles.Palette.Border.GetForeground()).
|
||||||
Padding(0, 1).
|
Padding(0, 1).
|
||||||
Width(d.width).
|
Width(d.width).
|
||||||
Height(d.height)
|
Height(d.height)
|
||||||
@@ -104,7 +104,7 @@ func (d *DetailsViewer) View() string {
|
|||||||
// Optional: highlight border when focused (for future interactivity)
|
// Optional: highlight border when focused (for future interactivity)
|
||||||
if d.focused {
|
if d.focused {
|
||||||
containerStyle = containerStyle.
|
containerStyle = containerStyle.
|
||||||
BorderForeground(lipgloss.Color("86"))
|
BorderForeground(d.common.Styles.Palette.Accent.GetForeground())
|
||||||
}
|
}
|
||||||
|
|
||||||
content := lipgloss.JoinVertical(
|
content := lipgloss.JoinVertical(
|
||||||
|
|||||||
@@ -90,7 +90,22 @@ func New(
|
|||||||
delegate.ShowDescription = false
|
delegate.ShowDescription = false
|
||||||
delegate.SetSpacing(0)
|
delegate.SetSpacing(0)
|
||||||
|
|
||||||
|
// Update Styles
|
||||||
|
delegate.Styles.NormalTitle = lipgloss.NewStyle().Foreground(c.Styles.Palette.Text.GetForeground())
|
||||||
|
delegate.Styles.SelectedTitle = lipgloss.NewStyle().Foreground(c.Styles.Palette.Accent.GetForeground()).Bold(true).PaddingLeft(2)
|
||||||
|
delegate.Styles.NormalDesc = lipgloss.NewStyle().Foreground(c.Styles.Palette.Muted.GetForeground())
|
||||||
|
delegate.Styles.SelectedDesc = lipgloss.NewStyle().Foreground(c.Styles.Palette.Accent.GetForeground()).PaddingLeft(2)
|
||||||
|
delegate.Styles.FilterMatch = lipgloss.NewStyle().Foreground(c.Styles.Palette.Secondary.GetForeground()).Underline(true)
|
||||||
|
|
||||||
l := list.New([]list.Item{}, delegate, 0, 0)
|
l := list.New([]list.Item{}, delegate, 0, 0)
|
||||||
|
l.Styles.FilterPrompt = lipgloss.NewStyle().Foreground(c.Styles.Palette.Accent.GetForeground())
|
||||||
|
l.Styles.FilterCursor = lipgloss.NewStyle().Foreground(c.Styles.Palette.Accent.GetForeground())
|
||||||
|
|
||||||
|
// Ensure the filter input text is readable (using Text color instead of potentially inheriting something else)
|
||||||
|
l.FilterInput.TextStyle = lipgloss.NewStyle().Foreground(c.Styles.Palette.Text.GetForeground())
|
||||||
|
l.FilterInput.PromptStyle = l.Styles.FilterPrompt
|
||||||
|
l.FilterInput.CursorStyle = l.Styles.FilterCursor
|
||||||
|
|
||||||
l.SetShowTitle(false)
|
l.SetShowTitle(false)
|
||||||
l.SetShowHelp(false)
|
l.SetShowHelp(false)
|
||||||
l.SetShowStatusBar(false)
|
l.SetShowStatusBar(false)
|
||||||
|
|||||||
@@ -530,7 +530,7 @@ func (m *Model) renderRow(r int) string {
|
|||||||
if m.rows[r].IsGap {
|
if m.rows[r].IsGap {
|
||||||
gapText := m.rows[r].GetString("gap_display")
|
gapText := m.rows[r].GetString("gap_display")
|
||||||
gapStyle := lipgloss.NewStyle().
|
gapStyle := lipgloss.NewStyle().
|
||||||
Foreground(lipgloss.Color("240")).
|
Foreground(m.common.Styles.Palette.Muted.GetForeground()).
|
||||||
Align(lipgloss.Center).
|
Align(lipgloss.Center).
|
||||||
Width(m.Width())
|
Width(m.Width())
|
||||||
return gapStyle.Render(gapText)
|
return gapStyle.Render(gapText)
|
||||||
|
|||||||
63
flake.lock
generated
63
flake.lock
generated
@@ -1,58 +1,59 @@
|
|||||||
{
|
{
|
||||||
"nodes": {
|
"nodes": {
|
||||||
"flake-utils": {
|
"flake-parts": {
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"systems": "systems"
|
"nixpkgs-lib": "nixpkgs-lib"
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1731533236,
|
"lastModified": 1769996383,
|
||||||
"narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
|
"narHash": "sha256-AnYjnFWgS49RlqX7LrC4uA+sCCDBj0Ry/WOJ5XWAsa0=",
|
||||||
"owner": "numtide",
|
"owner": "hercules-ci",
|
||||||
"repo": "flake-utils",
|
"repo": "flake-parts",
|
||||||
"rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
|
"rev": "57928607ea566b5db3ad13af0e57e921e6b12381",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
"owner": "numtide",
|
"owner": "hercules-ci",
|
||||||
"repo": "flake-utils",
|
"repo": "flake-parts",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"nixpkgs": {
|
"nixpkgs": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1770197578,
|
"lastModified": 1771369470,
|
||||||
"narHash": "sha256-AYqlWrX09+HvGs8zM6ebZ1pwUqjkfpnv8mewYwAo+iM=",
|
"narHash": "sha256-0NBlEBKkN3lufyvFegY4TYv5mCNHbi5OmBDrzihbBMQ=",
|
||||||
"owner": "NixOS",
|
"owner": "nixos",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "00c21e4c93d963c50d4c0c89bfa84ed6e0694df2",
|
"rev": "0182a361324364ae3f436a63005877674cf45efb",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
"id": "nixpkgs",
|
"owner": "nixos",
|
||||||
"ref": "nixos-unstable",
|
"ref": "nixos-unstable",
|
||||||
"type": "indirect"
|
"repo": "nixpkgs",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"nixpkgs-lib": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1769909678,
|
||||||
|
"narHash": "sha256-cBEymOf4/o3FD5AZnzC3J9hLbiZ+QDT/KDuyHXVJOpM=",
|
||||||
|
"owner": "nix-community",
|
||||||
|
"repo": "nixpkgs.lib",
|
||||||
|
"rev": "72716169fe93074c333e8d0173151350670b824c",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "nix-community",
|
||||||
|
"repo": "nixpkgs.lib",
|
||||||
|
"type": "github"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"root": {
|
"root": {
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"flake-utils": "flake-utils",
|
"flake-parts": "flake-parts",
|
||||||
"nixpkgs": "nixpkgs"
|
"nixpkgs": "nixpkgs"
|
||||||
}
|
}
|
||||||
},
|
|
||||||
"systems": {
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1681028828,
|
|
||||||
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
|
||||||
"owner": "nix-systems",
|
|
||||||
"repo": "default",
|
|
||||||
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "nix-systems",
|
|
||||||
"repo": "default",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"root": "root",
|
"root": "root",
|
||||||
|
|||||||
35
flake.nix
35
flake.nix
@@ -2,18 +2,16 @@
|
|||||||
description = "Tasksquire";
|
description = "Tasksquire";
|
||||||
|
|
||||||
inputs = {
|
inputs = {
|
||||||
nixpkgs.url = "nixpkgs/nixos-unstable";
|
nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable";
|
||||||
flake-utils.url = "github:numtide/flake-utils";
|
flake-parts.url = "github:hercules-ci/flake-parts";
|
||||||
};
|
};
|
||||||
|
|
||||||
outputs = { self, nixpkgs, flake-utils }:
|
outputs = inputs@{ self, nixpkgs, flake-parts, ... }:
|
||||||
flake-utils.lib.eachDefaultSystem (system:
|
flake-parts.lib.mkFlake { inherit inputs; } {
|
||||||
let
|
systems = [ "x86_64-linux" "aarch64-linux" "aarch64-darwin" "x86_64-darwin" ];
|
||||||
pkgs = import nixpkgs {
|
|
||||||
inherit system;
|
|
||||||
};
|
|
||||||
|
|
||||||
tasksquire = pkgs.buildGoModule {
|
perSystem = { config, self', inputs', pkgs, system, ... }: {
|
||||||
|
packages.tasksquire = pkgs.buildGoModule {
|
||||||
pname = "tasksquire";
|
pname = "tasksquire";
|
||||||
version = "0.1.0";
|
version = "0.1.0";
|
||||||
src = ./.;
|
src = ./.;
|
||||||
@@ -32,13 +30,13 @@
|
|||||||
mainProgram = "tasksquire";
|
mainProgram = "tasksquire";
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
in
|
|
||||||
{
|
|
||||||
packages.default = tasksquire;
|
|
||||||
packages.tasksquire = tasksquire;
|
|
||||||
apps.default = flake-utils.lib.mkApp { drv = tasksquire; };
|
|
||||||
|
|
||||||
|
# Set the default package
|
||||||
|
packages.default = self'.packages.tasksquire;
|
||||||
|
|
||||||
|
# Development shell
|
||||||
devShells.default = pkgs.mkShell {
|
devShells.default = pkgs.mkShell {
|
||||||
|
inputsFrom = [ self'.packages.tasksquire ];
|
||||||
buildInputs = with pkgs; [
|
buildInputs = with pkgs; [
|
||||||
go_1_24
|
go_1_24
|
||||||
gcc
|
gcc
|
||||||
@@ -50,14 +48,9 @@
|
|||||||
go-tools
|
go-tools
|
||||||
gotests
|
gotests
|
||||||
delve
|
delve
|
||||||
taskwarrior3
|
|
||||||
timewarrior
|
|
||||||
];
|
];
|
||||||
CGO_CFLAGS = "-O";
|
CGO_CFLAGS = "-O";
|
||||||
};
|
};
|
||||||
|
};
|
||||||
# Backward compatibility
|
};
|
||||||
devShell = self.devShells.${system}.default;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
9
main.go
9
main.go
@@ -40,12 +40,15 @@ func main() {
|
|||||||
timewConfigPath = ""
|
timewConfigPath = ""
|
||||||
}
|
}
|
||||||
|
|
||||||
ts := taskwarrior.NewTaskSquire(taskrcPath)
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
ts := taskwarrior.NewTaskSquire(ctx, taskrcPath)
|
||||||
if ts == nil {
|
if ts == nil {
|
||||||
log.Fatal("Failed to initialize TaskSquire. Please check your Taskwarrior installation and taskrc file.")
|
log.Fatal("Failed to initialize TaskSquire. Please check your Taskwarrior installation and taskrc file.")
|
||||||
}
|
}
|
||||||
tws := timewarrior.NewTimeSquire(timewConfigPath)
|
|
||||||
ctx := context.Background()
|
tws := timewarrior.NewTimeSquire(ctx, timewConfigPath)
|
||||||
common := common.NewCommon(ctx, ts, tws)
|
common := common.NewCommon(ctx, ts, tws)
|
||||||
|
|
||||||
file, err := os.OpenFile("/tmp/tasksquire.log", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
|
file, err := os.OpenFile("/tmp/tasksquire.log", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
|
||||||
|
|||||||
@@ -65,16 +65,9 @@ func NewContextPickerPage(common *common.Common) *ContextPickerPage {
|
|||||||
func (p *ContextPickerPage) SetSize(width, height int) {
|
func (p *ContextPickerPage) SetSize(width, height int) {
|
||||||
p.common.SetSize(width, height)
|
p.common.SetSize(width, height)
|
||||||
|
|
||||||
// Set list size with some padding/limits to look like a picker
|
// Use shared modal sizing logic
|
||||||
listWidth := width - 4
|
modalWidth, modalHeight := p.common.Styles.GetModalSize(width, height)
|
||||||
if listWidth > 40 {
|
p.picker.SetSize(modalWidth-2, modalHeight-2)
|
||||||
listWidth = 40
|
|
||||||
}
|
|
||||||
listHeight := height - 6
|
|
||||||
if listHeight > 20 {
|
|
||||||
listHeight = 20
|
|
||||||
}
|
|
||||||
p.picker.SetSize(listWidth, listHeight)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *ContextPickerPage) Init() tea.Cmd {
|
func (p *ContextPickerPage) Init() tea.Cmd {
|
||||||
@@ -124,20 +117,23 @@ func (p *ContextPickerPage) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (p *ContextPickerPage) View() string {
|
func (p *ContextPickerPage) View() string {
|
||||||
width := p.common.Width() - 4
|
modalWidth, modalHeight := p.common.Styles.GetModalSize(p.common.Width(), p.common.Height())
|
||||||
if width > 40 {
|
|
||||||
width = 40
|
|
||||||
}
|
|
||||||
|
|
||||||
content := p.picker.View()
|
content := p.picker.View()
|
||||||
styledContent := lipgloss.NewStyle().Width(width).Render(content)
|
styledContent := lipgloss.NewStyle().
|
||||||
|
Width(modalWidth).
|
||||||
|
Height(modalHeight).
|
||||||
|
Border(lipgloss.RoundedBorder()).
|
||||||
|
BorderForeground(p.common.Styles.Palette.Border.GetForeground()).
|
||||||
|
Padding(0, 1).
|
||||||
|
Render(content)
|
||||||
|
|
||||||
return lipgloss.Place(
|
return lipgloss.Place(
|
||||||
p.common.Width(),
|
p.common.Width(),
|
||||||
p.common.Height(),
|
p.common.Height(),
|
||||||
lipgloss.Center,
|
lipgloss.Center,
|
||||||
lipgloss.Center,
|
lipgloss.Center,
|
||||||
p.common.Styles.Base.Render(styledContent),
|
styledContent,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -55,16 +55,9 @@ func NewProjectPickerPage(common *common.Common, activeProject string) *ProjectP
|
|||||||
func (p *ProjectPickerPage) SetSize(width, height int) {
|
func (p *ProjectPickerPage) SetSize(width, height int) {
|
||||||
p.common.SetSize(width, height)
|
p.common.SetSize(width, height)
|
||||||
|
|
||||||
// Set list size with some padding/limits to look like a picker
|
// Use shared modal sizing logic
|
||||||
listWidth := width - 4
|
modalWidth, modalHeight := p.common.Styles.GetModalSize(width, height)
|
||||||
if listWidth > 40 {
|
p.picker.SetSize(modalWidth-2, modalHeight-2)
|
||||||
listWidth = 40
|
|
||||||
}
|
|
||||||
listHeight := height - 6
|
|
||||||
if listHeight > 20 {
|
|
||||||
listHeight = 20
|
|
||||||
}
|
|
||||||
p.picker.SetSize(listWidth, listHeight)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *ProjectPickerPage) Init() tea.Cmd {
|
func (p *ProjectPickerPage) Init() tea.Cmd {
|
||||||
@@ -112,20 +105,23 @@ func (p *ProjectPickerPage) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (p *ProjectPickerPage) View() string {
|
func (p *ProjectPickerPage) View() string {
|
||||||
width := p.common.Width() - 4
|
modalWidth, modalHeight := p.common.Styles.GetModalSize(p.common.Width(), p.common.Height())
|
||||||
if width > 40 {
|
|
||||||
width = 40
|
|
||||||
}
|
|
||||||
|
|
||||||
content := p.picker.View()
|
content := p.picker.View()
|
||||||
styledContent := lipgloss.NewStyle().Width(width).Render(content)
|
styledContent := lipgloss.NewStyle().
|
||||||
|
Width(modalWidth).
|
||||||
|
Height(modalHeight).
|
||||||
|
Border(lipgloss.RoundedBorder()).
|
||||||
|
BorderForeground(p.common.Styles.Palette.Border.GetForeground()).
|
||||||
|
Padding(0, 1).
|
||||||
|
Render(content)
|
||||||
|
|
||||||
return lipgloss.Place(
|
return lipgloss.Place(
|
||||||
p.common.Width(),
|
p.common.Width(),
|
||||||
p.common.Height(),
|
p.common.Height(),
|
||||||
lipgloss.Center,
|
lipgloss.Center,
|
||||||
lipgloss.Center,
|
lipgloss.Center,
|
||||||
p.common.Styles.Base.Render(styledContent),
|
styledContent,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -335,30 +335,33 @@ func (p *ProjectTaskPickerPage) View() string {
|
|||||||
// Create distinct styling for focused vs blurred pickers
|
// Create distinct styling for focused vs blurred pickers
|
||||||
var projectStyled, taskStyled string
|
var projectStyled, taskStyled string
|
||||||
|
|
||||||
|
focusedBorder := p.common.Styles.Palette.Accent.GetForeground()
|
||||||
|
blurredBorder := p.common.Styles.Palette.Border.GetForeground()
|
||||||
|
|
||||||
if p.focusedPicker == 0 {
|
if p.focusedPicker == 0 {
|
||||||
// Project picker is focused
|
// Project picker is focused
|
||||||
projectStyled = lipgloss.NewStyle().
|
projectStyled = lipgloss.NewStyle().
|
||||||
Border(lipgloss.ThickBorder()).
|
Border(lipgloss.ThickBorder()).
|
||||||
BorderForeground(lipgloss.Color("6")). // Cyan for focused
|
BorderForeground(focusedBorder).
|
||||||
Padding(0, 1).
|
Padding(0, 1).
|
||||||
Render(projectView)
|
Render(projectView)
|
||||||
|
|
||||||
taskStyled = lipgloss.NewStyle().
|
taskStyled = lipgloss.NewStyle().
|
||||||
Border(lipgloss.NormalBorder()).
|
Border(lipgloss.NormalBorder()).
|
||||||
BorderForeground(lipgloss.Color("240")). // Gray for blurred
|
BorderForeground(blurredBorder).
|
||||||
Padding(0, 1).
|
Padding(0, 1).
|
||||||
Render(taskView)
|
Render(taskView)
|
||||||
} else {
|
} else {
|
||||||
// Task picker is focused
|
// Task picker is focused
|
||||||
projectStyled = lipgloss.NewStyle().
|
projectStyled = lipgloss.NewStyle().
|
||||||
Border(lipgloss.NormalBorder()).
|
Border(lipgloss.NormalBorder()).
|
||||||
BorderForeground(lipgloss.Color("240")). // Gray for blurred
|
BorderForeground(blurredBorder).
|
||||||
Padding(0, 1).
|
Padding(0, 1).
|
||||||
Render(projectView)
|
Render(projectView)
|
||||||
|
|
||||||
taskStyled = lipgloss.NewStyle().
|
taskStyled = lipgloss.NewStyle().
|
||||||
Border(lipgloss.ThickBorder()).
|
Border(lipgloss.ThickBorder()).
|
||||||
BorderForeground(lipgloss.Color("6")). // Cyan for focused
|
BorderForeground(focusedBorder).
|
||||||
Padding(0, 1).
|
Padding(0, 1).
|
||||||
Render(taskView)
|
Render(taskView)
|
||||||
}
|
}
|
||||||
@@ -376,7 +379,7 @@ func (p *ProjectTaskPickerPage) View() string {
|
|||||||
// Add help text
|
// Add help text
|
||||||
helpText := "Tab/Shift+Tab: switch focus • Enter: select • a: add task • e: edit • t: track time • Esc: cancel"
|
helpText := "Tab/Shift+Tab: switch focus • Enter: select • a: add task • e: edit • t: track time • Esc: cancel"
|
||||||
helpStyled := lipgloss.NewStyle().
|
helpStyled := lipgloss.NewStyle().
|
||||||
Foreground(lipgloss.Color("241")).
|
Foreground(p.common.Styles.Palette.Muted.GetForeground()).
|
||||||
Italic(true).
|
Italic(true).
|
||||||
Render(helpText)
|
Render(helpText)
|
||||||
|
|
||||||
|
|||||||
@@ -57,16 +57,9 @@ func NewReportPickerPage(common *common.Common, activeReport *taskwarrior.Report
|
|||||||
func (p *ReportPickerPage) SetSize(width, height int) {
|
func (p *ReportPickerPage) SetSize(width, height int) {
|
||||||
p.common.SetSize(width, height)
|
p.common.SetSize(width, height)
|
||||||
|
|
||||||
// Set list size with some padding/limits to look like a picker
|
// Use shared modal sizing logic
|
||||||
listWidth := width - 4
|
modalWidth, modalHeight := p.common.Styles.GetModalSize(width, height)
|
||||||
if listWidth > 40 {
|
p.picker.SetSize(modalWidth-2, modalHeight-2)
|
||||||
listWidth = 40
|
|
||||||
}
|
|
||||||
listHeight := height - 6
|
|
||||||
if listHeight > 20 {
|
|
||||||
listHeight = 20
|
|
||||||
}
|
|
||||||
p.picker.SetSize(listWidth, listHeight)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *ReportPickerPage) Init() tea.Cmd {
|
func (p *ReportPickerPage) Init() tea.Cmd {
|
||||||
@@ -112,20 +105,23 @@ func (p *ReportPickerPage) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (p *ReportPickerPage) View() string {
|
func (p *ReportPickerPage) View() string {
|
||||||
width := p.common.Width() - 4
|
modalWidth, modalHeight := p.common.Styles.GetModalSize(p.common.Width(), p.common.Height())
|
||||||
if width > 40 {
|
|
||||||
width = 40
|
|
||||||
}
|
|
||||||
|
|
||||||
content := p.picker.View()
|
content := p.picker.View()
|
||||||
styledContent := lipgloss.NewStyle().Width(width).Render(content)
|
styledContent := lipgloss.NewStyle().
|
||||||
|
Width(modalWidth).
|
||||||
|
Height(modalHeight).
|
||||||
|
Border(lipgloss.RoundedBorder()).
|
||||||
|
BorderForeground(p.common.Styles.Palette.Border.GetForeground()).
|
||||||
|
Padding(0, 1).
|
||||||
|
Render(content)
|
||||||
|
|
||||||
return lipgloss.Place(
|
return lipgloss.Place(
|
||||||
p.common.Width(),
|
p.common.Width(),
|
||||||
p.common.Height(),
|
p.common.Height(),
|
||||||
lipgloss.Center,
|
lipgloss.Center,
|
||||||
lipgloss.Center,
|
lipgloss.Center,
|
||||||
p.common.Styles.Base.Render(styledContent),
|
styledContent,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ package pages
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
"strings"
|
|
||||||
"tasksquire/common"
|
"tasksquire/common"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -324,11 +323,13 @@ func (p *TaskEditorPage) View() string {
|
|||||||
|
|
||||||
tabs := ""
|
tabs := ""
|
||||||
for i, a := range p.areas {
|
for i, a := range p.areas {
|
||||||
|
style := p.common.Styles.Base
|
||||||
if i == p.area {
|
if i == p.area {
|
||||||
tabs += p.common.Styles.Base.Bold(true).Render(fmt.Sprintf(" %s ", a.GetName()))
|
style = style.Bold(true).Foreground(p.common.Styles.Palette.Accent.GetForeground())
|
||||||
} else {
|
} else {
|
||||||
tabs += p.common.Styles.Base.Render(fmt.Sprintf(" %s ", a.GetName()))
|
style = style.Foreground(p.common.Styles.Palette.Muted.GetForeground())
|
||||||
}
|
}
|
||||||
|
tabs += style.Render(fmt.Sprintf(" %s ", a.GetName()))
|
||||||
}
|
}
|
||||||
|
|
||||||
page := lipgloss.JoinVertical(
|
page := lipgloss.JoinVertical(
|
||||||
@@ -510,6 +511,9 @@ func NewTaskEdit(com *common.Common, task *taskwarrior.Task, isNew bool) *taskEd
|
|||||||
|
|
||||||
udaValues := make(map[string]*string)
|
udaValues := make(map[string]*string)
|
||||||
for _, uda := range com.Udas {
|
for _, uda := range com.Udas {
|
||||||
|
if uda.Name == "parenttask" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
switch uda.Type {
|
switch uda.Type {
|
||||||
case taskwarrior.UdaTypeNumeric:
|
case taskwarrior.UdaTypeNumeric:
|
||||||
val := ""
|
val := ""
|
||||||
@@ -691,13 +695,9 @@ type tagEdit struct {
|
|||||||
fields []huh.Field
|
fields []huh.Field
|
||||||
|
|
||||||
cursor int
|
cursor int
|
||||||
|
|
||||||
newTagsValue *string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewTagEdit(common *common.Common, selected *[]string, options []string) *tagEdit {
|
func NewTagEdit(common *common.Common, selected *[]string, options []string) *tagEdit {
|
||||||
newTags := ""
|
|
||||||
|
|
||||||
defaultKeymap := huh.NewDefaultKeyMap()
|
defaultKeymap := huh.NewDefaultKeyMap()
|
||||||
|
|
||||||
t := tagEdit{
|
t := tagEdit{
|
||||||
@@ -711,14 +711,7 @@ func NewTagEdit(common *common.Common, selected *[]string, options []string) *ta
|
|||||||
Filterable(true).
|
Filterable(true).
|
||||||
WithKeyMap(defaultKeymap).
|
WithKeyMap(defaultKeymap).
|
||||||
WithTheme(common.Styles.Form),
|
WithTheme(common.Styles.Form),
|
||||||
huh.NewInput().
|
|
||||||
Title("New Tags").
|
|
||||||
Value(&newTags).
|
|
||||||
Inline(true).
|
|
||||||
Prompt(": ").
|
|
||||||
WithTheme(common.Styles.Form),
|
|
||||||
},
|
},
|
||||||
newTagsValue: &newTags,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return &t
|
return &t
|
||||||
@@ -1010,149 +1003,8 @@ func (d *detailsEdit) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
|||||||
|
|
||||||
func (d *detailsEdit) View() string {
|
func (d *detailsEdit) View() string {
|
||||||
return d.ta.View()
|
return d.ta.View()
|
||||||
// dtls := `
|
|
||||||
// # Cool Details!
|
|
||||||
// ## Things I need
|
|
||||||
// - [ ] A thing
|
|
||||||
// - [x] Done thing
|
|
||||||
|
|
||||||
// ## People
|
|
||||||
// - pe1
|
|
||||||
// - pe2
|
|
||||||
// `
|
|
||||||
|
|
||||||
// details, err := d.renderer.Render(dtls)
|
|
||||||
// if err != nil {
|
|
||||||
// slog.Error(err.Error())
|
|
||||||
// return "Could not parse markdown"
|
|
||||||
// }
|
|
||||||
|
|
||||||
// d.vp.SetContent(details)
|
|
||||||
// return d.vp.View()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// func (p *TaskEditorPage) SetSize(width, height int) {
|
|
||||||
// p.common.SetSize(width, height)
|
|
||||||
// }
|
|
||||||
|
|
||||||
// func (p *TaskEditorPage) Init() tea.Cmd {
|
|
||||||
// // return p.form.Init()
|
|
||||||
// return nil
|
|
||||||
// }
|
|
||||||
|
|
||||||
// func (p *TaskEditorPage) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
|
||||||
// var cmds []tea.Cmd
|
|
||||||
|
|
||||||
// switch msg := msg.(type) {
|
|
||||||
// case SwitchModeMsg:
|
|
||||||
// switch mode(msg) {
|
|
||||||
// case modeNormal:
|
|
||||||
// p.mode = modeNormal
|
|
||||||
// case modeInsert:
|
|
||||||
// p.mode = modeInsert
|
|
||||||
// }
|
|
||||||
// case changeAreaMsg:
|
|
||||||
// p.selectedArea = area(msg)
|
|
||||||
// p.columns = append([]tea.Model{p.areaList}, p.areas[p.selectedArea]...)
|
|
||||||
// case nextColumnMsg:
|
|
||||||
// p.columnCursor++
|
|
||||||
// if p.columnCursor > len(p.columns)-1 {
|
|
||||||
// p.columnCursor = 0
|
|
||||||
// }
|
|
||||||
// case prevColumnMsg:
|
|
||||||
// p.columnCursor--
|
|
||||||
// if p.columnCursor < 0 {
|
|
||||||
// p.columnCursor = len(p.columns) - 1
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// switch p.mode {
|
|
||||||
// case modeNormal:
|
|
||||||
// switch msg := msg.(type) {
|
|
||||||
// case tea.KeyMsg:
|
|
||||||
// switch {
|
|
||||||
// case key.Matches(msg, p.common.Keymap.Back):
|
|
||||||
// model, err := p.common.PopPage()
|
|
||||||
// if err != nil {
|
|
||||||
// slog.Error("page stack empty")
|
|
||||||
// return nil, tea.Quit
|
|
||||||
// }
|
|
||||||
// return model, BackCmd
|
|
||||||
// case key.Matches(msg, p.common.Keymap.Insert):
|
|
||||||
// return p, p.switchModeCmd(modeInsert)
|
|
||||||
// // case key.Matches(msg, p.common.Keymap.Ok):
|
|
||||||
// // p.form.State = huh.StateCompleted
|
|
||||||
// case key.Matches(msg, p.common.Keymap.Left):
|
|
||||||
// return p, prevColumn()
|
|
||||||
// case key.Matches(msg, p.common.Keymap.Right):
|
|
||||||
// return p, nextColumn()
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// case modeInsert:
|
|
||||||
// switch msg := msg.(type) {
|
|
||||||
// case tea.KeyMsg:
|
|
||||||
// switch {
|
|
||||||
// case key.Matches(msg, p.common.Keymap.Back):
|
|
||||||
// return p, p.switchModeCmd(modeNormal)
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// var cmd tea.Cmd
|
|
||||||
// if p.columnCursor == 0 {
|
|
||||||
// p.areaList, cmd = p.areaList.Update(msg)
|
|
||||||
// p.selectedArea = p.areaList.(areaList).Area()
|
|
||||||
// cmds = append(cmds, cmd)
|
|
||||||
// } else {
|
|
||||||
// p.areas[p.selectedArea][p.columnCursor-1], cmd = p.areas[p.selectedArea][p.columnCursor-1].Update(msg)
|
|
||||||
// cmds = append(cmds, cmd)
|
|
||||||
// }
|
|
||||||
|
|
||||||
// }
|
|
||||||
|
|
||||||
// var cmd tea.Cmd
|
|
||||||
// if p.columnCursor == 0 {
|
|
||||||
// p.areaList, cmd = p.areaList.Update(msg)
|
|
||||||
// p.selectedArea = p.areaList.(areaList).Area()
|
|
||||||
// cmds = append(cmds, cmd)
|
|
||||||
// } else {
|
|
||||||
// p.areas[p.selectedArea][p.columnCursor-1], cmd = p.areas[p.selectedArea][p.columnCursor-1].Update(msg)
|
|
||||||
// cmds = append(cmds, cmd)
|
|
||||||
// }
|
|
||||||
|
|
||||||
// p.statusline, cmd = p.statusline.Update(msg)
|
|
||||||
// cmds = append(cmds, cmd)
|
|
||||||
|
|
||||||
// // if p.form.State == huh.StateCompleted {
|
|
||||||
// // cmds = append(cmds, p.updateTasksCmd)
|
|
||||||
// // model, err := p.common.PopPage()
|
|
||||||
// // if err != nil {
|
|
||||||
// // slog.Error("page stack empty")
|
|
||||||
// // return nil, tea.Quit
|
|
||||||
// // }
|
|
||||||
// // return model, tea.Batch(cmds...)
|
|
||||||
// // }
|
|
||||||
|
|
||||||
// return p, tea.Batch(cmds...)
|
|
||||||
// }
|
|
||||||
|
|
||||||
// func (p *TaskEditorPage) View() string {
|
|
||||||
// columns := make([]string, len(p.columns))
|
|
||||||
// for i, c := range p.columns {
|
|
||||||
// columns[i] = c.View()
|
|
||||||
// }
|
|
||||||
|
|
||||||
// return lipgloss.JoinVertical(
|
|
||||||
// lipgloss.Left,
|
|
||||||
// lipgloss.JoinHorizontal(
|
|
||||||
// lipgloss.Top,
|
|
||||||
// // lipgloss.Place(p.common.Width(), p.common.Height()-1, 0.5, 0.5, p.form.View(), lipgloss.WithWhitespaceBackground(p.common.Styles.Warning.GetForeground())),
|
|
||||||
// // lipgloss.Place(p.common.Width(), p.common.Height()-1, 0.5, 0.5, p.form.View(), lipgloss.WithWhitespaceChars(" . ")),
|
|
||||||
// columns...,
|
|
||||||
// ),
|
|
||||||
// p.statusline.View(),
|
|
||||||
// )
|
|
||||||
// }
|
|
||||||
|
|
||||||
func (p *TaskEditorPage) updateTasksCmd() tea.Msg {
|
func (p *TaskEditorPage) updateTasksCmd() tea.Msg {
|
||||||
p.task.Project = p.areas[0].(*taskEdit).projectPicker.GetValue()
|
p.task.Project = p.areas[0].(*taskEdit).projectPicker.GetValue()
|
||||||
|
|
||||||
@@ -1169,17 +1021,6 @@ func (p *TaskEditorPage) updateTasksCmd() tea.Msg {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// if *(p.areas[0].(*taskEdit).newProjectName) != "" {
|
|
||||||
// p.task.Project = *p.areas[0].(*taskEdit).newProjectName
|
|
||||||
// }
|
|
||||||
|
|
||||||
if *(p.areas[1].(*tagEdit).newTagsValue) != "" {
|
|
||||||
newTags := strings.Split(*p.areas[1].(*tagEdit).newTagsValue, " ")
|
|
||||||
if len(newTags) > 0 {
|
|
||||||
p.task.Tags = append(p.task.Tags, newTags...)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sync timestamp fields from the timeEdit area (area 2)
|
// Sync timestamp fields from the timeEdit area (area 2)
|
||||||
p.areas[2].(*timeEdit).syncToTaskFields()
|
p.areas[2].(*timeEdit).syncToTaskFields()
|
||||||
|
|
||||||
@@ -1188,8 +1029,6 @@ func (p *TaskEditorPage) updateTasksCmd() tea.Msg {
|
|||||||
Entry: time.Now().Format("20060102T150405Z"),
|
Entry: time.Now().Format("20060102T150405Z"),
|
||||||
Description: *(p.areas[0].(*taskEdit).newAnnotation),
|
Description: *(p.areas[0].(*taskEdit).newAnnotation),
|
||||||
})
|
})
|
||||||
|
|
||||||
// p.common.TW.AddTaskAnnotation(p.task.Uuid, *p.areas[0].(*taskEdit).newAnnotation)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, ok := p.task.Udas["details"]; ok || p.areas[3].(*detailsEdit).ta.Value() != "" {
|
if _, ok := p.task.Udas["details"]; ok || p.areas[3].(*detailsEdit).ta.Value() != "" {
|
||||||
@@ -1200,100 +1039,5 @@ func (p *TaskEditorPage) updateTasksCmd() tea.Msg {
|
|||||||
return UpdatedTasksMsg{}
|
return UpdatedTasksMsg{}
|
||||||
}
|
}
|
||||||
|
|
||||||
// type StatusLine struct {
|
// type StatusLine struct { ... }
|
||||||
// common *common.Common
|
// ...
|
||||||
// mode mode
|
|
||||||
// input textinput.Model
|
|
||||||
// }
|
|
||||||
|
|
||||||
// func NewStatusLine(common *common.Common, mode mode) *StatusLine {
|
|
||||||
// input := textinput.New()
|
|
||||||
// input.Placeholder = ""
|
|
||||||
// input.Prompt = ""
|
|
||||||
// input.Blur()
|
|
||||||
|
|
||||||
// return &StatusLine{
|
|
||||||
// input: textinput.New(),
|
|
||||||
// common: common,
|
|
||||||
// mode: mode,
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// func (s *StatusLine) Init() tea.Cmd {
|
|
||||||
// s.input.Blur()
|
|
||||||
// return nil
|
|
||||||
// }
|
|
||||||
|
|
||||||
// func (s *StatusLine) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
|
||||||
// var cmd tea.Cmd
|
|
||||||
|
|
||||||
// switch msg := msg.(type) {
|
|
||||||
// case SwitchModeMsg:
|
|
||||||
// s.mode = mode(msg)
|
|
||||||
// switch s.mode {
|
|
||||||
// case modeNormal:
|
|
||||||
// s.input.Blur()
|
|
||||||
// case modeInsert:
|
|
||||||
// s.input.Focus()
|
|
||||||
// }
|
|
||||||
// case tea.KeyMsg:
|
|
||||||
// switch {
|
|
||||||
// case key.Matches(msg, s.common.Keymap.Back):
|
|
||||||
// s.input.Blur()
|
|
||||||
// case key.Matches(msg, s.common.Keymap.Input):
|
|
||||||
// s.input.Focus()
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// s.input, cmd = s.input.Update(msg)
|
|
||||||
// return s, cmd
|
|
||||||
// }
|
|
||||||
|
|
||||||
// func (s *StatusLine) View() string {
|
|
||||||
// var mode string
|
|
||||||
// switch s.mode {
|
|
||||||
// case modeNormal:
|
|
||||||
// mode = s.common.Styles.Base.Render("NORMAL")
|
|
||||||
// case modeInsert:
|
|
||||||
// mode = s.common.Styles.Active.Inline(true).Reverse(true).Render("INSERT")
|
|
||||||
// }
|
|
||||||
// return lipgloss.JoinHorizontal(lipgloss.Left, mode, s.input.View())
|
|
||||||
// }
|
|
||||||
|
|
||||||
// // TODO: move this to taskwarrior; add missing date formats
|
|
||||||
|
|
||||||
// type itemDelegate struct{}
|
|
||||||
|
|
||||||
// func (d itemDelegate) Height() int { return 1 }
|
|
||||||
// func (d itemDelegate) Spacing() int { return 0 }
|
|
||||||
// func (d itemDelegate) Update(_ tea.Msg, _ *list.Model) tea.Cmd { return nil }
|
|
||||||
// func (d itemDelegate) Render(w io.Writer, m list.Model, index int, listItem list.Item) {
|
|
||||||
// i, ok := listItem.(item)
|
|
||||||
// if !ok {
|
|
||||||
// return
|
|
||||||
// }
|
|
||||||
|
|
||||||
// str := fmt.Sprintf("%s", i)
|
|
||||||
|
|
||||||
// fn := itemStyle.Render
|
|
||||||
// if index == m.Index() {
|
|
||||||
// fn = func(s ...string) string {
|
|
||||||
// return selectedItemStyle.Render("> " + strings.Join(s, " "))
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// fmt.Fprint(w, fn(str))
|
|
||||||
// }
|
|
||||||
|
|
||||||
// var (
|
|
||||||
// titleStyle = lipgloss.NewStyle().MarginLeft(2)
|
|
||||||
// itemStyle = lipgloss.NewStyle().PaddingLeft(4)
|
|
||||||
// selectedItemStyle = lipgloss.NewStyle().PaddingLeft(2).Foreground(lipgloss.Color("170"))
|
|
||||||
// paginationStyle = list.DefaultStyles().PaginationStyle.PaddingLeft(4)
|
|
||||||
// helpStyle = list.DefaultStyles().HelpStyle.PaddingLeft(4).PaddingBottom(1)
|
|
||||||
// quitTextStyle = lipgloss.NewStyle().Margin(1, 0, 2, 4)
|
|
||||||
// )
|
|
||||||
|
|
||||||
// type item string
|
|
||||||
|
|
||||||
// func (i item) FilterValue() string { return "" }
|
|
||||||
|
|||||||
@@ -568,7 +568,15 @@ func (p *TimeEditorPage) View() string {
|
|||||||
helpStyle := lipgloss.NewStyle().Foreground(lipgloss.Color("8"))
|
helpStyle := lipgloss.NewStyle().Foreground(lipgloss.Color("8"))
|
||||||
sections = append(sections, helpStyle.Render("tab/shift+tab: navigate • enter: select/save • esc: cancel"))
|
sections = append(sections, helpStyle.Render("tab/shift+tab: navigate • enter: select/save • esc: cancel"))
|
||||||
|
|
||||||
return lipgloss.JoinVertical(lipgloss.Left, sections...)
|
content := lipgloss.JoinVertical(lipgloss.Left, sections...)
|
||||||
|
|
||||||
|
return lipgloss.Place(
|
||||||
|
p.common.Width(),
|
||||||
|
p.common.Height(),
|
||||||
|
lipgloss.Center,
|
||||||
|
lipgloss.Center,
|
||||||
|
content,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *TimeEditorPage) SetSize(width, height int) {
|
func (p *TimeEditorPage) SetSize(width, height int) {
|
||||||
|
|||||||
@@ -144,7 +144,7 @@ func (p *TimePage) renderHeader() string {
|
|||||||
|
|
||||||
slog.Info("Rendering time page header", "text", headerText, "timespan", p.selectedTimespan)
|
slog.Info("Rendering time page header", "text", headerText, "timespan", p.selectedTimespan)
|
||||||
// Make header bold and prominent
|
// Make header bold and prominent
|
||||||
headerStyle := lipgloss.NewStyle().Bold(true).Foreground(lipgloss.Color("6"))
|
headerStyle := lipgloss.NewStyle().Bold(true).Foreground(p.common.Styles.Palette.Accent.GetForeground())
|
||||||
return headerStyle.Render(headerText)
|
return headerStyle.Render(headerText)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -229,7 +229,7 @@ func (t *Task) GetString(fieldWFormat string) string {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
slog.Error(fmt.Sprintf("Field not implemented: %s", field))
|
slog.Error("Field not implemented", "field", field)
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -262,7 +262,7 @@ func (t *Task) GetDate(field string) time.Time {
|
|||||||
|
|
||||||
dt, err := time.Parse(dtformat, dateString)
|
dt, err := time.Parse(dtformat, dateString)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
slog.Error("Failed to parse time:", err)
|
slog.Error("Failed to parse time", "error", err)
|
||||||
return time.Time{}
|
return time.Time{}
|
||||||
}
|
}
|
||||||
return dt
|
return dt
|
||||||
@@ -384,7 +384,7 @@ func formatDate(date string, format string) string {
|
|||||||
|
|
||||||
dt, err := time.Parse(dtformat, date)
|
dt, err := time.Parse(dtformat, date)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
slog.Error("Failed to parse time:", err)
|
slog.Error("Failed to parse time", "error", err)
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -408,7 +408,7 @@ func formatDate(date string, format string) string {
|
|||||||
case "countdown":
|
case "countdown":
|
||||||
return parseCountdown(time.Since(dt))
|
return parseCountdown(time.Since(dt))
|
||||||
default:
|
default:
|
||||||
slog.Error(fmt.Sprintf("Date format not implemented: %s", format))
|
slog.Error("Date format not implemented", "format", format)
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ package taskwarrior
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
@@ -111,11 +112,12 @@ type TaskSquire struct {
|
|||||||
config *TWConfig
|
config *TWConfig
|
||||||
reports Reports
|
reports Reports
|
||||||
contexts Contexts
|
contexts Contexts
|
||||||
|
ctx context.Context
|
||||||
|
|
||||||
mutex sync.Mutex
|
mutex sync.Mutex
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewTaskSquire(configLocation string) *TaskSquire {
|
func NewTaskSquire(ctx context.Context, configLocation string) *TaskSquire {
|
||||||
if _, err := exec.LookPath(twBinary); err != nil {
|
if _, err := exec.LookPath(twBinary); err != nil {
|
||||||
slog.Error("Taskwarrior not found")
|
slog.Error("Taskwarrior not found")
|
||||||
return nil
|
return nil
|
||||||
@@ -125,6 +127,7 @@ func NewTaskSquire(configLocation string) *TaskSquire {
|
|||||||
ts := &TaskSquire{
|
ts := &TaskSquire{
|
||||||
configLocation: configLocation,
|
configLocation: configLocation,
|
||||||
defaultArgs: defaultArgs,
|
defaultArgs: defaultArgs,
|
||||||
|
ctx: ctx,
|
||||||
mutex: sync.Mutex{},
|
mutex: sync.Mutex{},
|
||||||
}
|
}
|
||||||
ts.config = ts.extractConfig()
|
ts.config = ts.extractConfig()
|
||||||
@@ -169,17 +172,20 @@ func (ts *TaskSquire) GetTasks(report *Report, filter ...string) Tasks {
|
|||||||
exportArgs = append(exportArgs, report.Name)
|
exportArgs = append(exportArgs, report.Name)
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd := exec.Command(twBinary, append(args, exportArgs...)...)
|
cmd := exec.CommandContext(ts.ctx, twBinary, append(args, exportArgs...)...)
|
||||||
output, err := cmd.CombinedOutput()
|
output, err := cmd.CombinedOutput()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
slog.Error("Failed getting report:", err)
|
if ts.ctx.Err() == context.Canceled {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
slog.Error("Failed getting report", "error", err)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
tasks := make(Tasks, 0)
|
tasks := make(Tasks, 0)
|
||||||
err = json.Unmarshal(output, &tasks)
|
err = json.Unmarshal(output, &tasks)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
slog.Error("Failed unmarshalling tasks:", err)
|
slog.Error("Failed unmarshalling tasks", "error", err)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -198,10 +204,10 @@ func (ts *TaskSquire) GetTasks(report *Report, filter ...string) Tasks {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (ts *TaskSquire) getIds(filter []string) string {
|
func (ts *TaskSquire) getIds(filter []string) string {
|
||||||
cmd := exec.Command(twBinary, append(append(ts.defaultArgs, filter...), "_ids")...)
|
cmd := exec.CommandContext(ts.ctx, twBinary, append(append(ts.defaultArgs, filter...), "_ids")...)
|
||||||
out, err := cmd.CombinedOutput()
|
out, err := cmd.CombinedOutput()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
slog.Error("Failed getting field:", err)
|
slog.Error("Failed getting field", "error", err)
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -219,7 +225,7 @@ func (ts *TaskSquire) GetContext(context string) *Context {
|
|||||||
if context, ok := ts.contexts[context]; ok {
|
if context, ok := ts.contexts[context]; ok {
|
||||||
return context
|
return context
|
||||||
} else {
|
} else {
|
||||||
slog.Error(fmt.Sprintf("Context not found: %s", context.Name))
|
slog.Error("Context not found", "name", context)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -248,11 +254,11 @@ func (ts *TaskSquire) GetProjects() []string {
|
|||||||
ts.mutex.Lock()
|
ts.mutex.Lock()
|
||||||
defer ts.mutex.Unlock()
|
defer ts.mutex.Unlock()
|
||||||
|
|
||||||
cmd := exec.Command(twBinary, append(ts.defaultArgs, []string{"_projects"}...)...)
|
cmd := exec.CommandContext(ts.ctx, twBinary, append(ts.defaultArgs, []string{"_projects"}...)...)
|
||||||
|
|
||||||
output, err := cmd.CombinedOutput()
|
output, err := cmd.CombinedOutput()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
slog.Error("Failed getting projects:", err)
|
slog.Error("Failed getting projects", "error", err)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -286,11 +292,11 @@ func (ts *TaskSquire) GetTags() []string {
|
|||||||
ts.mutex.Lock()
|
ts.mutex.Lock()
|
||||||
defer ts.mutex.Unlock()
|
defer ts.mutex.Unlock()
|
||||||
|
|
||||||
cmd := exec.Command(twBinary, append(ts.defaultArgs, []string{"_tags"}...)...)
|
cmd := exec.CommandContext(ts.ctx, twBinary, append(ts.defaultArgs, []string{"_tags"}...)...)
|
||||||
|
|
||||||
output, err := cmd.CombinedOutput()
|
output, err := cmd.CombinedOutput()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
slog.Error("Failed getting tags:", err)
|
slog.Error("Failed getting tags", "error", err)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -333,10 +339,13 @@ func (ts *TaskSquire) GetUdas() []Uda {
|
|||||||
ts.mutex.Lock()
|
ts.mutex.Lock()
|
||||||
defer ts.mutex.Unlock()
|
defer ts.mutex.Unlock()
|
||||||
|
|
||||||
cmd := exec.Command(twBinary, append(ts.defaultArgs, "_udas")...)
|
cmd := exec.CommandContext(ts.ctx, twBinary, append(ts.defaultArgs, "_udas")...)
|
||||||
output, err := cmd.CombinedOutput()
|
output, err := cmd.CombinedOutput()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
slog.Error("Failed getting config:", err)
|
if ts.ctx.Err() == context.Canceled {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
slog.Error("Failed getting UDAs", "error", err)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -345,7 +354,7 @@ func (ts *TaskSquire) GetUdas() []Uda {
|
|||||||
if uda != "" {
|
if uda != "" {
|
||||||
udatype := UdaType(ts.config.Get(fmt.Sprintf("uda.%s.type", uda)))
|
udatype := UdaType(ts.config.Get(fmt.Sprintf("uda.%s.type", uda)))
|
||||||
if udatype == "" {
|
if udatype == "" {
|
||||||
slog.Error(fmt.Sprintf("UDA type not found: %s", uda))
|
slog.Error("UDA type not found", "uda", uda)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -376,9 +385,9 @@ func (ts *TaskSquire) SetContext(context *Context) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd := exec.Command(twBinary, []string{"context", context.Name}...)
|
cmd := exec.CommandContext(ts.ctx, twBinary, []string{"context", context.Name}...)
|
||||||
if err := cmd.Run(); err != nil {
|
if err := cmd.Run(); err != nil {
|
||||||
slog.Error("Failed setting context:", err)
|
slog.Error("Failed setting context", "error", err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -433,14 +442,17 @@ func (ts *TaskSquire) ImportTask(task *Task) {
|
|||||||
|
|
||||||
tasks, err := json.Marshal(Tasks{task})
|
tasks, err := json.Marshal(Tasks{task})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
slog.Error("Failed marshalling task:", err)
|
slog.Error("Failed marshalling task", "error", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd := exec.Command(twBinary, append(ts.defaultArgs, []string{"import", "-"}...)...)
|
cmd := exec.CommandContext(ts.ctx, twBinary, append(ts.defaultArgs, []string{"import", "-"}...)...)
|
||||||
cmd.Stdin = bytes.NewBuffer(tasks)
|
cmd.Stdin = bytes.NewBuffer(tasks)
|
||||||
out, err := cmd.CombinedOutput()
|
out, err := cmd.CombinedOutput()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
slog.Error("Failed modifying task:", err, string(out))
|
if ts.ctx.Err() == context.Canceled {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
slog.Error("Failed modifying task", "error", err, "output", string(out))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -448,10 +460,10 @@ func (ts *TaskSquire) SetTaskDone(task *Task) {
|
|||||||
ts.mutex.Lock()
|
ts.mutex.Lock()
|
||||||
defer ts.mutex.Unlock()
|
defer ts.mutex.Unlock()
|
||||||
|
|
||||||
cmd := exec.Command(twBinary, append(ts.defaultArgs, []string{"done", task.Uuid}...)...)
|
cmd := exec.CommandContext(ts.ctx, twBinary, append(ts.defaultArgs, []string{"done", task.Uuid}...)...)
|
||||||
err := cmd.Run()
|
err := cmd.Run()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
slog.Error("Failed setting task done:", err)
|
slog.Error("Failed setting task done", "error", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -459,10 +471,10 @@ func (ts *TaskSquire) DeleteTask(task *Task) {
|
|||||||
ts.mutex.Lock()
|
ts.mutex.Lock()
|
||||||
defer ts.mutex.Unlock()
|
defer ts.mutex.Unlock()
|
||||||
|
|
||||||
cmd := exec.Command(twBinary, append(ts.defaultArgs, []string{task.Uuid, "delete"}...)...)
|
cmd := exec.CommandContext(ts.ctx, twBinary, append(ts.defaultArgs, []string{task.Uuid, "delete"}...)...)
|
||||||
err := cmd.Run()
|
err := cmd.Run()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
slog.Error("Failed deleting task:", err)
|
slog.Error("Failed deleting task", "error", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -471,10 +483,10 @@ func (ts *TaskSquire) Undo() {
|
|||||||
ts.mutex.Lock()
|
ts.mutex.Lock()
|
||||||
defer ts.mutex.Unlock()
|
defer ts.mutex.Unlock()
|
||||||
|
|
||||||
cmd := exec.Command(twBinary, append(ts.defaultArgs, []string{"undo"}...)...)
|
cmd := exec.CommandContext(ts.ctx, twBinary, append(ts.defaultArgs, []string{"undo"}...)...)
|
||||||
err := cmd.Run()
|
err := cmd.Run()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
slog.Error("Failed undoing task:", err)
|
slog.Error("Failed undoing task", "error", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -482,10 +494,10 @@ func (ts *TaskSquire) StartTask(task *Task) {
|
|||||||
ts.mutex.Lock()
|
ts.mutex.Lock()
|
||||||
defer ts.mutex.Unlock()
|
defer ts.mutex.Unlock()
|
||||||
|
|
||||||
cmd := exec.Command(twBinary, append(ts.defaultArgs, []string{"start", task.Uuid}...)...)
|
cmd := exec.CommandContext(ts.ctx, twBinary, append(ts.defaultArgs, []string{"start", task.Uuid}...)...)
|
||||||
err := cmd.Run()
|
err := cmd.Run()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
slog.Error("Failed starting task:", err)
|
slog.Error("Failed starting task", "error", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -493,10 +505,10 @@ func (ts *TaskSquire) StopTask(task *Task) {
|
|||||||
ts.mutex.Lock()
|
ts.mutex.Lock()
|
||||||
defer ts.mutex.Unlock()
|
defer ts.mutex.Unlock()
|
||||||
|
|
||||||
cmd := exec.Command(twBinary, append(ts.defaultArgs, []string{"stop", task.Uuid}...)...)
|
cmd := exec.CommandContext(ts.ctx, twBinary, append(ts.defaultArgs, []string{"stop", task.Uuid}...)...)
|
||||||
err := cmd.Run()
|
err := cmd.Run()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
slog.Error("Failed stopping task:", err)
|
slog.Error("Failed stopping task", "error", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -504,25 +516,28 @@ func (ts *TaskSquire) StopActiveTasks() {
|
|||||||
ts.mutex.Lock()
|
ts.mutex.Lock()
|
||||||
defer ts.mutex.Unlock()
|
defer ts.mutex.Unlock()
|
||||||
|
|
||||||
cmd := exec.Command(twBinary, append(ts.defaultArgs, []string{"+ACTIVE", "export"}...)...)
|
cmd := exec.CommandContext(ts.ctx, twBinary, append(ts.defaultArgs, []string{"+ACTIVE", "export"}...)...)
|
||||||
output, err := cmd.CombinedOutput()
|
output, err := cmd.CombinedOutput()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
slog.Error("Failed getting active tasks:", "error", err, "output", string(output))
|
if ts.ctx.Err() == context.Canceled {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
slog.Error("Failed getting active tasks", "error", err, "output", string(output))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
tasks := make(Tasks, 0)
|
tasks := make(Tasks, 0)
|
||||||
err = json.Unmarshal(output, &tasks)
|
err = json.Unmarshal(output, &tasks)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
slog.Error("Failed unmarshalling active tasks:", err)
|
slog.Error("Failed unmarshalling active tasks", "error", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, task := range tasks {
|
for _, task := range tasks {
|
||||||
cmd := exec.Command(twBinary, append(ts.defaultArgs, []string{"stop", task.Uuid}...)...)
|
cmd := exec.CommandContext(ts.ctx, twBinary, append(ts.defaultArgs, []string{"stop", task.Uuid}...)...)
|
||||||
err := cmd.Run()
|
err := cmd.Run()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
slog.Error("Failed stopping task:", err)
|
slog.Error("Failed stopping task", "error", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -531,10 +546,13 @@ func (ts *TaskSquire) GetInformation(task *Task) string {
|
|||||||
ts.mutex.Lock()
|
ts.mutex.Lock()
|
||||||
defer ts.mutex.Unlock()
|
defer ts.mutex.Unlock()
|
||||||
|
|
||||||
cmd := exec.Command(twBinary, append(ts.defaultArgs, []string{fmt.Sprintf("uuid:%s", task.Uuid), "information"}...)...)
|
cmd := exec.CommandContext(ts.ctx, twBinary, append(ts.defaultArgs, []string{fmt.Sprintf("uuid:%s", task.Uuid), "information"}...)...)
|
||||||
output, err := cmd.CombinedOutput()
|
output, err := cmd.CombinedOutput()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
slog.Error("Failed getting task information:", err)
|
if ts.ctx.Err() == context.Canceled {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
slog.Error("Failed getting task information", "error", err)
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -545,17 +563,20 @@ func (ts *TaskSquire) AddTaskAnnotation(uuid string, annotation string) {
|
|||||||
ts.mutex.Lock()
|
ts.mutex.Lock()
|
||||||
defer ts.mutex.Unlock()
|
defer ts.mutex.Unlock()
|
||||||
|
|
||||||
cmd := exec.Command(twBinary, append(ts.defaultArgs, []string{uuid, "annotate", annotation}...)...)
|
cmd := exec.CommandContext(ts.ctx, twBinary, append(ts.defaultArgs, []string{uuid, "annotate", annotation}...)...)
|
||||||
err := cmd.Run()
|
err := cmd.Run()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
slog.Error("Failed adding annotation:", err)
|
slog.Error("Failed adding annotation", "error", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ts *TaskSquire) extractConfig() *TWConfig {
|
func (ts *TaskSquire) extractConfig() *TWConfig {
|
||||||
cmd := exec.Command(twBinary, append(ts.defaultArgs, []string{"_show"}...)...)
|
cmd := exec.CommandContext(ts.ctx, twBinary, append(ts.defaultArgs, []string{"_show"}...)...)
|
||||||
output, err := cmd.CombinedOutput()
|
output, err := cmd.CombinedOutput()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
if ts.ctx.Err() == context.Canceled {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
slog.Error("Failed getting config", "error", err, "output", string(output))
|
slog.Error("Failed getting config", "error", err, "output", string(output))
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -564,7 +585,7 @@ func (ts *TaskSquire) extractConfig() *TWConfig {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (ts *TaskSquire) extractReports() Reports {
|
func (ts *TaskSquire) extractReports() Reports {
|
||||||
cmd := exec.Command(twBinary, append(ts.defaultArgs, []string{"_config"}...)...)
|
cmd := exec.CommandContext(ts.ctx, twBinary, append(ts.defaultArgs, []string{"_config"}...)...)
|
||||||
output, err := cmd.CombinedOutput()
|
output, err := cmd.CombinedOutput()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil
|
return nil
|
||||||
@@ -610,10 +631,13 @@ func extractReports(config string) []string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (ts *TaskSquire) extractContexts() Contexts {
|
func (ts *TaskSquire) extractContexts() Contexts {
|
||||||
cmd := exec.Command(twBinary, append(ts.defaultArgs, []string{"_context"}...)...)
|
cmd := exec.CommandContext(ts.ctx, twBinary, append(ts.defaultArgs, []string{"_context"}...)...)
|
||||||
|
|
||||||
output, err := cmd.CombinedOutput()
|
output, err := cmd.CombinedOutput()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
if ts.ctx.Err() == context.Canceled {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
slog.Error("Failed getting contexts", "error", err, "output", string(output))
|
slog.Error("Failed getting contexts", "error", err, "output", string(output))
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package taskwarrior
|
package taskwarrior
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"testing"
|
"testing"
|
||||||
@@ -56,7 +57,7 @@ func TestTaskSquire_GetContext(t *testing.T) {
|
|||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
tt.prep()
|
tt.prep()
|
||||||
ts := NewTaskSquire(tt.fields.configLocation)
|
ts := NewTaskSquire(context.Background(), tt.fields.configLocation)
|
||||||
if got := ts.GetActiveContext(); got.Name != tt.want {
|
if got := ts.GetActiveContext(); got.Name != tt.want {
|
||||||
t.Errorf("TaskSquire.GetContext() = %v, want %v", got, tt.want)
|
t.Errorf("TaskSquire.GetContext() = %v, want %v", got, tt.want)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -104,7 +104,7 @@ func (i *Interval) GetString(field string) string {
|
|||||||
return ""
|
return ""
|
||||||
|
|
||||||
default:
|
default:
|
||||||
slog.Error(fmt.Sprintf("Field not implemented: %s", field))
|
slog.Error("Field not implemented", "field", field)
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -112,7 +112,7 @@ func (i *Interval) GetString(field string) string {
|
|||||||
func (i *Interval) GetDuration() string {
|
func (i *Interval) GetDuration() string {
|
||||||
start, err := time.Parse(dtformat, i.Start)
|
start, err := time.Parse(dtformat, i.Start)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
slog.Error("Failed to parse start time:", err)
|
slog.Error("Failed to parse start time", "error", err)
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -122,7 +122,7 @@ func (i *Interval) GetDuration() string {
|
|||||||
} else {
|
} else {
|
||||||
end, err = time.Parse(dtformat, i.End)
|
end, err = time.Parse(dtformat, i.End)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
slog.Error("Failed to parse end time:", err)
|
slog.Error("Failed to parse end time", "error", err)
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -134,7 +134,7 @@ func (i *Interval) GetDuration() string {
|
|||||||
func (i *Interval) GetStartTime() time.Time {
|
func (i *Interval) GetStartTime() time.Time {
|
||||||
dt, err := time.Parse(dtformat, i.Start)
|
dt, err := time.Parse(dtformat, i.Start)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
slog.Error("Failed to parse time:", err)
|
slog.Error("Failed to parse time", "error", err)
|
||||||
return time.Time{}
|
return time.Time{}
|
||||||
}
|
}
|
||||||
return dt
|
return dt
|
||||||
@@ -146,7 +146,7 @@ func (i *Interval) GetEndTime() time.Time {
|
|||||||
}
|
}
|
||||||
dt, err := time.Parse(dtformat, i.End)
|
dt, err := time.Parse(dtformat, i.End)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
slog.Error("Failed to parse time:", err)
|
slog.Error("Failed to parse time", "error", err)
|
||||||
return time.Time{}
|
return time.Time{}
|
||||||
}
|
}
|
||||||
return dt
|
return dt
|
||||||
@@ -187,7 +187,7 @@ func formatDate(date string, format string) string {
|
|||||||
|
|
||||||
dt, err := time.Parse(dtformat, date)
|
dt, err := time.Parse(dtformat, date)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
slog.Error("Failed to parse time:", err)
|
slog.Error("Failed to parse time", "error", err)
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -211,7 +211,7 @@ func formatDate(date string, format string) string {
|
|||||||
case "relative":
|
case "relative":
|
||||||
return parseDurationVague(time.Until(dt))
|
return parseDurationVague(time.Until(dt))
|
||||||
default:
|
default:
|
||||||
slog.Error(fmt.Sprintf("Date format not implemented: %s", format))
|
slog.Error("Date format not implemented", "format", format)
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ package timewarrior
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
@@ -44,11 +45,12 @@ type TimeSquire struct {
|
|||||||
configLocation string
|
configLocation string
|
||||||
defaultArgs []string
|
defaultArgs []string
|
||||||
config *TWConfig
|
config *TWConfig
|
||||||
|
ctx context.Context
|
||||||
|
|
||||||
mutex sync.Mutex
|
mutex sync.Mutex
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewTimeSquire(configLocation string) *TimeSquire {
|
func NewTimeSquire(ctx context.Context, configLocation string) *TimeSquire {
|
||||||
if _, err := exec.LookPath(twBinary); err != nil {
|
if _, err := exec.LookPath(twBinary); err != nil {
|
||||||
slog.Error("Timewarrior not found")
|
slog.Error("Timewarrior not found")
|
||||||
return nil
|
return nil
|
||||||
@@ -57,6 +59,7 @@ func NewTimeSquire(configLocation string) *TimeSquire {
|
|||||||
ts := &TimeSquire{
|
ts := &TimeSquire{
|
||||||
configLocation: configLocation,
|
configLocation: configLocation,
|
||||||
defaultArgs: []string{},
|
defaultArgs: []string{},
|
||||||
|
ctx: ctx,
|
||||||
mutex: sync.Mutex{},
|
mutex: sync.Mutex{},
|
||||||
}
|
}
|
||||||
ts.config = ts.extractConfig()
|
ts.config = ts.extractConfig()
|
||||||
@@ -75,11 +78,11 @@ func (ts *TimeSquire) GetTags() []string {
|
|||||||
ts.mutex.Lock()
|
ts.mutex.Lock()
|
||||||
defer ts.mutex.Unlock()
|
defer ts.mutex.Unlock()
|
||||||
|
|
||||||
cmd := exec.Command(twBinary, append(ts.defaultArgs, []string{"tags"}...)...)
|
cmd := exec.CommandContext(ts.ctx, twBinary, append(ts.defaultArgs, []string{"tags"}...)...)
|
||||||
|
|
||||||
output, err := cmd.CombinedOutput()
|
output, err := cmd.CombinedOutput()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
slog.Error("Failed getting tags:", err)
|
slog.Error("Failed getting tags", "error", err)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -150,17 +153,20 @@ func (ts *TimeSquire) getIntervalsUnlocked(filter ...string) Intervals {
|
|||||||
args = append(args, filter...)
|
args = append(args, filter...)
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd := exec.Command(twBinary, args...)
|
cmd := exec.CommandContext(ts.ctx, twBinary, args...)
|
||||||
output, err := cmd.CombinedOutput()
|
output, err := cmd.CombinedOutput()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
slog.Error("Failed getting intervals:", err)
|
if ts.ctx.Err() == context.Canceled {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
slog.Error("Failed getting intervals", "error", err)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
intervals := make(Intervals, 0)
|
intervals := make(Intervals, 0)
|
||||||
err = json.Unmarshal(output, &intervals)
|
err = json.Unmarshal(output, &intervals)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
slog.Error("Failed unmarshalling intervals:", err)
|
slog.Error("Failed unmarshalling intervals", "error", err)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -194,9 +200,9 @@ func (ts *TimeSquire) StartTracking(tags []string) error {
|
|||||||
args := append(ts.defaultArgs, "start")
|
args := append(ts.defaultArgs, "start")
|
||||||
args = append(args, tags...)
|
args = append(args, tags...)
|
||||||
|
|
||||||
cmd := exec.Command(twBinary, args...)
|
cmd := exec.CommandContext(ts.ctx, twBinary, args...)
|
||||||
if err := cmd.Run(); err != nil {
|
if err := cmd.Run(); err != nil {
|
||||||
slog.Error("Failed starting tracking:", err)
|
slog.Error("Failed starting tracking", "error", err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -207,9 +213,9 @@ func (ts *TimeSquire) StopTracking() error {
|
|||||||
ts.mutex.Lock()
|
ts.mutex.Lock()
|
||||||
defer ts.mutex.Unlock()
|
defer ts.mutex.Unlock()
|
||||||
|
|
||||||
cmd := exec.Command(twBinary, append(ts.defaultArgs, "stop")...)
|
cmd := exec.CommandContext(ts.ctx, twBinary, append(ts.defaultArgs, "stop")...)
|
||||||
if err := cmd.Run(); err != nil {
|
if err := cmd.Run(); err != nil {
|
||||||
slog.Error("Failed stopping tracking:", err)
|
slog.Error("Failed stopping tracking", "error", err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -220,9 +226,9 @@ func (ts *TimeSquire) ContinueTracking() error {
|
|||||||
ts.mutex.Lock()
|
ts.mutex.Lock()
|
||||||
defer ts.mutex.Unlock()
|
defer ts.mutex.Unlock()
|
||||||
|
|
||||||
cmd := exec.Command(twBinary, append(ts.defaultArgs, "continue")...)
|
cmd := exec.CommandContext(ts.ctx, twBinary, append(ts.defaultArgs, "continue")...)
|
||||||
if err := cmd.Run(); err != nil {
|
if err := cmd.Run(); err != nil {
|
||||||
slog.Error("Failed continuing tracking:", err)
|
slog.Error("Failed continuing tracking", "error", err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -233,9 +239,9 @@ func (ts *TimeSquire) ContinueInterval(id int) error {
|
|||||||
ts.mutex.Lock()
|
ts.mutex.Lock()
|
||||||
defer ts.mutex.Unlock()
|
defer ts.mutex.Unlock()
|
||||||
|
|
||||||
cmd := exec.Command(twBinary, append(ts.defaultArgs, []string{"continue", fmt.Sprintf("@%d", id)}...)...)
|
cmd := exec.CommandContext(ts.ctx, twBinary, append(ts.defaultArgs, []string{"continue", fmt.Sprintf("@%d", id)}...)...)
|
||||||
if err := cmd.Run(); err != nil {
|
if err := cmd.Run(); err != nil {
|
||||||
slog.Error("Failed continuing interval:", err)
|
slog.Error("Failed continuing interval", "error", err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -246,9 +252,9 @@ func (ts *TimeSquire) CancelTracking() error {
|
|||||||
ts.mutex.Lock()
|
ts.mutex.Lock()
|
||||||
defer ts.mutex.Unlock()
|
defer ts.mutex.Unlock()
|
||||||
|
|
||||||
cmd := exec.Command(twBinary, append(ts.defaultArgs, "cancel")...)
|
cmd := exec.CommandContext(ts.ctx, twBinary, append(ts.defaultArgs, "cancel")...)
|
||||||
if err := cmd.Run(); err != nil {
|
if err := cmd.Run(); err != nil {
|
||||||
slog.Error("Failed canceling tracking:", err)
|
slog.Error("Failed canceling tracking", "error", err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -259,9 +265,9 @@ func (ts *TimeSquire) DeleteInterval(id int) error {
|
|||||||
ts.mutex.Lock()
|
ts.mutex.Lock()
|
||||||
defer ts.mutex.Unlock()
|
defer ts.mutex.Unlock()
|
||||||
|
|
||||||
cmd := exec.Command(twBinary, append(ts.defaultArgs, []string{"delete", fmt.Sprintf("@%d", id)}...)...)
|
cmd := exec.CommandContext(ts.ctx, twBinary, append(ts.defaultArgs, []string{"delete", fmt.Sprintf("@%d", id)}...)...)
|
||||||
if err := cmd.Run(); err != nil {
|
if err := cmd.Run(); err != nil {
|
||||||
slog.Error("Failed deleting interval:", err)
|
slog.Error("Failed deleting interval", "error", err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -272,9 +278,9 @@ func (ts *TimeSquire) FillInterval(id int) error {
|
|||||||
ts.mutex.Lock()
|
ts.mutex.Lock()
|
||||||
defer ts.mutex.Unlock()
|
defer ts.mutex.Unlock()
|
||||||
|
|
||||||
cmd := exec.Command(twBinary, append(ts.defaultArgs, []string{"fill", fmt.Sprintf("@%d", id)}...)...)
|
cmd := exec.CommandContext(ts.ctx, twBinary, append(ts.defaultArgs, []string{"fill", fmt.Sprintf("@%d", id)}...)...)
|
||||||
if err := cmd.Run(); err != nil {
|
if err := cmd.Run(); err != nil {
|
||||||
slog.Error("Failed filling interval:", err)
|
slog.Error("Failed filling interval", "error", err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -287,9 +293,9 @@ func (ts *TimeSquire) JoinInterval(id int) error {
|
|||||||
|
|
||||||
// Join the current interval with the previous one
|
// Join the current interval with the previous one
|
||||||
// The previous interval has id+1 (since intervals are ordered newest first)
|
// The previous interval has id+1 (since intervals are ordered newest first)
|
||||||
cmd := exec.Command(twBinary, append(ts.defaultArgs, []string{"join", fmt.Sprintf("@%d", id+1), fmt.Sprintf("@%d", id)}...)...)
|
cmd := exec.CommandContext(ts.ctx, twBinary, append(ts.defaultArgs, []string{"join", fmt.Sprintf("@%d", id+1), fmt.Sprintf("@%d", id)}...)...)
|
||||||
if err := cmd.Run(); err != nil {
|
if err := cmd.Run(); err != nil {
|
||||||
slog.Error("Failed joining interval:", err)
|
slog.Error("Failed joining interval", "error", err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -303,7 +309,7 @@ func (ts *TimeSquire) ModifyInterval(interval *Interval, adjust bool) error {
|
|||||||
// Export the modified interval
|
// Export the modified interval
|
||||||
intervals, err := json.Marshal(Intervals{interval})
|
intervals, err := json.Marshal(Intervals{interval})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
slog.Error("Failed marshalling interval:", err)
|
slog.Error("Failed marshalling interval", "error", err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -314,11 +320,11 @@ func (ts *TimeSquire) ModifyInterval(interval *Interval, adjust bool) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Import the modified interval
|
// Import the modified interval
|
||||||
cmd := exec.Command(twBinary, args...)
|
cmd := exec.CommandContext(ts.ctx, twBinary, args...)
|
||||||
cmd.Stdin = bytes.NewBuffer(intervals)
|
cmd.Stdin = bytes.NewBuffer(intervals)
|
||||||
out, err := cmd.CombinedOutput()
|
out, err := cmd.CombinedOutput()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
slog.Error("Failed modifying interval:", err, string(out))
|
slog.Error("Failed modifying interval", "error", err, "output", string(out))
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -334,10 +340,10 @@ func (ts *TimeSquire) GetSummary(filter ...string) string {
|
|||||||
args = append(args, filter...)
|
args = append(args, filter...)
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd := exec.Command(twBinary, args...)
|
cmd := exec.CommandContext(ts.ctx, twBinary, args...)
|
||||||
output, err := cmd.CombinedOutput()
|
output, err := cmd.CombinedOutput()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
slog.Error("Failed getting summary:", err)
|
slog.Error("Failed getting summary", "error", err)
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -348,7 +354,7 @@ func (ts *TimeSquire) GetActive() *Interval {
|
|||||||
ts.mutex.Lock()
|
ts.mutex.Lock()
|
||||||
defer ts.mutex.Unlock()
|
defer ts.mutex.Unlock()
|
||||||
|
|
||||||
cmd := exec.Command(twBinary, append(ts.defaultArgs, []string{"get", "dom.active"}...)...)
|
cmd := exec.CommandContext(ts.ctx, twBinary, append(ts.defaultArgs, []string{"get", "dom.active"}...)...)
|
||||||
output, err := cmd.CombinedOutput()
|
output, err := cmd.CombinedOutput()
|
||||||
if err != nil || string(output) == "0\n" {
|
if err != nil || string(output) == "0\n" {
|
||||||
return nil
|
return nil
|
||||||
@@ -369,18 +375,18 @@ func (ts *TimeSquire) Undo() {
|
|||||||
ts.mutex.Lock()
|
ts.mutex.Lock()
|
||||||
defer ts.mutex.Unlock()
|
defer ts.mutex.Unlock()
|
||||||
|
|
||||||
cmd := exec.Command(twBinary, append(ts.defaultArgs, []string{"undo"}...)...)
|
cmd := exec.CommandContext(ts.ctx, twBinary, append(ts.defaultArgs, []string{"undo"}...)...)
|
||||||
err := cmd.Run()
|
err := cmd.Run()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
slog.Error("Failed undoing:", err)
|
slog.Error("Failed undoing", "error", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ts *TimeSquire) extractConfig() *TWConfig {
|
func (ts *TimeSquire) extractConfig() *TWConfig {
|
||||||
cmd := exec.Command(twBinary, append(ts.defaultArgs, []string{"show"}...)...)
|
cmd := exec.CommandContext(ts.ctx, twBinary, append(ts.defaultArgs, []string{"show"}...)...)
|
||||||
output, err := cmd.CombinedOutput()
|
output, err := cmd.CombinedOutput()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
slog.Error("Failed getting config:", err)
|
slog.Error("Failed getting config", "error", err)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user