* Proposal: emacsclient --readonly @ 2013-03-14 2:23 michael 2013-03-14 2:45 ` W. Greenhouse 2013-03-14 3:31 ` Stefan Monnier 0 siblings, 2 replies; 13+ messages in thread From: michael @ 2013-03-14 2:23 UTC (permalink / raw) To: emacs-devel [-- Attachment #1: Type: text/plain, Size: 867 bytes --] As distraction to our toddler temper-tantrum, I'd like to propose a new feature for emacsclient. I use emacsclient to open files frequently and I generally immediately do M-x view-mode once the frame pops up. I'd like to be able to open the file directly in view-mode when I invoke emacsclient. I'd be interested in any suggestions people have on how to accomplish this using emacsclient currently. In the absence of any ideas, I'd like to propose the folowing patch which adds a `--readonly,-r' option to emacsclient (and server.el) (I had originally called it --view-mode,-v but getopt_long_only complained that it was ambiguous if abbreviated.) I'm eager to hear any suggestions people might have to do this without changing emacsclient. Or maybe this is a reason to add a more generic feature to emacsclient to enable/disable modes after a file is opened. [-- Warning: decoded text below may be mangled, UTF-8 assumed --] [-- Attachment #2: emacsclient --readonly feature --] [-- Type: text/x-diff, Size: 6848 bytes --] *** trunk/lib-src/emacsclient.c 2013-03-13 19:48:00.017919000 -0400 --- my-work/lib-src/emacsclient.c 2013-03-13 19:48:16.237220000 -0400 *************** *** 129,134 **** --- 129,137 ---- /* Nonzero means don't wait for a response from Emacs. --no-wait. */ int nowait = 0; + /* Nonzero means file should be readonly and placed in view-mode. --readonly. */ + int readonly = 0; + /* Nonzero means don't print messages for successful operations. --quiet. */ int quiet = 0; *************** *** 173,178 **** --- 176,182 ---- struct option longopts[] = { { "no-wait", no_argument, NULL, 'n' }, + { "readonly", no_argument, NULL, 'r' }, { "quiet", no_argument, NULL, 'q' }, { "eval", no_argument, NULL, 'e' }, { "help", no_argument, NULL, 'H' }, *************** *** 519,524 **** --- 523,532 ---- nowait = 1; break; + case 'r': + readonly = 1; + break; + case 'e': eval = 1; break; *************** *** 644,649 **** --- 652,658 ---- Set the parameters of a new frame\n\ -e, --eval Evaluate the FILE arguments as ELisp expressions\n\ -n, --no-wait Don't wait for the server to return\n\ + -r, --readonly Make the buffer readonly and enable view-mode\n\ -q, --quiet Don't display messages on success\n\ -d DISPLAY, --display=DISPLAY\n\ Visit the file in the given display\n\ *************** *** 1604,1609 **** --- 1613,1621 ---- if (nowait) send_to_emacs (emacs_socket, "-nowait "); + if (readonly) + send_to_emacs (emacs_socket, "-readonly "); + if (current_frame) send_to_emacs (emacs_socket, "-current-frame "); *** trunk/lisp/server.el 2013-03-02 22:36:33.309144000 -0500 --- my-work/lisp/server.el 2013-03-06 23:58:19.157146157 -0500 *************** *** 1050,1055 **** --- 1050,1056 ---- (or file-name-coding-system default-file-name-coding-system))) nowait ; t if emacsclient does not want to wait for us. + readonly ; t to view files in read-only/view-mode frame ; Frame opened for the client (if any). display ; Open frame on this display. parent-id ; Window ID for XEmbed *************** *** 1075,1080 **** --- 1076,1084 ---- ;; -nowait: Emacsclient won't wait for a result. (`"-nowait" (setq nowait t)) + ;; -readonly: place files in view-mode + (`"-readonly" (setq readonly t)) + ;; -current-frame: Don't create frames. (`"-current-frame" (setq use-current-frame t)) *************** *** 1233,1239 **** (let ((default-directory (if (and dir (file-directory-p dir)) dir default-directory))) ! (server-execute proc files nowait commands dontkill frame tty-name))))) (when (or frame files) --- 1237,1243 ---- (let ((default-directory (if (and dir (file-directory-p dir)) dir default-directory))) ! (server-execute proc files nowait readonly commands dontkill frame tty-name))))) (when (or frame files) *************** *** 1243,1249 **** ;; condition-case (error (server-return-error proc err)))) ! (defun server-execute (proc files nowait commands dontkill frame tty-name) ;; This is run from timers and process-filters, i.e. "asynchronously". ;; But w.r.t the user, this is not really asynchronous since the timer ;; is run after 0s and the process-filter is run in response to the --- 1247,1253 ---- ;; condition-case (error (server-return-error proc err)))) ! (defun server-execute (proc files nowait readonly commands dontkill frame tty-name) ;; This is run from timers and process-filters, i.e. "asynchronously". ;; But w.r.t the user, this is not really asynchronous since the timer ;; is run after 0s and the process-filter is run in response to the *************** *** 1253,1259 **** ;; including code that needs to wait. (with-local-quit (condition-case err ! (let ((buffers (server-visit-files files proc nowait))) (mapc 'funcall (nreverse commands)) ;; If we were told only to open a new client, obey --- 1257,1263 ---- ;; including code that needs to wait. (with-local-quit (condition-case err ! (let ((buffers (server-visit-files files proc nowait readonly))) (mapc 'funcall (nreverse commands)) ;; If we were told only to open a new client, obey *************** *** 1319,1331 **** (when (> column-number 0) (move-to-column (1- column-number)))))) ! (defun server-visit-files (files proc &optional nowait) "Find FILES and return a list of buffers created. FILES is an alist whose elements are (FILENAME . FILEPOS) where FILEPOS can be nil or a pair (LINENUMBER . COLUMNNUMBER). PROC is the client that requested this operation. NOWAIT non-nil means this client is not waiting for the results, ! so don't mark these buffers specially, just visit them normally." ;; Bind last-nonmenu-event to force use of keyboard, not mouse, for queries. (let ((last-nonmenu-event t) client-record) ;; Restore the current buffer afterward, but not using save-excursion, --- 1323,1337 ---- (when (> column-number 0) (move-to-column (1- column-number)))))) ! (defun server-visit-files (files proc &optional nowait readonly) "Find FILES and return a list of buffers created. FILES is an alist whose elements are (FILENAME . FILEPOS) where FILEPOS can be nil or a pair (LINENUMBER . COLUMNNUMBER). PROC is the client that requested this operation. NOWAIT non-nil means this client is not waiting for the results, ! so don't mark these buffers specially, just visit them normally. ! READONLY non-nil means place the file in `view-mode'; quiting ! the file will kill the buffer." ;; Bind last-nonmenu-event to force use of keyboard, not mouse, for queries. (let ((last-nonmenu-event t) client-record) ;; Restore the current buffer afterward, but not using save-excursion, *************** *** 1363,1368 **** --- 1369,1379 ---- (run-hooks 'server-visit-hook) ;; hooks may be specific to current buffer: (run-hooks 'post-command-hook)) + (when readonly + ;; enable view mode and view-quit will mark the buffer as done + (view-mode +1) + (unless nowait + (setq view-exit-action 'server-buffer-done))) (unless nowait ;; When the buffer is killed, inform the clients. (add-hook 'kill-buffer-hook 'server-kill-buffer nil t) [-- Attachment #3: Type: text/plain, Size: 71 bytes --] -- Michael R. Mauger - FSF Member #4247 - Emacs sql.el Maintainer ^ permalink raw reply [flat|nested] 13+ messages in thread
* Re: Proposal: emacsclient --readonly 2013-03-14 2:23 Proposal: emacsclient --readonly michael @ 2013-03-14 2:45 ` W. Greenhouse 2013-03-14 3:31 ` Stefan Monnier 1 sibling, 0 replies; 13+ messages in thread From: W. Greenhouse @ 2013-03-14 2:45 UTC (permalink / raw) To: emacs-devel-mXXj517/zsQ michael-ntT3PcgARwzQT0dZR+AlfA@public.gmane.org writes: > As distraction to our toddler temper-tantrum, I'd like to propose a > new feature for emacsclient. > > I use emacsclient to open files frequently and I generally immediately > do M-x view-mode once the frame pops up. I'd like to be able to open > the file directly in view-mode when I invoke emacsclient. > > I'd be interested in any suggestions people have on how to accomplish > this using emacsclient currently. In the absence of any ideas, I'd like > to propose the folowing patch which adds a `--readonly,-r' option to > emacsclient (and server.el) (I had originally called it --view-mode,-v but > getopt_long_only complained that it was ambiguous if abbreviated.) > > I'm eager to hear any suggestions people might have to do this without > changing emacsclient. Or maybe this is a reason to add a more generic > feature to emacsclient to enable/disable modes after a file is opened. > > > > -- > Michael R. Mauger - FSF Member #4247 - Emacs sql.el Maintainer Without changing emacsclient, my approach would be the slightly unintuitive/user-hostile emacsclient --eval '(view-file "path/to/file")' -- Regards, WGG ^ permalink raw reply [flat|nested] 13+ messages in thread
* Re: Proposal: emacsclient --readonly 2013-03-14 2:23 Proposal: emacsclient --readonly michael 2013-03-14 2:45 ` W. Greenhouse @ 2013-03-14 3:31 ` Stefan Monnier 2013-03-24 3:45 ` Michael Mauger 1 sibling, 1 reply; 13+ messages in thread From: Stefan Monnier @ 2013-03-14 3:31 UTC (permalink / raw) To: michael; +Cc: emacs-devel > I'd be interested in any suggestions people have on how to accomplish > this using emacsclient currently. In the absence of any ideas, I'd like > to propose the folowing patch which adds a `--readonly,-r' option to > emacsclient (and server.el) (I had originally called it --view-mode,-v but > getopt_long_only complained that it was ambiguous if abbreviated.) I think the way to do it is to change emacsclient.c so that any "--foo" arg simply gets sent to server.el. This way the addition of --readonly support can be implemented all in server.el. Then server.el should be changed to provide a hook that lets user add handling for their own --foo args so that you can even do it without changing server.el. Stefan ^ permalink raw reply [flat|nested] 13+ messages in thread
* Re: Proposal: emacsclient --readonly 2013-03-14 3:31 ` Stefan Monnier @ 2013-03-24 3:45 ` Michael Mauger 0 siblings, 0 replies; 13+ messages in thread From: Michael Mauger @ 2013-03-24 3:45 UTC (permalink / raw) To: Stefan Monnier; +Cc: emacs-devel@gnu.org [-- Attachment #1: Type: text/plain, Size: 4441 bytes --] [I got a message this bounced on the mailing list, twice; trying again. Sorry if this is a dupe. I've polished the text and the code bit; it was originally sent while I was attending LibrePlanet.] > > I'd be interested in any suggestions people have on how to accomplish > > this using emacsclient currently. In the absence of any ideas, I'd like > > to propose the folowing patch which adds a `--readonly,-r' option to > > emacsclient (and server.el) (I had originally called it --view-mode,-v but > > getopt_long_only complained that it was ambiguous if abbreviated.) > > I think the way to do it is to change emacsclient.c so that any "--foo" > arg simply gets sent to server.el. This way the addition of --readonly > > support can be implemented all in server.el. Then server.el should be > changed to provide a hook that lets user add handling for their > own --foo args so that you can even do it without changing server.el. > > Stefan Adding a feature to allow custom options in emacsclient is an idea that I've had in the past and failed at implementing it in the past miserably because I came up with too many complex situations. So I've spent the last week or so trying again. I've been through several iterations and have reduced it to this simplified solution. The code is still a little rough, but the idea is stable. The doc strings probably need lots of work... Basically, there are two variables used to control the parsing and processing of custom emacsclient options: server-custom-option-functions: A list of functions to handle custom options. The functions accept four arguments: a buffer, a process, the option string and the option value. If this variable is nil, then no options are accepted, regardless of the setting of `server-custom-option-list'. The functions on this hook variable are called once before files or expressions are processed with the buffer set to nil. After each file is opened, the hooks will be called again, this time with the file buffer passed as the first argument. server-custom-option-list: A list that defines the acceptable custom options. If this variable is nil, all unrecognized options are assumed to be valid custom options. If this variable is a list then each element defines an acceptable option. Each list entry should be either a string with the option name that does not accept a value, or a list whose first entry is a string containing the option name and the optional second entry is a type predicate. If the type predicate is `string-only', then the option value is simply treated as a string; any other predicate forces the value to be interpreted by the elisp reader and passed to the predicate to validate it's value. So, in my user-init-file, I have: (defun my-server-custom-options (buf proc option value) (if buf ;; options processed for each file opened (pcase option (`"readonly" (view-mode +1) (when (and view-mode server-buffer-clients) ;; not nowait (setq-local view-exit-action 'server-buffer-done))) (t nil)) (pcase option ;; global options (`"banner" ;; display banner value (cond ((executable-find "figlet") (call-process-shell-command "figlet" nil "*Banner*" t value)) ((executable-find "banner") (call-process-shell-command "banner" nil "*Banner*" t value)) (t (progn (get-buffer-create "*Banner*") (with-current-buffer "*Banner*" (insert (propertize value 'face 'font-lock-warning-face)))))) (display-buffer "*Banner*")) (t nil)))) ;; define option syntax (setq server-custom-option-list '("readonly" ;; --readonly ("banner" string-only))) ;; --banner=text ;; Process options (add-hook 'server-custom-option-functions 'my-server-custom-options) In the end, the changes were pretty small and localized. I have not tested it that hard, so it's not difficult to break. -- Michael R. Mauger FSF Member #4247 Emacs sql.el Maintainer michael # mauger com mmaug # member fsf org mmaug # yahoo com [-- Warning: decoded text below may be mangled, UTF-8 assumed --] [-- Attachment #2: custom-option.diff --] [-- Type: text/x-patch; name="custom-option.diff", Size: 15197 bytes --] *** trunk/lib-src/emacsclient.c 2013-03-13 19:48:00.017919000 -0400 --- my-work/lib-src/emacsclient.c 2013-03-23 21:47:53.065402783 -0400 *************** *** 163,168 **** --- 163,173 ---- /* PID of the Emacs server process. */ int emacs_pid = 0; + /* Custom options */ + #define MAX_CUSTOM_OPTIONS (32) + const char *custom_options[MAX_CUSTOM_OPTIONS]; + int custopts = 0; + /* If non-NULL, a string that should form a frame parameter alist to be used for the new frame */ const char *frame_parameters = NULL; *************** *** 473,478 **** --- 478,484 ---- { alternate_editor = egetenv ("ALTERNATE_EDITOR"); + opterr = 0; /* don't display an error */ while (1) { int opt = getopt_long_only (argc, argv, *************** *** 554,559 **** --- 560,574 ---- frame_parameters = optarg; break; + case '?': + if (custopts < MAX_CUSTOM_OPTIONS) + custom_options [custopts++] = argv[optind-1]; + + else + message (TRUE, "%s: too many custom options: %s\n", progname, argv[optind-1]); + + break; + default: message (TRUE, "Try `%s --help' for more information\n", progname); exit (EXIT_FAILURE); *************** *** 660,666 **** " If EDITOR is the empty string, start Emacs in daemon\n\ mode and try connecting again\n" #endif /* not WINDOWSNT */ ! "\n\ Report bugs with M-x report-emacs-bug.\n", progname); exit (EXIT_SUCCESS); } --- 675,686 ---- " If EDITOR is the empty string, start Emacs in daemon\n\ mode and try connecting again\n" #endif /* not WINDOWSNT */ ! "--XXX Custom option XXX\n\ ! --XXX=YYY Custom option XXX with value YYY\n\ ! See `server-custom-option-list' and ! `server-custom-option-functions' to setup \n\ ! custom options\n\ ! \n\ Report bugs with M-x report-emacs-bug.\n", progname); exit (EXIT_SUCCESS); } *************** *** 1549,1555 **** /* Process options. */ decode_options (argc, argv); ! if ((argc - optind < 1) && !eval && current_frame) { message (TRUE, "%s: file name or argument required\n" "Try `%s --help' for more information\n", --- 1569,1575 ---- /* Process options. */ decode_options (argc, argv); ! if ((argc - optind < 1) && !eval && !custopts && current_frame) { message (TRUE, "%s: file name or argument required\n" "Try `%s --help' for more information\n", *************** *** 1651,1656 **** --- 1671,1693 ---- if (!current_frame && !tty) send_to_emacs (emacs_socket, "-window-system "); + if (custopts) + { + int i; + const char *opt; + for (i = 0; i < custopts; ++i) + { + /* Skip leading hyphens */ + for (opt = custom_options[i]; *opt == '-'; ++opt) + ; + + /* Send custom options to the server */ + send_to_emacs (emacs_socket, "-custom "); + quote_argument (emacs_socket, opt); + send_to_emacs (emacs_socket, " "); + } + } + if ((argc - optind > 0)) { int i; *** trunk/lisp/server.el 2013-03-02 22:36:33.309144000 -0500 --- my-work/lisp/server.el 2013-03-23 21:02:22.090413274 -0400 *************** *** 262,267 **** --- 262,302 ---- :type 'string :version "23.1") + ;; Custom options + (defcustom server-custom-option-functions nil + "A list of functions to handle custom options. + The functions accept four arguments: a buffer, a process, the + option string and the option value. If this variable is nil, + then no options are accepted, regardless of the setting of + `server-custom-option-list'. The functions on this hook variable + are called once before files or expressions are processed with + the buffer set to nil. After each file is opened, the hooks will + be called again, this time with the file buffer passed as the + first argument." + :group 'server + :type 'hook + :version "24.4") + + (defcustom server-custom-option-list nil + "A list that defines the acceptable custom options. + If this variable is nil, all unrecognized options are assumed to + be valid custom options. If this variable is a list then each + element defines an acceptable option. Each list entry should be + either a string with the option name that does not accept a + value, or a list whose first entry is a string containing the + option name and the optional second entry is a type predicate. + If the type predicate is `string-only', then the option value is + simply treated as a string; any other predicate forces the value + to be interpreted by the elisp reader and passed to the predicate + to validate it's value. " + :group 'server + :type '(repeat (choice (string :tag "Option") + (list :tag "Option=Parameter" + (string :tag "Option") + (choice (const :tag "String-only" string-only) + (function :tag "Parameter predicate"))))) + :version "24.4") + ;; We do not use `temporary-file-directory' here, because emacsclient ;; does not read the init file. (defvar server-socket-dir *************** *** 955,960 **** --- 990,998 ---- Go to the given line and column number in the next file opened. + `-custom OPTION[=PARM] + Invoke a custom option. + `-file FILENAME' Load the given file in the current frame. *************** *** 1055,1060 **** --- 1093,1099 ---- parent-id ; Window ID for XEmbed dontkill ; t if client should not be killed. commands + customs dir use-current-frame frame-parameters ;parameters for newly created frame *************** *** 1157,1162 **** --- 1196,1215 ---- (string-to-number (or (match-string 2 arg) "")))))) + ;; -custom OPTION[=PARM] + ;; invoke custom lisp code + (`"-custom" + (let ((option (pop args-left))) + (if (not server-custom-option-functions) + (error "Custom options are not accepted --%s" option) + + (if coding-system + (setq option (decode-coding-string option coding-system))) + (server-log (format "Custom: %s" option) proc) + + (push (server-custom-option-parse option) customs))) + (setq filepos nil)) + ;; -file FILENAME: Load the given file. (`"-file" (let ((file (pop args-left))) *************** *** 1233,1239 **** (let ((default-directory (if (and dir (file-directory-p dir)) dir default-directory))) ! (server-execute proc files nowait commands dontkill frame tty-name))))) (when (or frame files) --- 1286,1292 ---- (let ((default-directory (if (and dir (file-directory-p dir)) dir default-directory))) ! (server-execute proc files nowait commands customs dontkill frame tty-name))))) (when (or frame files) *************** *** 1243,1249 **** ;; condition-case (error (server-return-error proc err)))) ! (defun server-execute (proc files nowait commands dontkill frame tty-name) ;; This is run from timers and process-filters, i.e. "asynchronously". ;; But w.r.t the user, this is not really asynchronous since the timer ;; is run after 0s and the process-filter is run in response to the --- 1296,1302 ---- ;; condition-case (error (server-return-error proc err)))) ! (defun server-execute (proc files nowait commands customs dontkill frame tty-name) ;; This is run from timers and process-filters, i.e. "asynchronously". ;; But w.r.t the user, this is not really asynchronous since the timer ;; is run after 0s and the process-filter is run in response to the *************** *** 1253,1265 **** ;; including code that needs to wait. (with-local-quit (condition-case err ! (let ((buffers (server-visit-files files proc nowait))) (mapc 'funcall (nreverse commands)) ;; If we were told only to open a new client, obey ;; `initial-buffer-choice' if it specifies a file ;; or a function. ! (unless (or files commands) (let ((buf (cond ((stringp initial-buffer-choice) (find-file-noselect initial-buffer-choice)) --- 1306,1318 ---- ;; including code that needs to wait. (with-local-quit (condition-case err ! (let ((buffers (server-visit-files files customs proc nowait))) (mapc 'funcall (nreverse commands)) ;; If we were told only to open a new client, obey ;; `initial-buffer-choice' if it specifies a file ;; or a function. ! (unless (or files commands customs) (let ((buf (cond ((stringp initial-buffer-choice) (find-file-noselect initial-buffer-choice)) *************** *** 1319,1333 **** (when (> column-number 0) (move-to-column (1- column-number)))))) ! (defun server-visit-files (files proc &optional nowait) "Find FILES and return a list of buffers created. FILES is an alist whose elements are (FILENAME . FILEPOS) where FILEPOS can be nil or a pair (LINENUMBER . COLUMNNUMBER). PROC is the client that requested this operation. NOWAIT non-nil means this client is not waiting for the results, ! so don't mark these buffers specially, just visit them normally." ;; Bind last-nonmenu-event to force use of keyboard, not mouse, for queries. (let ((last-nonmenu-event t) client-record) ;; Restore the current buffer afterward, but not using save-excursion, ;; because we don't want to save point in this buffer ;; if it happens to be one of those specified by the server. --- 1372,1391 ---- (when (> column-number 0) (move-to-column (1- column-number)))))) ! (defun server-visit-files (files customs proc &optional nowait) "Find FILES and return a list of buffers created. FILES is an alist whose elements are (FILENAME . FILEPOS) where FILEPOS can be nil or a pair (LINENUMBER . COLUMNNUMBER). PROC is the client that requested this operation. NOWAIT non-nil means this client is not waiting for the results, ! so don't mark these buffers specially, just visit them normally. ! ! CUSTOMS are run within the context of the buffer of the opened ! file." ;; Bind last-nonmenu-event to force use of keyboard, not mouse, for queries. (let ((last-nonmenu-event t) client-record) + ;; Invoke custom options at the process level + (server-custom-option-call nil proc customs) ;; Restore the current buffer afterward, but not using save-excursion, ;; because we don't want to save point in this buffer ;; if it happens to be one of those specified by the server. *************** *** 1367,1372 **** --- 1425,1434 ---- ;; When the buffer is killed, inform the clients. (add-hook 'kill-buffer-hook 'server-kill-buffer nil t) (push proc server-buffer-clients)) + ;; Wait until here to invoke custom options so all the server + ;; related stuff is established. But doing this earlier + ;; before some hooks can be justified as well. + (server-custom-option-call (current-buffer) proc customs) (push (current-buffer) client-record))) (unless nowait (process-put proc 'buffers *************** *** 1510,1515 **** --- 1572,1642 ---- (when server-process (server-buffer-done (current-buffer) t)))))) \f + (defun server-custom-option-parse-1 (option svalue pred) + ;; We have encountered an OPTION with a string value SVALUE. If + ;; PRED is non-nil, then a value is expected; if nil, then no value + ;; is accepted. If PRED is `string-only, then the VALUE is left as + ;; a string; otherwise VALUE is run through the elisp parser and + ;; converted to elisp types. If PRED is t, then any value is + ;; acceptable, otherwise the PRED is a single operand predicate to + ;; verify the data type of the parsed value is acceptable. + (let (value) + ;; We can't supply a value if there is no predicate + (when (and (not pred) svalue) + (error "Custom option --%s does not accept a value" option)) + ;; Is there a value? + (if (or svalue (< 0 (length svalue))) + ;; We have a string value, is that all we want + (if (eq pred 'string-only) + (cons option svalue) + ;; We want a lisp value, parse it make sure we got it all + (setq value (read-from-string svalue)) + (when (> (length svalue) (cdr value)) + (error "Invalid custom option value --%s=%s" option svalue)) + (setq value (car value)) + ;; Validate it + (if (or (eq pred t) + (funcall pred value)) + (cons option value) + (error "Custom option --%s expected a(n) `%s' value, got `%s'" + option pred value))) + ;; Treat nil and empty strings as a nil value + (cons option nil)))) + + (defun server-custom-option-parse (option-value) + (let (option svalue result) + (if (not (string-match "\\`\\([^=]+\\)\\(?:[=]\\(.*\\)\\)?\\'" option-value)) + (error "Invalid custom option format --%s" option-value) + (setq option (match-string 1 option-value) + svalue (match-string 2 option-value)) + ;; look-up the option + (if (not server-custom-option-list) + (setq result (cons option svalue)) + (dolist (o server-custom-option-list) + (cond + ;; defn is a string, expect no value + ((and (stringp o) + (string= o option)) + (setq result + (server-custom-option-parse-1 option svalue nil))) + ;; defn is a list, expect a value, second elem is type + ((and (consp o) + (string= (car o) option)) + (setq result + (server-custom-option-parse-1 option svalue (or (cadr o) t)))))))) + (if result + result + (error "Unrecognized custom option --%s" option)))) + + (defun server-custom-option-call (buf proc customs) + (server-log (format "server-custom-option-call: `%s'" + (or (buffer-name buf) (process-name proc))) proc) + (with-local-quit + (dolist (c customs) + (run-hook-with-args 'server-custom-option-functions + buf proc (car c) (cdr c))))) + + \f (defun server-edit (&optional arg) "Switch to next server editing buffer; say \"Done\" for current buffer. If a server buffer is current, it is marked \"done\" and optionally saved. ^ permalink raw reply [flat|nested] 13+ messages in thread
[parent not found: <87zjxtq304.fsf@michael-laptop.hsd1.ma.comcast.net>]
[parent not found: <jwv38vlpnpl.fsf-monnier+emacs@gnu.org>]
* Re: Proposal: emacsclient --readonly [not found] ` <jwv38vlpnpl.fsf-monnier+emacs@gnu.org> @ 2013-03-24 16:22 ` Michael Mauger 2013-03-25 13:15 ` Stefan Monnier 0 siblings, 1 reply; 13+ messages in thread From: Michael Mauger @ 2013-03-24 16:22 UTC (permalink / raw) To: Stefan Monnier; +Cc: emacs-devel@gnu.org ----- Original Message ----- > From: Stefan Monnier <monnier@iro.umontreal.ca> > >> server-custom-option-functions: > [...] >> server-custom-option-list nil > > How 'bout a `server-custom-option-function' which takes the list of > arguments and returns a new list of arguments? > > > Stefan I don't understand your comment--the `server-custom-option-list' variable is the list of options for parsing; the `server-custom-option-functions' variable is the list of functions to handle the options. I can certainly see how we can merge these two but I'm still confused by your comment. If we were to merge the variables, we could end up with a list with entries like the following: ("OPTION" {PRED|t|nil|string-only} HANDLER) I don't understand what arguments "takes the list of arguments and returns a new list of arguments" you are referring to? Do you mean parsing the command line args and removing the ones that it is processing? I had looked at that a little, but figured I'd let the parsing be done in C in emacsclient and just forward the unrecognized to lisp. But even if we assemble the custom args into a list and try to handle them in lisp, how does that simplify the lisp side of the fence? Thanks, Michael ^ permalink raw reply [flat|nested] 13+ messages in thread
* Re: Proposal: emacsclient --readonly 2013-03-24 16:22 ` Michael Mauger @ 2013-03-25 13:15 ` Stefan Monnier 2013-03-26 2:56 ` Michael Mauger 0 siblings, 1 reply; 13+ messages in thread From: Stefan Monnier @ 2013-03-25 13:15 UTC (permalink / raw) To: Michael Mauger; +Cc: emacs-devel@gnu.org > I don't understand your comment--the `server-custom-option-list' > variable is the list of options for parsing; the > `server-custom-option-functions' variable is the list of functions to > handle the options. I can certainly see how we can merge these two > but I'm still confused by your comment. > If we were to merge the variables, we could end up with a list with > entries like the following: > ("OPTION" {PRED|t|nil|string-only} HANDLER) My proposal is that you don't declare which function handles which argument. Just use a single server-custom-option-function which is called regardless of which arguments were received and can do anything it likes with them. > I don't understand what arguments "takes the list of arguments and > returns a new list of arguments" you are referring to? Do you mean > parsing the command line args and removing the ones that it is > processing? Yes. > I had looked at that a little, but figured I'd let the parsing > be done in C in emacsclient and just forward the unrecognized to lisp. Hmm... I don't understand what you're saying here. I'm only talking about the server.el side, and AFAICT you also were only talking about the server.el side, since the client side does not have access to Elisp customizations. But yes, clearly the C side would do the parsing it does now, except that it would additionally pass through any unrecognized argument. > But even if we assemble the custom args into a list and try to handle > them in lisp, how does that simplify the lisp side of the fence? The server.el code already turns the string received from emacsclient into a list of strings. My suggestion is to try and limit the server.el change to something like the patch below. Stefan === modified file 'lisp/server.el' --- lisp/server.el 2013-02-13 04:31:09 +0000 +++ lisp/server.el 2013-03-25 13:07:44 +0000 @@ -909,6 +909,12 @@ (process-put proc 'continuation nil) (if continuation (ignore-errors (funcall continuation))))) +(defvar server-custom-option-function #'identity + "Function to process additional emacsclient arguments. +The function is called with a single argument (a list of args received +from emacsclient) and returns the list of args left to process. +The easiest way to modify this variable is through `add-function'.") + (cl-defun server-process-filter (proc string) "Process a request from the server to edit some files. PROC is the server process. STRING consists of a sequence of @@ -1067,7 +1076,8 @@ (setq string (substring string (match-end 0))) (setq args-left (mapcar 'server-unquote-arg (split-string request " " t))) - (while args-left + (while (setq arg-left (funcall server-custom-option-function + args-left)) (pcase (pop args-left) ;; -version CLIENT-VERSION: obsolete at birth. (`"-version" (pop args-left)) ^ permalink raw reply [flat|nested] 13+ messages in thread
* Re: Proposal: emacsclient --readonly 2013-03-25 13:15 ` Stefan Monnier @ 2013-03-26 2:56 ` Michael Mauger 2013-03-26 13:10 ` Stefan Monnier 0 siblings, 1 reply; 13+ messages in thread From: Michael Mauger @ 2013-03-26 2:56 UTC (permalink / raw) To: Stefan Monnier; +Cc: emacs-devel@gnu.org > From: Stefan Monnier <monnier@iro.umontreal.ca> > > My suggestion is to try and limit the server.el change to > something like the patch below. > > === modified file 'lisp/server.el' > --- lisp/server.el 2013-02-13 04:31:09 +0000 > +++ lisp/server.el 2013-03-25 13:07:44 +0000 > @@ -909,6 +909,12 @@ > (process-put proc 'continuation nil) > (if continuation (ignore-errors (funcall continuation))))) > > +(defvar server-custom-option-function #'identity > + "Function to process additional emacsclient arguments. > +The function is called with a single argument (a list of args received > +from emacsclient) and returns the list of args left to process. > +The easiest way to modify this variable is through `add-function'.") > + > (cl-defun server-process-filter (proc string) > "Process a request from the server to edit some files. > PROC is the server process. STRING consists of a sequence of > @@ -1067,7 +1076,8 @@ > (setq string (substring string (match-end 0))) > (setq args-left > (mapcar 'server-unquote-arg (split-string request " " > t))) > - (while args-left > + (while (setq arg-left (funcall server-custom-option-function > + args-left)) > (pcase (pop args-left) > ;; -version CLIENT-VERSION: obsolete at birth. > (`"-version" (pop args-left)) > So with this structure, how would I implement the --readonly custom argument? My guess is that the server-custom-option-function would scan the incoming list of options for the -nowait entry because that will determine our exit action. However, we will leave the entry in place because it needs the standard processing. We will then look for the --readonly entry and record that we want to view files rather than edit them. We will then remove the --readonly entry from the list to allow the remainder of the processing to continue. We can only record these settings at this point because the files have not been actually opened yet. And then we need to attach ourselves to the server-visit-hook possibly to actually activate the readonly behavior. Or we could essentially duplicate the server processing by consuming all of the arguments and replicating their actions with the wrinkles we wish to introduce which is obviously not a great solution. So it looks with the solution may be more flexible but requires global variables specific to the feature and two hook functions to implement it. My goal was for a solution the declares the options we want to add and somewhat automate the parsing and validation of the option value so that custom options behave as other command line arguments do. It then provides a handler function that implements the feature either globally or on a per-buffer basis. With this design, features could be added to emacsclient by loading a library and adding an entry to a list that defines the syntax and handling. I have reworked my code to use a single list with option-name, handler function, and value-predicate. The result is that we could implement modules in ELPA that would enhance emacsclient with new options without requiring the modules to be integrated together into a single function. The readonly feature becomes about 10 lines of code; easily maintained and separate from any other features. -- Michael ^ permalink raw reply [flat|nested] 13+ messages in thread
* Re: Proposal: emacsclient --readonly 2013-03-26 2:56 ` Michael Mauger @ 2013-03-26 13:10 ` Stefan Monnier 2013-03-27 2:02 ` Michael Mauger 0 siblings, 1 reply; 13+ messages in thread From: Stefan Monnier @ 2013-03-26 13:10 UTC (permalink / raw) To: Michael Mauger; +Cc: emacs-devel@gnu.org > So with this structure, how would I implement the --readonly custom > argument? Good question. > My guess is that the server-custom-option-function would scan the > incoming list of options for the -nowait entry because that will > determine our exit action. Hmm... why do we care about -nowait? > We will then look for the --readonly entry and record that we want to > view files rather than edit them. We will then remove the --readonly > entry from the list to allow the remainder of the processing > to continue. Right, we check (equal "--readonly" (car args)) and if non-nil, we (pop args) and somehow register the "put in read-only mode" somewhere. > behavior. Or we could essentially duplicate the server processing by > consuming all of the arguments and replicating their actions with the > wrinkles we wish to introduce which is obviously not a great solution. Right, we don't want to go there. > So it looks with the solution may be more flexible but requires global > variables specific to the feature and two hook functions to implement > it. My goal was for a solution the declares the options we want to add > and somewhat automate the parsing and validation of the option value > so that custom options behave as other command line arguments do. It > then provides a handler function that implements the feature either > globally or on a per-buffer basis. With this design, features could > be added to emacsclient by loading a library and adding an entry to a > list that defines the syntax and handling. Obviously, my proposal is incomplete indeed because it does not address the need to "register the `put in read-only mode' somewhere". But I think your design is too narrowly constrained by the needs you see now. I think we should aim for a design that could at least accommodate some of the features currently hard-coded such as --eval. Ideally it should also accommodate something like "--diff FILE1 FILE2" which would call `diff' on the two files and might even be made to accept "--diff FILE1 FILE2 FILE3" to do a 3-way merge. Stefan ^ permalink raw reply [flat|nested] 13+ messages in thread
* Re: Proposal: emacsclient --readonly 2013-03-26 13:10 ` Stefan Monnier @ 2013-03-27 2:02 ` Michael Mauger 2013-03-27 2:33 ` Stefan Monnier 0 siblings, 1 reply; 13+ messages in thread From: Michael Mauger @ 2013-03-27 2:02 UTC (permalink / raw) To: Stefan Monnier; +Cc: emacs-devel@gnu.org > From: Stefan Monnier <monnier@iro.umontreal.ca> > >> My guess is that the server-custom-option-function would scan the >> incoming list of options for the -nowait entry because that will >> determine our exit action. > > Hmm... why do we care about -nowait? > If there is an emacsclient waiting for us to finish, then we have to call server-buffer-done. > But I think your design is too narrowly constrained by the needs you see > now. I think we should aim for a design that could at least accommodate > some of the features currently hard-coded such as --eval. > > Ideally it should also accommodate something like "--diff FILE1 FILE2" > which would call `diff' on the two files and might even be made to > accept "--diff FILE1 FILE2 FILE3" to do a 3-way merge. > Okay at least we're sorta on the same page. I had been thinking about --diff at one point and the issues got very messy fast. So here's my thinking, let me know if you think I should continue: In the C code, if there are any unrecognized options they will be sent over as a -custom option followed by the non-option parameters as -args On the server side, the elisp will call the handler for each -custom option and pass the args along with it. The handler can pull off the args it needs. If there are any arguments left over, then they will be passed either to --eval or treated as files. The other required change will be to break out some of the file processing so that the logic can be reused in custom options. * file-name generation * visit a file This will make the change more complex but won't really alter the behavior, just refactor it so it's more useful to custom options. I should have a prototype in a day or two, unless you think I'm still missing the mark. -- Michael ^ permalink raw reply [flat|nested] 13+ messages in thread
* Re: Proposal: emacsclient --readonly 2013-03-27 2:02 ` Michael Mauger @ 2013-03-27 2:33 ` Stefan Monnier 2013-03-27 13:47 ` Michael Mauger 0 siblings, 1 reply; 13+ messages in thread From: Stefan Monnier @ 2013-03-27 2:33 UTC (permalink / raw) To: Michael Mauger; +Cc: emacs-devel@gnu.org > In the C code, if there are any unrecognized options they will be sent over > as a -custom option followed by the non-option parameters as -args I'm not sure I understand right, but it doesn't sound like it's going a direction I like: I'd like to reduce the difference between the C-level command line argument vector received by emacsclient and the list of strings received by server.el. > On the server side, the elisp will call the handler for each -custom option > and pass the args along with it. The --diff and --eval options don't take just one "argument", which is why I don't want to hardcode this idea of a "--option ARG" format. > The other required change will be to break out some of the file processing > so that the logic can be reused in custom options. > * file-name generation > * visit a file > This will make the change more complex but won't really alter the > behavior, just refactor it so it's more useful to custom options. Refactoring sounds fine. Stefan ^ permalink raw reply [flat|nested] 13+ messages in thread
* Re: Proposal: emacsclient --readonly 2013-03-27 2:33 ` Stefan Monnier @ 2013-03-27 13:47 ` Michael Mauger 2013-03-30 23:03 ` Michael Mauger 0 siblings, 1 reply; 13+ messages in thread From: Michael Mauger @ 2013-03-27 13:47 UTC (permalink / raw) To: Stefan Monnier; +Cc: emacs-devel@gnu.org > >> In the C code, if there are any unrecognized options they will be sent > over >> as a -custom option followed by the non-option parameters as -args > > I'm not sure I understand right, but it doesn't sound like it's > going > a direction I like: I'd like to reduce the difference between the > C-level command line argument vector received by emacsclient and the > list of strings received by server.el. > Not sure how this is possible because the parameters are not sent as they are encountered. Based on the settings the client programs behavior is altered so I will try to accomplish this as best as I can. >> On the server side, the elisp will call the handler for each -custom > option >> and pass the args along with it. > > The --diff and --eval options don't take just one "argument", > which is > why I don't want to hardcode this idea of a "--option ARG" format. > I clearly wasn't very eloquent last nite, in fact, after reading what I wrote, I'm not sure I knew what I meant. I started to hack at this and I think I can describe it with more clarity now: In the C code, we use getopt_long_only to parse the command line args, so I want to make sure that the custom options behave as the built-in options do, so there is some complexity here. If an option is encountered that is not recognized, then we will parse the argument string to see if it is in the format OPTION=ARG, if it is, then we send the following "-custom OPTION -arg ARG". In this case, that is the only option value and parsing continues with the next comand line arg. If the option lacks an equal sign, then the option is sent with "-custom OPTION", it then scans thru the following arguments and any that don't start with a hyphen will be sent as "-arg XXX" so that there may be many -arg entries following the -custom. The danger, of course, is that we may associate an arg with an option because the C program knows nothing about the option or the number of args it takes. So the elisp side will have to take any unused args and treat them as either -file or -eval entries. There be dragons here. If the --eval option was specified, the flag is not sent so the elisp side doesn't know to treat the unused args as expressions rather than file names unless there are other non- option arguments supplied. This is going to require some addl info being sent to elisp but it's not a real complication. Here's an example: emacsclient --eval --my-new-option A B C Sends: -... standard entries dispay, dir, env, et al -custom my-new-option -arg A -arg B -arg C If my-new-option requires two args then it consumes A and B. This leaves C as a unused entry. Since there are no -file or -eval entries we don't know how to handle the unused unless there is a separate entry to indicate how unused args should be handled. The ambiguity can be addressed by specifying the arguments as: emacsclient --eval C --my-new-option A B or emacsclient --my-new-option A B --eval C There may be some issues since the options and parameters are not sent to the elisp code in the same order they are specified. This isn't easy to correct so I expect that some custom options may have unintended behavior because the sequence of file-opens or expression-evaluations may not happen in the same order. > > Refactoring sounds fine. > I hope this clarifies things a bit. As I said, I've got some of the code ready and should wrap it up tonite. I'll then try to implement --readonly and --diff to demonstrate how this will all look. -- Michael ^ permalink raw reply [flat|nested] 13+ messages in thread
* Re: Proposal: emacsclient --readonly 2013-03-27 13:47 ` Michael Mauger @ 2013-03-30 23:03 ` Michael Mauger 0 siblings, 0 replies; 13+ messages in thread From: Michael Mauger @ 2013-03-30 23:03 UTC (permalink / raw) To: emacs-devel@gnu.org; +Cc: Stefan Monnier >> I'm not sure I understand right, but it doesn't sound like it's >> going a direction I like: I'd like to reduce the difference between >> the C-level command line argument vector received by >> emacsclient and the list of strings received by server.el. >> I'm giving up. Without a significant rewrite of emacsclient.c and server.el, I don't see your vision of custom options coming to fruition. I am running a local edit similar to the second version I sent to the mailing list that supports --readonly, --diff, --merge, and a little more invasive patch that implements passing a default Tramp prefix to allow a remote editor to open the local file. The patch I use has: * separate handlers for each option with built-in parsing and validation of a single option argument * a hook for handling each non-option parameter of emacsclient to alter the filename before the filename is opened * a hook to replace the default open action of each file arg * a hook to handle each buffer that gets opened; similar to `server-visit-hook' but called later once all server setup is complete and dedicated to custom options. * a final hook at the end of the file args to validate the overall server state before control is passed to the user. * allows custom options to be defined, maintained and installed independently of each other without a point of contact beyond server.el (this is important for me since the custom features I implement at home differ from those I might use at a client site.) As I tried to implement the simplified parsing that was proposed but encountered the existing server processing which lacks the structure to implement the requested features. The patch that I am using takes advantage of several new hooks that allow the new functionality to be inserted into the existing processing model. The challenge for me will now be for me to keep my patch in sync. -- Michael ^ permalink raw reply [flat|nested] 13+ messages in thread
* Re: Proposal: emacsclient --readonly
@ 2013-09-27 18:22 Rüdiger Sonderfeld
0 siblings, 0 replies; 13+ messages in thread
From: Rüdiger Sonderfeld @ 2013-09-27 18:22 UTC (permalink / raw)
To: monnier; +Cc: emacs-devel
Hello,
what happened to this patch? It would be very useful to me right now.
There should probably be a way to add functions to `commands' (in server-
process-filter) though. With this patch it could be done by adding an "--
eval" statement to the args. But there should probably be a clean way.
I'm currently attempting to write an info(1) like program which loads the info
page in the running Emacs session. I need this to support the "doc" comment
in `inferior-octave'. With this patch it could be done without a lot of
hacks.
Regards,
Rüdiger
> === modified file 'lisp/server.el'
> --- lisp/server.el 2013-02-13 04:31:09 +0000
> +++ lisp/server.el 2013-03-25 13:07:44 +0000
> @@ -909,6 +909,12 @@
>
> (process-put proc 'continuation nil)
> (if continuation (ignore-errors (funcall continuation)))))
>
> +(defvar server-custom-option-function #'identity
> + "Function to process additional emacsclient arguments.
> +The function is called with a single argument (a list of args received
> +from emacsclient) and returns the list of args left to process.
> +The easiest way to modify this variable is through `add-function'.")
> +
>
> (cl-defun server-process-filter (proc string)
>
> "Process a request from the server to edit some files.
>
> PROC is the server process. STRING consists of a sequence of
>
> @@ -1067,7 +1076,8 @@
>
> (setq string (substring string (match-end 0)))
> (setq args-left
>
> (mapcar 'server-unquote-arg (split-string request " " t)))
>
> - (while args-left
> + (while (setq arg-left (funcall server-custom-option-function
> + args-left))
>
> (pcase (pop args-left)
>
> ;; -version CLIENT-VERSION: obsolete at birth.
> (`"-version" (pop args-left))
^ permalink raw reply [flat|nested] 13+ messages in thread
end of thread, other threads:[~2013-09-27 18:22 UTC | newest] Thread overview: 13+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2013-03-14 2:23 Proposal: emacsclient --readonly michael 2013-03-14 2:45 ` W. Greenhouse 2013-03-14 3:31 ` Stefan Monnier 2013-03-24 3:45 ` Michael Mauger [not found] <87zjxtq304.fsf@michael-laptop.hsd1.ma.comcast.net> [not found] ` <jwv38vlpnpl.fsf-monnier+emacs@gnu.org> 2013-03-24 16:22 ` Michael Mauger 2013-03-25 13:15 ` Stefan Monnier 2013-03-26 2:56 ` Michael Mauger 2013-03-26 13:10 ` Stefan Monnier 2013-03-27 2:02 ` Michael Mauger 2013-03-27 2:33 ` Stefan Monnier 2013-03-27 13:47 ` Michael Mauger 2013-03-30 23:03 ` Michael Mauger -- strict thread matches above, loose matches on Subject: below -- 2013-09-27 18:22 Rüdiger Sonderfeld
Code repositories for project(s) associated with this external index https://git.savannah.gnu.org/cgit/emacs.git https://git.savannah.gnu.org/cgit/emacs/org-mode.git This is an external index of several public inboxes, see mirroring instructions on how to clone and mirror all data and code used by this external index.