From dffc102677a3933c6aac3d3d162e55141a077bff Mon Sep 17 00:00:00 2001 From: joaopluigi Date: Fri, 5 Sep 2025 17:05:31 -0300 Subject: [PATCH 1/8] make headers configurable --- docs/configuration.md | 26 ++++++++++++++++++++++++++ lua/eca/config.lua | 6 ++++++ lua/eca/sidebar.lua | 14 ++++++++++---- 3 files changed, 42 insertions(+), 4 deletions(-) diff --git a/docs/configuration.md b/docs/configuration.md index b9be743..9b345e7 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -45,6 +45,14 @@ require("eca").setup({ toggle = "et", -- Toggle sidebar }, + -- === CHAT === + chat = { + headers = { + user = "## 👤 You", + assistant = "## 🤖 ECA", + }, + }, + -- === WINDOW SETTINGS === windows = { -- Automatic line wrapping @@ -93,6 +101,24 @@ require("eca").setup({ --- +## Chat headers +You can customize the chat role headers shown in the sidebar. For example: + +```lua +require("eca").setup({ + chat = { + headers = { + user = "## 👨 Me", -- default: "## 👤 You" + assistant = "## 🤖 Copilot", -- default: "## 🤖 ECA" + }, + }, +}) +``` + +Notes: +- These are plain strings; they will be printed verbatim as a single line. +- A blank line is automatically inserted after the header. + ## Presets ### Minimalist diff --git a/lua/eca/config.lua b/lua/eca/config.lua index 79376ad..0395ab1 100644 --- a/lua/eca/config.lua +++ b/lua/eca/config.lua @@ -43,6 +43,12 @@ M._defaults = { focus = "ef", toggle = "et", }, + chat = { + headers = { + user = "## 👤 You", + assistant = "## 🤖 ECA", + }, + }, windows = { wrap = true, width = 40, -- Window width as percentage (40 = 40% of screen width) diff --git a/lua/eca/sidebar.lua b/lua/eca/sidebar.lua index 6cd7671..da2c42e 100644 --- a/lua/eca/sidebar.lua +++ b/lua/eca/sidebar.lua @@ -1367,11 +1367,14 @@ function M:_add_message(role, content) table.insert(lines, "") end - -- Add role header with better markdown formatting + -- Add role header with better markdown formatting (configurable) + local user_header = (Config.chat and Config.chat.headers and Config.chat.headers.user) or "## 👤 You" + local assistant_header = (Config.chat and Config.chat.headers and Config.chat.headers.assistant) or "## 🤖 ECA" + if role == "user" then - table.insert(lines, "## 👤 You") + table.insert(lines, user_header) else - table.insert(lines, "## 🤖 ECA") + table.insert(lines, assistant_header) end table.insert(lines, "") @@ -1461,8 +1464,11 @@ function M:_get_last_message_line() end local lines = vim.api.nvim_buf_get_lines(chat.bufnr, 0, -1, false) + local assistant_header = (Config.chat and Config.chat.headers and Config.chat.headers.assistant) or "## 🤖 ECA" + for i = #lines, 1, -1 do - if lines[i] and lines[i]:match("^## 🤖 ECA") then + local line = lines[i] + if line and line:sub(1, #assistant_header) == assistant_header then return i end end From f0e230108f1eaa41847d32032e359da24aef52fa Mon Sep 17 00:00:00 2001 From: joaopluigi Date: Fri, 5 Sep 2025 18:09:13 -0300 Subject: [PATCH 2/8] avoid duplicating user message in chat --- lua/eca/sidebar.lua | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/lua/eca/sidebar.lua b/lua/eca/sidebar.lua index da2c42e..ed780e5 100644 --- a/lua/eca/sidebar.lua +++ b/lua/eca/sidebar.lua @@ -1161,6 +1161,9 @@ function M:_send_message(message) -- Store the last user message to avoid duplication self._last_user_message = message + -- Add user message to chat + self:_add_message("user", message) + local contexts = self:get_contexts() self.mediator:send("chat/prompt", { chatId = self.id, @@ -1273,6 +1276,11 @@ function M:_handle_streaming_text(text) end Logger.debug("Received text chunk: '" .. text:sub(1, 50) .. (text:len() > 50 and "..." or "") .. "'") + if vim.trim(text) == vim.trim(self._last_user_message) then + Logger.debug("Ignoring duplicate user message in response") + return + end + if not self._is_streaming then Logger.debug("Starting streaming response") -- Start streaming - simple and direct From 5706fba2446b25d11e19586a216a3fdb3928cc18 Mon Sep 17 00:00:00 2001 From: joaopluigi Date: Sun, 7 Sep 2025 18:33:29 -0300 Subject: [PATCH 3/8] make custom header works without new lines --- lua/eca/config.lua | 4 ++-- lua/eca/sidebar.lua | 52 ++++++++++++++++++++++++++++----------------- 2 files changed, 35 insertions(+), 21 deletions(-) diff --git a/lua/eca/config.lua b/lua/eca/config.lua index 0395ab1..fcbd286 100644 --- a/lua/eca/config.lua +++ b/lua/eca/config.lua @@ -45,8 +45,8 @@ M._defaults = { }, chat = { headers = { - user = "## 👤 You", - assistant = "## 🤖 ECA", + user = "## 👤 You\n\n", + assistant = "## 🤖 ECA\n\n", }, }, windows = { diff --git a/lua/eca/sidebar.lua b/lua/eca/sidebar.lua index ed780e5..24b0d53 100644 --- a/lua/eca/sidebar.lua +++ b/lua/eca/sidebar.lua @@ -25,6 +25,7 @@ local Split = require("nui.split") ---@field private _augroup integer Autocmd group ID ---@field private _response_start_time number Timestamp when streaming started ---@field private _max_response_length number Maximum allowed response length +---@field private _headers table Table of headers for the chat local M = {} M.__index = M @@ -58,6 +59,10 @@ function M.new(id, mediator) instance._augroup = vim.api.nvim_create_augroup("eca_sidebar_" .. id, { clear = true }) instance._response_start_time = 0 instance._max_response_length = 50000 -- 50KB max response + instance._headers = { + user = (Config.chat and Config.chat.headers and Config.chat.headers.user) or "## 👤 You\n\n", + assistant = (Config.chat and Config.chat.headers and Config.chat.headers.assistant) or "## 🤖 ECA\n\n", + } require("eca.observer").subscribe(id, function(message) instance:handle_chat_content(message) @@ -874,6 +879,7 @@ function M:_set_welcome_content() "- **RepoMap**: Use `:EcaAddRepoMap` to add repository structure context", "", "---", + "" } Logger.debug("Setting welcome content for new chat") @@ -1271,7 +1277,7 @@ end function M:_handle_streaming_text(text) -- Only check for empty text if not text or text == "" then - Logger.trace("Ignoring empty text response") + Logger.debug("Ignoring empty text response") return end Logger.debug("Received text chunk: '" .. text:sub(1, 50) .. (text:len() > 50 and "..." or "") .. "'") @@ -1319,10 +1325,13 @@ function M:_update_streaming_message(content) -- Make buffer modifiable vim.api.nvim_set_option_value("modifiable", true, { buf = chat.bufnr }) + -- Concat content with header + content = self._headers.assistant .. content + -- Get current lines local lines = vim.api.nvim_buf_get_lines(chat.bufnr, 0, -1, false) local content_lines = Utils.split_lines(content) - local start_line = self._last_assistant_line + 2 -- Skip "## 🤖 ECA" and empty line + local start_line = self._last_assistant_line Logger.debug("DEBUG: Assistant line: " .. self._last_assistant_line .. ", start_line: " .. start_line) Logger.debug("DEBUG: Content lines: " .. #content_lines) @@ -1367,25 +1376,16 @@ function M:_add_message(role, content) self:_safe_buffer_update(chat.bufnr, function() local lines = vim.api.nvim_buf_get_lines(chat.bufnr, 0, -1, false) - - -- Add separator if not the first message - if #lines > 0 and lines[#lines] ~= "" then - table.insert(lines, "") - table.insert(lines, "---") - table.insert(lines, "") - end - - -- Add role header with better markdown formatting (configurable) - local user_header = (Config.chat and Config.chat.headers and Config.chat.headers.user) or "## 👤 You" - local assistant_header = (Config.chat and Config.chat.headers and Config.chat.headers.assistant) or "## 🤖 ECA" + local header = "" if role == "user" then - table.insert(lines, user_header) - else - table.insert(lines, assistant_header) + header = self._headers.user + elseif role == "assistant" then + header = self._headers.assistant end - table.insert(lines, "") + -- Concat header and content + content = header .. content -- Add content with better markdown formatting local content_lines = Utils.split_lines(content) @@ -1413,7 +1413,9 @@ function M:_add_message(role, content) end end - table.insert(lines, "") + if header ~= "" then + table.insert(lines, "") + end -- Update buffer safely vim.api.nvim_buf_set_lines(chat.bufnr, 0, -1, false, lines) @@ -1472,7 +1474,19 @@ function M:_get_last_message_line() end local lines = vim.api.nvim_buf_get_lines(chat.bufnr, 0, -1, false) - local assistant_header = (Config.chat and Config.chat.headers and Config.chat.headers.assistant) or "## 🤖 ECA" + + Logger.debug("DEBUG: Finding last assistant message line in " .. #lines .. " lines") + Logger.debug("DEBUG: Finding last assistant message line in " .. vim.inspect(lines) .. " lines") + + local assistant_header_lines = Utils.split_lines(self._headers.assistant) + local assistant_header = "" + + for i = #assistant_header_lines, 1, -1 do + if assistant_header_lines[i] and assistant_header_lines[i] ~= "" then + assistant_header = assistant_header_lines[i] + break + end + end for i = #lines, 1, -1 do local line = lines[i] From 69175fa74dad81709104699d65a9c5fd18981e15 Mon Sep 17 00:00:00 2001 From: joaopluigi Date: Sun, 7 Sep 2025 19:08:23 -0300 Subject: [PATCH 4/8] update docs --- docs/configuration.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/configuration.md b/docs/configuration.md index 9b345e7..a20e78d 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -48,8 +48,8 @@ require("eca").setup({ -- === CHAT === chat = { headers = { - user = "## 👤 You", - assistant = "## 🤖 ECA", + user = "## 👤 You\n\n", + assistant = "## 🤖 ECA\n\n", }, }, @@ -108,8 +108,8 @@ You can customize the chat role headers shown in the sidebar. For example: require("eca").setup({ chat = { headers = { - user = "## 👨 Me", -- default: "## 👤 You" - assistant = "## 🤖 Copilot", -- default: "## 🤖 ECA" + user = "## 👨 Me\n\n", -- default: "## 👤 You\n\n" + assistant = "## 🤖 Assistant\n\n", -- default: "## 🤖 ECA\n\n" }, }, }) From b46af5c8cd9320e2d124e7774050db1b7f9cb62c Mon Sep 17 00:00:00 2001 From: joaopluigi Date: Sun, 7 Sep 2025 19:10:00 -0300 Subject: [PATCH 5/8] only add new line when add message if content is not empty --- lua/eca/sidebar.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lua/eca/sidebar.lua b/lua/eca/sidebar.lua index 24b0d53..00dd8cc 100644 --- a/lua/eca/sidebar.lua +++ b/lua/eca/sidebar.lua @@ -1413,7 +1413,7 @@ function M:_add_message(role, content) end end - if header ~= "" then + if content ~= "" then table.insert(lines, "") end From 01d585173933746ba03d25531a0142a4dd3e75f8 Mon Sep 17 00:00:00 2001 From: joaopluigi Date: Sun, 7 Sep 2025 19:10:38 -0300 Subject: [PATCH 6/8] remove some debugging log --- lua/eca/sidebar.lua | 4 ---- 1 file changed, 4 deletions(-) diff --git a/lua/eca/sidebar.lua b/lua/eca/sidebar.lua index 00dd8cc..578a9d7 100644 --- a/lua/eca/sidebar.lua +++ b/lua/eca/sidebar.lua @@ -1474,10 +1474,6 @@ function M:_get_last_message_line() end local lines = vim.api.nvim_buf_get_lines(chat.bufnr, 0, -1, false) - - Logger.debug("DEBUG: Finding last assistant message line in " .. #lines .. " lines") - Logger.debug("DEBUG: Finding last assistant message line in " .. vim.inspect(lines) .. " lines") - local assistant_header_lines = Utils.split_lines(self._headers.assistant) local assistant_header = "" From eaaf207e5780c88065ae4a77d93f39f1f88bdb60 Mon Sep 17 00:00:00 2001 From: joaopluigi Date: Sun, 7 Sep 2025 19:16:01 -0300 Subject: [PATCH 7/8] update docs --- docs/configuration.md | 30 ++++++++++++------------------ 1 file changed, 12 insertions(+), 18 deletions(-) diff --git a/docs/configuration.md b/docs/configuration.md index a20e78d..871f2c0 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -101,24 +101,6 @@ require("eca").setup({ --- -## Chat headers -You can customize the chat role headers shown in the sidebar. For example: - -```lua -require("eca").setup({ - chat = { - headers = { - user = "## 👨 Me\n\n", -- default: "## 👤 You\n\n" - assistant = "## 🤖 Assistant\n\n", -- default: "## 🤖 ECA\n\n" - }, - }, -}) -``` - -Notes: -- These are plain strings; they will be printed verbatim as a single line. -- A blank line is automatically inserted after the header. - ## Presets ### Minimalist @@ -126,6 +108,12 @@ Notes: require("eca").setup({ behaviour = { show_status_updates = false }, windows = { width = 30 }, + chat = { + headers = { + user = "> ", + assistant = "", + }, + }, }) ``` @@ -139,6 +127,12 @@ require("eca").setup({ sidebar_header = { enabled = true, rounded = true }, input = { prefix = "💬 ", height = 10 }, }, + chat = { + headers = { + user = "## 👤 You\n\n", + assistant = "## 🤖 ECA\n\n", + }, + }, }) ``` From 9cf4a8b7139a91f12c326577c138cdb166c56ddd Mon Sep 17 00:00:00 2001 From: joaopluigi Date: Sun, 7 Sep 2025 19:17:35 -0300 Subject: [PATCH 8/8] tiny adjustment --- lua/eca/sidebar.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/lua/eca/sidebar.lua b/lua/eca/sidebar.lua index 578a9d7..8ddb940 100644 --- a/lua/eca/sidebar.lua +++ b/lua/eca/sidebar.lua @@ -26,6 +26,7 @@ local Split = require("nui.split") ---@field private _response_start_time number Timestamp when streaming started ---@field private _max_response_length number Maximum allowed response length ---@field private _headers table Table of headers for the chat + local M = {} M.__index = M