Skip to content

Commit ae25b22

Browse files
committed
feat: allow copying context to system clipboard
1 parent c8c2be4 commit ae25b22

File tree

10 files changed

+105
-31
lines changed

10 files changed

+105
-31
lines changed

README.md

+2
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,7 @@ B open all bookmarks in the current task's context
198198
c update context for a task
199199
ctrl+d archive/unarchive task
200200
ctrl+x delete task
201+
y copy selected task's context to system clipboard
201202
v toggle between compact and spacious view
202203
203204
Active Tasks List
@@ -214,6 +215,7 @@ K move task one position up
214215
215216
Task Details Pane
216217
h/l move backwards/forwards when in the task details view
218+
y copy selected task's context to system clipboard
217219
B open all bookmarks in the current task's context
218220
219221
Context Bookmarks List

cmd/guide.go

+2
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,8 @@ order). You can override this behavior by passing the "editor" flag to omm, like
177177
178178
Go ahead, press "c". Try changing the text, and then save the file. This context
179179
text should get updated accordingly.
180+
181+
Once saved, you can also copy a tasks's context to your system clipboard by pressing "y".
180182
`,
181183
true,
182184
},

go.mod

+1-1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ module github.com/dhth/omm
33
go 1.22.5
44

55
require (
6+
github.com/atotto/clipboard v0.1.4
67
github.com/charmbracelet/bubbles v0.18.0
78
github.com/charmbracelet/bubbletea v0.26.6
89
github.com/charmbracelet/lipgloss v0.11.0
@@ -15,7 +16,6 @@ require (
1516
)
1617

1718
require (
18-
github.com/atotto/clipboard v0.1.4 // indirect
1919
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
2020
github.com/charmbracelet/x/ansi v0.1.2 // indirect
2121
github.com/charmbracelet/x/input v0.1.2 // indirect

internal/ui/cmds.go

+8
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77

88
"database/sql"
99

10+
"github.com/atotto/clipboard"
1011
tea "github.com/charmbracelet/bubbletea"
1112
pers "github.com/dhth/omm/internal/persistence"
1213
"github.com/dhth/omm/internal/types"
@@ -115,3 +116,10 @@ func openURLsDarwin(urls []string) tea.Cmd {
115116
return urlsOpenedDarwinMsg{urls, err}
116117
})
117118
}
119+
120+
func copyContextToClipboard(context string) tea.Cmd {
121+
return func() tea.Msg {
122+
err := clipboard.WriteAll(context)
123+
return contextWrittenToCBMsg{err}
124+
}
125+
}

internal/ui/help.go

+2
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ B open all bookmarks in the current task's context
5454
c update context for a task
5555
ctrl+d archive/unarchive task
5656
ctrl+x delete task
57+
y copy selected task's context to system clipboard
5758
v toggle between compact and spacious view`),
5859
helpSubHeadingStyle.Render("Active Tasks List"),
5960
helpSectionStyle.Render(`q/esc/ctrl+c quit
@@ -68,6 +69,7 @@ J move task one position down
6869
K move task one position up`),
6970
helpSubHeadingStyle.Render("Task Details Pane"),
7071
helpSectionStyle.Render(`h/l move backwards/forwards when in the task details view
72+
y copy current task's context to system clipboard
7173
B open all bookmarks in the current task's context`),
7274
helpSubHeadingStyle.Render("Context Bookmarks List"),
7375
helpSectionStyle.Render(`⏎ open URL in browser`),

internal/ui/model.go

