pax_global_header 0000666 0000000 0000000 00000000064 13010346026 0014505 g ustar 00root root 0000000 0000000 52 comment=38ce9127285e1ff45f0f39b9da36a682103bdb96
atomic-chrome-el-2.0.0/ 0000775 0000000 0000000 00000000000 13010346026 0014631 5 ustar 00root root 0000000 0000000 atomic-chrome-el-2.0.0/README.md 0000664 0000000 0000000 00000007136 13010346026 0016117 0 ustar 00root root 0000000 0000000 # Atomic Chrome for Emacs [](http://melpa.org/#/atomic-chrome) [](http://stable.melpa.org/#/atomic-chrome)
This is the Emacs version of [Atomic Chrome](https://github.com/tuvistavie/atomic-chrome) which is an extension for Google Chrome browser that allows you to edit text areas of the browser in Emacs. It's similar to [Edit with Emacs](https://github.com/stsquad/emacs_chrome), but has some advantages as below with the help of websocket.
* The input on Emacs is reflected to the browser instantly and continuously.
* You can use both the browser and Emacs at the same time. They are updated to the same content bi-directionally.
__Since v2.0.0, Atomic Chrome for Emacs supports [Ghost Text](https://github.com/GhostText/GhostText) as browser extension.__
## Screencast

## Requirements
* Emacs: 24.3 or later
* OS: Tested on Windows and Linux
## Installation
### For Chrome
Atomic Chrome for Emacs shares browser extension with the one for [Atomic Chrome](https://chrome.google.com/webstore/detail/atomic-chrome/lhaoghhllmiaaagaffababmkdllgfcmc). Just install and setup it. Alternatively, you can use [Ghost Text](https://chrome.google.com/webstore/detail/ghosttext/godiecgffnchndlihlpaajjcplehddca) as browser extension.
### For Emacs
Atomic Chrome for Emacs can be installed via MELPA as below.
M-x package-install [RET] atomic-chrome [RET]
Then add the following lines to your `.emacs`.
``` emacs-lisp
(require 'atomic-chrome)
(atomic-chrome-start-server)
```
## Usage
1. Start Emacs.
2. Focus the text area you want to edit on the browser.
3. Press Atomic Chrome button on the tool bar.
4. Contet of the text area is opened in a new buffer of Emacs.
5. Edit content on Emacs buffer.
6. C-c C-c to finish editing, or the buffer killed if the browser closes the connection.
## Customization
### Set major mode for editing buffer
The default major mode of editing buffer is `text-mode`. You can change the major mode manually. If you want to use a different major mode as the default, set `atomic-chrome-default-major-mode` like below.
``` emacs-lisp
(setq atomic-chrome-default-major-mode 'markdown-mode)
```
Additionally, you can use `atomic-chrome-url-major-mode-alist` to choose the major mode for a specific website based on the page URL like below.
``` emacs-lisp
(setq atomic-chrome-url-major-mode-alist
'(("github\\.com" . gfm-mode)
("redmine" . textile-mode)))
```
This is an association list of regexp and major mode function. If the page URL matches to on of the regexp, the corresponding major mode is selected.
### Select editing buffer style
You can select the style of opening the editing buffer by `atomic-chrome-buffer-open-style` like below.
``` emacs-lisp
(setq atomic-chrome-buffer-open-style 'frame)
```
Available values are the followings.
* `full`: Open in the selected window.
* `split`: Open in the new window by splitting the selected window (default).
* `frame`: Create a new frame and window in it.
`frame` is available only when you are using Emacs on some window system.
If you select `frame`, you can also set the width and height of the frame with `atomic-chrome-buffer-frame-width` and `atomic-chrome-buffer-frame-height`. Their default values are `80` and `25`.
## History
version 2.0.0 (2016-11-08)
* Add Ghost Text support
version 1.0.1 (2016-11-06)
* Fix updating text from Chrome doesn't work
version 1.0.0 (2016-11-05)
* Initial version
atomic-chrome-el-2.0.0/atomic-chrome.el 0000664 0000000 0000000 00000035175 13010346026 0017715 0 ustar 00root root 0000000 0000000 ;;; atomic-chrome.el --- Edit Chrome text area with Emacs using Atomic Chrome
;; Copyright (C) 2016 alpha22jp
;; Author: alpha22jp
;; Package-Requires: ((emacs "24.3") (let-alist "1.0.4") (websocket "1.4"))
;; Keywords: chrome edit textarea
;; URL: https://github.com/alpha22jp/atomic-chrome
;; Version: 2.0.0
;; This program is free software; you can redistribute it and/or modify it under
;; the terms of the GNU General Public License as published by the Free Software
;; Foundation; either version 2 of the License, or (at your option) any later
;; version.
;; This program is distributed in the hope that it will be useful, but WITHOUT
;; ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
;; FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
;; details.
;; You should have received a copy of the GNU General Public License along with
;; this program; if not, write to the Free Software Foundation, Inc., 51
;; Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
;;; Commentary:
;; This is the Emacs version of Atomic Chrome which is an extension for Google
;; Chrome browser that allows you to edit text areas of the browser in Emacs.
;;
;; It's similar to Edit with Emacs, but has some advantages as below with the
;; help of websocket.
;;
;; * Live update
;; The input on Emacs is reflected to the browser instantly and continuously.
;; * Bidirectional communication
;; You can edit both on the browser and Emacs, they are synced to the same.
;;; Code:
(eval-when-compile (require 'cl))
(require 'json)
(require 'let-alist)
(require 'websocket)
(defgroup atomic-chrome nil
"Edit Chrome text area with Emacs using Atomic Chrome."
:prefix "atomic-chrome-"
:group 'applications)
(defcustom atomic-chrome-extension-type-list '(atomic-chrome ghost-text)
"List of chrome extension type available."
:type '(repeat (choice (const :tag "Atomic Chrome" atomic-chrome)
(const :tag "Ghost Text" ghost-text)))
:group 'atomic-chrome)
(defcustom atomic-chrome-buffer-open-style 'split
"Specify the style to open new buffer for editing."
:type '(choice (const :tag "Open buffer with full window" full)
(const :tag "Open buffer with splitted window" split)
(const :tag "Open buffer with new frame" frame))
:group 'atomic-chrome)
(defcustom atomic-chrome-buffer-frame-width 80
"Width of editing buffer frame."
:type 'integer
:group 'atomic-chrome)
(defcustom atomic-chrome-buffer-frame-height 25
"Height of editing buffer frame."
:type 'integer
:group 'atomic-chrome)
(defcustom atomic-chrome-enable-auto-update t
"If non-nil, edit on Emacs is reflected to Chrome instantly, \
otherwise you need to type \"C-xC-s\" manually."
:type 'boolean
:group 'atomic-chrome)
(defcustom atomic-chrome-enable-bidirectional-edit t
"If non-nil, you can edit both on Chrome text area and Emacs, \
otherwise edit on Chrome is ignored while editing on Emacs."
:type 'boolean
:group 'atomic-chrome)
(defcustom atomic-chrome-default-major-mode 'text-mode
"Default major mode for editing buffer."
:type 'function
:group 'atomic-chrome)
(defcustom atomic-chrome-url-major-mode-alist nil
"Association list of URL regexp and corresponding major mode \
which is used to select major mode for specified website."
:type '(alist :key-type (string :tag "regexp")
:value-type (function :tag "major mode"))
:group 'atomic-chrome)
(defcustom atomic-chrome-edit-mode-hook nil
"Customizable hook which run when the editing buffer is created."
:type 'hook
:group 'atomic-chrome)
(defcustom atomic-chrome-edit-done-hook nil
"Customizable hook which run when the editing buffer is closed."
:type 'hook
:group 'atomic-chrome)
(defvar atomic-chrome-server-atomic-chrome nil
"Websocket server connection handle for Atomic Chrome.")
(defvar atomic-chrome-server-ghost-text nil
"Websocket server connection handle for Ghost Text.")
(defvar atomic-chrome-buffer-table (make-hash-table :test 'equal)
"Hash table of editing buffer and its assciated data.
Each element has a list consisting of (websocket, frame).")
(defun atomic-chrome-get-websocket (buffer)
"Lookup websocket associated with buffer BUFFER \
from `atomic-chrome-buffer-table'."
(nth 0 (gethash buffer atomic-chrome-buffer-table)))
(defun atomic-chrome-get-frame (buffer)
"Lookup frame associated with buffer BUFFER \
from `atomic-chrome-buffer-table'."
(nth 1 (gethash buffer atomic-chrome-buffer-table)))
(defun atomic-chrome-get-buffer-by-socket (socket)
"Lookup buffer which is associated to the websocket SOCKET \
from `atomic-chrome-buffer-table'."
(let (buffer)
(cl-loop for key being the hash-keys of atomic-chrome-buffer-table
using (hash-values val)
do (when (equal (nth 0 val) socket) (setq buffer key)))
buffer))
(defun atomic-chrome-close-connection ()
"Close client connection associated with current buffer."
(let ((socket (atomic-chrome-get-websocket (current-buffer))))
(when socket
(remhash (current-buffer) atomic-chrome-buffer-table)
(websocket-close socket))))
(defun atomic-chrome-send-buffer-text ()
"Send request to update text with current buffer content."
(interactive)
(let ((socket (atomic-chrome-get-websocket (current-buffer)))
(text (buffer-substring-no-properties (point-min) (point-max))))
(when (and socket text)
(websocket-send-text
socket
(json-encode
(if (eq (websocket-server-conn socket) atomic-chrome-server-ghost-text)
(list (cons "text" text))
(list '("type" . "updateText")
(cons "payload" (list (cons "text" text))))))))))
(defun atomic-chrome-set-major-mode (url)
"Set major mode for editing buffer depending on URL.
`atomic-chrome-url-major-mode-alist' can be used to select major mode.
The specified major mode is used if URL matches to one of the alist,
otherwise fallback to `atomic-chrome-default-major-mode'"
(funcall (or (and url (assoc-default url
atomic-chrome-url-major-mode-alist
'string-match))
atomic-chrome-default-major-mode)))
(defun atomic-chrome-show-edit-buffer (buffer title)
"Show editing buffer BUFFER by creating a frame with title TITLE, \
or raising the selected frame depending on `atomic-chrome-buffer-open-style'."
(let ((edit-frame nil)
(frame-params (list (cons 'name (format "Atomic Chrome: %s" title))
(cons 'width atomic-chrome-buffer-frame-width)
(cons 'height atomic-chrome-buffer-frame-height))))
(when (eq atomic-chrome-buffer-open-style 'frame)
(setq edit-frame
(if (memq window-system '(ns mac))
;; Avoid using make-frame-on-display for Mac OS.
(make-frame frame-params)
(make-frame-on-display (getenv "DISPLAY") frame-params)))
(select-frame edit-frame))
(if (eq atomic-chrome-buffer-open-style 'split)
(pop-to-buffer buffer)
(switch-to-buffer buffer))
(raise-frame edit-frame)
(select-frame-set-input-focus (window-frame (selected-window)))
edit-frame))
(defun atomic-chrome-create-buffer (socket url title text)
"Create buffer associated with websocket specified by SOCKET.
URL is used to determine the major mode of the buffer created,
TITLE is used for the buffer name and TEXT is inserted to the buffer."
(let ((buffer (generate-new-buffer title)))
(with-current-buffer buffer
(puthash buffer
(list socket (atomic-chrome-show-edit-buffer buffer title))
atomic-chrome-buffer-table)
(atomic-chrome-set-major-mode url)
(insert text))))
(defun atomic-chrome-close-edit-buffer (buffer)
"Close buffer BUFFER if it's one of Atomic Chrome edit buffers."
(let ((frame (atomic-chrome-get-frame buffer)))
(with-current-buffer buffer
(save-restriction
(run-hooks 'atomic-chrome-edit-done-hook)
(when frame (delete-frame frame))
(kill-buffer buffer)))))
(defun atomic-chrome-close-current-buffer ()
"Close current buffer and connection from client."
(interactive)
(atomic-chrome-close-edit-buffer (current-buffer)))
(defun atomic-chrome-update-buffer (socket text)
"Update text on buffer associated with SOCKET to TEXT."
(let ((buffer (atomic-chrome-get-buffer-by-socket socket)))
(when buffer
(with-current-buffer buffer
(erase-buffer)
(insert text)))))
(defun atomic-chrome-on-message (socket frame)
"Function to handle data received from websocket client specified by SOCKET, \
where FRAME show raw data received."
(let ((msg (json-read-from-string
(decode-coding-string
(string-make-unibyte (websocket-frame-payload frame)) 'utf-8))))
(let-alist msg
(if (eq (websocket-server-conn socket) atomic-chrome-server-ghost-text)
(if (atomic-chrome-get-buffer-by-socket socket)
(atomic-chrome-update-buffer socket .text)
(atomic-chrome-create-buffer socket .url .title .text))
(cond ((string= .type "register")
(atomic-chrome-create-buffer socket .payload.url .payload.title .payload.text))
((string= .type "updateText")
(when atomic-chrome-enable-bidirectional-edit
(atomic-chrome-update-buffer socket .payload.text))))))))
(defun atomic-chrome-on-close (socket)
"Function to handle request from client to close websocket SOCKET."
(let ((buffer (atomic-chrome-get-buffer-by-socket socket)))
(when buffer (atomic-chrome-close-edit-buffer buffer))))
(defvar atomic-chrome-edit-mode-map
(let ((map (make-sparse-keymap)))
(define-key map (kbd "C-x C-s") 'atomic-chrome-send-buffer-text)
(define-key map (kbd "C-c C-c") 'atomic-chrome-close-current-buffer)
map)
"Keymap for minor mode `atomic-chrome-edit-mode'.")
(define-minor-mode atomic-chrome-edit-mode
"Minor mode enabled on buffers opened by Emacs Chrome server."
:group 'atomic-chrome
:lighter " AtomicChrome"
:init-value nil
:keymap atomic-chrome-edit-mode-map
(when atomic-chrome-edit-mode
(add-hook 'kill-buffer-hook 'atomic-chrome-close-connection nil t)
(when atomic-chrome-enable-auto-update
(add-hook 'post-command-hook 'atomic-chrome-send-buffer-text nil t))))
(defun atomic-chrome-turn-on-edit-mode ()
"Turn on `atomic-chrome-edit-mode' if the buffer is an editing buffer."
(when (gethash (current-buffer) atomic-chrome-buffer-table)
(atomic-chrome-edit-mode t)))
(define-global-minor-mode global-atomic-chrome-edit-mode
atomic-chrome-edit-mode atomic-chrome-turn-on-edit-mode)
(defadvice save-buffers-kill-emacs
(before atomic-chrome-server-stop-before-kill-emacs)
"Call `atomic-chrome-close-server' before closing Emacs to avoid users \
being prompted to kill the websocket server process."
(atomic-chrome-stop-server))
(defun atomic-chrome-start-websocket-server (port)
"Create websocket server on port PORT."
(websocket-server
port
:host 'local
:on-message #'atomic-chrome-on-message
:on-open nil
:on-close #'atomic-chrome-on-close))
(defun atomic-chrome-start-httpd ()
"Start the HTTP server for Ghost Text query."
(interactive)
(make-network-process
:name "atomic-chrome-httpd"
:family 'ipv4
:host 'local
:service 4001
:filter 'atomic-chrome-httpd-process-filter
:filter-multibyte nil
:server t))
(defun atomic-chrome-normalize-header (header)
"Destructively capitalize the components of HEADER."
(mapconcat #'capitalize (split-string header "-") "-"))
(defun atomic-chrome-httpd-parse-string (string)
"Parse client http header STRING into alist."
(let* ((lines (split-string string "[\n\r]+"))
(req (list (split-string (car lines))))
(post (cadr (split-string string "\r\n\r\n"))))
(dolist (line (butlast (cdr lines)))
(push (list (atomic-chrome-normalize-header (car (split-string line ": ")))
(mapconcat #'identity
(cdr (split-string line ": ")) ": "))
req))
(push (list "Content" post) req)
(reverse req)))
(defun atomic-chrome-httpd-process-filter (proc string)
"Process filter of PROC which run each time client make a request.
STRING is the string process received."
(setf string (concat (process-get proc :previous-string) string))
(let* ((request (atomic-chrome-httpd-parse-string string))
(content-length (cadr (assoc "Content-Length" request)))
(uri (cl-cadar request))
(content (cadr (assoc "Content" request))))
(if (and content-length
(< (string-bytes content) (string-to-number content-length)))
(process-put proc :previous-string string)
(atomic-chrome-httpd-send-response proc))))
(defun atomic-chrome-httpd-send-response (proc)
"Send an HTTP 200 OK response back to process PROC."
(when (processp proc)
(unless atomic-chrome-server-ghost-text
(setq atomic-chrome-server-ghost-text
(atomic-chrome-start-websocket-server 64293)))
(let ((header "HTTP/1.0 200 OK\nContent-Type: application/json\n")
(body (json-encode '(:ProtocolVersion 1 :WebSocketPort 64293))))
(process-send-string proc (concat header "\n" body))
(process-send-eof proc))))
;;;###autoload
(defun atomic-chrome-start-server ()
"Start websocket server for atomic-chrome."
(interactive)
(and (not atomic-chrome-server-atomic-chrome)
(memq 'atomic-chrome atomic-chrome-extension-type-list)
(setq atomic-chrome-server-atomic-chrome
(atomic-chrome-start-websocket-server 64292)))
(and (not (process-status "atomic-chrome-httpd"))
(memq 'ghost-text atomic-chrome-extension-type-list)
(atomic-chrome-start-httpd))
(global-atomic-chrome-edit-mode 1)
(ad-activate 'save-buffers-kill-emacs))
;;;###autoload
(defun atomic-chrome-stop-server nil
"Stop websocket server for atomic-chrome."
(interactive)
(when atomic-chrome-server-atomic-chrome
(websocket-server-close atomic-chrome-server-atomic-chrome)
(setq atomic-chrome-server-atomic-chrome nil))
(when atomic-chrome-server-ghost-text
(websocket-server-close atomic-chrome-server-ghost-text)
(setq atomic-chrome-server-ghost-text nil))
(when (process-status "atomic-chrome-httpd")
(delete-process "atomic-chrome-httpd"))
(ad-disable-advice 'save-buffers-kill-emacs
'before 'atomic-chrome-server-stop-before-kill-emacs)
;; Disabling advice doesn't take effect until you (re-)activate
;; all advice for the function.
(ad-activate 'save-buffers-kill-emacs)
(global-atomic-chrome-edit-mode 0))
(provide 'atomic-chrome)
;;; atomic-chrome.el ends here
atomic-chrome-el-2.0.0/images/ 0000775 0000000 0000000 00000000000 13010346026 0016076 5 ustar 00root root 0000000 0000000 atomic-chrome-el-2.0.0/images/screencast.gif 0000664 0000000 0000000 00001022202 13010346026 0020716 0 ustar 00root root 0000000 0000000 GIF89a~ <: -, +3 *4 *6 +6 +7+6+7+6 ,1,5+3+5,2,6 +:
+5 ,:3%
2<.--55%387/?2.')**.2(;)(368:'2221253432464225254425563686860883:=1<:0>=58:69<5<9;54958986>91<85=97<:7<;7=;7>;7=<78888;<:=9:<=<:8<;<<<8<<:=>9<8><:>>9>>:===Ig -D0B1E1A2D6B6C7B5E2L8G8K
5B5J9D
9M7V.F'W9D