kward 0.75.0 → 0.77.0
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 +4 -4
- data/.github/workflows/ci.yml +8 -1
- data/CHANGELOG.md +48 -0
- data/Gemfile.lock +2 -2
- data/README.md +12 -4
- data/doc/agent-tools.md +0 -3
- data/doc/api.md +4 -0
- data/doc/composer.md +219 -0
- data/doc/configuration.md +87 -27
- data/doc/editor.md +18 -2
- data/doc/files.md +1 -1
- data/doc/getting-started.md +4 -0
- data/doc/git.md +2 -2
- data/doc/lifecycle-hooks.md +2 -14
- data/doc/pan.md +167 -0
- data/doc/plugins.md +1 -0
- data/doc/rpc.md +3 -22
- data/doc/security.md +180 -0
- data/doc/usage.md +15 -32
- data/lib/kward/agent.rb +5 -0
- data/lib/kward/ansi.rb +2 -2
- data/lib/kward/cancellation.rb +2 -2
- data/lib/kward/cli/commands.rb +1 -3
- data/lib/kward/cli/interactive_turn.rb +3 -17
- data/lib/kward/cli/plugins.rb +2 -7
- data/lib/kward/cli/prompt_interface.rb +10 -17
- data/lib/kward/cli/rendering.rb +7 -3
- data/lib/kward/cli/runtime_helpers.rb +0 -38
- data/lib/kward/cli/sessions.rb +14 -5
- data/lib/kward/cli/settings.rb +22 -0
- data/lib/kward/cli/slash_commands.rb +1 -561
- data/lib/kward/cli/tabs.rb +14 -2
- data/lib/kward/cli.rb +1 -17
- data/lib/kward/cli_transcript_formatter.rb +21 -11
- data/lib/kward/compactor.rb +3 -3
- data/lib/kward/config_files.rb +21 -0
- data/lib/kward/ekwsh.rb +0 -4
- data/lib/kward/events.rb +1 -0
- data/lib/kward/hooks/catalog.rb +0 -5
- data/lib/kward/image_attachments.rb +1 -1
- data/lib/kward/model/client.rb +12 -9
- data/lib/kward/model/model_info.rb +14 -5
- data/lib/kward/model/stream_parser.rb +12 -12
- data/lib/kward/pan/index.html.erb +460 -64
- data/lib/kward/pan/kward_logo.png +0 -0
- data/lib/kward/pan/server.rb +250 -23
- data/lib/kward/plugin_registry.rb +20 -0
- data/lib/kward/prompt_interface/editor/auto_indent.rb +31 -23
- data/lib/kward/prompt_interface/editor/modes/vibe.rb +72 -3
- data/lib/kward/prompt_interface/layout.rb +0 -4
- data/lib/kward/prompt_interface/project_browser.rb +40 -1
- data/lib/kward/prompt_interface/runtime_state.rb +4 -0
- data/lib/kward/prompt_interface/selection_prompt.rb +0 -1
- data/lib/kward/prompt_interface/transcript_renderer.rb +1 -13
- data/lib/kward/prompt_interface.rb +18 -1
- data/lib/kward/prompts/commands.rb +1 -3
- data/lib/kward/prompts.rb +20 -0
- data/lib/kward/rpc/server.rb +14 -42
- data/lib/kward/rpc/session_manager.rb +77 -25
- data/lib/kward/rpc/tool_event_normalizer.rb +2 -0
- data/lib/kward/rpc/tool_metadata.rb +23 -0
- data/lib/kward/rpc/transcript_normalizer.rb +14 -10
- data/lib/kward/session_store.rb +55 -16
- data/lib/kward/skills/registry.rb +21 -1
- data/lib/kward/tools/base.rb +14 -0
- data/lib/kward/tools/registry.rb +11 -16
- data/lib/kward/tools/search/code.rb +1 -1
- data/lib/kward/tools/search/web.rb +1 -1
- data/lib/kward/tools/tool_call.rb +0 -4
- data/lib/kward/transcript_export.rb +1 -1
- data/lib/kward/update_check.rb +12 -7
- data/lib/kward/version.rb +1 -1
- data/templates/default/kward_navigation.rb +5 -1
- data/templates/default/layout/html/layout.erb +0 -2
- metadata +5 -12
- data/lib/kward/workers/git_guard.rb +0 -93
- data/lib/kward/workers/job.rb +0 -99
- data/lib/kward/workers/live_view.rb +0 -49
- data/lib/kward/workers/manager.rb +0 -328
- data/lib/kward/workers/queue_runner.rb +0 -166
- data/lib/kward/workers/queue_store.rb +0 -112
- data/lib/kward/workers/store.rb +0 -72
- data/lib/kward/workers/tool_policy.rb +0 -23
- data/lib/kward/workers/worker.rb +0 -82
- data/lib/kward/workers/write_lock.rb +0 -38
- data/lib/kward/workers.rb +0 -10
data/lib/kward/agent.rb
CHANGED
|
@@ -215,6 +215,10 @@ module Kward
|
|
|
215
215
|
on_reasoning_delta&.call(delta)
|
|
216
216
|
yield Events::ReasoningDelta.new(delta: delta) if block_given?
|
|
217
217
|
end
|
|
218
|
+
reasoning_boundary = lambda do
|
|
219
|
+
cancellation&.raise_if_cancelled!
|
|
220
|
+
yield Events::ReasoningBoundary.new if block_given?
|
|
221
|
+
end
|
|
218
222
|
assistant_delta = lambda do |delta|
|
|
219
223
|
cancellation&.raise_if_cancelled!
|
|
220
224
|
yield Events::AssistantDelta.new(delta: delta) if block_given?
|
|
@@ -244,6 +248,7 @@ module Kward
|
|
|
244
248
|
{
|
|
245
249
|
tools: request[:tools] || request["tools"],
|
|
246
250
|
on_reasoning_delta: reasoning_delta,
|
|
251
|
+
on_reasoning_boundary: reasoning_boundary,
|
|
247
252
|
on_assistant_delta: assistant_delta,
|
|
248
253
|
on_retry: retry_callback,
|
|
249
254
|
cancellation: cancellation,
|
data/lib/kward/ansi.rb
CHANGED
|
@@ -262,8 +262,8 @@ module Kward
|
|
|
262
262
|
end
|
|
263
263
|
|
|
264
264
|
def inline_links(text, enabled: enabled?)
|
|
265
|
-
text.split(/(\[[^\
|
|
266
|
-
if (match = part.match(/\A\[([^\
|
|
265
|
+
text.split(/(\[[^\n\]]+\]\([^)\s]+\))/).map do |part|
|
|
266
|
+
if (match = part.match(/\A\[([^\n\]]+)\]\(([^)\s]+)\)\z/))
|
|
267
267
|
"#{colorize(match[1], :cyan, enabled: enabled)} (#{colorize(match[2], :dim, enabled: enabled)})"
|
|
268
268
|
else
|
|
269
269
|
inline_emphasis(part, enabled: enabled)
|
data/lib/kward/cancellation.rb
CHANGED
|
@@ -2,9 +2,9 @@ require "thread"
|
|
|
2
2
|
|
|
3
3
|
# Namespace for the Kward CLI agent runtime.
|
|
4
4
|
module Kward
|
|
5
|
-
# Cooperative cancellation token shared by model calls, tools, and
|
|
5
|
+
# Cooperative cancellation token shared by model calls, tools, and background operations.
|
|
6
6
|
class Cancellation
|
|
7
|
-
# Cooperative cancellation token shared by model calls, tools, and
|
|
7
|
+
# Cooperative cancellation token shared by model calls, tools, and background operations.
|
|
8
8
|
class CancelledError < StandardError; end
|
|
9
9
|
|
|
10
10
|
def initialize
|
data/lib/kward/cli/commands.rb
CHANGED
|
@@ -156,7 +156,7 @@ module Kward
|
|
|
156
156
|
},
|
|
157
157
|
"pan" => {
|
|
158
158
|
usage: "kward pan",
|
|
159
|
-
description: "Start Pan mode, a
|
|
159
|
+
description: "Start Pan mode, a mobile-friendly LAN web UI with persistent sessions.",
|
|
160
160
|
examples: ["kward pan", "kward --working-directory ~/code/project pan"]
|
|
161
161
|
},
|
|
162
162
|
"rpc" => {
|
|
@@ -220,8 +220,6 @@ module Kward
|
|
|
220
220
|
@prompt_delimited = true
|
|
221
221
|
remaining.concat(arguments[(index + 1)..] || [])
|
|
222
222
|
break
|
|
223
|
-
when "--experimental-workers"
|
|
224
|
-
@experimental_workers = true
|
|
225
223
|
when "--skip-config"
|
|
226
224
|
@skip_config = true
|
|
227
225
|
when "--filter"
|
|
@@ -7,7 +7,6 @@ module Kward
|
|
|
7
7
|
private
|
|
8
8
|
|
|
9
9
|
def run_interactive_turn(agent, input, display_input: nil)
|
|
10
|
-
stop_live_worker_view if respond_to?(:stop_live_worker_view, true)
|
|
11
10
|
prepare_memory_context(agent.conversation, input) if agent.respond_to?(:conversation)
|
|
12
11
|
print_user_transcript(input, display_input: display_input) if prompt_interface?
|
|
13
12
|
return run_blocking_interactive_turn(agent, input, display_input: display_input) unless prompt_interface?
|
|
@@ -116,6 +115,9 @@ module Kward
|
|
|
116
115
|
when Events::ReasoningDelta
|
|
117
116
|
stream_state[:streamed] = true
|
|
118
117
|
append_markdown_delta(markdown_chunks, "Reasoning", event.delta)
|
|
118
|
+
when Events::ReasoningBoundary
|
|
119
|
+
stream_state[:streamed] = true
|
|
120
|
+
finish_interactive_markdown_deltas(markdown_chunks, stream_state)
|
|
119
121
|
when Events::AssistantDelta
|
|
120
122
|
stream_state[:streamed] = true
|
|
121
123
|
append_markdown_delta(markdown_chunks, "Assistant", event.delta)
|
|
@@ -232,22 +234,6 @@ module Kward
|
|
|
232
234
|
["/exit", "/quit", "/new"].include?(input.to_s.strip)
|
|
233
235
|
end
|
|
234
236
|
|
|
235
|
-
def handle_busy_worker_input(input, agent, queued_inputs)
|
|
236
|
-
return false unless agent
|
|
237
|
-
|
|
238
|
-
command = input.to_s.strip
|
|
239
|
-
return false unless command == "/workers" || command.start_with?("/workers ")
|
|
240
|
-
|
|
241
|
-
_handled, replacement_agent = handle_local_slash_command(command, agent, @session_store)
|
|
242
|
-
@busy_replacement_agent = replacement_agent if replacement_agent?(replacement_agent)
|
|
243
|
-
restore_busy_input_prompt
|
|
244
|
-
true
|
|
245
|
-
rescue StandardError => e
|
|
246
|
-
runtime_output("Error: #{e.message}")
|
|
247
|
-
restore_busy_input_prompt
|
|
248
|
-
true
|
|
249
|
-
end
|
|
250
|
-
|
|
251
237
|
def replacement_agent?(object)
|
|
252
238
|
object.respond_to?(:conversation) && object.respond_to?(:ask)
|
|
253
239
|
end
|
data/lib/kward/cli/plugins.rb
CHANGED
|
@@ -32,6 +32,7 @@ module Kward
|
|
|
32
32
|
|
|
33
33
|
def reload_plugins(conversation)
|
|
34
34
|
@plugin_registry = PluginRegistry.load(reserved_commands: reserved_slash_command_names)
|
|
35
|
+
@prompt.update_slash_commands(slash_command_entries) if @prompt.respond_to?(:update_slash_commands)
|
|
35
36
|
conversation.plugin_registry = @plugin_registry if conversation.respond_to?(:plugin_registry=)
|
|
36
37
|
conversation.refresh_system_message! if conversation.respond_to?(:refresh_system_message!)
|
|
37
38
|
runtime_output("Plugins reloaded.")
|
|
@@ -126,19 +127,13 @@ module Kward
|
|
|
126
127
|
end
|
|
127
128
|
|
|
128
129
|
def builtin_slash_commands
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
BUILTIN_SLASH_COMMANDS.reject { |command| %w[workers queue].include?(command[:name]) }
|
|
130
|
+
BUILTIN_SLASH_COMMANDS
|
|
132
131
|
end
|
|
133
132
|
|
|
134
133
|
def builtin_slash_command_names
|
|
135
134
|
builtin_slash_commands.map { |command| command[:name] }
|
|
136
135
|
end
|
|
137
136
|
|
|
138
|
-
def experimental_workers_enabled?
|
|
139
|
-
@experimental_workers == true
|
|
140
|
-
end
|
|
141
|
-
|
|
142
137
|
def expand_prompt_template(input)
|
|
143
138
|
PromptCommands.expand(input, templates: prompt_templates, reserved_commands: builtin_slash_command_names)
|
|
144
139
|
end
|
|
@@ -18,6 +18,7 @@ module Kward
|
|
|
18
18
|
@prompt = prompt_interface.new(
|
|
19
19
|
slash_commands: slash_command_entries,
|
|
20
20
|
overlay_settings: ConfigFiles.overlay_settings,
|
|
21
|
+
project_browser_icon_theme: ConfigFiles.project_browser_icon_theme,
|
|
21
22
|
footer: prompt_footer_renderer,
|
|
22
23
|
composer_status: method(:composer_status_text),
|
|
23
24
|
busy_help: ConfigFiles.composer_busy_help?,
|
|
@@ -70,13 +71,12 @@ module Kward
|
|
|
70
71
|
def print_visual_banner
|
|
71
72
|
return unless @prompt.respond_to?(:print_visual_banner)
|
|
72
73
|
|
|
73
|
-
@prompt.print_visual_banner(startup_info_screen)
|
|
74
|
-
refresh_startup_update_check if prompt_interface?
|
|
74
|
+
@prompt.print_visual_banner(startup_info_screen(refresh_update_check: prompt_interface?))
|
|
75
75
|
end
|
|
76
76
|
|
|
77
|
-
def startup_info_screen
|
|
77
|
+
def startup_info_screen(refresh_update_check: false)
|
|
78
78
|
[
|
|
79
|
-
startup_status_line,
|
|
79
|
+
startup_status_line(refresh_update_check: refresh_update_check),
|
|
80
80
|
*startup_update_notice_lines,
|
|
81
81
|
"",
|
|
82
82
|
startup_info_line("Workspace", startup_workspace_label),
|
|
@@ -119,8 +119,8 @@ module Kward
|
|
|
119
119
|
filenames.empty? ? "none" : filenames.join(", ")
|
|
120
120
|
end
|
|
121
121
|
|
|
122
|
-
def startup_status_line
|
|
123
|
-
color = startup_update_notice ? :yellow : :green
|
|
122
|
+
def startup_status_line(refresh_update_check: false)
|
|
123
|
+
color = startup_update_notice(refresh: refresh_update_check) ? :yellow : :green
|
|
124
124
|
"#{ANSI.colorize("●", color, enabled: @color_enabled)} Kward v#{Kward::VERSION} is online."
|
|
125
125
|
end
|
|
126
126
|
|
|
@@ -134,18 +134,11 @@ module Kward
|
|
|
134
134
|
]
|
|
135
135
|
end
|
|
136
136
|
|
|
137
|
-
def startup_update_notice
|
|
138
|
-
return @startup_update_notice if defined?(@startup_update_notice)
|
|
137
|
+
def startup_update_notice(refresh: false)
|
|
138
|
+
return @startup_update_notice if defined?(@startup_update_notice) && !refresh
|
|
139
139
|
|
|
140
|
-
@startup_update_check
|
|
141
|
-
@startup_update_notice = @startup_update_check.notice
|
|
142
|
-
end
|
|
143
|
-
|
|
144
|
-
def refresh_startup_update_check
|
|
145
|
-
checker = @startup_update_check || UpdateCheck.new(current_version: Kward::VERSION)
|
|
146
|
-
return unless checker.enabled?
|
|
147
|
-
|
|
148
|
-
Thread.new { checker.refresh_if_stale }
|
|
140
|
+
@startup_update_check ||= UpdateCheck.new(current_version: Kward::VERSION)
|
|
141
|
+
@startup_update_notice = @startup_update_check.notice(refresh: refresh)
|
|
149
142
|
end
|
|
150
143
|
|
|
151
144
|
def startup_info_line(label, value)
|
data/lib/kward/cli/rendering.rb
CHANGED
|
@@ -38,8 +38,9 @@ module Kward
|
|
|
38
38
|
end
|
|
39
39
|
|
|
40
40
|
def render_reasoning(message)
|
|
41
|
-
|
|
42
|
-
|
|
41
|
+
CLITranscriptFormatter.reasoning_blocks(message).each do |reasoning|
|
|
42
|
+
render_transcript_block("Reasoning", reasoning)
|
|
43
|
+
end
|
|
43
44
|
end
|
|
44
45
|
|
|
45
46
|
def render_assistant_message(message)
|
|
@@ -66,7 +67,7 @@ module Kward
|
|
|
66
67
|
def render_transcript_block(label, content)
|
|
67
68
|
return if content.to_s.empty?
|
|
68
69
|
|
|
69
|
-
rendered = render_markdown_transcript(content)
|
|
70
|
+
rendered = render_markdown_transcript(content).sub(/\n+\z/, "")
|
|
70
71
|
if prompt_interface?
|
|
71
72
|
print_block_delta(label, rendered)
|
|
72
73
|
finish_stream_block
|
|
@@ -84,6 +85,9 @@ module Kward
|
|
|
84
85
|
when Events::ReasoningDelta
|
|
85
86
|
append_markdown_delta(markdown_chunks, "Reasoning", event.delta)
|
|
86
87
|
:streamed
|
|
88
|
+
when Events::ReasoningBoundary
|
|
89
|
+
flush_markdown_deltas(markdown_chunks)
|
|
90
|
+
:streamed
|
|
87
91
|
when Events::AssistantDelta
|
|
88
92
|
append_markdown_delta(markdown_chunks, "Assistant", event.delta)
|
|
89
93
|
:assistant_streamed
|
|
@@ -43,23 +43,13 @@ module Kward
|
|
|
43
43
|
end
|
|
44
44
|
|
|
45
45
|
def build_interactive_agent(conversation)
|
|
46
|
-
@active_worker_role = "implementation"
|
|
47
|
-
set_visible_worker("implementation", status: "active")
|
|
48
|
-
build_worker_agent(conversation, role: "implementation")
|
|
49
|
-
end
|
|
50
|
-
|
|
51
|
-
def build_worker_agent(conversation, role: "implementation")
|
|
52
46
|
conversation.plugin_registry ||= plugin_registry if conversation.respond_to?(:plugin_registry)
|
|
53
47
|
workspace = configured_workspace(root: conversation.workspace_root)
|
|
54
|
-
writer_id = worker_writer_id(role)
|
|
55
48
|
hook_manager = lifecycle_hook_manager(conversation)
|
|
56
49
|
hook_context = lifecycle_hook_context(conversation)
|
|
57
50
|
tool_registry = ToolRegistry.new(
|
|
58
51
|
workspace: workspace,
|
|
59
52
|
prompt: @prompt,
|
|
60
|
-
allowed_tool_names: Workers::ToolPolicy.allowed_tool_names(role),
|
|
61
|
-
write_lock: @worker_write_lock,
|
|
62
|
-
writer_id: writer_id,
|
|
63
53
|
hook_manager: hook_manager,
|
|
64
54
|
hook_context: hook_context
|
|
65
55
|
)
|
|
@@ -74,34 +64,6 @@ module Kward
|
|
|
74
64
|
)
|
|
75
65
|
end
|
|
76
66
|
|
|
77
|
-
def set_visible_worker(id, status: nil, worker: nil)
|
|
78
|
-
@visible_worker_id = id.to_s
|
|
79
|
-
@visible_worker_status = status
|
|
80
|
-
@visible_worker = worker
|
|
81
|
-
end
|
|
82
|
-
|
|
83
|
-
def worker_writer_id(role)
|
|
84
|
-
return nil unless Workers::ToolPolicy.write_capable?(role)
|
|
85
|
-
|
|
86
|
-
@worker_write_lock ||= Workers::WriteLock.new
|
|
87
|
-
owner_id = role.to_s.empty? ? "implementation" : role.to_s
|
|
88
|
-
return owner_id if @worker_write_lock.acquire(owner_id)
|
|
89
|
-
|
|
90
|
-
nil
|
|
91
|
-
end
|
|
92
|
-
|
|
93
|
-
def refresh_implementation_writer(agent)
|
|
94
|
-
return agent unless @active_worker_role == "implementation"
|
|
95
|
-
return agent unless agent&.respond_to?(:tool_registry)
|
|
96
|
-
return agent if agent.tool_registry.writer_id && @worker_write_lock&.owned_by?(agent.tool_registry.writer_id)
|
|
97
|
-
|
|
98
|
-
build_interactive_agent(agent.conversation)
|
|
99
|
-
end
|
|
100
|
-
|
|
101
|
-
def release_implementation_writer
|
|
102
|
-
@worker_write_lock&.release("implementation")
|
|
103
|
-
end
|
|
104
|
-
|
|
105
67
|
def handle_interactive_shell_command(input, agent)
|
|
106
68
|
command = input.to_s.sub(/\A!\s*/, "")
|
|
107
69
|
if command.strip.empty?
|
data/lib/kward/cli/sessions.rb
CHANGED
|
@@ -80,6 +80,15 @@ module Kward
|
|
|
80
80
|
@cleanup_sessions.clear
|
|
81
81
|
end
|
|
82
82
|
|
|
83
|
+
def live_session_paths
|
|
84
|
+
(Array(@tabs).filter_map { |tab| tab.session&.path } + [@active_session&.path]).compact.uniq
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def recent_sessions(session_store, tree: false, limit: nil)
|
|
88
|
+
method = tree ? :recent_tree : :recent
|
|
89
|
+
session_store.public_send(method, limit: limit, keep_empty_path: live_session_paths)
|
|
90
|
+
end
|
|
91
|
+
|
|
83
92
|
def cleanup_replaced_session(previous_session)
|
|
84
93
|
return unless previous_session
|
|
85
94
|
return if @active_session && File.expand_path(previous_session.path) == File.expand_path(@active_session.path)
|
|
@@ -590,8 +599,8 @@ module Kward
|
|
|
590
599
|
end
|
|
591
600
|
|
|
592
601
|
def insert_session_copy(session_store, sessions, labels, source, copy_path)
|
|
593
|
-
copy_info = session_store
|
|
594
|
-
copy_info ||= session_store
|
|
602
|
+
copy_info = recent_sessions(session_store, tree: true).find { |session| File.expand_path(session.path) == File.expand_path(copy_path) }
|
|
603
|
+
copy_info ||= recent_sessions(session_store).find { |session| File.expand_path(session.path) == File.expand_path(copy_path) }
|
|
595
604
|
return nil unless copy_info
|
|
596
605
|
|
|
597
606
|
source_index = sessions.index(source) || 0
|
|
@@ -618,7 +627,7 @@ module Kward
|
|
|
618
627
|
return nil unless source
|
|
619
628
|
|
|
620
629
|
session_store.load(source.path).first.rename(name)
|
|
621
|
-
updated = session_store
|
|
630
|
+
updated = recent_sessions(session_store, tree: true)
|
|
622
631
|
sessions.replace(updated)
|
|
623
632
|
labels.replace(session_picker_labels(sessions))
|
|
624
633
|
index = sessions.index { |session| File.expand_path(session.path) == File.expand_path(source.path) } || 0
|
|
@@ -716,7 +725,7 @@ module Kward
|
|
|
716
725
|
end
|
|
717
726
|
|
|
718
727
|
def select_session_path(session_store)
|
|
719
|
-
select_session_path_from_sessions(session_store
|
|
728
|
+
select_session_path_from_sessions(recent_sessions(session_store, tree: true), session_store: session_store)
|
|
720
729
|
end
|
|
721
730
|
|
|
722
731
|
def reopen_sessions_after_fork(session_store, source_path, source_label)
|
|
@@ -724,7 +733,7 @@ module Kward
|
|
|
724
733
|
fork_session_from_picker(session_store, source_path)
|
|
725
734
|
end
|
|
726
735
|
|
|
727
|
-
sessions = session_store
|
|
736
|
+
sessions = recent_sessions(session_store, tree: true)
|
|
728
737
|
labels = session_picker_labels(sessions)
|
|
729
738
|
initial_index = if fork_path
|
|
730
739
|
sessions.index { |session| File.expand_path(session.path) == File.expand_path(fork_path) }
|
data/lib/kward/cli/settings.rb
CHANGED
|
@@ -243,6 +243,8 @@ module Kward
|
|
|
243
243
|
next unless width
|
|
244
244
|
|
|
245
245
|
update_overlay_settings("width" => width)
|
|
246
|
+
when /\Afile icons/
|
|
247
|
+
configure_project_browser_icon_theme
|
|
246
248
|
when /\Ashow busy help/, /\Ahide busy help/
|
|
247
249
|
set_composer_busy_help(!composer_busy_help?)
|
|
248
250
|
runtime_output("Busy help #{composer_busy_help? ? "enabled" : "disabled"}. Restart the TUI to apply this setting.")
|
|
@@ -277,6 +279,7 @@ module Kward
|
|
|
277
279
|
[
|
|
278
280
|
"Overlay alignment (#{settings["alignment"]})",
|
|
279
281
|
"Overlay width (#{settings["width"]})",
|
|
282
|
+
"File icons (#{project_browser_icon_theme})",
|
|
280
283
|
"#{composer_busy_help? ? "Hide" : "Show"} busy help (currently #{on_off(composer_busy_help?)})",
|
|
281
284
|
"Tab keybindings (#{composer_tab_keybindings})",
|
|
282
285
|
"Editor mode (#{editor_mode})",
|
|
@@ -290,6 +293,25 @@ module Kward
|
|
|
290
293
|
]
|
|
291
294
|
end
|
|
292
295
|
|
|
296
|
+
def project_browser_icon_theme
|
|
297
|
+
ConfigFiles.project_browser_icon_theme(safely_read_config.to_h)
|
|
298
|
+
end
|
|
299
|
+
|
|
300
|
+
def configure_project_browser_icon_theme
|
|
301
|
+
selected = @prompt.select("File icons", project_browser_icon_theme_choices, title: "Settings")
|
|
302
|
+
theme = selected.to_s.split.first.to_s.downcase
|
|
303
|
+
return unless ConfigFiles::PROJECT_BROWSER_ICON_THEMES.include?(theme)
|
|
304
|
+
|
|
305
|
+
update_nested_config("project_browser", "icons" => theme)
|
|
306
|
+
@prompt.update_project_browser_icon_theme(theme) if @prompt.respond_to?(:update_project_browser_icon_theme)
|
|
307
|
+
runtime_output("File icons set to #{theme}.")
|
|
308
|
+
end
|
|
309
|
+
|
|
310
|
+
def project_browser_icon_theme_choices
|
|
311
|
+
current = project_browser_icon_theme
|
|
312
|
+
ConfigFiles::PROJECT_BROWSER_ICON_THEMES.map { |theme| theme == current ? "#{theme} (current)" : theme }
|
|
313
|
+
end
|
|
314
|
+
|
|
293
315
|
def composer_busy_help?
|
|
294
316
|
ConfigFiles.composer_busy_help?(safely_read_config.to_h)
|
|
295
317
|
end
|