|
40 | 40 | Can be `'left', `'right', `'top', or `'bottom'. This setting will only |
41 | 41 | be used when `eca-chat-use-side-window' is non-nil." |
42 | 42 | :type '(choice (const :tag "Left" left) |
43 | | - (const :tag "Right" right) |
44 | | - (const :tag "Top" top) |
45 | | - (const :tag "Bottom" bottom)) |
| 43 | + (const :tag "Right" right) |
| 44 | + (const :tag "Top" top) |
| 45 | + (const :tag "Bottom" bottom)) |
46 | 46 | :group 'eca) |
47 | 47 |
|
48 | 48 | (defcustom eca-chat-window-width 0.40 |
@@ -170,14 +170,14 @@ Must be a valid model supported by server, check `eca-chat-select-model`." |
170 | 170 | (defcustom eca-chat-diff-tool 'smerge |
171 | 171 | "Select the method for displaying file-change diffs in ECA chat." |
172 | 172 | :type '(choice (const :tag "Side-by-side Ediff" ediff) |
173 | | - (const :tag "Merge-style Smerge" smerge)) |
| 173 | + (const :tag "Merge-style Smerge" smerge)) |
174 | 174 | :group 'eca) |
175 | 175 |
|
176 | 176 | (defcustom eca-chat-tool-call-prepare-throttle 'smart |
177 | 177 | "Throttle strategy for handling `toolCallPrepare` events. |
178 | 178 | Possible values: `all` or `smart` (default)." |
179 | 179 | :type '(choice (const :tag "Process all updates" all) |
180 | | - (const :tag "Smart throttle" smart)) |
| 180 | + (const :tag "Smart throttle" smart)) |
181 | 181 | :group 'eca) |
182 | 182 |
|
183 | 183 | (defcustom eca-chat-tool-call-prepare-update-interval 5 |
@@ -404,6 +404,7 @@ Must be a positive integer." |
404 | 404 | (defvar eca-chat--last-known-model nil) |
405 | 405 | (defvar eca-chat--last-known-behavior nil) |
406 | 406 |
|
| 407 | + |
407 | 408 | (defun eca-chat-new-buffer-name (session) |
408 | 409 | "Return the chat buffer name for SESSION." |
409 | 410 | (format "<eca-chat:%s:%s>" (eca--session-id session) eca-chat--new-chat-id)) |
@@ -441,6 +442,7 @@ Must be a positive integer." |
441 | 442 | (define-key map (kbd "C-c <up>") #'eca-chat-go-to-prev-expandable-block) |
442 | 443 | (define-key map (kbd "C-c <down>") #'eca-chat-go-to-next-expandable-block) |
443 | 444 | (define-key map (kbd "C-c <tab>") #'eca-chat-toggle-expandable-block) |
| 445 | + (define-key map (kbd "C-c C-y") #'eca-chat-media-yank-screenshot) |
444 | 446 | map) |
445 | 447 | "Keymap used by `eca-chat-mode'.") |
446 | 448 |
|
@@ -851,12 +853,28 @@ the prompt/context line." |
851 | 853 | (goto-char prompt-start) |
852 | 854 | (string-trim (buffer-substring (point) (point-max)))))) |
853 | 855 |
|
| 856 | +(defun eca-chat--extract-contexts-from-prompt () |
| 857 | + "Extract contexts from prompt text properties. |
| 858 | +Returns a list of context plists found in the prompt field." |
| 859 | + (when-let ((prompt-start (eca-chat--prompt-field-start-point))) |
| 860 | + (let ((contexts '()) |
| 861 | + (pos prompt-start) |
| 862 | + (end (point-max))) |
| 863 | + (while (< pos end) |
| 864 | + (when-let ((context (get-text-property pos 'eca-chat-context-item))) |
| 865 | + (unless (member context contexts) |
| 866 | + (push context contexts))) |
| 867 | + (setq pos (next-single-property-change pos 'eca-chat-context-item nil end))) |
| 868 | + (nreverse contexts)))) |
| 869 | + |
854 | 870 | (defun eca-chat--send-prompt (session prompt) |
855 | 871 | "Send PROMPT to server for SESSION." |
856 | 872 | (when eca-chat--closed |
857 | 873 | (user-error (eca-error "This chat is closed"))) |
858 | 874 | (let* ((prompt-start (eca-chat--prompt-field-start-point)) |
859 | | - (refined-contexts (-map #'eca-chat--refine-context eca-chat--context))) |
| 875 | + (prompt-contexts (eca-chat--extract-contexts-from-prompt)) |
| 876 | + (refined-contexts (-map #'eca-chat--refine-context |
| 877 | + (append eca-chat--context prompt-contexts)))) |
860 | 878 | (when (seq-empty-p eca-chat--history) (eca-chat--clear)) |
861 | 879 | (add-to-list 'eca-chat--history prompt) |
862 | 880 | (setq eca-chat--history-index -1) |
@@ -1289,14 +1307,16 @@ If STATIC? return strs with no dynamic values." |
1289 | 1307 | (-let* (((&plist :type type) context) |
1290 | 1308 | (context-str |
1291 | 1309 | (pcase type |
1292 | | - ("file" (propertize (concat eca-chat-context-prefix |
1293 | | - (eca-chat--context-presentable-path (plist-get context :path)) |
1294 | | - (-when-let ((&plist :start start :end end) (plist-get context :linesRange)) |
1295 | | - (format "(%d-%d)" start end))) |
1296 | | - 'eca-chat-expanded-item-str (concat eca-chat-context-prefix (plist-get context :path) |
1297 | | - (-when-let ((&plist :start start :end end) (plist-get context :linesRange)) |
1298 | | - (format ":L%d-L%d" start end))) |
1299 | | - 'font-lock-face 'eca-chat-context-file-face)) |
| 1310 | + ("file" (let ((path (plist-get context :path)) |
| 1311 | + (lines-range (plist-get context :linesRange))) |
| 1312 | + (propertize (concat eca-chat-context-prefix |
| 1313 | + (eca-chat--context-presentable-path path) |
| 1314 | + (-when-let ((&plist :start start :end end) lines-range) |
| 1315 | + (format "(%d-%d)" start end))) |
| 1316 | + 'eca-chat-expanded-item-str (concat eca-chat-context-prefix path |
| 1317 | + (-when-let ((&plist :start start :end end) lines-range) |
| 1318 | + (format ":L%d-L%d" start end))) |
| 1319 | + 'font-lock-face 'eca-chat-context-file-face))) |
1300 | 1320 | ("directory" (propertize (concat eca-chat-context-prefix (eca-chat--context-presentable-path (plist-get context :path))) |
1301 | 1321 | 'eca-chat-expanded-item-str (concat eca-chat-context-prefix (plist-get context :path)) |
1302 | 1322 | 'font-lock-face 'eca-chat-context-file-face)) |
@@ -1329,7 +1349,7 @@ If STATIC? return strs with no dynamic values." |
1329 | 1349 | ")")) |
1330 | 1350 | 'eca-chat-expanded-item-str (concat eca-chat-context-prefix "cursor") |
1331 | 1351 | 'font-lock-face 'eca-chat-context-cursor-face)) |
1332 | | - (_ (concat eca-chat-context-prefix "unkown:" type))))) |
| 1352 | + (_ (concat eca-chat-context-prefix "unknown:" type))))) |
1333 | 1353 | (propertize context-str |
1334 | 1354 | 'eca-chat-item-type 'context |
1335 | 1355 | 'eca-chat-item-str-length (length context-str) |
@@ -1468,20 +1488,22 @@ Add text property to prompt text to match context." |
1468 | 1488 |
|
1469 | 1489 | (defun eca-chat--context-to-completion (context) |
1470 | 1490 | "Convert CONTEXT to a completion item." |
1471 | | - (let ((raw-label (pcase (plist-get context :type) |
1472 | | - ("file" (f-filename (plist-get context :path))) |
1473 | | - ("directory" (f-filename (plist-get context :path))) |
1474 | | - ("repoMap" "repoMap") |
1475 | | - ("cursor" "cursor") |
1476 | | - ("mcpResource" (concat (plist-get context :server) ":" (plist-get context :name))) |
1477 | | - (_ (concat "Unknown - " (plist-get context :type))))) |
1478 | | - (face (pcase (plist-get context :type) |
1479 | | - ("file" 'eca-chat-context-file-face) |
1480 | | - ("directory" 'eca-chat-context-file-face) |
1481 | | - ("repoMap" 'eca-chat-context-repo-map-face) |
1482 | | - ("cursor" 'eca-chat-context-cursor-face) |
1483 | | - ("mcpResource" 'eca-chat-context-mcp-resource-face) |
1484 | | - (_ nil)))) |
| 1491 | + (let* ((ctx-type (plist-get context :type)) |
| 1492 | + (ctx-path (plist-get context :path)) |
| 1493 | + (raw-label (pcase ctx-type |
| 1494 | + ("file" (f-filename ctx-path)) |
| 1495 | + ("directory" (f-filename ctx-path)) |
| 1496 | + ("repoMap" "repoMap") |
| 1497 | + ("cursor" "cursor") |
| 1498 | + ("mcpResource" (concat (plist-get context :server) ":" (plist-get context :name))) |
| 1499 | + (_ (concat "Unknown - " ctx-type)))) |
| 1500 | + (face (pcase ctx-type |
| 1501 | + ("file" 'eca-chat-context-file-face) |
| 1502 | + ("directory" 'eca-chat-context-file-face) |
| 1503 | + ("repoMap" 'eca-chat-context-repo-map-face) |
| 1504 | + ("cursor" 'eca-chat-context-cursor-face) |
| 1505 | + ("mcpResource" 'eca-chat-context-mcp-resource-face) |
| 1506 | + (_ nil)))) |
1485 | 1507 | (propertize raw-label |
1486 | 1508 | 'eca-chat-completion-item context |
1487 | 1509 | 'face face))) |
@@ -1671,6 +1693,8 @@ CHILD, NAME, DOCSTRING and BODY are passed down." |
1671 | 1693 | (setq-local eca-chat--history-index -1) |
1672 | 1694 |
|
1673 | 1695 | ;; Show diff blocks in markdown-mode with colors. |
| 1696 | + (when (fboundp 'yank-media-handler) |
| 1697 | + (yank-media-handler "image/.*" #'eca-chat-media--yank-image-handler)) |
1674 | 1698 | (setq-local markdown-fontify-code-blocks-natively t) |
1675 | 1699 | ;; Enable gfm-view-mode-like rendering without read-only |
1676 | 1700 | (setq-local markdown-hide-markup t) |
@@ -1839,8 +1863,8 @@ Calls CB with the resulting message." |
1839 | 1863 | (cond |
1840 | 1864 | ((eq action 'metadata) |
1841 | 1865 | '(metadata (category . eca-capf) |
1842 | | - (display-sort-function . identity) |
1843 | | - (cycle-sort-function . identity))) |
| 1866 | + (display-sort-function . identity) |
| 1867 | + (cycle-sort-function . identity))) |
1844 | 1868 | ((eq (car-safe action) 'boundaries) nil) |
1845 | 1869 | (t |
1846 | 1870 | (complete-with-action action (funcall candidates-fn) probe pred)))) |
@@ -2718,4 +2742,5 @@ Returns selected message plist or nil if no messages or cancelled." |
2718 | 2742 | (eca-info (format "Saved chat to '%s'" file))))) |
2719 | 2743 |
|
2720 | 2744 | (provide 'eca-chat) |
| 2745 | +(require 'eca-chat-media) |
2721 | 2746 | ;;; eca-chat.el ends here |
0 commit comments