From d3605ed7d173aa1c23ad8944cf92e7b810330cb8 Mon Sep 17 00:00:00 2001 From: Samer Masterson Date: Thu, 28 May 2015 14:30:31 -0700 Subject: [PATCH] Show files when `initial-buffer-choice' is non-nil * startup.el (command-line-1): When Emacs is given a file as an argument and `initial-buffer-choice' is non-nil, display both the file and `initial-buffer-choice'. For more than one file, show `initial-buffer-choice' and *Buffer List*. Refactor display-changing commands out of the command line arg parser. (initial-buffer-choice): Clarify docstring. --- doc/lispref/os.texi | 12 +- etc/NEWS | 8 + lisp/startup.el | 521 +++++++++++++++++++++++--------------------- 3 files changed, 290 insertions(+), 251 deletions(-) diff --git a/doc/lispref/os.texi b/doc/lispref/os.texi index 97fa083e07d..d18dee39429 100644 --- a/doc/lispref/os.texi +++ b/doc/lispref/os.texi @@ -193,17 +193,23 @@ It processes any command-line options that were not handled earlier. @item It now exits if the option @code{--batch} was specified. +@item +If the @file{*scratch*} buffer exists and is empty, it inserts +@code{initial-scratch-message} into that buffer. + @item If @code{initial-buffer-choice} is a string, it visits the file (or directory) with that name. If it is a function, it calls the function -with no arguments and selects the buffer that it returns. +with no arguments and selects the buffer that it returns. If one file +is given as a command line argument, the file is opened alongside +@code{initial-buffer-choice}. If more than one file is given, the +@file{*Buffer List*} buffer is shown alongside +@code{initial-buffer-choice}. @ignore @c I do not think this should be mentioned. AFAICS it is just a dodge @c around inhibit-startup-screen not being settable on a site-wide basis. If it is @code{t}, it selects the @file{*scratch*} buffer. @end ignore -If the @file{*scratch*} buffer exists and is empty, it inserts -@code{initial-scratch-message} into that buffer. @c To make things nice and confusing, the next three items can be @c called from two places. If displaying a startup screen, they are diff --git a/etc/NEWS b/etc/NEWS index 77953c8e9ab..d07057ef4b2 100644 --- a/etc/NEWS +++ b/etc/NEWS @@ -70,6 +70,14 @@ so if you want to use it, you can always take a copy from an older Emacs. * Startup Changes in Emacs 25.1 ++++ +** When Emacs is given a file as a command line argument and +`initial-buffer-choice' is non-nil, display both the file and +`initial-buffer-choice'. When Emacs is given more than one file and +`initial-buffer-choice' is non-nil, show `initial-buffer-choice' +and *Buffer List*. This makes Emacs convenient to use from the +command line when `initial-buffer-choice' is non-nil. + * Changes in Emacs 25.1 diff --git a/lisp/startup.el b/lisp/startup.el index a24198bc651..3c9ada682d3 100644 --- a/lisp/startup.el +++ b/lisp/startup.el @@ -47,6 +47,9 @@ visiting the file or directory that the string specifies. If the value is a function, call it with no arguments and switch to the buffer that it returns. If t, open the `*scratch*' buffer. +When `initial-buffer-choice' is non-nil, the startup screen is +inhibited. + If you use `emacsclient' with no target file, then it obeys any string or function value that this variable has." :type '(choice @@ -2146,238 +2149,238 @@ A fancy display is used on graphic displays, normal otherwise." (See the node Pure Storage in the Lisp manual for details.)" :warning)) - (let ((file-count 0) - (command-line-args-left args-left) - first-file-buffer) - (when command-line-args-left - ;; We have command args; process them. - (let ((dir command-line-default-directory) - tem - ;; This approach loses for "-batch -L DIR --eval "(require foo)", - ;; if foo is intended to be found in DIR. - ;; - ;; The directories listed in --directory/-L options will *appear* - ;; at the front of `load-path' in the order they appear on the - ;; command-line. We cannot do this by *placing* them at the front - ;; in the order they appear, so we need this variable to hold them, - ;; temporarily. - ;; - ;; To DTRT we keep track of the splice point and modify `load-path' - ;; straight away upon any --directory/-L option. - splice - just-files ;; t if this follows the magic -- option. - ;; This includes our standard options' long versions - ;; and long versions of what's on command-switch-alist. - (longopts - (append '("--funcall" "--load" "--insert" "--kill" - "--directory" "--eval" "--execute" "--no-splash" - "--find-file" "--visit" "--file" "--no-desktop") - (mapcar (lambda (elt) (concat "-" (car elt))) - command-switch-alist))) - (line 0) - (column 0)) + ;; `displayable-buffers' is a list of buffers that may be displayed, + ;; which includes files parsed from the command line arguments and + ;; `initial-buffer-choice'. All of the display logic happens at the + ;; end of this `let'. As files as processed from the command line + ;; arguments, their buffers are prepended to `displayable-buffers' + ;; but they are not displayed until command line parsing has + ;; finished. + (let ((displayable-buffers nil)) + ;; This `let' processes the command line arguments. + (let ((command-line-args-left args-left)) + (when command-line-args-left + ;; We have command args; process them. + (let* ((dir command-line-default-directory) + tem + ;; This approach loses for "-batch -L DIR --eval "(require foo)", + ;; if foo is intended to be found in DIR. + ;; + ;; The directories listed in --directory/-L options will *appear* + ;; at the front of `load-path' in the order they appear on the + ;; command-line. We cannot do this by *placing* them at the front + ;; in the order they appear, so we need this variable to hold them, + ;; temporarily. + ;; + ;; To DTRT we keep track of the splice point and modify `load-path' + ;; straight away upon any --directory/-L option. + splice + just-files ;; t if this follows the magic -- option. + ;; This includes our standard options' long versions + ;; and long versions of what's on command-switch-alist. + (longopts + (append '("--funcall" "--load" "--insert" "--kill" + "--directory" "--eval" "--execute" "--no-splash" + "--find-file" "--visit" "--file" "--no-desktop") + (mapcar (lambda (elt) (concat "-" (car elt))) + command-switch-alist))) + (line 0) + (column 0) + ;; `process-file-arg' opens a file buffer for `name' + ;; without switching to the buffer, adds the buffer to + ;; `displayable-buffers', and puts the point at + ;; `line':`column'. `line' and `column' are both reset + ;; to zero when `process-file-arg' returns. + (process-file-arg + (lambda (name) + (let* ((file (expand-file-name + (command-line-normalize-file-name name) + dir)) + (buf (find-file-noselect file))) + (setq displayable-buffers (cons buf displayable-buffers)) + (with-current-buffer buf + (unless (zerop line) + (goto-char (point-min)) + (forward-line (1- line))) + (setq line 0) + (unless (< column 1) + (move-to-column (1- column))) + (setq column 0)))))) - ;; Add the long X options to longopts. - (dolist (tem command-line-x-option-alist) - (if (string-match "^--" (car tem)) - (push (car tem) longopts))) + ;; Add the long X options to longopts. + (dolist (tem command-line-x-option-alist) + (if (string-match "^--" (car tem)) + (push (car tem) longopts))) - ;; Add the long NS options to longopts. - (dolist (tem command-line-ns-option-alist) - (if (string-match "^--" (car tem)) - (push (list (car tem)) longopts))) + ;; Add the long NS options to longopts. + (dolist (tem command-line-ns-option-alist) + (if (string-match "^--" (car tem)) + (push (list (car tem)) longopts))) - ;; Loop, processing options. - (while command-line-args-left - (let* ((argi (car command-line-args-left)) - (orig-argi argi) - argval completion) - (setq command-line-args-left (cdr command-line-args-left)) + ;; Loop, processing options. + (while command-line-args-left + (let* ((argi (car command-line-args-left)) + (orig-argi argi) + argval completion) + (setq command-line-args-left (cdr command-line-args-left)) - ;; Do preliminary decoding of the option. - (if just-files - ;; After --, don't look for options; treat all args as files. - (setq argi "") - ;; Convert long options to ordinary options - ;; and separate out an attached option argument into argval. - (when (string-match "\\`\\(--[^=]*\\)=" argi) - (setq argval (substring argi (match-end 0)) - argi (match-string 1 argi))) - (when (string-match "\\`--?[^-]" orig-argi) - (setq completion (try-completion argi longopts)) - (if (eq completion t) - (setq argi (substring argi 1)) - (if (stringp completion) - (let ((elt (member completion longopts))) - (or elt - (error "Option `%s' is ambiguous" argi)) - (setq argi (substring (car elt) 1))) - (setq argval nil - argi orig-argi))))) + ;; Do preliminary decoding of the option. + (if just-files + ;; After --, don't look for options; treat all args as files. + (setq argi "") + ;; Convert long options to ordinary options + ;; and separate out an attached option argument into argval. + (when (string-match "\\`\\(--[^=]*\\)=" argi) + (setq argval (substring argi (match-end 0)) + argi (match-string 1 argi))) + (when (string-match "\\`--?[^-]" orig-argi) + (setq completion (try-completion argi longopts)) + (if (eq completion t) + (setq argi (substring argi 1)) + (if (stringp completion) + (let ((elt (member completion longopts))) + (or elt + (error "Option `%s' is ambiguous" argi)) + (setq argi (substring (car elt) 1))) + (setq argval nil + argi orig-argi))))) - ;; Execute the option. - (cond ((setq tem (assoc argi command-switch-alist)) - (if argval - (let ((command-line-args-left - (cons argval command-line-args-left))) - (funcall (cdr tem) argi)) - (funcall (cdr tem) argi))) + ;; Execute the option. + (cond ((setq tem (assoc argi command-switch-alist)) + (if argval + (let ((command-line-args-left + (cons argval command-line-args-left))) + (funcall (cdr tem) argi)) + (funcall (cdr tem) argi))) - ((equal argi "-no-splash") - (setq inhibit-startup-screen t)) + ((equal argi "-no-splash") + (setq inhibit-startup-screen t)) - ((member argi '("-f" ; what the manual claims - "-funcall" - "-e")) ; what the source used to say - (setq inhibit-startup-screen t) - (setq tem (intern (or argval (pop command-line-args-left)))) - (if (commandp tem) - (command-execute tem) - (funcall tem))) + ((member argi '("-f" ; what the manual claims + "-funcall" + "-e")) ; what the source used to say + (setq inhibit-startup-screen t) + (setq tem (intern (or argval (pop command-line-args-left)))) + (if (commandp tem) + (command-execute tem) + (funcall tem))) - ((member argi '("-eval" "-execute")) - (setq inhibit-startup-screen t) - (eval (read (or argval (pop command-line-args-left))))) + ((member argi '("-eval" "-execute")) + (setq inhibit-startup-screen t) + (eval (read (or argval (pop command-line-args-left))))) - ((member argi '("-L" "-directory")) - ;; -L :/foo adds /foo to the _end_ of load-path. - (let (append) - (if (string-match-p - (format "\\`%s" path-separator) - (setq tem (or argval (pop command-line-args-left)))) - (setq tem (substring tem 1) - append t)) - (setq tem (expand-file-name - (command-line-normalize-file-name tem))) - (cond (append (setq load-path - (append load-path (list tem))) - (if splice (setq splice load-path))) - (splice (setcdr splice (cons tem (cdr splice))) - (setq splice (cdr splice))) - (t (setq load-path (cons tem load-path) - splice load-path))))) + ((member argi '("-L" "-directory")) + ;; -L :/foo adds /foo to the _end_ of load-path. + (let (append) + (if (string-match-p + (format "\\`%s" path-separator) + (setq tem (or argval (pop command-line-args-left)))) + (setq tem (substring tem 1) + append t)) + (setq tem (expand-file-name + (command-line-normalize-file-name tem))) + (cond (append (setq load-path + (append load-path (list tem))) + (if splice (setq splice load-path))) + (splice (setcdr splice (cons tem (cdr splice))) + (setq splice (cdr splice))) + (t (setq load-path (cons tem load-path) + splice load-path))))) - ((member argi '("-l" "-load")) - (let* ((file (command-line-normalize-file-name - (or argval (pop command-line-args-left)))) - ;; Take file from default dir if it exists there; - ;; otherwise let `load' search for it. - (file-ex (expand-file-name file))) - (when (file-exists-p file-ex) - (setq file file-ex)) - (load file nil t))) + ((member argi '("-l" "-load")) + (let* ((file (command-line-normalize-file-name + (or argval (pop command-line-args-left)))) + ;; Take file from default dir if it exists there; + ;; otherwise let `load' search for it. + (file-ex (expand-file-name file))) + (when (file-exists-p file-ex) + (setq file file-ex)) + (load file nil t))) - ;; This is used to handle -script. It's not clear - ;; we need to document it (it is totally internal). - ((member argi '("-scriptload")) - (let* ((file (command-line-normalize-file-name - (or argval (pop command-line-args-left)))) - ;; Take file from default dir. - (file-ex (expand-file-name file))) - (load file-ex nil t t))) + ;; This is used to handle -script. It's not clear + ;; we need to document it (it is totally internal). + ((member argi '("-scriptload")) + (let* ((file (command-line-normalize-file-name + (or argval (pop command-line-args-left)))) + ;; Take file from default dir. + (file-ex (expand-file-name file))) + (load file-ex nil t t))) - ((equal argi "-insert") - (setq inhibit-startup-screen t) - (setq tem (or argval (pop command-line-args-left))) - (or (stringp tem) - (error "File name omitted from `-insert' option")) - (insert-file-contents (command-line-normalize-file-name tem))) + ((equal argi "-insert") + (setq inhibit-startup-screen t) + (setq tem (or argval (pop command-line-args-left))) + (or (stringp tem) + (error "File name omitted from `-insert' option")) + (insert-file-contents (command-line-normalize-file-name tem))) - ((equal argi "-kill") - (kill-emacs t)) + ((equal argi "-kill") + (kill-emacs t)) - ;; This is for when they use --no-desktop with -q, or - ;; don't load Desktop in their .emacs. If desktop.el - ;; _is_ loaded, it will handle this switch, and we - ;; won't see it by the time we get here. - ((equal argi "-no-desktop") - (message "\"--no-desktop\" ignored because the Desktop package is not loaded")) + ;; This is for when they use --no-desktop with -q, or + ;; don't load Desktop in their .emacs. If desktop.el + ;; _is_ loaded, it will handle this switch, and we + ;; won't see it by the time we get here. + ((equal argi "-no-desktop") + (message "\"--no-desktop\" ignored because the Desktop package is not loaded")) - ((string-match "^\\+[0-9]+\\'" argi) - (setq line (string-to-number argi))) + ((string-match "^\\+[0-9]+\\'" argi) + (setq line (string-to-number argi))) - ((string-match "^\\+\\([0-9]+\\):\\([0-9]+\\)\\'" argi) - (setq line (string-to-number (match-string 1 argi)) - column (string-to-number (match-string 2 argi)))) + ((string-match "^\\+\\([0-9]+\\):\\([0-9]+\\)\\'" argi) + (setq line (string-to-number (match-string 1 argi)) + column (string-to-number (match-string 2 argi)))) - ((setq tem (assoc orig-argi command-line-x-option-alist)) - ;; Ignore X-windows options and their args if not using X. - (setq command-line-args-left - (nthcdr (nth 1 tem) command-line-args-left))) + ((setq tem (assoc orig-argi command-line-x-option-alist)) + ;; Ignore X-windows options and their args if not using X. + (setq command-line-args-left + (nthcdr (nth 1 tem) command-line-args-left))) - ((setq tem (assoc orig-argi command-line-ns-option-alist)) - ;; Ignore NS-windows options and their args if not using NS. - (setq command-line-args-left - (nthcdr (nth 1 tem) command-line-args-left))) + ((setq tem (assoc orig-argi command-line-ns-option-alist)) + ;; Ignore NS-windows options and their args if not using NS. + (setq command-line-args-left + (nthcdr (nth 1 tem) command-line-args-left))) - ((member argi '("-find-file" "-file" "-visit")) - (setq inhibit-startup-screen t) - ;; An explicit option to specify visiting a file. - (setq tem (or argval (pop command-line-args-left))) - (unless (stringp tem) - (error "File name omitted from `%s' option" argi)) - (setq file-count (1+ file-count)) - (let ((file (expand-file-name - (command-line-normalize-file-name tem) - dir))) - (if (= file-count 1) - (setq first-file-buffer (find-file file)) - (find-file-other-window file))) - (unless (zerop line) - (goto-char (point-min)) - (forward-line (1- line))) - (setq line 0) - (unless (< column 1) - (move-to-column (1- column))) - (setq column 0)) + ((member argi '("-find-file" "-file" "-visit")) + (setq inhibit-startup-screen t) + ;; An explicit option to specify visiting a file. + (setq tem (or argval (pop command-line-args-left))) + (unless (stringp tem) + (error "File name omitted from `%s' option" argi)) + (funcall process-file-arg tem)) - ;; These command lines now have no effect. - ((string-match "\\`--?\\(no-\\)?\\(uni\\|multi\\)byte$" argi) - (display-warning 'initialization - (format "Ignoring obsolete arg %s" argi))) + ;; These command lines now have no effect. + ((string-match "\\`--?\\(no-\\)?\\(uni\\|multi\\)byte$" argi) + (display-warning 'initialization + (format "Ignoring obsolete arg %s" argi))) - ((equal argi "--") - (setq just-files t)) - (t - ;; We have almost exhausted our options. See if the - ;; user has made any other command-line options available - (let ((hooks command-line-functions) - (did-hook nil)) - (while (and hooks - (not (setq did-hook (funcall (car hooks))))) - (setq hooks (cdr hooks))) - (if (not did-hook) - ;; Presume that the argument is a file name. - (progn - (if (string-match "\\`-" argi) - (error "Unknown option `%s'" argi)) - (unless initial-window-system - (setq inhibit-startup-screen t)) - (setq file-count (1+ file-count)) - (let ((file - (expand-file-name - (command-line-normalize-file-name orig-argi) - dir))) - (cond ((= file-count 1) - (setq first-file-buffer (find-file file))) - (inhibit-startup-screen - (find-file-other-window file)) - (t (find-file file)))) - (unless (zerop line) - (goto-char (point-min)) - (forward-line (1- line))) - (setq line 0) - (unless (< column 1) - (move-to-column (1- column))) - (setq column 0)))))) - ;; In unusual circumstances, the execution of Lisp code due - ;; to command-line options can cause the last visible frame - ;; to be deleted. In this case, kill emacs to avoid an - ;; abort later. - (unless (frame-live-p (selected-frame)) (kill-emacs nil)))))) + ((equal argi "--") + (setq just-files t)) + (t + ;; We have almost exhausted our options. See if the + ;; user has made any other command-line options available + (let ((hooks command-line-functions) + (did-hook nil)) + (while (and hooks + (not (setq did-hook (funcall (car hooks))))) + (setq hooks (cdr hooks))) + (unless did-hook + ;; Presume that the argument is a file name. + (if (string-match "\\`-" argi) + (error "Unknown option `%s'" argi)) + ;; FIXME: Why do we only inhibit the startup + ;; screen for -nw? + (unless initial-window-system + (setq inhibit-startup-screen t)) + (funcall process-file-arg orig-argi))))) + + ;; In unusual circumstances, the execution of Lisp code due + ;; to command-line options can cause the last visible frame + ;; to be deleted. In this case, kill emacs to avoid an + ;; abort later. + (unless (frame-live-p (selected-frame)) (kill-emacs nil))))))) (when (eq initial-buffer-choice t) - ;; When initial-buffer-choice equals t make sure that *scratch* + ;; When `initial-buffer-choice' equals t make sure that *scratch* ;; exists. (get-buffer-create "*scratch*")) @@ -2390,59 +2393,81 @@ A fancy display is used on graphic displays, normal otherwise." (insert initial-scratch-message) (set-buffer-modified-p nil)))) + ;; Prepend `initial-buffer-choice' to `displayable-buffers'. (when initial-buffer-choice (let ((buf (cond ((stringp initial-buffer-choice) (find-file-noselect initial-buffer-choice)) ((functionp initial-buffer-choice) - (funcall initial-buffer-choice))))) - (switch-to-buffer - (if (buffer-live-p buf) buf (get-buffer-create "*scratch*")) - 'norecord))) + (funcall initial-buffer-choice)) + ((eq initial-buffer-choice t) + (get-buffer-create "*scratch*")) + (t + (error "initial-buffer-choice must be a string, a function, or t."))))) + (unless (buffer-live-p buf) + (error "initial-buffer-choice is not a live buffer.")) + (setq displayable-buffers (cons buf displayable-buffers)))) - (if (or inhibit-startup-screen - initial-buffer-choice - noninteractive - (daemonp) - inhibit-x-resources) + ;; Display the first two buffers in `displayable-buffers'. If + ;; `initial-buffer-choice' is non-nil, its buffer will be the + ;; first buffer in `displayable-buffers'. The first buffer will + ;; be focused. + (let ((displayable-buffers-len (length displayable-buffers)) + ;; `nondisplayed-buffers-p' is true if there exist buffers + ;; in `displayable-buffers' that were not displayed to the + ;; user. + (nondisplayed-buffers-p nil)) + (when (> displayable-buffers-len 0) + (switch-to-buffer (car displayable-buffers))) + (when (> displayable-buffers-len 1) + (switch-to-buffer-other-window (car (cdr displayable-buffers))) + ;; Focus on the first buffer. + (other-window -1)) + (when (> displayable-buffers-len 2) + (setq nondisplayed-buffers-p t)) - ;; Not displaying a startup screen. If 3 or more files - ;; visited, and not all visible, show user what they all are. - (and (> file-count 2) - (not noninteractive) - (not inhibit-startup-buffer-menu) - (or (get-buffer-window first-file-buffer) - (list-buffers))) + (if (or inhibit-startup-screen + initial-buffer-choice + noninteractive + (daemonp) + inhibit-x-resources) - ;; Display a startup screen, after some preparations. + ;; Not displaying a startup screen. Display *Buffer List* if + ;; there exist buffers that were not displayed. + (when (and nondisplayed-buffers-p + (not noninteractive) + (not inhibit-startup-buffer-menu)) + (list-buffers)) - ;; If there are no switches to process, we might as well - ;; run this hook now, and there may be some need to do it - ;; before doing any output. - (run-hooks 'emacs-startup-hook 'term-setup-hook) + ;; Display a startup screen, after some preparations. - ;; It's important to notice the user settings before we - ;; display the startup message; otherwise, the settings - ;; won't take effect until the user gives the first - ;; keystroke, and that's distracting. - (when (fboundp 'frame-notice-user-settings) - (frame-notice-user-settings)) + ;; If there are no switches to process, we might as well + ;; run this hook now, and there may be some need to do it + ;; before doing any output. + (run-hooks 'emacs-startup-hook 'term-setup-hook) - ;; If there are no switches to process, we might as well - ;; run this hook now, and there may be some need to do it - ;; before doing any output. - (run-hooks 'window-setup-hook) + ;; It's important to notice the user settings before we + ;; display the startup message; otherwise, the settings + ;; won't take effect until the user gives the first + ;; keystroke, and that's distracting. + (when (fboundp 'frame-notice-user-settings) + (frame-notice-user-settings)) - (setq inhibit-startup-hooks t) + ;; If there are no switches to process, we might as well + ;; run this hook now, and there may be some need to do it + ;; before doing any output. + (run-hooks 'window-setup-hook) - ;; ;; Do this now to avoid an annoying delay if the user - ;; ;; clicks the menu bar during the sit-for. - ;; (when (display-popup-menus-p) - ;; (precompute-menubar-bindings)) - ;; (with-no-warnings - ;; (setq menubar-bindings-done t)) + (setq inhibit-startup-hooks t) - (display-startup-screen (> file-count 0))))) + ;; ;; Do this now to avoid an annoying delay if the user + ;; ;; clicks the menu bar during the sit-for. + ;; (when (display-popup-menus-p) + ;; (precompute-menubar-bindings)) + ;; (with-no-warnings + ;; (setq menubar-bindings-done t)) + + (display-startup-screen (> displayable-buffers-len 0)))))) (defun command-line-normalize-file-name (file) "Collapse multiple slashes to one, to handle non-Emacs file names."