@@ -36,9 +36,45 @@ local WINDOW_MARGIN = 3 -- Additional margin for window borders and spacing
3636local UI_ELEMENTS_HEIGHT = 2 -- Reserve space for statusline and tabline
3737local SAFETY_MARGIN = 2 -- Extra margin to prevent "Not enough room" errors
3838
39- -- Tool call icons (can be overridden via Config.chat.tool_call.icons)
39+ local function _shorten_tokens (n )
40+ n = tonumber (n ) or 0
41+ if n >= 1000 then
42+ local rounded = math.floor (n / 1000 + 0.5 )
43+ return string.format (" %dk" , rounded )
44+ end
45+ return tostring (n )
46+ end
47+
48+ local function _format_usage (tokens , limit , costs )
49+ local usage_cfg = (Config .windows and Config .windows .usage ) or {}
50+ local fmt = usage_cfg .format
51+ or Config .usage_string_format -- backwards compatibility
52+ or " {session_tokens_short} / {limit_tokens_short} (${session_cost})"
53+
54+ local placeholders = {
55+ session_tokens = tostring (tokens or 0 ),
56+ limit_tokens = tostring (limit or 0 ),
57+ session_tokens_short = _shorten_tokens (tokens ),
58+ limit_tokens_short = _shorten_tokens (limit ),
59+ session_cost = tostring (costs or " 0.00" ),
60+ }
61+
62+ local result = fmt :gsub (" {(.-)}" , function (key )
63+ return placeholders [key ] or " "
64+ end )
65+
66+ return result
67+ end
68+
69+ -- Tool call icons (can be overridden via Config.windows.chat.tool_call.icons)
70+ local function _get_chat_config ()
71+ -- Prefer `windows.chat`, but fall back to top-level `chat` for backwards compatibility
72+ return (Config .windows and Config .windows .chat ) or Config .chat or {}
73+ end
74+
4075local function get_tool_call_icons ()
41- local icons_cfg = (Config .chat and Config .chat .tool_call and Config .chat .tool_call .icons ) or {}
76+ local chat_cfg = _get_chat_config ()
77+ local icons_cfg = (chat_cfg .tool_call and chat_cfg .tool_call .icons ) or {}
4278 return {
4379 success = icons_cfg .success or " ✅" ,
4480 error = icons_cfg .error or " ❌" ,
5086
5187-- Label texts used for tool call diffs.
5288--
53- -- Preferred configuration (under `Config .chat.tool_call`):
89+ -- Preferred configuration (under `windows .chat.tool_call`):
5490-- tool_call = {
5591-- diff = {
5692-- collapsed_label = "+ view diff", -- Label when the diff is collapsed
64100-- - `diff_label_collapsed` / `diff_label_expanded` (flat keys)
65101-- - `diff_start_expanded` (boolean flag) to control initial expansion
66102local function get_tool_call_diff_labels ()
67- local cfg = (Config .chat and Config .chat .tool_call ) or {}
103+ local chat_cfg = _get_chat_config ()
104+ local cfg = chat_cfg .tool_call or {}
68105
69106 local diff_cfg = cfg .diff or {}
70107 local labels_cfg = cfg .diff_label or {}
@@ -86,7 +123,8 @@ local function get_tool_call_diff_labels()
86123end
87124
88125local function should_start_diff_expanded ()
89- local cfg = (Config .chat and Config .chat .tool_call ) or {}
126+ local chat_cfg = _get_chat_config ()
127+ local cfg = chat_cfg .tool_call or {}
90128 local diff_cfg = cfg .diff or {}
91129
92130 if diff_cfg .expanded ~= nil then
@@ -101,7 +139,8 @@ local function should_start_diff_expanded()
101139end
102140
103141local function get_reasoning_labels ()
104- local cfg = (Config .chat and Config .chat .reasoning ) or {}
142+ local chat_cfg = _get_chat_config ()
143+ local cfg = chat_cfg .reasoning or {}
105144 local running = cfg .running_label or " Thinking..."
106145 local finished = cfg .finished_label or " Thought"
107146
@@ -132,9 +171,10 @@ function M.new(id, mediator)
132171 instance ._augroup = vim .api .nvim_create_augroup (" eca_sidebar_" .. id , { clear = true })
133172 instance ._response_start_time = 0
134173 instance ._max_response_length = 50000 -- 50KB max response
174+ local chat_cfg = _get_chat_config ()
135175 instance ._headers = {
136- user = (Config . chat and Config . chat . headers and Config . chat .headers .user ) or " > " ,
137- assistant = (Config . chat and Config . chat . headers and Config . chat .headers .assistant ) or " " ,
176+ user = (chat_cfg . headers and chat_cfg .headers .user ) or " > " ,
177+ assistant = (chat_cfg . headers and chat_cfg .headers .assistant ) or " " ,
138178 }
139179 instance ._welcome_message_applied = false
140180 instance ._contexts_placeholder_line = " "
@@ -426,7 +466,7 @@ function M:_create_containers()
426466 modifiable = false ,
427467 }),
428468 win_options = vim .tbl_deep_extend (" force" , base_win_options , {
429- winhighlight = " Normal:EcaUsage " ,
469+ winhighlight = " Normal:EcaLabel " ,
430470 statusline = " " ,
431471 }),
432472 })
@@ -863,7 +903,7 @@ function M:_update_input_display(opts)
863903 self .extmarks .contexts ._ns ,
864904 0 ,
865905 i ,
866- vim .tbl_extend (" force" , { virt_text = { { context_name , " Comment " } }, virt_text_pos = " inline" , hl_mode = " replace" }, { id = self .extmarks .contexts ._id [i ] })
906+ vim .tbl_extend (" force" , { virt_text = { { context_name , " EcaLabel " } }, virt_text_pos = " inline" , hl_mode = " replace" }, { id = self .extmarks .contexts ._id [i ] })
867907 )
868908 end
869909
@@ -1011,21 +1051,21 @@ function M:_update_config_display()
10111051 -- While any MCP is still starting, dim the active count
10121052 local active_hl = " Normal"
10131053 if starting_count > 0 then
1014- active_hl = " Comment "
1054+ active_hl = " EcaLabel "
10151055 end
10161056
10171057 local registered_hl = " Normal"
10181058 if has_failed then
10191059 registered_hl = " Exception" -- highlight registered count in red when any MCP failed
1020- elseif active_hl == " Comment " then
1060+ elseif active_hl == " EcaLabel " then
10211061 -- While MCPs are still starting, dim the total count as well
1022- registered_hl = " Comment "
1062+ registered_hl = " EcaLabel "
10231063 end
10241064
10251065 local texts = {
1026- { " model:" , " Comment " }, { model , " Normal" }, { " " },
1027- { " behavior:" , " Comment " }, { behavior , " Normal" }, { " " },
1028- { " mcps:" , " Comment " }, { tostring (active_count ), active_hl }, { " /" , " Comment " },
1066+ { " model:" , " EcaLabel " }, { model , " Normal" }, { " " },
1067+ { " behavior:" , " EcaLabel " }, { behavior , " Normal" }, { " " },
1068+ { " mcps:" , " EcaLabel " }, { tostring (active_count ), active_hl }, { " /" , " EcaLabel " },
10291069 { tostring (registered_count ), registered_hl },
10301070 }
10311071
@@ -1068,7 +1108,7 @@ function M:_update_usage_info()
10681108 local costs = self .mediator :costs_session () or " 0.00"
10691109
10701110 self ._current_status = string.format (" %s" , status_text )
1071- self ._usage_info = string.format ( " %d / %d (%s) " , tokens , limit , costs )
1111+ self ._usage_info = _format_usage ( tokens , limit , costs )
10721112
10731113 self .extmarks = self .extmarks or {}
10741114
@@ -1121,7 +1161,8 @@ function M:_update_welcome_content()
11211161 return
11221162 end
11231163
1124- local cfg = (Config .chat and Config .chat .welcome ) or {}
1164+ local chat_cfg = _get_chat_config ()
1165+ local cfg = chat_cfg .welcome or {}
11251166 local cfg_msg = (cfg .message and cfg .message ~= " " and cfg .message ) or nil
11261167 local welcome_message = cfg_msg or (self .mediator and self .mediator :welcome_message () or nil )
11271168
@@ -1497,7 +1538,9 @@ function M:_update_streaming_message(content)
14971538 return
14981539 end
14991540
1500- -- Simple and direct buffer update
1541+ -- Simple and direct buffer update that only rewrites the assistant's
1542+ -- own streaming region. This avoids clobbering content that may have
1543+ -- been appended after it (e.g. tool calls or reasoning blocks).
15011544 local success , err = pcall (function ()
15021545 -- Make buffer modifiable
15031546 vim .api .nvim_set_option_value (" modifiable" , true , { buf = chat .bufnr })
@@ -2242,7 +2285,7 @@ local function _eca_sidebar_hl(kind)
22422285 if kind == " tool_header" then
22432286 return " EcaToolCall"
22442287 elseif kind == " reason_header" then
2245- return " EcaUsage "
2288+ return " EcaLabel "
22462289 elseif kind == " diff_label" then
22472290 return " EcaHyperlink"
22482291 end
0 commit comments