Skip to content

Instantly share code, notes, and snippets.

@smithzvk
Created January 10, 2014 20:58
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save smithzvk/8362474 to your computer and use it in GitHub Desktop.
Save smithzvk/8362474 to your computer and use it in GitHub Desktop.
Syncing Google Docs and Emacs. Do not use for anything serious! See http://directed-procrastination.blogspot.com/2011/06/syncing-emacs-with-google-documents.html for more info
;; See http://directed-procrastination.blogspot.com/2011/06/syncing-emacs-with-google-documents.html for more info
;; Okay, here is the plan. You need to run a browser in a different display (so
;; it does'nt mess up your focus). You need the following programs: Emacs,
;; xdotool, xsel, xpra, and of course X windows.
;; 1. Setup the mirroring by evaluating (setup-mirror).
;; 2. Start xpra:
;; xpra start :1
;; xpra attach :1
;; 3. Start browser under display :1
;; DISPLAY=:1 browser
;; 4. Then start mirroring. This is controlled by C-x Space.
;; a. If the mirroring is disabled, then it will be enabled
;; b. If it is enabled, but the point is at a different point than in the
;; Doc, the points are set equal (good for getting things back into sync)
;; c. If the points are already synced, then the mirroring is disabled
;; 5. Alt+direction allows you to move the cursor in the Docs window (also
;; useful for getting things back in sync).
(defun gdmir-gdocify-string (str)
"Escape every character"
(replace-regexp-in-string "\\(.\\)" "\\\\\\1" str))
(defun gdmir-grab-selection ()
(gdmir-send-key "ctrl+c" t)
;; This is to select the Google Doc copy command
;; (gdmir-send-key "alt+e Down Down Down Down Return")
(shell-command-to-string "xsel --display :1 -o -b"))
;; (shell-command-to-string "xclip -d :1 -o"))
(defvar *empty-clipboard*
(concat "empty-clipboard-" (symbol-name (gensym))))
(defun empty-clipboard ()
(shell-command
(concat "echo -n "
*empty-clipboard*
"| xsel --display :1 -i -b")))
;; So here is an ugly bit. Something about Google docs or the X selection
;; clipboard makes it so selecting a blank line produces no selection. This
;; means that we cannot correctly skip lines that don't have at least a space on
;; them... what to do...
;; Replace all newlines with space+newlines in the google doc. Then we need to
;; translate between the emacs point and the gd point by finding all Newlines
;; between the point and the end of the buffer and giving increasing the point
;; by one for each
;; We are already translating between the two buffer, most notably by measuring
;; the point from the end of the buffer.
(defvar *ems-per-line* 50
"A conservative estimate for how many `m's are in a line in the google document.")
(defun gdmir-move-to-zero-column ()
(flush-commands)
(save-excursion
(goto-char gd-point)
(gdmir-move-left (max (- (current-column) (- *ems-per-line* 1)) 0))
(goto-char (- gd-point (max (- (current-column) (- *ems-per-line* 1)) 0)))
(cond ((< (current-column) *ems-per-line*)
(gdmir-send-key "Home Right" t)
(setf gd-point (line-beginning-position)))
(t (gdmir-move-left
(current-column))))))
(defun gdmir-move-up ()
(flush-commands)
(save-excursion
(goto-char gd-point)
;; More to the left most position on the screen
(gdmir-move-to-zero-column)
(goto-char gd-point)
;; (gdmir-move-left (- (point) (line-beginning-position)))
;; Move to the left of the code delimiter
(shell-command "DISPLAY=:1 xdotool windowFocus 4194333 key --delay 40 Left")
;; move up selecting the differnce
(let ((orig-line (line-number-at-pos)))
(loop until (/= (line-number-at-pos) orig-line)
do (shell-command "DISPLAY=:1 xdotool windowFocus 4194333 key --delay 40 shift+Up")
(let ((selection (gdmir-grab-selection)))
(backward-char (length selection)))
;; This doesn't move the point, it just moves to the left of the
;; selection and unselects the text.
(shell-command "DISPLAY=:1 xdotool windowFocus 4194333 key --delay 40 Left")))
(setf gd-point (point))
;; Move to the right of the code delimiter
(shell-command "DISPLAY=:1 xdotool windowFocus 4194333 key --delay 40 Right")))
(defun gdmir-move-down ()
(flush-commands)
(save-excursion
(goto-char gd-point)
;; More to the left most position on the screen
(gdmir-move-to-zero-column)
(goto-char gd-point)
;; (gdmir-move-left (- (point) (line-beginning-position)))
;; Move to the left of the code delimiter
(shell-command "DISPLAY=:1 xdotool windowFocus 4194333 key --delay 40 Left")
;; move down selecting the differnce
(let ((orig-line (line-number-at-pos)))
(loop until (/= (line-number-at-pos) orig-line)
do (shell-command "DISPLAY=:1 xdotool windowFocus 4194333 key --delay 40 shift+Down")
(let ((selection (gdmir-grab-selection)))
(forward-char (length selection)))
;; This doesn't move the point, it just moves to the left of the
;; selection and unselects the text.
(shell-command "DISPLAY=:1 xdotool windowFocus 4194333 key --delay 40 Right")))
(setf gd-point (point))
;; Move to the right of the code delimiter
(shell-command "DISPLAY=:1 xdotool windowFocus 4194333 key --delay 40 Right")))
(defun gdmir-move-left (n)
(save-excursion
(goto-char gd-point)
(loop repeat n
do (when (= 0 (current-column))
(gdmir-send-key "Left"))
(gdmir-send-key "Left")
(decf gd-point)
(backward-char))))
(defun gdmir-move-right (n)
(save-excursion
(goto-char gd-point)
(loop repeat n
do (gdmir-send-key "Right")
(incf gd-point)
(forward-char)
(when (= 0 (current-column))
(gdmir-send-key "Right")))))
(defvar gd-point 0)
;; (defun gdmir-mirror-buffer-to-gd (&optional (buffer (current-buffer)))
;; "Mirror BUFFER \(defaults to current buffer) to you Google Docs
;; session."
;; ())
;; (defun gdmir-move-to-end-of-gd ()
;; "Try to move to the end of the document. No way to be sure it
;; worked, so, fingers crossed."
;; (flush-commands)
;; (setq gd-point (point-max))
;; ;; Sending a few repeditive right moves might help things
;; (shell-command "DISPLAY=:1 xdotool windowFocus 4194333 key ctrl+a Right Right Right")
;; (sleep-for 0 500)
;; (gdmir-send-key "Down Up Right Left"))
;; (defun gdmir-move-to-absolute-point (point)
;; (gdmir-move-to-end-of-gd)
;; (loop while (> gd-point point)
;; do (gdmir-move-up))
;; (loop while (< gd-point point)
;; do (gdmir-move-right)))
(defun gdmir-move-to-relative-point (point-difference)
(let ((target (+ gd-point point-difference)))
(cond ((< point-difference -80)
(while (> gd-point target)
(gdmir-move-up)))
((> point-difference 80)
(while (< gd-point target)
(gdmir-move-down))))
(let ((point-difference (- target gd-point)))
(if (< point-difference 0)
(gdmir-move-left (abs point-difference))
(gdmir-move-right (abs point-difference))))))
;; (defvar *mirrored-buffers* nil)
;; ;; This just doesn't work right now. I'm trying not to use it.
;; (defun gdmir-sync-gd ();;&optional (buffer *mirrored-buffers*))
;; "Re-sync the Emacs buffers or buffers with the Google Docs
;; session. This involves reading the contents of the GD document,
;; then comparing to what you have. In heuristics are performed to
;; predict if differences are the result of interface errors or if a
;; different user has edited the file.
;; As of now, the heuristic is to ditch everything and use the Emacs
;; version. All edits are deleted..."
;; (let ((old-buff (current-buffer))
;; (pmin (point-min))
;; (pmax (point-max))
;; (temp-buffer (get-buffer-create "temp-gd")))
;; (with-temp-buffer
;; ;; (set-buffer temp-buffer)
;; ;; Delete GD current content
;; (shell-command "DISPLAY=:1 xdotool windowFocus 4194333 key ctrl+a BackSpace")
;; ;; Write header
;; (insert
;; (let ((str "=====================================vv=\n"))
;; (setf (subseq str 3) (concat " " (buffer-name old-buff) " "))
;; str))
;; ;; Write buffer
;; (let ((buffer-start (point)))
;; (insert-buffer-substring old-buff pmin pmax)
;; ;; Insert a vertical bar before every line. This is necessary to make
;; ;; sure that the selection is actually filled with something (any
;; ;; character would do).
;; (replace-regexp "^" "|" nil buffer-start (point-max))
;; (goto-char (point-max))
;; (when (= 10 (aref (buffer-substring (- (point) 1) (point)) 0))
;; (insert "|")))
;; ;; Write footer
;; (insert
;; (let ((str "\n=====================================^^="))
;; (setf (subseq str 3) (concat " " (buffer-name old-buff) " "))
;; str))
;; ;; Write buffer contents to clipboard
;; (shell-command-on-region
;; (point-min) (point-max)
;; "xsel -i --display :1 -b")
;; ;; Paste, delesect if necessary, Up to end of emacs buffer, then right and
;; ;; left to set the target column of vertical moves.
;; (shell-command "DISPLAY=:1 xdotool windowFocus 4194333 key --delay 40 ctrl+v Down Up Right Left")
;; ;; Wiat, then do it again for good measure
;; (sleep-for 1)
;; (shell-command "DISPLAY=:1 xdotool windowFocus 4194333 key Down Up Right Left"))
;; (setf gd-point (point-max))))
(defvar *prechange-text* nil)
(defvar *last-flush* (float-time))
(defvar *pending-keys* nil)
(setf *idle-flusher*
(run-with-idle-timer 4 t (lambda () (flush-commands))))
(defun flush-commands (&optional string)
(let* ((cmd (if *pending-keys*
(apply #'concat "key " (mapcar (lambda (x) (concat " " x)) (reverse *pending-keys*)))
" "))
(cmd (if string
(concat cmd " type --delay 40 " string)
cmd)))
(when (or *pending-keys* string)
(shell-command (concat "DISPLAY=:1 xdotool windowFocus 4194333 " cmd)))
(setf *pending-keys* nil)
(setf *last-flush* (float-time))))
(defun gdmir-send-key (keys &optional instant)
(push keys *pending-keys*)
(when (or instant (< 20 (length *pending-keys*)))
(flush-commands)))
(defun gdmir-send-string (string)
(flush-commands string))
(defun sync-points ()
(setf gd-point (point)))
(defvar *change-hook-in-effect* nil)
(defun insert-change-hook ()
(setf *change-hook-in-effect* t)
(push
(lambda (start end)
;; (print (list start end))
(gdmir-move-to-relative-point (- end gd-point))
(setf *prechange-text* (list start end (buffer-substring start end)))
;; Delete the string in GD before in Emacs
(save-excursion
(goto-char gd-point)
(loop for i below (- end start)
do (progn
(when (= 0 (current-column))
;; Clear out code marker
(gdmir-send-key "BackSpace"))
(decf gd-point)
(gdmir-send-key "BackSpace")
(backward-char)))))
before-change-functions)
(push
(lambda (start end old-length)
;; (print (list start end old-length))
(when (< 0 (- end start))
(loop for line in (split-for-xdotool (buffer-substring start end))
do (cond ((eql line 'question)
(gdmir-send-key "shift+slash"))
((eql line 'amp)
(gdmir-send-key "shift+7"))
((eql line 'ret)
(gdmir-send-key "Return")
(gdmir-send-string "\\|"))
((< 0 (length line))
(gdmir-send-string (gdmir-gdocify-string line)))))
(incf gd-point (length (buffer-substring start end))))
(flush-commands))
after-change-functions))
(defun split-for-xdotool (string)
(rest
(let ((count 0))
(loop for segment in (split-string string "[\n?&]")
appending (list (when (> count 0)
(cond ((eql (aref string (- count 1)) (aref "?" 0))
'question)
((eql (aref string (- count 1)) (aref "&" 0)) 'amp)
((eql (aref string (- count 1)) (aref "\n" 0))
'ret)))
segment)
do (incf count (1+ (length segment)))))))
;;; Save some sane after/before-change-functions in case of emergencies.
;; (setf after-change-functions '(jit-lock-after-change t)
;; before-change-functions '(t syntax-ppss-flush-cache))
(defun setup-mirror ()
(local-set-key (kbd "M-<left>") (lambda () (interactive) (gdmir-send-key "Left" t)))
(local-set-key (kbd "M-<right>") (lambda () (interactive) (gdmir-send-key "Right" t)))
(local-set-key (kbd "M-<up>") (lambda () (interactive) (gdmir-send-key "Up" t)))
(local-set-key (kbd "M-<down>") (lambda () (interactive) (gdmir-send-key "Down" t)))
(local-set-key (kbd "C-x SPC")
(lambda ()
(interactive)
(cond ((and after-change-functions
*change-hook-in-effect*
(= (point) gd-point))
(message "Mirroring disabled")
(setf *change-hook-in-effect* nil
after-change-functions old-after-change-functions
before-change-functions old-before-change-functions))
((and after-change-functions
*change-hook-in-effect*)
(message "Syncing points")
(sync-points))
(t
(message "Mirroring enabled")
(setf old-after-change-functions after-change-functions
old-before-change-functions before-change-functions)
(setf *change-hook-in-effect* t)
(insert-change-hook)
(sync-points))))))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment