lots innit
This commit is contained in:
parent
f96e613ca5
commit
dd8f97d60d
115
init.org
115
init.org
|
|
@ -312,7 +312,11 @@ Helper function for switching themes.
|
|||
** emacs-related
|
||||
#+begin_src emacs-lisp
|
||||
(setq help-window-select t
|
||||
help-window-keep-selected t)
|
||||
help-window-keep-selected t
|
||||
scroll-conservatively 20
|
||||
scroll-margin 3
|
||||
scroll-preserve-screen-position t
|
||||
mouse-wheel-progressive-speed nil)
|
||||
#+end_src
|
||||
** Fonts
|
||||
Font size [[https://www.gnu.org/software/emacs/manual/html_node/elisp/Face-Attributes.html][(height)]] in emacs works in $\frac{1}{10}$ths of points, so ~110~ is the same as ~11~ points.
|
||||
|
|
@ -483,7 +487,14 @@ Honestly not very happy with this at the moment, but it's kind of hacked togethe
|
|||
esup-depth 1))
|
||||
#+end_src
|
||||
|
||||
** =xref=
|
||||
#+begin_src emacs-lisp
|
||||
(use-package xref
|
||||
:straight (:type built-in)
|
||||
)
|
||||
#+end_src
|
||||
** =general.el =
|
||||
|
||||
TODO: figure out how to get which-key to correctly display the ‘m‘ label
|
||||
|
||||
#+begin_src emacs-lisp
|
||||
|
|
@ -498,9 +509,10 @@ TODO: figure out how to get which-key to correctly display the ‘m‘ label
|
|||
:straight t
|
||||
:init
|
||||
(define-prefix-command 'nemo/xref-command 'nemo/xref-map "xref")
|
||||
(define-prefix-command 'nemo/buffers-command 'nemo/buffers-map "Buffers..")
|
||||
(define-prefix-command 'nemo/windows-command 'nemo/windows-map "Windows..")
|
||||
(define-prefix-command 'nemo/files-command 'nemo/files-map "Files..")
|
||||
(define-prefix-command 'nemo/buffers-command 'nemo/buffers-map "Buffers")
|
||||
(define-prefix-command 'nemo/windows-command 'nemo/windows-map "Windows")
|
||||
(define-prefix-command 'nemo/files-command 'nemo/files-map "Files")
|
||||
(define-prefix-command 'nemo/custom-command 'nemo/custom-map "Custom")
|
||||
:config
|
||||
(general-define-key
|
||||
:states '(emacs normal)
|
||||
|
|
@ -530,7 +542,8 @@ TODO: figure out how to get which-key to correctly display the ‘m‘ label
|
|||
"g" '("xref" . nemo/xref-command)
|
||||
"b" '("Buffers" . nemo/buffers-command)
|
||||
"w" '("Windows" . nemo/windows-command)
|
||||
"f" '("Files" . nemo/files-command))
|
||||
"f" '("Files" . nemo/files-command)
|
||||
"c" '("Custom" . nemo/custom-command))
|
||||
|
||||
(general-def
|
||||
:keymaps 'nemo/xref-map
|
||||
|
|
@ -546,7 +559,7 @@ TODO: figure out how to get which-key to correctly display the ‘m‘ label
|
|||
"n" '("Switch to next buffer" . switch-to-next-buffer)
|
||||
"b" '("Consult buffer" . consult-buffer)
|
||||
"d" '("Kill current buffer" . kill-current-buffer)
|
||||
"s" '("Scratch buffer" . scratch-buffer))
|
||||
"s" '("Scratch buffer" . scratch))
|
||||
|
||||
(general-def
|
||||
:keymaps 'nemo/windows-map
|
||||
|
|
@ -561,6 +574,10 @@ TODO: figure out how to get which-key to correctly display the ‘m‘ label
|
|||
"r" '("Ripgrep" . consult-ripgrep)
|
||||
"y" '("Yank Current Filename" . nemo/buffer-file-name-to-clipboard)
|
||||
"R" '("Ripgrep in dir" . nemo/consult-ripgrep-dir))
|
||||
|
||||
(general-def
|
||||
:keymaps 'nemo/custom-map
|
||||
"d" '("Whisper record" . whisper-run))
|
||||
)
|
||||
#+end_src
|
||||
** =dired=
|
||||
|
|
@ -647,6 +664,7 @@ Todo: figure out how best to use =image-dired=.
|
|||
:keymaps 'nemo/dired-map
|
||||
"p" '("Preview Files" . dired-preview-mode)
|
||||
"+" '("Create File/Directory" . nemo/dired-create-file-or-directory)
|
||||
"f" '("Yank File Name" . dired-copy-filename-as-kill)
|
||||
"#" '("Toggle Dotfiles" . dired-omit-mode))
|
||||
|
||||
(setopt dired-compress-directory-default-suffix ".tar.zst"
|
||||
|
|
@ -909,7 +927,7 @@ Also use =undo-fu=, which evil can use.
|
|||
"p" '(:keymap project-prefix-map :wk "Projectile")
|
||||
)
|
||||
:config
|
||||
(setq projectile-project-search-path '("~/code/"))
|
||||
(setq projectile-project-search-path '("/var/code/"))
|
||||
(projectile-mode))
|
||||
#+end_src
|
||||
|
||||
|
|
@ -934,7 +952,36 @@ Also use =undo-fu=, which evil can use.
|
|||
:straight t)
|
||||
#+end_src
|
||||
|
||||
|
||||
* Stuff
|
||||
|
||||
** =scratch.el=
|
||||
|
||||
#+begin_src emacs-lisp
|
||||
(use-package scratch
|
||||
:straight t)
|
||||
#+end_src
|
||||
|
||||
** Dictation
|
||||
I want to test out dictation using whisper.el, which uses OpenAI’s whisper model locally.
|
||||
|
||||
#+begin_src emacs-lisp
|
||||
(use-package whisper
|
||||
:straight (:type git :host github :repo "natrys/whisper.el")
|
||||
:bind
|
||||
(("<f9>" . whisper-run))
|
||||
:config
|
||||
(setopt whisper-model "small.en"
|
||||
whisper-language "en"
|
||||
;; whisper--mode-line-recording-indicator "⏺"
|
||||
whisper-isntall-directory (concat user-emacs-directory "whisper")
|
||||
whisper-translate nil
|
||||
whisper-return-cursor 'end
|
||||
whisper-use-threads (/ (num-processors) 2)
|
||||
)
|
||||
)
|
||||
#+end_src
|
||||
|
||||
** Mini-buffer escape
|
||||
#+begin_src emacs-lisp :tangle no
|
||||
(setq minibuffer-prompt-properties
|
||||
|
|
@ -962,7 +1009,7 @@ Also use =undo-fu=, which evil can use.
|
|||
:defer t
|
||||
;; :bind (:map custom-bindings-map ("C-c o" . olivetti-mode))
|
||||
:hook (org-mode . olivetti-mode)
|
||||
:hook (olivetti-mode-on . (lambda () (olivetti-set-width 88)))
|
||||
:hook (olivetti-mode-on . (lambda () (olivetti-set-width 120)))
|
||||
:config
|
||||
(setq olivetti-style t))
|
||||
#+end_src
|
||||
|
|
@ -1034,6 +1081,15 @@ PDF tools for latex previewing:
|
|||
)
|
||||
#+end_src
|
||||
|
||||
** Markdown
|
||||
#+begin_src emacs-lisp
|
||||
(use-package markdown-mode
|
||||
:ensure t
|
||||
:mode ("\\.md\\'" . markdown-mode)
|
||||
:config
|
||||
(setq markdown-command "multimarkdown"))
|
||||
#+end_src
|
||||
|
||||
** Org
|
||||
Taken from [[https://sophiebos.io/posts/prettifying-emacs-org-mode/][ here]].
|
||||
#+begin_src emacs-lisp
|
||||
|
|
@ -1096,7 +1152,7 @@ Inline images as well.
|
|||
|
||||
In case LaTeX previews are too small, use this to increase them.
|
||||
#+begin_src emacs-lisp
|
||||
(plist-put org-format-latex-options :scale 1.35)
|
||||
(plist-put org-format-latex-options :scale 0.5)
|
||||
#+end_src
|
||||
|
||||
=pretty-entities= allows for latex symbols to be embedded into org-mode.
|
||||
|
|
@ -1889,6 +1945,17 @@ build formatting on save functionality:
|
|||
** Eglot
|
||||
Use =eglot= as an alternative to =lsp-mode=:
|
||||
#+begin_src emacs-lisp
|
||||
(defun nemo/kotlin-lsp-socket (interactively)
|
||||
"Start kotlin-lsp in socket mode."
|
||||
(let ((port 9998)
|
||||
(host "127.0.0.1"))
|
||||
(unless (ignore-errors (delete-process (open-network-stream "test" nil host port)))
|
||||
(message "Starting kotlin-lsp in socket mode on %s:%d" host port)
|
||||
(start-process "kotlin-lsp" "*kotlin-lsp*"
|
||||
"kotlin-lsp" "--socket" "127.0.0.1:9998")
|
||||
(sleep-for 5))
|
||||
(list host port)))
|
||||
|
||||
(use-package eglot
|
||||
:straight t
|
||||
:hook ((rust-mode nix-mode prog-mode) . eglot-ensure)
|
||||
|
|
@ -1903,10 +1970,10 @@ Use =eglot= as an alternative to =lsp-mode=:
|
|||
'eglot-server-programs
|
||||
'(just-mode
|
||||
"just-lsp"))
|
||||
(add-to-list
|
||||
'eglot-server-programs
|
||||
'((kotlin-mode kotlin-ts-mode) .
|
||||
("kotlin-lsp" "--stdio")))
|
||||
(add-to-list
|
||||
'eglot-server-programs
|
||||
'((kotlin-mode kotlin-ts-mode) .
|
||||
nemo/kotlin-lsp-socket))
|
||||
(setq-default eglot-workspace-configuration
|
||||
'(:rust-analyzer
|
||||
(:cargo (:buildScripts (:enable t)
|
||||
|
|
@ -1914,21 +1981,31 @@ Use =eglot= as an alternative to =lsp-mode=:
|
|||
:features "all"
|
||||
:extraEnv (:RUSTFLAGS "-Clinker=clang -Clink-arg=-fuse-ld=mold"))
|
||||
:check (:command "clippy")
|
||||
:completion (:autoimport (:enable t))
|
||||
:completion
|
||||
(:autoimport (:enable t)
|
||||
:postfix (:enable t)
|
||||
:snippets
|
||||
(:custom
|
||||
(:mem_replace
|
||||
(:postfix "replace"
|
||||
:body "mem::replace(&mut ${receiver}, ${0:new_value})"
|
||||
:description "Replace the value and return the old value"
|
||||
:requires "core::mem"
|
||||
:scope "expr"))))
|
||||
:inlayHints (
|
||||
;; :maxLength 20
|
||||
:bindingModeHints (:enable t)
|
||||
:closureReturnTypeHints (:enable t))
|
||||
:procMacro (:enable t)))
|
||||
)
|
||||
:procMacro (:enable t))))
|
||||
(setq eglot-code-action-indications '(margin mode-line)
|
||||
eglot-extend-to-xref t
|
||||
eglot-connect-timeout 60
|
||||
eglot-request-timeout 60
|
||||
eglot-extend-to-xref t
|
||||
eglot-sync-connect 0
|
||||
eglot-sync-connect 1
|
||||
eglot-report-progress t
|
||||
eglot-autoshutdown t
|
||||
eglot-events-buffer-size 0
|
||||
;; eglot-events-buffer-config '(:size 5000 :format short)
|
||||
eglot-max-file-watches 5000
|
||||
completion-category-overrides '((eglot (styles orderless))
|
||||
|
|
@ -1955,6 +2032,8 @@ Use =eglot= as an alternative to =lsp-mode=:
|
|||
"= =" '("Format Buffer" . eglot-format-buffer)
|
||||
"= r" '("Format Region" . eglot-format-region)
|
||||
)
|
||||
(general-def '(emacs normal) :keymaps 'eglot-mode-map
|
||||
"C-<down-mouse-1>" 'xref-find-definitions-at-mouse)
|
||||
(leader-major-def
|
||||
:keymaps 'eglot-mode-map
|
||||
"l" '("Eglot" . nemo/eglot-map)
|
||||
|
|
@ -2536,8 +2615,6 @@ delimiters < and >'s."
|
|||
|
||||
(use-package kotlin-ts-mode
|
||||
:straight t
|
||||
:after eglot
|
||||
:after (inheritenv envrc)
|
||||
:mode ("\\.kt\\'" . kotlin-ts-mode)
|
||||
:config
|
||||
(if (fboundp 'eglot-uri-to-path)
|
||||
|
|
|
|||
25
local-lisp/dired-hide-dotfiles.el
Normal file
25
local-lisp/dired-hide-dotfiles.el
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
;;; dired-hide-dotfiles.el --- Hide dotfiles in dired buffers -*- lexical-binding: t; -*-
|
||||
|
||||
(require 'dired)
|
||||
|
||||
(defgroup dired-hide-dotfiles nil
|
||||
:group 'dired)
|
||||
|
||||
(define-minor-mode dired-hide-dotfiles-mode
|
||||
:init-value nil
|
||||
:group 'dired
|
||||
(if dired-hide-dotfiles-mode
|
||||
(progn
|
||||
(add-hook 'dired-after-readin-hook #'dired-hide-dotfiles)
|
||||
(dired-hide-dotfiles))
|
||||
(remove-hook 'dired-after-readin-hook #'dired-hide-dotfiles)
|
||||
(revert-buffer))
|
||||
|
||||
(defun dired-hide-dotfiles ()
|
||||
"Hide dotfiles in dired buffer."
|
||||
(let ((inhibit-message t))
|
||||
(dired-mark-files-regexp "^\\.|^.*/\\.")
|
||||
(dired-do-kill-lines nil "Hid %d dotfiles."))))
|
||||
|
||||
|
||||
(provide 'dired-hide-dotfiles)
|
||||
221
local-lisp/nemo-whisper.el
Normal file
221
local-lisp/nemo-whisper.el
Normal file
|
|
@ -0,0 +1,221 @@
|
|||
;;; -*- 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)
|
||||
272
local-lisp/whisper-posframe.el
Normal file
272
local-lisp/whisper-posframe.el
Normal file
|
|
@ -0,0 +1,272 @@
|
|||
;;; whisper-posframe.el --- Record speech into a posframe -*- lexical-binding: t; -*-
|
||||
|
||||
(require 'whisper)
|
||||
(require 'posframe)
|
||||
|
||||
(defgroup whisper-posframe nil
|
||||
"Record speech into a temporary posframe buffer."
|
||||
:group 'whisper)
|
||||
|
||||
(defcustom whisper-posframe-buffer-name "*whisper-capture*"
|
||||
"Name of the temporary Whisper capture buffer."
|
||||
:type 'string
|
||||
:group 'whisper-posframe)
|
||||
|
||||
(defcustom whisper-posframe-width 80
|
||||
"Width of the Whisper posframe in columns."
|
||||
:type 'integer
|
||||
:group 'whisper-posframe)
|
||||
|
||||
(defcustom whisper-posframe-height 12
|
||||
"Height of the Whisper posframe in lines."
|
||||
:type 'integer
|
||||
:group 'whisper-posframe)
|
||||
|
||||
(defvar whisper-posframe--parent-frame nil)
|
||||
|
||||
(defvar whisper-posframe-mode-map
|
||||
(let ((map (make-sparse-keymap)))
|
||||
;; Finish recording and begin transcription.
|
||||
(define-key map (kbd "C-c C-c") #'whisper-posframe-finish)
|
||||
|
||||
;; Cancel recording/transcription without copying anything.
|
||||
(define-key map (kbd "C-c C-k") #'whisper-posframe-cancel)
|
||||
|
||||
;; Make C-g and Escape dismiss the capture.
|
||||
(define-key map (kbd "C-g") #'whisper-posframe-cancel)
|
||||
(define-key map (kbd "<escape>") #'whisper-posframe-cancel)
|
||||
|
||||
map)
|
||||
"Keymap for `whisper-posframe-mode'.")
|
||||
|
||||
(defvar-local whisper-posframe--status-timer nil)
|
||||
|
||||
(defun whisper-posframe--recording-p ()
|
||||
"Return non-nil when Whisper is recording."
|
||||
(process-live-p whisper--recording-process))
|
||||
|
||||
(defun whisper-posframe--transcribing-p ()
|
||||
"Return non-nil when Whisper is transcribing."
|
||||
(process-live-p whisper--transcribing-process))
|
||||
|
||||
(defvar whisper-posframe--capture-buffer nil)
|
||||
|
||||
(defun whisper-posframe--set-status (status)
|
||||
"Display STATUS at the beginning of the capture buffer."
|
||||
(when (derived-mode-p 'whisper-posframe-mode)
|
||||
(let ((inhibit-read-only t))
|
||||
(save-excursion
|
||||
(goto-char (point-min))
|
||||
|
||||
;; Remove the previous status line.
|
||||
(when (looking-at "^\\[Whisper:.*\\]\n")
|
||||
(delete-region (point)
|
||||
(line-beginning-position 2)))
|
||||
|
||||
;; Insert the new status line as actual displayed buffer text.
|
||||
(insert
|
||||
(propertize
|
||||
(format "[Whisper: %s]\n" status)
|
||||
'face 'font-lock-keyword-face
|
||||
'read-only t
|
||||
'rear-nonsticky '(read-only face)))))))
|
||||
|
||||
(defun whisper-posframe--update-status ()
|
||||
"Update the status line according to Whisper's current process."
|
||||
(when (buffer-live-p (current-buffer))
|
||||
(cond
|
||||
((whisper-posframe--recording-p)
|
||||
(whisper-posframe--set-status
|
||||
"recording — C-c C-c to finish, C-g or Esc to cancel"))
|
||||
|
||||
((whisper-posframe--transcribing-p)
|
||||
(whisper-posframe--set-status
|
||||
"transcribing…"))
|
||||
|
||||
(t
|
||||
;; This can occur briefly while whisper.el is compiling, checking
|
||||
;; its model, or transitioning between recording and transcription.
|
||||
(whisper-posframe--set-status
|
||||
"preparing Whisper…")))))
|
||||
|
||||
(defun whisper-posframe--stop-status-timer ()
|
||||
"Stop the status update timer."
|
||||
(when (timerp whisper-posframe--status-timer)
|
||||
(cancel-timer whisper-posframe--status-timer)
|
||||
(setq whisper-posframe--status-timer nil)))
|
||||
|
||||
(defun whisper-posframe--copy-and-close ()
|
||||
"Copy only the transcription and close the capture posframe."
|
||||
(let ((buffer whisper-posframe--capture-buffer))
|
||||
(when (buffer-live-p buffer)
|
||||
(with-current-buffer buffer
|
||||
(whisper-posframe--stop-status-timer)
|
||||
|
||||
(let ((inhibit-read-only t)
|
||||
text)
|
||||
;; Remove the status line from the actual capture buffer.
|
||||
(save-excursion
|
||||
(goto-char (point-min))
|
||||
(when (looking-at "^\\[Whisper:.*\\]\n")
|
||||
(delete-region (point)
|
||||
(line-beginning-position 2))))
|
||||
|
||||
;; Extract only the remaining transcription.
|
||||
(setq text
|
||||
(string-trim
|
||||
(buffer-substring-no-properties
|
||||
(point-min)
|
||||
(point-max))))
|
||||
|
||||
(when (> (length text) 0)
|
||||
(kill-new text)
|
||||
(message "Whisper transcription copied to the kill ring")))))
|
||||
|
||||
;; Clear Whisper's asynchronous insertion target.
|
||||
(set-marker whisper--marker nil)
|
||||
(setq whisper--point-buffer nil)
|
||||
|
||||
;; Delete the posframe and the known capture buffer.
|
||||
(ignore-errors
|
||||
(posframe-delete-frame whisper-posframe-buffer-name))
|
||||
|
||||
(when (buffer-live-p buffer)
|
||||
(kill-buffer buffer))
|
||||
|
||||
(setq whisper-posframe--capture-buffer nil)))
|
||||
|
||||
|
||||
(define-derived-mode whisper-posframe-mode text-mode "Whisper"
|
||||
"Major mode for a temporary Whisper capture buffer."
|
||||
(use-local-map whisper-posframe-mode-map)
|
||||
|
||||
(setq-local whisper-insert-text-at-point t)
|
||||
(setq-local whisper-display-transcription-buffer nil)
|
||||
|
||||
(whisper-posframe--set-status "preparing Whisper…")
|
||||
|
||||
;; This hook runs after whisper.el has inserted the final transcription.
|
||||
(add-hook 'whisper-after-insert-hook
|
||||
#'whisper-posframe--copy-and-close
|
||||
nil
|
||||
t))
|
||||
|
||||
(defun whisper-posframe--delete ()
|
||||
"Delete the Whisper posframe, if it exists."
|
||||
(when (buffer-live-p (get-buffer whisper-posframe-buffer-name))
|
||||
(ignore-errors
|
||||
(posframe-delete-frame whisper-posframe-buffer-name))))
|
||||
|
||||
(defun whisper-posframe-finish ()
|
||||
"Stop recording and begin Whisper transcription."
|
||||
(interactive)
|
||||
(cond
|
||||
((whisper-posframe--recording-p)
|
||||
(whisper-posframe--set-status "transcribing…")
|
||||
;; whisper-run toggles recording off and starts transcription.
|
||||
(whisper-run))
|
||||
|
||||
((whisper-posframe--transcribing-p)
|
||||
(message "Whisper is already transcribing"))
|
||||
|
||||
(t
|
||||
(message "Whisper is not currently recording"))))
|
||||
|
||||
(defun whisper-posframe-cancel ()
|
||||
"Cancel the active Whisper operation and close the posframe."
|
||||
(interactive)
|
||||
(when (or (whisper-recording-p)
|
||||
(whisper-transcribing-p))
|
||||
;; `whisper-run' interrupts recording or cancels transcription,
|
||||
;; depending on Whisper's current state.
|
||||
(whisper-run))
|
||||
|
||||
(whisper-posframe--stop-status-timer)
|
||||
(set-marker whisper--marker nil)
|
||||
(setq whisper--point-buffer nil)
|
||||
|
||||
(whisper-posframe--delete)
|
||||
|
||||
(let ((buffer (current-buffer)))
|
||||
(when (buffer-live-p buffer)
|
||||
(kill-buffer buffer)))
|
||||
|
||||
(message "Whisper recording canceled"))
|
||||
|
||||
|
||||
(defun whisper-posframe--focus (buffer frame)
|
||||
"Focus FRAME and select its window displaying BUFFER."
|
||||
(when (frame-live-p frame)
|
||||
(select-frame-set-input-focus frame)
|
||||
(when-let ((window (get-buffer-window buffer frame)))
|
||||
(select-window window))
|
||||
(with-current-buffer buffer
|
||||
(goto-char (point-max)))))
|
||||
|
||||
(defun whisper-posframe--start-whisper (buffer)
|
||||
"Start Whisper with BUFFER as its explicit insertion target."
|
||||
(with-current-buffer buffer
|
||||
(setq-local whisper-insert-text-at-point t)
|
||||
|
||||
;; These are the variables whisper.el uses asynchronously when
|
||||
;; inserting the completed transcription.
|
||||
(setq whisper--point-buffer buffer)
|
||||
(set-marker whisper--marker (point) buffer)
|
||||
|
||||
(whisper-run)))
|
||||
|
||||
;;;###autoload
|
||||
(defun whisper-posframe-record ()
|
||||
"Open a posframe and start recording with Whisper."
|
||||
(interactive)
|
||||
(require 'whisper)
|
||||
(require 'posframe)
|
||||
|
||||
(when (or (whisper-recording-p)
|
||||
(whisper-transcribing-p))
|
||||
(user-error "Whisper is already busy"))
|
||||
|
||||
(let ((buffer (get-buffer-create whisper-posframe-buffer-name)))
|
||||
(setq whisper-posframe--capture-buffer buffer)
|
||||
(setq whisper-posframe--parent-frame (selected-frame))
|
||||
|
||||
(with-current-buffer buffer
|
||||
(erase-buffer)
|
||||
(whisper-posframe-mode)
|
||||
|
||||
;; Refresh the visible status every 200 ms.
|
||||
(setq-local whisper-posframe--status-timer
|
||||
(run-at-time
|
||||
0
|
||||
0.2
|
||||
(lambda (buffer)
|
||||
(when (buffer-live-p buffer)
|
||||
(with-current-buffer buffer
|
||||
(whisper-posframe--update-status))))
|
||||
buffer)))
|
||||
|
||||
;; `:accept-focus t' ensures that C-g, Escape, and the other local
|
||||
;; keybindings are received by the posframe.
|
||||
(let ((posframe
|
||||
(posframe-show
|
||||
buffer
|
||||
:parent-frame (selected-frame)
|
||||
:position (point)
|
||||
:poshandler #'posframe-poshandler-frame-center
|
||||
:width whisper-posframe-width
|
||||
:height whisper-posframe-height
|
||||
:internal-border-color "gray50"
|
||||
:border-width 2
|
||||
:accept-focus t)))
|
||||
|
||||
;; Focus after posframe-show has created and displayed the child frame.
|
||||
(run-at-time
|
||||
0 nil
|
||||
(lambda ()
|
||||
(whisper-posframe--focus buffer posframe))))
|
||||
|
||||
;; Start recording. `whisper-run' records into the current buffer.
|
||||
(whisper-posframe--start-whisper buffer)))
|
||||
|
||||
(provide 'whisper-posframe)
|
||||
;;; whisper-posframe.el ends here
|
||||
Loading…
Reference in a new issue