Created
January 10, 2014 20:58
-
-
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
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
;; 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