tasku 0.3.1 → 0.3.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d01584cb8583cbcb7623bf5ba3487efa0f9ae428db88b5cd3cf2e6d90393381d
4
- data.tar.gz: cb79bddf2a6c141a23ee9f0dcf52169767dfb34ab9e86870afeba7d7dc158d33
3
+ metadata.gz: 74f35cfb28f9ec9b5b7cc0ce5a0a62028d7162faa7ed1a8e9efedb4f63fa2638
4
+ data.tar.gz: a064d7fa64823a9f3362e9220b4bc69311f2c20f21e10e6ae85501ed5f5f92de
5
5
  SHA512:
6
- metadata.gz: 92d51c1a51d85b185ba14e982ccc5eb8a5bbe7bb6f04f74c5dac3d6b0ae0b916174e7afbc09a3230f9525a08650fd8b228a2e4724c20c7f5bdb3a1de71db5ba0
7
- data.tar.gz: de7d23c5f2032c57ded24ff9d8920001ba7e650538d6a58d0e08d9647d538ee67aa0588d98e29b5986c080ebfe798a5557d02fb100ac53904db29c378fa19df0
6
+ metadata.gz: 93955913ae30ae85a403b3e11977739d64eac51c1fbebfeb97f07b6beac7149bba991e7da74f067778838f7fdda455ebfe6d74f273cc83e5239daaedc697be48
7
+ data.tar.gz: 0033c68f813e016cb2891fb7ed63e29d736f14460f18617fe021dbeeace9dc46c08639fc25f8764c1ccd0961edea2ad151225677ca7fb561e7214531706e338d
data/lib/tasku/cli.rb CHANGED
@@ -210,7 +210,11 @@ module Tasku
210
210
 
211
211
  tasks = dataset.all
212
212
  bar = { "bar_project" => Config.get("bar_project"), "bar_priority" => Config.get("bar_priority"), "bar_status" => Config.get("bar_status") }
213
- terminal.render_list(tasks, colour_map: Project.colour_map, spacing: Config.get("list_spacing"), bar: bar)
213
+ if ENV["MADO"] == "1"
214
+ Tasku::TUI::MadoList.new(tasks, colour_map: Project.colour_map, bar: bar).run
215
+ else
216
+ terminal.render_list(tasks, colour_map: Project.colour_map, spacing: Config.get("list_spacing"), bar: bar)
217
+ end
214
218
  end
215
219
 
216
220
  desc "show ID", "Show task details"
@@ -153,6 +153,24 @@ module Tasku
153
153
  puts ""
154
154
  end
155
155
 
156
+ # Returns an array of pre-formatted row strings for MadoList.
157
+ # Accounts for the "[>] " prefix (4 extra chars) when computing widths.
158
+ def build_mado_rows(tasks, colour_map = {}, bar = {})
159
+ return [] if tasks.empty?
160
+
161
+ orig_width = @term_width
162
+ @term_width = [@term_width - 4, 40].max
163
+
164
+ col_rows = tasks.map { |t| build_columns(t, colour_map, bar) }
165
+ col_widths = compute_widths(col_rows)
166
+ result = col_rows.map do |cols|
167
+ cols.each_with_index.map { |c, i| ansi_ljust(c.to_s, col_widths[i]) }.join(COL_SEP)
168
+ end
169
+
170
+ @term_width = orig_width
171
+ result
172
+ end
173
+
156
174
  private
157
175
 
158
176
  def build_columns(task, colour_map = {}, bar = {})
