From: Andreas Gerler Date: Thu, 23 Nov 2023 18:58:35 +0000 (+0100) Subject: update atomic-chrome X-Git-Url: http://git.bundesbrandschatzamt.de/?a=commitdiff_plain;h=bcc746591201838b72b388bf6b8334c7c72dbc06;p=emacsconfig.git update atomic-chrome Signed-off-by: Andreas Gerler --- diff --git a/elpa/atomic-chrome-20220723.113/atomic-chrome-autoloads.el b/elpa/atomic-chrome-20220723.113/atomic-chrome-autoloads.el deleted file mode 100644 index 9dcc5deb..00000000 --- a/elpa/atomic-chrome-20220723.113/atomic-chrome-autoloads.el +++ /dev/null @@ -1,29 +0,0 @@ -;;; atomic-chrome-autoloads.el --- automatically extracted autoloads -*- lexical-binding: t -*- -;; -;;; Code: - -(add-to-list 'load-path (directory-file-name - (or (file-name-directory #$) (car load-path)))) - - -;;;### (autoloads nil "atomic-chrome" "atomic-chrome.el" (0 0 0 0)) -;;; Generated autoloads from atomic-chrome.el - -(autoload 'atomic-chrome-start-server "atomic-chrome" "\ -Start websocket server for atomic-chrome. -Fails silently if a server is already running." t nil) - -(autoload 'atomic-chrome-stop-server "atomic-chrome" "\ -Stop websocket server for atomic-chrome." t nil) - -(register-definition-prefixes "atomic-chrome" '("atomic-chrome-" "global-atomic-chrome-edit-mode")) - -;;;*** - -;; Local Variables: -;; version-control: never -;; no-byte-compile: t -;; no-update-autoloads: t -;; coding: utf-8 -;; End: -;;; atomic-chrome-autoloads.el ends here diff --git a/elpa/atomic-chrome-20220723.113/atomic-chrome-pkg.el b/elpa/atomic-chrome-20220723.113/atomic-chrome-pkg.el deleted file mode 100644 index ec261073..00000000 --- a/elpa/atomic-chrome-20220723.113/atomic-chrome-pkg.el +++ /dev/null @@ -1,2 +0,0 @@ -;;; Generated package description from atomic-chrome.el -*- no-byte-compile: t -*- -(define-package "atomic-chrome" "20220723.113" "Edit Chrome text area with Emacs using Atomic Chrome" '((emacs "24.4") (let-alist "1.0.4") (websocket "1.4")) :commit "061958ab96c31085b5daf449b1d826b052777b59" :authors '(("alpha22jp" . "alpha22jp@gmail.com")) :maintainer '("alpha22jp" . "alpha22jp@gmail.com") :keywords '("chrome" "edit" "textarea") :url "https://github.com/alpha22jp/atomic-chrome") diff --git a/elpa/atomic-chrome-20220723.113/atomic-chrome.el b/elpa/atomic-chrome-20220723.113/atomic-chrome.el deleted file mode 100644 index 025428c5..00000000 --- a/elpa/atomic-chrome-20220723.113/atomic-chrome.el +++ /dev/null @@ -1,393 +0,0 @@ -;;; atomic-chrome.el --- Edit Chrome text area with Emacs using Atomic Chrome - -;; Copyright (C) 2016 alpha22jp - -;; Author: alpha22jp -;; Package-Requires: ((emacs "24.4") (let-alist "1.0.4") (websocket "1.4")) -;; Package-Version: 20220723.113 -;; Package-Commit: 061958ab96c31085b5daf449b1d826b052777b59 -;; 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. -;; -;; Firefox is supported via the GhostText browser addon. - -;;; Code: - -(eval-when-compile (require 'cl-lib)) -(require 'json) -(require 'let-alist) -(eval-when-compile (require 'subr-x)) -(require 'websocket) - -(defgroup atomic-chrome nil - "Edit browser text area with Emacs using Atomic Chrome or GhostText." - :prefix "atomic-chrome-" - :group 'applications) - -(defcustom atomic-chrome-extension-type-list '(atomic-chrome ghost-text) - "List of browser 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-server-ghost-text-port 4001 - "HTTP server port for Ghost Text." - :type 'integer - :group 'atomic-chrome) - -(defcustom atomic-chrome-enable-auto-update t - "If non-nil, edit on Emacs is reflected to the browser instantly. -If nil, you need to type \"C-cC-s\" manually." - :type 'boolean - :group 'atomic-chrome) - -(defcustom atomic-chrome-enable-bidirectional-edit t - "If non-nil, you can edit both on the browser text area and Emacs. -If nil, edit on browser 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 to select a major mode for a website. -Relates URL (or, for GhostText, hostname) regular expressions to -corresponding major modes." - :type '(alist :key-type (regexp :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) - "Look up websocket associated with buffer BUFFER. -Looks in `atomic-chrome-buffer-table'." - (nth 0 (gethash buffer atomic-chrome-buffer-table))) - -(defun atomic-chrome-get-frame (buffer) - "Look up frame associated with buffer BUFFER. -Looks in `atomic-chrome-buffer-table'." - (nth 1 (gethash buffer atomic-chrome-buffer-table))) - -(defun atomic-chrome-get-buffer-by-socket (socket) - "Look up buffer which is associated to the websocket SOCKET. -Looks in `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)))))))) - (set-buffer-modified-p nil))) - -(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. -Either creates a frame with title TITLE, or raises 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 - (cond - ((memq window-system '(pgtk x)) - (if (string-match-p "wayland" x-display-name) - (make-frame frame-params) - (make-frame-on-display (getenv "DISPLAY") frame-params))) - ;; Avoid using make-frame-on-display for Mac OS - ((memq window-system '(ns mac)) - (make-frame frame-params)) - ((memq window-system '(w32)) - (make-frame-on-display "w32" frame-params)) - (t - (make-frame 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 (if (string-empty-p title) "No title" 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)) - (window (get-buffer-window buffer))) - (with-current-buffer buffer - (save-restriction - (run-hooks 'atomic-chrome-edit-done-hook) - (when frame (delete-frame frame)) - (if (and (eq atomic-chrome-buffer-open-style 'split) - window) - (quit-window t window) - (kill-buffer buffer)))))) - -(defun atomic-chrome-close-current-buffer () - "Close current buffer and connection from client." - (interactive) - (when (or (not (buffer-modified-p)) - (yes-or-no-p "Buffer has not been saved, close anyway? ")) - (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) - "Handle data received from the websocket client specified by SOCKET. -FRAME holds the raw data received." - (let ((msg (json-read-from-string - (decode-coding-string - (encode-coding-string (websocket-frame-payload frame) 'utf-8) - '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-c 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 Atomic 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) - -(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 atomic-chrome-server-ghost-text-port - :filter 'atomic-chrome-httpd-process-filter - :filter-multibyte nil - :server t - :noquery 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. -Fails silently if a server is already running." - (interactive) - (ignore-errors - (progn - (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)))) - -;;;###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")) - (global-atomic-chrome-edit-mode 0)) - -(provide 'atomic-chrome) - -;;; atomic-chrome.el ends here diff --git a/elpa/atomic-chrome-20230304.112/atomic-chrome-autoloads.el b/elpa/atomic-chrome-20230304.112/atomic-chrome-autoloads.el new file mode 100644 index 00000000..c40bee7d --- /dev/null +++ b/elpa/atomic-chrome-20230304.112/atomic-chrome-autoloads.el @@ -0,0 +1,33 @@ +;;; atomic-chrome-autoloads.el --- automatically extracted autoloads -*- lexical-binding: t -*- +;; +;;; Code: + +(add-to-list 'load-path (directory-file-name + (or (file-name-directory #$) (car load-path)))) + + +;;;### (autoloads nil "atomic-chrome" "atomic-chrome.el" (0 0 0 0)) +;;; Generated autoloads from atomic-chrome.el + +(autoload 'atomic-chrome-start-server "atomic-chrome" "\ +Start websocket server for atomic-chrome. +Fails silently if a server is already running." t nil) + +(autoload 'atomic-chrome-stop-server "atomic-chrome" "\ +Stop websocket server for atomic-chrome." t nil) + +(register-definition-prefixes "atomic-chrome" '("atomic-chrome-" "global-atomic-chrome-edit-mode")) + +;;;*** + +;;;### (autoloads nil nil ("atomic-chrome-pkg.el") (0 0 0 0)) + +;;;*** + +;; Local Variables: +;; version-control: never +;; no-byte-compile: t +;; no-update-autoloads: t +;; coding: utf-8 +;; End: +;;; atomic-chrome-autoloads.el ends here diff --git a/elpa/atomic-chrome-20230304.112/atomic-chrome-pkg.el b/elpa/atomic-chrome-20230304.112/atomic-chrome-pkg.el new file mode 100644 index 00000000..3729c22e --- /dev/null +++ b/elpa/atomic-chrome-20230304.112/atomic-chrome-pkg.el @@ -0,0 +1,16 @@ +(define-package "atomic-chrome" "20230304.112" "Edit Chrome text area with Emacs using Atomic Chrome" + '((emacs "24.4") + (let-alist "1.0.4") + (websocket "1.4")) + :commit "f1b077be7e414f457191d72dcf5eedb4371f9309" :authors + '(("alpha22jp" . "alpha22jp@gmail.com")) + :maintainers + '(("alpha22jp" . "alpha22jp@gmail.com")) + :maintainer + '("alpha22jp" . "alpha22jp@gmail.com") + :keywords + '("chrome" "edit" "textarea") + :url "https://github.com/alpha22jp/atomic-chrome") +;; Local Variables: +;; no-byte-compile: t +;; End: diff --git a/elpa/atomic-chrome-20230304.112/atomic-chrome.el b/elpa/atomic-chrome-20230304.112/atomic-chrome.el new file mode 100644 index 00000000..9affc7cd --- /dev/null +++ b/elpa/atomic-chrome-20230304.112/atomic-chrome.el @@ -0,0 +1,391 @@ +;;; atomic-chrome.el --- Edit Chrome text area with Emacs using Atomic Chrome + +;; Copyright (C) 2016 alpha22jp + +;; Author: alpha22jp +;; Package-Requires: ((emacs "24.4") (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. +;; +;; Firefox is supported via the GhostText browser addon. + +;;; Code: + +(eval-when-compile (require 'cl-lib)) +(require 'json) +(require 'let-alist) +(eval-when-compile (require 'subr-x)) +(require 'websocket) + +(defgroup atomic-chrome nil + "Edit browser text area with Emacs using Atomic Chrome or GhostText." + :prefix "atomic-chrome-" + :group 'applications) + +(defcustom atomic-chrome-extension-type-list '(atomic-chrome ghost-text) + "List of browser 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-server-ghost-text-port 4001 + "HTTP server port for Ghost Text." + :type 'integer + :group 'atomic-chrome) + +(defcustom atomic-chrome-enable-auto-update t + "If non-nil, edit on Emacs is reflected to the browser instantly. +If nil, you need to type \"C-cC-s\" manually." + :type 'boolean + :group 'atomic-chrome) + +(defcustom atomic-chrome-enable-bidirectional-edit t + "If non-nil, you can edit both on the browser text area and Emacs. +If nil, edit on browser 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 to select a major mode for a website. +Relates URL (or, for GhostText, hostname) regular expressions to +corresponding major modes." + :type '(alist :key-type (regexp :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) + "Look up websocket associated with buffer BUFFER. +Looks in `atomic-chrome-buffer-table'." + (nth 0 (gethash buffer atomic-chrome-buffer-table))) + +(defun atomic-chrome-get-frame (buffer) + "Look up frame associated with buffer BUFFER. +Looks in `atomic-chrome-buffer-table'." + (nth 1 (gethash buffer atomic-chrome-buffer-table))) + +(defun atomic-chrome-get-buffer-by-socket (socket) + "Look up buffer which is associated to the websocket SOCKET. +Looks in `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)))))))) + (set-buffer-modified-p nil))) + +(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. +Either creates a frame with title TITLE, or raises 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 + (cond + ((memq window-system '(pgtk x)) + (if (or (not x-display-name) (string-match-p "wayland" x-display-name)) + (make-frame frame-params) + (make-frame-on-display (getenv "DISPLAY") frame-params))) + ;; Avoid using make-frame-on-display for Mac OS + ((memq window-system '(ns mac)) + (make-frame frame-params)) + ((memq window-system '(w32)) + (make-frame-on-display "w32" frame-params)) + (t + (make-frame 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 (if (string-empty-p title) "No title" 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)) + (window (get-buffer-window buffer))) + (with-current-buffer buffer + (save-restriction + (run-hooks 'atomic-chrome-edit-done-hook) + (when frame (delete-frame frame)) + (if (and (eq atomic-chrome-buffer-open-style 'split) + window) + (quit-window t window) + (kill-buffer buffer)))))) + +(defun atomic-chrome-close-current-buffer () + "Close current buffer and connection from client." + (interactive) + (when (or (not (buffer-modified-p)) + (yes-or-no-p "Buffer has not been saved, close anyway? ")) + (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) + "Handle data received from the websocket client specified by SOCKET. +FRAME holds the raw data received." + (let ((msg (json-read-from-string + (decode-coding-string + (encode-coding-string (websocket-frame-payload frame) 'utf-8) + '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-c 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 Atomic 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) + +(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 atomic-chrome-server-ghost-text-port + :filter 'atomic-chrome-httpd-process-filter + :filter-multibyte nil + :server t + :noquery 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. +Fails silently if a server is already running." + (interactive) + (ignore-errors + (progn + (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)))) + +;;;###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")) + (global-atomic-chrome-edit-mode 0)) + +(provide 'atomic-chrome) + +;;; atomic-chrome.el ends here