222 lines
7.1 KiB
EmacsLisp
222 lines
7.1 KiB
EmacsLisp
;;; -*- lexical-binding: t -*-
|
|
|
|
(require 'whisper)
|
|
|
|
;; Optional: install/load posframe if you want floating display.
|
|
;; `nemo-whisper-use-posframe' controls whether it is used.
|
|
(require 'posframe nil t)
|
|
|
|
(defgroup nemo-whisper nil
|
|
"Whisper recording utilities."
|
|
:group 'whisper)
|
|
|
|
(defcustom nemo-whisper-use-posframe t
|
|
"Display the Whisper buffer in a floating posframe when non-nil."
|
|
:type 'boolean
|
|
:group 'nemo-whisper)
|
|
|
|
(defcustom nemo-whisper-hide-posframe-after-transcription t
|
|
"Hide the posframe after copying the transcription to the kill ring."
|
|
:type 'boolean
|
|
:group 'nemo-whisper)
|
|
|
|
(defcustom nemo-whisper-posframe-width 80
|
|
"Width of the Whisper posframe."
|
|
:type 'integer
|
|
:group 'nemo-whisper)
|
|
|
|
(defcustom nemo-whisper-posframe-height 8
|
|
"Height of the Whisper posframe."
|
|
:type 'integer
|
|
:group 'nemo-whisper)
|
|
|
|
(defvar nemo-whisper--buffer nil
|
|
"Buffer currently used for Whisper recording.")
|
|
|
|
(defvar nemo-whisper--previous-buffer nil
|
|
"Buffer that was selected before Whisper capture started.")
|
|
|
|
(defvar nemo-whisper--recording-p nil
|
|
"Whether Whisper is currently recording.")
|
|
|
|
(defvar nemo-whisper--transcribing-p nil
|
|
"Whether Whisper is currently transcribing.")
|
|
|
|
(defvar nemo-whisper--posframe-active-p nil
|
|
"Whether the current capture buffer is displayed in a posframe.")
|
|
|
|
|
|
(defvar nemo-whisper--capture-map
|
|
(let ((map (make-sparse-keymap)))
|
|
;; Bind both forms because terminal and GUI Emacs can report Escape
|
|
;; differently.
|
|
(define-key map (kbd "ESC") #'nemo-whisper-escape)
|
|
(define-key map [escape] #'nemo-whisper-escape)
|
|
map)
|
|
"Keymap for a Whisper capture buffer.")
|
|
|
|
(define-minor-mode nemo-whisper-capture-mode
|
|
"Minor mode used by the Whisper capture buffer."
|
|
:init-value nil
|
|
:lighter " Whisper"
|
|
:keymap nemo-whisper--capture-map)
|
|
|
|
(defun nemo-whisper--display-buffer (buffer)
|
|
"Display BUFFER normally or in a posframe."
|
|
(if (and nemo-whisper-use-posframe
|
|
(fboundp 'posframe-show)
|
|
(display-graphic-p))
|
|
(progn
|
|
(setq nemo-whisper--posframe-active-p t)
|
|
|
|
(posframe-show
|
|
buffer
|
|
:position nil
|
|
:poshandler #'posframe-poshandler-frame-center
|
|
:width nemo-whisper-posframe-width
|
|
:height nemo-whisper-posframe-height
|
|
:border-width 2
|
|
:internal-border-width 8
|
|
:internal-border-color "gray50"
|
|
:accept-focus t)
|
|
|
|
;; Explicitly focus the child frame. Without this, keyboard input
|
|
;; may continue going to the previously selected Emacs frame.
|
|
(let ((window (get-buffer-window buffer t)))
|
|
(when (window-live-p window)
|
|
(select-frame-set-input-focus (window-frame window))
|
|
(select-window window))))
|
|
|
|
(setq nemo-whisper--posframe-active-p nil)
|
|
(pop-to-buffer buffer)))
|
|
|
|
(defun nemo-whisper--remove-recording-preview ()
|
|
"Remove the recording preview before Whisper inserts its result."
|
|
(let ((inhibit-read-only t))
|
|
(erase-buffer)
|
|
(goto-char (point-min))))
|
|
|
|
(defun nemo-whisper--stop-recording ()
|
|
"Stop recording and start Whisper transcription."
|
|
(when (and nemo-whisper--buffer
|
|
(buffer-live-p nemo-whisper--buffer)
|
|
nemo-whisper--recording-p)
|
|
(setq nemo-whisper--recording-p nil
|
|
nemo-whisper--transcribing-p t)
|
|
|
|
(with-current-buffer nemo-whisper--buffer
|
|
;; Remove “Recording...” so the transcription is the only text
|
|
;; inserted into the buffer.
|
|
(nemo-whisper--remove-recording-preview)
|
|
(whisper-run))
|
|
|
|
(message "Recording stopped; transcribing...")))
|
|
|
|
(defun nemo-whisper--copy-result-to-kill-ring ()
|
|
"Copy the completed transcription to the kill ring."
|
|
(let ((text (string-trim
|
|
(buffer-substring-no-properties
|
|
(point-min)
|
|
(point-max)))))
|
|
(unless (string-empty-p text)
|
|
(kill-new text)
|
|
(message "Whisper transcription copied to the kill ring"))
|
|
|
|
(setq nemo-whisper--transcribing-p nil)
|
|
|
|
;; Select the completed transcription. This makes a subsequent ESC
|
|
;; return to the previous buffer when the capture buffer remains visible.
|
|
(goto-char (point-min))
|
|
(push-mark (point-max) t t)
|
|
|
|
(when (and nemo-whisper--posframe-active-p
|
|
nemo-whisper-hide-posframe-after-transcription
|
|
(fboundp 'posframe-hide))
|
|
(posframe-hide (buffer-name)))))
|
|
|
|
(defun nemo-whisper--return-to-previous-buffer ()
|
|
"Return to the buffer active before Whisper capture started."
|
|
(when (and nemo-whisper--posframe-active-p
|
|
(fboundp 'posframe-hide)
|
|
nemo-whisper--buffer)
|
|
(posframe-hide (buffer-name nemo-whisper--buffer)))
|
|
|
|
(let ((previous-buffer nemo-whisper--previous-buffer))
|
|
(setq nemo-whisper--buffer nil
|
|
nemo-whisper--previous-buffer nil
|
|
nemo-whisper--recording-p nil
|
|
nemo-whisper--transcribing-p nil
|
|
nemo-whisper--posframe-active-p nil)
|
|
|
|
(when (buffer-live-p previous-buffer)
|
|
(pop-to-buffer previous-buffer))))
|
|
|
|
(defun nemo-whisper-escape ()
|
|
"Stop recording, or return to the previous buffer after transcription."
|
|
(interactive)
|
|
(cond
|
|
(nemo-whisper--recording-p
|
|
(nemo-whisper--stop-recording))
|
|
|
|
(nemo-whisper--transcribing-p
|
|
(message "Whisper is still transcribing; please wait"))
|
|
|
|
(t
|
|
(nemo-whisper--return-to-previous-buffer))))
|
|
|
|
(defun nemo-whisper-record-to-kill-ring ()
|
|
"Record speech and copy the transcription to the kill ring.
|
|
|
|
Invoke this command to create a capture buffer and start recording.
|
|
Press ESC to stop recording. After transcription completes, press ESC
|
|
again to return to the previously selected buffer."
|
|
(interactive)
|
|
(cond
|
|
;; If called while recording, treat the command as a stop command.
|
|
(nemo-whisper--recording-p
|
|
(nemo-whisper--stop-recording))
|
|
|
|
;; Do not interrupt an active transcription.
|
|
(nemo-whisper--transcribing-p
|
|
(message "Whisper is still transcribing; please wait"))
|
|
|
|
;; Start a new capture.
|
|
(t
|
|
(setq nemo-whisper--previous-buffer
|
|
(window-buffer (selected-window))
|
|
nemo-whisper--buffer
|
|
(generate-new-buffer "*Whisper capture*")
|
|
nemo-whisper--recording-p t
|
|
nemo-whisper--transcribing-p nil)
|
|
|
|
(with-current-buffer nemo-whisper--buffer
|
|
(text-mode)
|
|
(nemo-whisper-capture-mode 1)
|
|
|
|
;; Insert the temporary preview text.
|
|
(insert (propertize "Recording...\n"
|
|
'face 'font-lock-warning-face))
|
|
|
|
;; Ask Whisper to insert its result into this buffer.
|
|
(setq-local whisper-insert-text-at-point t)
|
|
|
|
;; Prevent Whisper from opening another transcription buffer.
|
|
(setq-local whisper-display-transcription-buffer nil)
|
|
|
|
;; This hook is buffer-local, so it will not run in Whisper's
|
|
;; separate transcription buffer.
|
|
(add-hook 'whisper-after-insert-hook
|
|
#'nemo-whisper--copy-result-to-kill-ring
|
|
nil
|
|
t))
|
|
|
|
(nemo-whisper--display-buffer nemo-whisper--buffer)
|
|
|
|
;; The first invocation starts recording.
|
|
(with-current-buffer nemo-whisper--buffer
|
|
(whisper-run))
|
|
|
|
(message "Recording started; press ESC when finished speaking"))))
|
|
|
|
(provide 'nemo-whisper)
|