@@ -209,6 +227,14 @@ module Tasku
209
227
  str.gsub(/\e\[[0-9;]*m/, "")
210
228
  end
211
229
 
230
+ # Pad an ANSI-coloured string to `width` visible characters.
231
+ # String#ljust counts bytes (including escape codes), so we compute
232
+ # the visible length ourselves and append plain spaces for the deficit.
233
+ def ansi_ljust(str, width)
234
+ deficit = width - strip_ansi(str).length
235
+ deficit > 0 ? str + (" " * deficit) : str
236
+ end
237
+
212
238
  def terminal_width
213
239
  IO.console&.winsize&.[](1) || 80
214
240
  rescue
@@ -0,0 +1,127 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "io/console"
4
+ require "pastel"
5
+
6
+ module Tasku
7
+ module TUI
8
+ # Interactive cursor-based task list for the Mado terminal multiplexer.
9
+ # Triggered when ENV["MADO"] == "1". Arrow keys move the cursor; the
10
+ # selected task's ID is written to ENV["TASKU_SEL_FILE"] so that Mado
11
+ # button handlers can inject it into commands like `tasku edit <id>`.
12
+ class MadoList
13
+ HINT = " ↑↓ navigate a add e/↵ edit d delete q quit"
14
+
15
+ def initialize(tasks, colour_map: {}, bar: {})
16
+ @tasks = tasks
17
+ @cursor = 0
18
+ @pastel = Pastel.new
19
+ @sel_file = ENV["TASKU_SEL_FILE"]
20
+ @running = true
21
+
22
+ # Pre-render row content once so key-press redraws are cheap.
23
+ @rows = Output::Terminal.new.build_mado_rows(tasks, colour_map, bar)
24
+ end
25
+
26
+ def run
27
+ return if @tasks.empty?
28
+
29
+ write_selection
30
+
31
+ $stdout.print "\e[?25l" # hide cursor
32
+ at_exit { restore_terminal }
33
+ trap("INT") { @running = false }
34
+
35
+ $stdin.raw do |io|
36
+ full_render
37
+ while @running
38
+ prev = @cursor
39
+ key = read_key(io)
40
+ handle_key(key)
41
+ full_render if @running && @cursor != prev
42
+ end
43
+ end
44
+ end
45
+
46
+ private
47
+
48
+ def write_selection
49
+ return unless @sel_file
50
+ File.write(@sel_file, @tasks[@cursor].id.to_s)
51
+ rescue StandardError
52
+ nil
53
+ end
54
+
55
+ # Full clear + repaint (initial draw or after a command).
56
+ def full_render
57
+ $stdout.print "\e[H\e[J" # cursor home, clear to end of screen
58
+ @rows.each_with_index do |row, i|
59
+ marker = i == @cursor ? @pastel.bright_blue("[>]") : @pastel.dim("[ ]")
60
+ $stdout.print " #{marker} #{row}\r\n"
61
+ end
62
+ $stdout.print "\r\n#{@pastel.dim(HINT)}\r\n"
63
+ $stdout.flush
64
+ end
65
+
66
+ def read_key(io)
67
+ byte = io.getbyte
68
+ case byte
69
+ when 0x1B
70
+ second = io.getbyte
71
+ if second == 0x5B
72
+ case io.getbyte
73
+ when 0x41 then :up
74
+ when 0x42 then :down
75
+ else nil
76
+ end
77
+ else
78
+ :escape
79
+ end
80
+ when 0x03 then :ctrl_c
81
+ when 0x0D, 0x0A then :enter # Return / Enter
82
+ when 0x61 then :add # a
83
+ when 0x64 then :delete # d
84
+ when 0x65 then :edit # e
85
+ when 0x71 then :q # q
86
+ else nil
87
+ end
88
+ end
89
+
90
+ def handle_key(key)
91
+ case key
92
+ when :up
93
+ @cursor = [@cursor - 1, 0].max
94
+ write_selection
95
+ when :down
96
+ @cursor = [@cursor + 1, @tasks.length - 1].min
97
+ write_selection
98
+ when :q, :ctrl_c, :escape
99
+ @running = false
100
+ when :add
101
+ run_command("tasku add -i")
102
+ when :edit, :enter
103
+ return if @tasks.empty?
104
+ run_command("tasku edit #{@tasks[@cursor].id} -i")
105
+ when :delete
106
+ return if @tasks.empty?
107
+ run_command("tasku delete #{@tasks[@cursor].id} -f")
108
+ end
109
+ end
110
+
111
+ # Restore the terminal, run a tasku command interactively, then re-exec
112
+ # `tasku list` so the MadoList restarts fresh with updated data.
113
+ def run_command(cmd)
114
+ @running = false
115
+ restore_terminal
116
+ system(cmd)
117
+ exec("tasku list")
118
+ end
119
+
120
+ def restore_terminal
121
+ $stdout.print "\e[?25h\e[0m"
122
+ rescue IOError
123
+ nil
124
+ end
125
+ end
126
+ end
127
+ end
data/lib/tasku/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Tasku
4
- VERSION = "0.3.1"
4
+ VERSION = "0.3.9"
5
5
  end
data/lib/tasku.rb CHANGED
@@ -17,3 +17,8 @@ begin
17
17
  rescue LoadError
18
18
  # TUI not available in this build
19
19
  end
20
+ begin
21
+ require_relative "tasku/tui/mado_list"
22
+ rescue LoadError
23
+ # Mado integration not available
24
+ end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: tasku
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.1
4
+ version: 0.3.9
5
5
  platform: ruby
6
6
  authors:
7
7
  - Tom Dringer
8
8
  bindir: exe
9
9
  cert_chain: []
10
- date: 2026-08-29 00:00:00.000000000 Z
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: thor
@@ -94,6 +94,7 @@ files:
94
94
  - lib/tasku/output/terminal.rb
95
95
  - lib/tasku/project.rb
96
96
  - lib/tasku/task.rb
97
+ - lib/tasku/tui/mado_list.rb
97
98
  - lib/tasku/version.rb
98
99
  homepage: https://github.com/tomdringer/tasku
99
100
  licenses:
@@ -116,7 +117,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
116
117
  - !ruby/object:Gem::Version
117
118
  version: '0'
118
119
  requirements: []
119
- rubygems_version: 3.6.2
120
+ rubygems_version: 4.0.20
120
121
  specification_version: 4
121
122
  summary: タスクリスト — a beautiful terminal task manager
122
123
  test_files: []