среда, 30 сентября 2009 г.

Говорящий SXEmacs

Баловался тут давеча с разными способами более тесной интеграции SXEmacs с Mac OS. Сделал (а точнее стащил из GNU Emacs) интересную функцию, с помощью которой можно запускать различные сценарии AppleScript:

(defun do-applescript (&rest script-lines)
  (let ((script (mapconcat 'identity script-lines "\n"))
        start cmd return)
    (while (string-match "\n" script)
      (setq script (replace-match "\r" t t script)))
    (while (string-match "\"" script start)
      (setq start (+ 2 (match-beginning 0))
            script (replace-match "\\\"" t t script)))
    (setq cmd (concat "osascript -e \"\"\"" script "\"\"\""))
    (setq return (shell-command-to-string cmd))
    (concat "\"" return "\"")))

Применения этой процедуры очевидны. Например можно заставить Growl отображать сообщения SXEmacs:

(defun growl (title message)
  (do-applescript
   "tell application \"GrowlHelperApp\""
   (format "notify with name \"SXEmacs Notification\" title %S description %S application name \"SXEmacs\" image from location \"file:///opt/local/share/sxemacs-22.1.10/etc/sxemacs-icon.png\"" title message)
   "end tell"))

Но, что более интересно, можно научить SXEmacs разговаривать, используя VoiceOver!

(defvar lg-say-voices
  '((agnes . "Agnes") (albert . "Albert") (alex . "Alex")
    (badnews . "BadNews") (bahh . "Bahh") (bells . "Bells")
    (boing . "Boing") (bruce . "Bruce") (bubbles . "Bubbles")
    (cellos . "Cellos") (deranged . "Deranged") (fred . "Fred")
    (goodnews . "GoodNews") (hysterical . "Hysterical") (junior . "Junior")
    (kathy . "Kathy") (organ . "Organ") (princess . "Princess")
    (ralph . "Ralph") (trinoids . "Trinoids") (vicki . "Vicki")
    (victoria . "Victoria") (whisper . "Whisper") (zarvox . "Zarvox")))

(defun lg-say-the-text (text &optional voice)
  (let* ((sv (cdr (assq voice lg-say-voices)))
         (svoice (if sv (format " using %S" sv) ""))
         ;; " -> ' in order to avoid osascript confusion
         (nt (replace-in-string text "\"" "'")))
    (do-applescript (format "say %S%s" nt svoice))
    (growl "Text via VoiceOver" "Done!")))

(defun lg-say-region-or-buffer (arg)
  "Say contents of the buffer or region.
If prefix ARG is specified then use custom voice."
  (interactive "P")
  (let ((voice (and current-prefix-arg
                    (intern
                     (completing-read "Voice: "
                      (mapcar #'(lambda (v)
                                  (list (symbol-name (car v))))
                              lg-say-voices)
                      nil t))))
        (text (if (region-active-p)
                  (buffer-substring (region-beginning) (region-end))
                (buffer-substring))))
    (lg-say-the-text text voice)))

(define-key global-map (kbd "C-c s") 'lg-say-region-or-buffer)

Далее, можно например реализовать процедуру, которая будет читать тело письма в gnus:

(defun lg-gnus-say-article-body ()
  "Say body of the current gnus article."
  (interactive)
  (let ((atxt (with-current-buffer gnus-article-buffer
                (save-restriction
                  (article-goto-body)
                  (buffer-substring (point) (point-max))))))
    (lg-say-the-text atxt)))

Единственная проблема с данной реализацией это то, что SXEmacs блокирует во время выполнения сценария AppleScript. Нужно расширить процедуру do-applescript, чтобы она поддерживала параметр IN-BACKGROUND.

ReST source Скачать оригинал