+2-1
Original file line numberDiff line numberDiff line change
@@ -115,11 +115,12 @@ type model struct {
115115
helpVPReady bool
116116
quitting bool
117117
showHelpIndicator bool
118+
successMsg string
118119
errorMsg string
119120
taskInput textinput.Model
120121
activeView activeView
121122
lastActiveView activeView
122-
lastActiveList taskListType
123+
activeTaskList taskListType
123124
tlTitleStyle lipgloss.Style
124125
atlTitleStyle lipgloss.Style
125126
tlSelStyle lipgloss.Style

internal/ui/msgs.go

+4
Original file line numberDiff line numberDiff line change
@@ -75,3 +75,7 @@ type urlsOpenedDarwinMsg struct {
7575
urls []string
7676
err error
7777
}
78+
79+
type contextWrittenToCBMsg struct {
80+
err error
81+
}

internal/ui/styles.go

+10-4
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,8 @@ const (
2222
helpTitleColor = "#83a598"
2323
helpHeaderColor = "#83a598"
2424
helpSectionColor = "#bdae93"
25-
statusBarColor = "#fb4934"
25+
sBSuccessMsgColor = "#d3869b"
26+
sBErrMsgColor = "#fb4934"
2627
footerColor = "#928374"
2728
)
2829

@@ -58,9 +59,14 @@ var (
5859
PaddingBottom(1).
5960
PaddingLeft(2)
6061

61-
statusBarStyle = lipgloss.NewStyle().
62-
PaddingLeft(2).
63-
Foreground(lipgloss.Color(statusBarColor))
62+
statusBarMsgStyle = lipgloss.NewStyle().
63+
PaddingLeft(2)
64+
65+
sBErrMsgStyle = statusBarMsgStyle.
66+
Foreground(lipgloss.Color(sBErrMsgColor))
67+
68+
sBSuccessMsgStyle = statusBarMsgStyle.
69+
Foreground(lipgloss.Color(sBSuccessMsgColor))
6470

6571
taskDetailsStyle = lipgloss.NewStyle().
6672
PaddingLeft(2).

internal/ui/update.go

+62-21
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ const (
2525
func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
2626
var cmd tea.Cmd
2727
var cmds []tea.Cmd
28+
m.successMsg = ""
2829
m.errorMsg = ""
2930

3031
if m.activeView == taskEntryView {
@@ -33,14 +34,14 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
3334
switch keypress := msg.String(); keypress {
3435
case "esc", "ctrl+c":
3536
m.activeView = taskListView
36-
m.lastActiveList = activeTasks
37+
m.activeTaskList = activeTasks
3738
case "enter":
3839
taskSummary := m.taskInput.Value()
3940
taskSummary = strings.TrimSpace(taskSummary)
4041

4142
if taskSummary == "" {
4243
m.activeView = taskListView
43-
m.lastActiveList = activeTasks
44+
m.activeTaskList = activeTasks
4445
break
4546
}
4647

@@ -51,13 +52,13 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
5152
cmds = append(cmds, cmd)
5253
m.taskInput.Reset()
5354
m.activeView = taskListView
54-
m.lastActiveList = activeTasks
55+
m.activeTaskList = activeTasks
5556
case taskUpdateSummary:
5657
cmd = updateTaskSummary(m.db, m.taskIndex, m.taskId, taskSummary)
5758
cmds = append(cmds, cmd)
5859
m.taskInput.Reset()
5960
m.activeView = taskListView
60-
m.lastActiveList = activeTasks
61+
m.activeTaskList = activeTasks
6162
}
6263
}
6364
}
@@ -71,7 +72,7 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
7172
case tea.WindowSizeMsg:
7273
w, h := listStyle.GetFrameSize()
7374
_, h2 := headerStyle.GetFrameSize()
74-
_, h3 := statusBarStyle.GetFrameSize()
75+
_, h3 := statusBarMsgStyle.GetFrameSize()
7576
m.terminalWidth = msg.Width
7677
m.terminalHeight = msg.Height
7778
m.taskList.SetWidth(msg.Width - w)
@@ -143,7 +144,7 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
143144

144145
if m.activeView == archivedTaskListView {
145146
m.activeView = taskListView
146-
m.lastActiveList = activeTasks
147+
m.activeTaskList = activeTasks
147148
m.lastActiveView = av
148149
break
149150
}
@@ -152,9 +153,9 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
152153
m.activeView = m.lastActiveView
153154
switch m.activeView {
154155
case taskListView:
155-
m.lastActiveList = activeTasks
156+
m.activeTaskList = activeTasks
156157
case archivedTaskListView:
157-
m.lastActiveList = archivedTasks
158+
m.activeTaskList = archivedTasks
158159
}
159160
break
160161
}
@@ -182,10 +183,10 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
182183
switch m.activeView {
183184
case taskListView:
184185
m.activeView = archivedTaskListView
185-
m.lastActiveList = archivedTasks
186+
m.activeTaskList = archivedTasks
186187
case archivedTaskListView:
187188
m.activeView = taskListView
188-
m.lastActiveList = activeTasks
189+
m.activeTaskList = activeTasks
189190
}
190191

191192
case "2", "3", "4", "5", "6", "7", "8", "9":
@@ -510,7 +511,7 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
510511
var ok bool
511512
var index int
512513

513-
switch m.lastActiveList {
514+
switch m.activeTaskList {
514515
case activeTasks:
515516
t, ok = m.taskList.SelectedItem().(types.Task)
516517
if !ok {
@@ -557,7 +558,7 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
557558
var taskList list.Model
558559
var archivedTaskList list.Model
559560
_, h2 := headerStyle.GetFrameSize()
560-
_, h3 := statusBarStyle.GetFrameSize()
561+
_, h3 := statusBarMsgStyle.GetFrameSize()
561562

562563
tlIndex := m.taskList.Index()
563564
atlIndex := m.archivedTaskList.Index()
@@ -654,7 +655,7 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
654655
}
655656

656657
_, h2 := headerStyle.GetFrameSize()
657-
_, h3 := statusBarStyle.GetFrameSize()
658+
_, h3 := statusBarMsgStyle.GetFrameSize()
658659

659660
var contextHeight int
660661
if m.cfg.ListDensity == Compact {
@@ -693,9 +694,9 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
693694

694695
switch m.activeView {
695696
case taskListView:
696-
m.lastActiveList = activeTasks
697+
m.activeTaskList = activeTasks
697698
default:
698-
m.lastActiveList = archivedTasks
699+
m.activeTaskList = archivedTasks
699700
}
700701
m.lastActiveView = m.activeView
701702
m.activeView = taskDetailsView
@@ -707,7 +708,7 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
707708
var t types.Task
708709
var ok bool
709710

710-
switch m.lastActiveList {
711+
switch m.activeTaskList {
711712
case activeTasks:
712713
m.taskList.CursorUp()
713714
t, ok = m.taskList.SelectedItem().(types.Task)
@@ -730,7 +731,7 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
730731
var t types.Task
731732
var ok bool
732733

733-
switch m.lastActiveList {
734+
switch m.activeTaskList {
734735
case activeTasks:
735736
m.taskList.CursorDown()
736737
t, ok = m.taskList.SelectedItem().(types.Task)
@@ -772,9 +773,9 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
772773
m.contextBMList.SetItems(bmItems)
773774
switch m.activeView {
774775
case taskListView:
775-
m.lastActiveList = activeTasks
776+
m.activeTaskList = activeTasks
776777
case archivedTaskListView:
777-
m.lastActiveList = archivedTasks
778+
m.activeTaskList = archivedTasks
778779
}
779780
m.lastActiveView = m.activeView
780781
m.activeView = contextBookmarksView
@@ -807,6 +808,39 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
807808
for _, url := range urls {
808809
cmds = append(cmds, openURL(url))
809810
}
811+
812+
case "y":
813+
if m.activeView != taskListView && m.activeView != archivedTaskListView && m.activeView != taskDetailsView {
814+
break
815+
}
816+
817+
var t types.Task
818+
var ok bool
819+
820+
switch m.activeView {
821+
case taskListView:
822+
t, ok = m.taskList.SelectedItem().(types.Task)
823+
case archivedTaskListView:
824+
t, ok = m.archivedTaskList.SelectedItem().(types.Task)
825+
case taskDetailsView:
826+
switch m.activeTaskList {
827+
case activeTasks:
828+
t, ok = m.taskList.SelectedItem().(types.Task)
829+
case archivedTasks:
830+
t, ok = m.archivedTaskList.SelectedItem().(types.Task)
831+
}
832+
}
833+
834+
if !ok {
835+
break
836+
}
837+
838+
if t.Context == nil {
839+
m.errorMsg = "There's no context to copy"
840+
break
841+
}
842+
843+
cmds = append(cmds, copyContextToClipboard(*t.Context))
810844
}
811845

812846
case HideHelpMsg:
@@ -996,7 +1030,7 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
9961030
break
9971031
}
9981032

999-
cmds = append(cmds, updateTaskContext(m.db, msg.taskIndex, msg.taskId, string(context), m.lastActiveList))
1033+
cmds = append(cmds, updateTaskContext(m.db, msg.taskIndex, msg.taskId, string(context), m.activeTaskList))
10001034
case urlOpenedMsg:
10011035
if msg.err != nil {
10021036
m.errorMsg = fmt.Sprintf("Error opening url: %s", msg.err)
@@ -1005,6 +1039,13 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
10051039
if msg.err != nil {
10061040
m.errorMsg = fmt.Sprintf("Error opening urls: %s", msg.err)
10071041
}
1042+
1043+
case contextWrittenToCBMsg:
1044+
if msg.err != nil {
1045+
m.errorMsg = fmt.Sprintf("Couldn't copy context to clipboard: %s", msg.err)
1046+
} else {
1047+
m.successMsg = "Context copied to clipboard!"
1048+
}
10081049
}
10091050

10101051
if m.cfg.ListDensity == Compact {
@@ -1127,7 +1168,7 @@ func (m model) getContextUrls() ([]string, bool) {
11271168
case archivedTaskListView:
11281169
t, ok = m.archivedTaskList.SelectedItem().(types.Task)
11291170
case taskDetailsView:
1130-
switch m.lastActiveList {
1171+
switch m.activeTaskList {
11311172
case activeTasks:
11321173
t, ok = m.taskList.SelectedItem().(types.Task)
11331174
case archivedTasks:

internal/ui/view.go

+12-4
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"fmt"
55

66
"github.com/charmbracelet/lipgloss"
7+
"github.com/dhth/omm/internal/utils"
78
)
89

910
var (
@@ -22,8 +23,15 @@ func (m model) View() string {
2223
var helpMsg string
2324
var listEmpty bool
2425

25-
if m.errorMsg != "" {
26-
statusBar = m.errorMsg
26+
if m.errorMsg != "" && m.successMsg != "" {
27+
statusBar = fmt.Sprintf("%s%s",
28+
sBErrMsgStyle.Render(utils.Trim(m.errorMsg, (m.terminalWidth/2)-3)),
29+
sBSuccessMsgStyle.Render(utils.Trim(m.successMsg, (m.terminalWidth/2)-3)),
30+
)
31+
} else if m.errorMsg != "" {
32+
statusBar = sBErrMsgStyle.Render(m.errorMsg)
33+
} else if m.successMsg != "" {
34+
statusBar = sBSuccessMsgStyle.Render(m.successMsg)
2735
}
2836

2937
if m.showHelpIndicator && (m.activeView == taskListView || m.activeView == archivedTaskListView) {
@@ -117,7 +125,7 @@ func (m model) View() string {
117125
context = taskDetailsStyle.Render(m.taskDetailsVP.View())
118126
}
119127

120-
return lipgloss.JoinVertical(lipgloss.Left, headerStyle.Render(header), context, statusBarStyle.Render(statusBar))
128+
return lipgloss.JoinVertical(lipgloss.Left, headerStyle.Render(header), context, statusBar)
121129

122130
case contextBookmarksView:
123131
header = fmt.Sprintf("%s%s", contextBMTitleStyle.Render("Context Bookmarks"), helpMsg)
@@ -150,7 +158,7 @@ func (m model) View() string {
150158
components = append(components, context)
151159
}
152160

153-
components = append(components, statusBarStyle.Render(statusBar))
161+
components = append(components, statusBar)
154162

155163
return lipgloss.JoinVertical(lipgloss.Left, components...)
156164

0 commit comments

Comments
 (0)