diff --git a/tabnine-chat-curl.el b/tabnine-chat-curl.el index 94bd725..a714c04 100644 --- a/tabnine-chat-curl.el +++ b/tabnine-chat-curl.el @@ -113,7 +113,7 @@ the response is inserted into the current buffer after point." (set-process-sentinel process #'tabnine-chat-curl--sentinel))))) (defun tabnine-chat-abort (buffer) - "Stop any active tabnine-chat process associated with the current BUFFER." + "Stop any active `tabnine-chat' process associated with the current BUFFER." (interactive (list (current-buffer))) (unless tabnine-chat-use-curl (user-error "Cannot stop a `url-retrieve' request!")) @@ -199,7 +199,8 @@ See `tabnine-chat--url-get-response' for details." (defun tabnine-chat-curl--stream-filter (process output) "Filter for TabNine Chat curl process. PROCESS is the process under watch, OUTPUT is the output received." - (let* ((proc-info (alist-get process tabnine-chat-curl--process-alist))) + (let* ((proc-info (alist-get process tabnine-chat-curl--process-alist)) + (proc-token (plist-get proc-info :token))) (with-current-buffer (process-buffer process) ;; Insert output (save-excursion @@ -210,7 +211,7 @@ PROCESS is the process under watch, OUTPUT is the output received." ;; Find HTTP status (unless (plist-get proc-info :http-status) (pcase-let ((`(,_ ,http-status ,http-msg ,_ ) - (tabnine-chat--parse-http-response (process-buffer process) nil))) + (tabnine-chat--parse-http-response (process-buffer process) nil proc-token))) (when http-status (plist-put proc-info :http-status http-status) (plist-put proc-info :status (string-trim http-msg)))) diff --git a/tabnine-chat-transient.el b/tabnine-chat-transient.el new file mode 100644 index 0000000..ae37a27 --- /dev/null +++ b/tabnine-chat-transient.el @@ -0,0 +1,302 @@ +;;; tabnine-chat-transient.el --- TabNine Chat Transient -*- lexical-binding: t -*- + +;; Copyright (C) 2023 Aaron Ji + +;; Author: Aaron Ji;; +;; Keywords: convenience +;; + +;; Permission is hereby granted, free of charge, to any person obtaining a copy +;; of this software and associated documentation files (the "Software"), to deal +;; in the Software without restriction, including without limitation the rights +;; to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +;; copies of the Software, and to permit persons to whom the Software is +;; furnished to do so, subject to the following conditions: +;; +;; The above copyright notice and this permission notice shall be included in all +;; copies or substantial portions of the Software. +;; +;; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +;; IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +;; FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +;; AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +;; LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +;; OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +;; SOFTWARE. +;; +;;; Commentary: +;; + +;;; Code: + +;; +;; Dependencies +;; + +(eval-when-compile (require 'cl-lib)) +(require 'tabnine-chat) +(require 'tabnine-util) +(require 'transient) + +(declare-function ediff-regions-internal "ediff") +(declare-function ediff-make-cloned-buffer "ediff-utils") + +;; * Helper functions +(defun tabnine-chat--refactor-or-rewrite () + "Rewrite should be refactored into refactor. + +Or is it the other way around?" + (if (derived-mode-p 'prog-mode) + "Refactor" "Rewrite")) + +(defvar-local tabnine-chat--rewrite-message nil) +(defun tabnine-chat--rewrite-message () + "Set a generic refactor/rewrite message for the buffer." + (if (derived-mode-p 'prog-mode) + (format "You are a %s programmer. Refactor the selected code. Generate code only, no explanation." + (tabnine-util--language-id-buffer)) + (format "You are a prose editor. Rewrite the following text to be more professional."))) + + +;; BUG: The `:incompatible' spec doesn't work if there's a `:description' below it. +;;;###autoload (autoload 'tabnine-chat-menu "tabnine-chat-transient" nil t) +(transient-define-prefix tabnine-chat-menu () + "Change parameters of prompt to send TabNine Chat." + :incompatible '(("-m" "-n" "-k" "-e")) + [["Prompt:" + ("-r" "From minibuffer instead" "-r") + ("-i" "Replace/Delete prompt" "-i") + "Response to:" + ("-m" "Minibuffer instead" "-m") + ("-n" "New session" "-n" + :class transient-option + :prompt "Name for new session: " + :reader + (lambda (prompt _ history) + (read-string + prompt (generate-new-buffer-name tabnine-chat-default-session) history))) + ("-e" "Existing session" "-e" + :class transient-option + :prompt "Existing session: " + :reader + (lambda (prompt _ history) + (completing-read + prompt (mapcar #'buffer-name (buffer-list)) + (lambda (buf) (and (buffer-local-value 'tabnine-chat-mode (get-buffer buf)) + (not (equal (current-buffer) buf)))) + t nil history))) + ("-k" "Kill-ring" "-k")] + [:description tabnine-chat--refactor-or-rewrite + :if use-region-p + ("r" + ;;FIXME: Transient complains if I use `tabnine-chat--refactor-or-rewrite' here. It + ;;reads this function as a suffix instead of a function that returns the + ;;description. + (lambda () (if (derived-mode-p 'prog-mode) + "Refactor" "Rewrite")) + tabnine-chat-rewrite-menu)] + ["Send" (tabnine-chat--suffix-send)]]) + +(transient-define-infix tabnine-chat--infix-rewrite-prompt () + "Chat directive (system message) to use for rewriting or refactoring." + :description (lambda () (if (derived-mode-p 'prog-mode) + "Set directives for refactor" + "Set directives for rewrite")) + :format "%k %d" + :class 'transient-lisp-variable + :variable 'tabnine-chat--rewrite-message + :key "h" + :prompt "Set directive for rewrite: " + :reader (lambda (prompt _ history) + (read-string + prompt (tabnine-chat--rewrite-message) history))) + +(transient-define-suffix tabnine-chat--suffix-send (args) + "Send ARGS." + :key "RET" + :description "Send prompt" + (interactive (list (transient-args transient-current-command))) + (let ((stream tabnine-chat-stream) + (in-place (and (member "-i" args) t)) + (output-to-other-buffer-p) + (buffer) + (position) + (callback (and (member "-k" args) + (lambda (resp info) + (if (not resp) + (message "Chat response error: %s" (plist-get info :status)) + (kill-new resp) + (message "Chat response: copied to kill-ring."))))) + (tabnine-chat-buffer-name) + (prompt + (and (member "-r" args) + (read-string + "Ask Chat: ")))) + (cond + ((member "-m" args) + (setq stream nil) + (setq callback + (lambda (resp info) + (if resp + (message "Chat response: %s" resp) + (message "Chat response error: %s" (plist-get info :status)))))) + ((member "-k" args) + (setq stream nil)) + ((setq tabnine-chat-buffer-name + (cl-some (lambda (s) (and (string-prefix-p "-n" s) + (substring s 2))) + args)) + (setq buffer + (tabnine-chat tabnine-chat-buffer-name)) + (with-current-buffer buffer + (when prompt + (insert prompt)) + (tabnine-chat--update-header-line " Waiting..." 'warning) + (setq position (point))) + (setq output-to-other-buffer-p t)) + ((setq tabnine-chat-buffer-name + (cl-some (lambda (s) (and (string-prefix-p "-e" s) + (substring s 2))) + args)) + (setq buffer (get-buffer tabnine-chat-buffer-name)) + (setq output-to-other-buffer-p t) + (let ((reduced-prompt + (or prompt + (if (use-region-p) + (buffer-substring-no-properties (region-beginning) + (region-end)) + (buffer-substring-no-properties + (save-excursion + (text-property-search-backward + 'tabnine-chat 'response + (when (get-char-property (max (point-min) (1- (point))) + 'tabnine-chat) + t)) + (point)) + (point)))))) + (with-current-buffer buffer + (goto-char (point-max)) + (if (or buffer-read-only + (get-char-property (point) 'read-only)) + (setq prompt reduced-prompt) + (insert reduced-prompt)) + (setq position (point)) + (when tabnine-chat-mode + (tabnine-chat--update-header-line " Waiting..." 'warning)))))) + + (when in-place + (setq prompt (tabnine-chat--create-prompt (point))) + (let ((beg + (if (use-region-p) + (region-beginning) + (save-excursion + (text-property-search-backward + 'tabnine-chat 'response + (when (get-char-property (max (point-min) (1- (point))) + 'tabnine-chat) + t)) + (point)))) + (end (if (use-region-p) (region-end) (point)))) + (kill-region beg end))) + + (tabnine-chat--request + prompt + :buffer (or buffer (current-buffer)) + :position position + :in-place (and in-place (not output-to-other-buffer-p)) + :stream stream + :callback callback) + (when output-to-other-buffer-p + (message (concat "Prompt sent to buffer: " + (propertize tabnine-chat-buffer-name 'face 'help-key-binding))) + (display-buffer + buffer '((display-buffer-reuse-window + display-buffer-pop-up-window) + (reusable-frames . visible)))))) + +(transient-define-prefix tabnine-chat-rewrite-menu () + "Rewrite or refactor text region using TabNine." + [:description + (lambda () + (format "Directive: %s" + (truncate-string-to-width + (or tabnine-chat--rewrite-message (tabnine-chat--rewrite-message)) + (max (- (window-width) 14) 20) nil nil t))) + (tabnine-chat--infix-rewrite-prompt)] + [[:description "Diff Options" + ("-w" "Wordwise diff" "-w")] + [:description + (lambda () (if (derived-mode-p 'prog-mode) + "Refactor" "Rewrite")) + (tabnine-chat--suffix-rewrite) + (tabnine-chat--suffix-rewrite-and-replace) + (tabnine-chat--suffix-rewrite-and-ediff)] + ] + (interactive) + (unless tabnine-chat--rewrite-message + (setq tabnine-chat--rewrite-message (tabnine-chat--rewrite-message))) + (transient-setup 'tabnine-chat-rewrite-menu)) + + +(transient-define-suffix tabnine-chat--suffix-rewrite () + "Rewrite or refactor region contents." + :key "r" + :description #'tabnine-chat--refactor-or-rewrite + (interactive) + (let* ((prompt (or tabnine-chat--rewrite-message + (tabnine-chat--rewrite-message))) + (stream tabnine-chat-stream)) + (tabnine-chat--request prompt :stream stream))) + +(transient-define-suffix tabnine-chat--suffix-rewrite-and-replace () + "Refactor region contents and replace it." + :key "R" + :description (lambda () (concat (tabnine-chat--refactor-or-rewrite) " in place")) + (interactive) + (let* ((prompt (or tabnine-chat--rewrite-message (tabnine-chat--rewrite-message))) + (stream tabnine-chat-stream)) + (kill-region (region-beginning) (region-end)) + (message "Original text saved to kill-ring.") + (tabnine-chat--request prompt :stream stream :in-place t))) + +(transient-define-suffix tabnine-chat--suffix-rewrite-and-ediff (args) + "Refactoring or rewrite region contents and run Ediff." + :key "E" + :description (lambda () (concat (tabnine-chat--refactor-or-rewrite) " and Ediff")) + (interactive (list (transient-args transient-current-command))) + (let* ((prompt (or tabnine-chat--rewrite-message (tabnine-chat--rewrite-message)))) + (message "Waiting for response... ") + (tabnine-chat--request + prompt + :context (cons (region-beginning) (region-end)) + :callback + (lambda (response info) + (if (not response) + (message "TabNine Chat response error: %s" (plist-get info :status)) + (let* ((tabnine-chat-buffer (plist-get info :buffer)) + (tabnine-chat-bounds (plist-get info :context)) + (codeblocks (tabnine-util--markdown-codeblocks response)) + (code (or (and codeblocks (plist-get (elt codeblocks 0) :code)) response)) + (buffer-mode + (buffer-local-value 'major-mode tabnine-chat-buffer))) + (pcase-let ((`(,new-buf ,new-beg ,new-end) + (with-current-buffer (get-buffer-create "*tabnine-chat-rewrite-Region.B-*") + (erase-buffer) + (funcall buffer-mode) + (insert code) + (goto-char (point-min)) + (list (current-buffer) (point-min) (point-max))))) + (require 'ediff) + (apply + #'ediff-regions-internal + (get-buffer (ediff-make-cloned-buffer tabnine-chat-buffer "-Region.A-")) + (car tabnine-chat-bounds) (cdr tabnine-chat-bounds) + new-buf new-beg new-end + nil + (if (transient-arg-value "-w" args) + (list 'ediff-regions-wordwise 'word-wise nil) + (list 'ediff-regions-linewise nil nil)))))))))) + +(provide 'tabnine-chat-transient) + +;;; tabnine-chat-transient.el ends here diff --git a/tabnine-chat.el b/tabnine-chat.el index 573f40a..934cf63 100644 --- a/tabnine-chat.el +++ b/tabnine-chat.el @@ -68,7 +68,7 @@ last-editor-context-hash: last success context hash.") 'text-mode) "The default major mode for dedicated chat buffers. -If `markdown-mode' is available, it is used. Otherwise tabnine-chat +If `markdown-mode' is available, it is used. Otherwise `tabnine-chat' defaults to `text-mode'." :group 'tabnine :type 'symbol) @@ -254,14 +254,15 @@ use for TabNine Chat." (defun tabnine-chat--make-request (info) "TabNine api make request with INFO. Method can be explain-code, document-code, generate-test-for-code or fix-code." - (with-current-buffer (or (tabnine-chat--context-buffer) (current-buffer)) + (with-current-buffer (or (plist-get info :context-buffer) (tabnine-chat--context-buffer) (current-buffer)) (let* ((editor-context (tabnine-chat--editor-context)) (context (list ;; :retrievalContext :id (tabnine-util--random-uuid) :text (plist-get info :prompt) :by "user")) - (contexts (tabnine-chat--cached-contexts context editor-context))) + (contexts (tabnine-chat--cached-contexts + context editor-context))) (list :conversationId (tabnine-chat--conversion-id) :messageId (tabnine-util--random-uuid) @@ -373,26 +374,6 @@ the response is inserted into the current buffer after point." (kill-buffer))) nil t nil))) -(defun tabnine-chat--session-buffer() - "Create TabNine session buffer if not exists." - (let* ((name tabnine-chat-default-session) - (buffer (get-buffer name))) - (unless buffer - (with-current-buffer (get-buffer-create name) - (cond ;Set major mode - ((eq major-mode tabnine-chat-default-mode)) - ((eq tabnine-chat-default-mode 'text-mode) - (text-mode) - (visual-line-mode 1)) - (t (funcall tabnine-chat-default-mode))) - (unless tabnine-chat-mode (tabnine-chat-mode 1)) - (if (bobp) (insert (or ;; initial - (tabnine-chat-prompt-prefix-string)))) - (goto-char (point-max)) - (skip-chars-backward "\t\r\n") - (setq buffer (current-buffer)))) - buffer)) - (defun tabnine-chat--context-buffer() "Get TabNine Chat Context buffer." (let* ((buffer-list (buffer-list)) @@ -410,28 +391,101 @@ the response is inserted into the current buffer after point." (t (setq buffer last-prog-buffer))) buffer)) -(defun tabnine-chat--request(method &optional callback) - "Make tabnine chat request with METHOD and optional CALLBACK." - (let* ((buffer (tabnine-chat--session-buffer)) - (response-pt - (with-current-buffer buffer - (save-excursion - (goto-char (point-max)) - (point-marker)))) - (prompt-by-method (and method (alist-get method tabnine-chat-prompt-alist))) - (prompt (if prompt-by-method - prompt-by-method - (tabnine-chat--create-prompt response-pt))) - (info (list :prompt prompt +(cl-defun tabnine-chat--request + (&optional prompt &key callback + (buffer (current-buffer)) + (position (with-current-buffer buffer + (save-excursion + (goto-char (point-max)) + (point-marker)))) + context + context-buffer + (stream nil) + (in-place nil)) + "Request a response from TabNine Chat for PROMPT. + +If PROMPT is +- a string, it is used to create a full prompt suitable for + sending to TabNine Chat. +- nil, the current buffer's contents up to (point) are used. + Previous responses from TabNine Chat are identified as responses. + +Keyword arguments: + +CALLBACK, if supplied, is a function of two arguments, called +with the RESPONSE (a string) and INFO (a plist): + + (callback RESPONSE INFO) + +RESPONSE is nil if there was no response or an error. + +The INFO plist has (at least) the following keys: +:prompt - The full prompt that was sent with the request +:position - marker at the point the request was sent. +:buffer - The buffer current when the request was sent. +:status - Short string describing the result of the request + +Example of a callback that messages the user with the response +and info: + + (lambda (response info) + (if response + (let ((posn (marker-position (plist-get info :position))) + (buf (buffer-name (plist-get info :buffer)))) + (message \"Response for request from %S at %d: %s\" + buf posn response)) + (message \"gptel-request failed with message: %s\" + (plist-get info :status)))) + +Or, for just the response: + + (lambda (response _) + ;; Do something with response + (message (rot13-string response))) + +If CALLBACK is omitted, the response is inserted at the point the +request was sent. + +BUFFER is the buffer the request belongs to. If omitted the +current buffer is recorded. + +POSITION is a buffer position (integer or marker). If omitted, +the value of (point) or (region-end) is recorded, depending on +whether the region is active. + +CONTEXT is any additional data needed for the callback to run. It +is included in the INFO argument to the callback. + +CONTEXT-BUFFER is the editor context the request belongs to. + +The following keywords are mainly for internal use: + +IN-PLACE is a boolean used by the default callback when inserting +the response to determine if delimiters are needed between the +prompt and the response. + +STREAM is a boolean that determines if the response should be +streamed, as in `tabnine-chat-stream'. Do not set this if you are +specifying a custom CALLBACK!" + (let* ((tabnine-chat-stream stream) + (start-marker + (cond + ((null position) + (if (use-region-p) + (set-marker (make-marker) (region-end)) + (point-marker))) + ((markerp position) position) + ((integerp position) + (set-marker (make-marker) position buffer)))) + (info (list :prompt (if (stringp prompt) prompt + (tabnine-chat--create-prompt start-marker)) :buffer buffer - :position (with-current-buffer buffer - (tabnine-chat--update-header-line " Waiting..." 'warning) - (goto-char (point-max)) - (skip-chars-backward "\t\r\n") - (when (and prompt-by-method (stringp prompt-by-method)) - (insert prompt-by-method)) - (goto-char (point-max)) - (point-marker))))) + :position start-marker))) + (when context-buffer + (plist-put info :context-buffer context-buffer)) + (when context + (plist-put info :context context)) + (when in-place (plist-put info :in-place in-place)) (funcall (if tabnine-chat-use-curl #'tabnine-chat-curl-get-response #'tabnine-chat--url-get-response) @@ -439,6 +493,35 @@ the response is inserted into the current buffer after point." (unless (eq buffer (current-buffer)) (switch-to-buffer buffer)))) +(defun tabnine-chat--request-by-method (method) + "TabNine Chat request by METHOD." + (let* ((stream tabnine-chat-stream) + (output-to-other-buffer-p t) + (prompt (alist-get method tabnine-chat-prompt-alist)) + (tabnine-chat-buffer-name tabnine-chat-default-session) + (buffer (tabnine-chat tabnine-chat-buffer-name)) + (position)) + (with-current-buffer buffer + (unless (or buffer-read-only + (get-char-property (point) 'read-only)) + (insert prompt) + (setq position (point-max)))) + + (tabnine-chat--request + prompt + :buffer (or buffer (current-buffer)) + :position position + :in-place nil + :stream stream + :callback nil) + (when output-to-other-buffer-p + (message (concat "Prompt sent to buffer: " + (propertize tabnine-chat-buffer-name 'face 'help-key-binding))) + (display-buffer + buffer '((display-buffer-reuse-window + display-buffer-pop-up-window) + (reusable-frames . visible)))))) + (defun tabnine-chat--diagnostics-text() "Get diagnostic text with flycheck." (let ((errors (tabnine-util--get-list-errors))) @@ -491,19 +574,22 @@ Return body, http-status, http-msg and error in list." (text (tabnine-chat--results-to-text json-ss))) (if (s-present? text) (list text http-status http-msg) - (let* ((error-response (or (and json-ss (car json-ss)) - (tabnine-util--read-json trim-body))) - (error-msg (and error-response (plist-get error-response :message))) - (error-stack (and error-response (plist-get error-response :stack)))) - (if error-msg - (list nil http-status http-msg - (format "(%s-%s)" http-msg (string-trim (or error-stack "")))) - (list nil http-status http-msg (concat "Unknown error: " trim-body)))))) + (list nil http-status http-msg (concat "Unknown error: " trim-body)))) (list body http-status http-msg))) + ((equal http-status "500") + (let* ((trim-body (s-trim body)) + (error-response (tabnine-util--read-json trim-body))) + (cond + ((plist-get error-response :message) + (let* ((error-msg (plist-get error-response :message)) + (error-stack (plist-get error-response :stack))) + (list nil http-status http-msg + (format "(%s-%s)" http-msg (string-trim (or error-msg error-stack)))))) + (t (list nil http-status http-msg (concat "Unknown error: " trim-body)))))) ((equal http-status "404");; token expired (message "TabNine token is expired, set tabnine--access-token to nil.") (setq tabnine--access-token nil) - (list body http-status http-msg "TabNine token expired")) + (list nil http-status http-msg "TabNine token expired")) (t (unless (progn (goto-char (point-min)) (when (looking-at "^HTTP/[.0-9]+ +[0-9]+ Connection established") (string-trim @@ -511,7 +597,7 @@ Return body, http-status, http-msg and error in list." (line-beginning-position) (line-end-position))))) (message "Unknow error: %s, buffer text: %s" http-msg (buffer-string))) - (list body http-status http-msg "unknow error"))))))) + (list nil http-status http-msg (concat "Unknow error: " http-msg)))))))) (defun tabnine-chat--results-to-text(results) "TabNine RESULTS in sequence to text." @@ -615,6 +701,38 @@ hook." (format "Function %S returned an error" filter-func))))))) + +;;;###autoload +(defun tabnine-chat (name &optional initial) + "Switch to or start TabNine Chat session with NAME. + +With a prefix arg, query for a (new) session name. + +If region is active, use it as the INITIAL prompt. Returns the +buffer created or switched to." + (interactive (list (if current-prefix-arg + (read-string "Session name: " (generate-new-buffer-name tabnine-chat-default-session)) + tabnine-chat-default-session) + (and (use-region-p) + (buffer-substring (region-beginning) + (region-end))))) + (with-current-buffer (get-buffer-create name) + (cond ;Set major mode + ((eq major-mode tabnine-chat-default-mode)) + ((eq tabnine-chat-default-mode 'text-mode) + (text-mode) + (visual-line-mode 1)) + (t (funcall tabnine-chat-default-mode))) + (unless tabnine-chat-mode (tabnine-chat-mode 1)) + (if (bobp) (progn (message "insert init text: %s" (or initial (tabnine-chat-prompt-prefix-string))) (insert (or initial (tabnine-chat-prompt-prefix-string))))) + (goto-char (point-max)) + (skip-chars-backward "\t\r\n") + (when (called-interactively-p 'tabnine-chat) + (pop-to-buffer (current-buffer)) + (message "Send your query with %s!" + (substitute-command-keys "\\[tabnine-chat-send]"))) + (current-buffer))) + (defun tabnine-chat--convert-org (content buffer) "Transform CONTENT according to required major-mode. @@ -737,22 +855,22 @@ text stream." (defun tabnine-chat-explain-code() "Explain the selected code." (interactive) - (tabnine-chat--request 'explain-code)) + (tabnine-chat--request-by-method 'explain-code)) (defun tabnine-chat-generate-test-for-code() "Write test for the selected code." (interactive) - (tabnine-chat--request 'generate-test-for-code)) + (tabnine-chat--request-by-method 'generate-test-for-code)) (defun tabnine-chat-document-code() "Add documentation for the selected code." (interactive) - (tabnine-chat--request 'document-code)) + (tabnine-chat--request-by-method 'document-code)) (defun tabnine-chat-fix-code() "Find errors in the selected code and fix them." (interactive) - (tabnine-chat--request 'fix-code)) + (tabnine-chat--request-by-method 'fix-code)) (provide 'tabnine-chat) diff --git a/tabnine-util.el b/tabnine-util.el index f1bb1ee..3ee2fda 100644 --- a/tabnine-util.el +++ b/tabnine-util.el @@ -212,6 +212,32 @@ Example of a UUID: 1df63142-a513-c850-31a3-535fc3520c3d." message)) message))) flycheck-current-errors))) +(defun tabnine-util--markdown-codeblocks (text) + "Get codeblocks from markdown TEXT. + +Return codeblocks in squence." + (with-temp-buffer + (insert text) + (goto-char (point-min)) + + (let ((codeblocks)) + (while (and (re-search-forward "```\\(.+\\)?" nil t) (not (eobp))) + (let ((code) + (lang (match-string 1)) + (start) + (end)) + (forward-line) + (setq start (line-beginning-position)) + (when (re-search-forward "```" nil t) + (setq end (save-excursion (forward-line -1) (line-end-position))) + (setq code (list :code (decode-coding-string + (buffer-substring-no-properties start end) + 'utf-8) + :lang lang)) + (setq codeblocks (vconcat codeblocks (vector code)))) + (goto-char (save-excursion (forward-line) (line-beginning-position))))) + codeblocks))) + (provide 'tabnine-util) ;;; tabnine-util.el ends here diff --git a/tabnine.el b/tabnine.el index 7fa0e01..177a2b9 100644 --- a/tabnine.el +++ b/tabnine.el @@ -56,6 +56,7 @@ (require 'tabnine-core) (require 'tabnine-chat) +(require 'tabnine-chat-transient) (provide 'tabnine)