diff --git a/etc/NEWS b/etc/NEWS index bec5b3b3468..9837e6c95b8 100644 --- a/etc/NEWS +++ b/etc/NEWS @@ -283,6 +283,14 @@ variables `sql-product', `sql-user', `sql-server', `sql-database' and The custom variable `sql-port' can be specified for connection to MySQL servers. +*** Command continuation prompts in SQL interactive mode are suppressed. +Multiple line commands in SQL interactive mode, generate command +continuation prompts which needlessly confuse the output. These +prompts are now filtered out from the output. This change impacts +multiple line SQL statements entered with C-j between each line, +statements yanked into the buffer and statements sent with +`sql-send-*' functions. + *** Custom variables control prompting for login parameters. Each supported product has a custom variable `sql-*-login-params' which is a list of the parameters to be prompted for before a @@ -302,7 +310,8 @@ names (without the directory portion). Generally these strings will be of the form ".+\.SUF" where SUF is the desired file suffix. When :completion is specified, the ARG corresponds to the PREDICATE -argument to the `completing-read' function. +argument to the `completing-read' function (a list of possible values +or a function returning such a list). *** Added `sql-connection-alist' to record login parameter values. An alist for recording different username, database and server diff --git a/lisp/ChangeLog b/lisp/ChangeLog index 5d005c4e8a2..6f15ae942fc 100644 --- a/lisp/ChangeLog +++ b/lisp/ChangeLog @@ -1,3 +1,21 @@ +2010-08-10 Michael R. Mauger + + * progmodes/sql.el: Version 2.5 + (sql-product-alist): Add :prompt-cont-regexp property for several + database products. + (sql-prompt-cont-regexp): New variable. + (sql-output-newline-count, sql-output-by-send): New + variables. Record number of newlines in input text. + (sql-send-string): Handle multiple filters and count newlines. + (sql-send-magic-terminator): Count terminator newline. + (sql-interactive-remove-continuation-prompt): Filters output to + remove continuation prompts; one for each newline. + (sql-interactive-mode): Set up new variables, prompt regexp and + output filter. + (sql-mode-sqlite-font-lock-keywords): Correct some keywords. + (sql-make-alternate-buffer-name): Correct buffer name in edge + cases. + 2010-08-10 Stefan Monnier * emacs-lisp/pcase.el: New file. diff --git a/lisp/progmodes/sql.el b/lisp/progmodes/sql.el index ff75d46ff13..e44504688f2 100644 --- a/lisp/progmodes/sql.el +++ b/lisp/progmodes/sql.el @@ -5,7 +5,7 @@ ;; Author: Alex Schroeder ;; Maintainer: Michael Mauger -;; Version: 2.4 +;; Version: 2.5 ;; Keywords: comm languages processes ;; URL: http://savannah.gnu.org/cgi-bin/viewcvs/emacs/emacs/lisp/progmodes/sql.el ;; URL: http://www.emacswiki.org/cgi-bin/wiki.pl?SqlMode @@ -336,6 +336,7 @@ Customizing your password will store it in your ~/.emacs file." :sqli-comint-func sql-comint-db2 :prompt-regexp "^db2 => " :prompt-length 7 + :prompt-cont-regexp "^db2 (cont\.) => " :input-filter sql-escape-newlines-filter) (informix @@ -357,7 +358,8 @@ Customizing your password will store it in your ~/.emacs file." :sqli-login sql-ingres-login-params :sqli-comint-func sql-comint-ingres :prompt-regexp "^\* " - :prompt-length 2) + :prompt-length 2 + :prompt-cont-regexp "^\* ") (interbase :name "Interbase" @@ -401,6 +403,7 @@ Customizing your password will store it in your ~/.emacs file." :sqli-comint-func sql-comint-mysql :prompt-regexp "^mysql> " :prompt-length 6 + :prompt-cont-regexp "^ -> " :input-filter sql-remove-tabs-filter) (oracle @@ -412,6 +415,7 @@ Customizing your password will store it in your ~/.emacs file." :sqli-comint-func sql-comint-oracle :prompt-regexp "^SQL> " :prompt-length 5 + :prompt-cont-regexp "^\\s-*\\d+> " :syntax-alist ((?$ . "w") (?# . "w")) :terminator ("\\(^/\\|;\\)" . "/") :input-filter sql-placeholders-filter) @@ -424,8 +428,9 @@ Customizing your password will store it in your ~/.emacs file." :sqli-options sql-postgres-options :sqli-login sql-postgres-login-params :sqli-comint-func sql-comint-postgres - :prompt-regexp "^.*[#>] *" + :prompt-regexp "^.*=[#>] " :prompt-length 5 + :prompt-cont-regexp "^.*-[#>] " :input-filter sql-remove-tabs-filter :terminator ("\\(^[\\]g\\|;\\)" . ";")) @@ -448,7 +453,9 @@ Customizing your password will store it in your ~/.emacs file." :sqli-login sql-sqlite-login-params :sqli-comint-func sql-comint-sqlite :prompt-regexp "^sqlite> " - :prompt-length 8) + :prompt-length 8 + :prompt-cont-regexp "^ ...> " + :terminator ";") (sybase :name "Sybase" @@ -509,6 +516,10 @@ may be any one of the following: :prompt-length length of the prompt on the line. + :prompt-cont-regexp regular expression string that matches + the continuation prompt issued by the + product interpreter. + :input-filter function which can filter strings sent to the command interpreter. It is also used by the `sql-send-string', @@ -516,7 +527,8 @@ may be any one of the following: and `sql-send-buffer' functions. The function is passed the string sent to the command interpreter and must return the - filtered string. + filtered string. May also be a list of + such functions. :terminator the terminator to be sent after a `sql-send-string', `sql-send-region', @@ -1034,6 +1046,9 @@ You can change `sql-prompt-regexp' on `sql-interactive-mode-hook'.") You can change `sql-prompt-length' on `sql-interactive-mode-hook'.") +(defvar sql-prompt-cont-regexp nil + "Prompt pattern of statement continuation prompts.") + (defvar sql-alternate-buffer-name nil "Buffer-local string used to possibly rename the SQLi buffer. @@ -1969,10 +1984,9 @@ you define your own `sql-mode-mysql-font-lock-keywords'.") "abort" "action" "add" "after" "all" "alter" "analyze" "and" "as" "asc" "attach" "autoincrement" "before" "begin" "between" "by" "cascade" "case" "cast" "check" "collate" "column" "commit" "conflict" -"constraint" "create" "cross" "current_date" "current_time" -"current_timestamp" "database" "default" "deferrable" "deferred" -"delete" "desc" "detach" "distinct" "drop" "each" "else" "end" -"escape" "except" "exclusive" "exists" "explain" "fail" "for" +"constraint" "create" "cross" "database" "default" "deferrable" +"deferred" "delete" "desc" "detach" "distinct" "drop" "each" "else" +"end" "escape" "except" "exclusive" "exists" "explain" "fail" "for" "foreign" "from" "full" "glob" "group" "having" "if" "ignore" "immediate" "in" "index" "indexed" "initially" "inner" "insert" "instead" "intersect" "into" "is" "isnull" "join" "key" "left" "like" @@ -1987,9 +2001,9 @@ you define your own `sql-mode-mysql-font-lock-keywords'.") ;; SQLite Data types (sql-font-lock-keywords-builder 'font-lock-type-face nil "int" "integer" "tinyint" "smallint" "mediumint" "bigint" "unsigned" -"big" "int2" "int8" "character" "varchar" "varying" "nchar" "native " +"big" "int2" "int8" "character" "varchar" "varying" "nchar" "native" "nvarchar" "text" "clob" "blob" "real" "double" "precision" "float" -"numeric" "decimal" "boolean" "date" "datetime" +"numeric" "number" "decimal" "boolean" "date" "datetime" ) ;; SQLite Functions (sql-font-lock-keywords-builder 'font-lock-builtin-face nil @@ -2002,6 +2016,7 @@ you define your own `sql-mode-mysql-font-lock-keywords'.") "typeof" "upper" "zeroblob" ;; Date/time functions "time" "julianday" "strftime" +"current_date" "current_time" "current_timestamp" ;; Aggregate functions "avg" "count" "group_concat" "max" "min" "sum" "total" ))) @@ -2585,25 +2600,33 @@ server/database name." ;; Build a name using the :sqli-login setting (setq name (apply 'concat - (apply 'append nil - (sql-for-each-login - (sql-get-product-feature sql-product :sqli-login) - (lambda (token type arg) - (cond - ((eq token 'user) (list "/" sql-user)) - ((eq token 'port) (list ":" sql-port)) - ((eq token 'server) - (list "." (if (eq type :file) - (file-name-nondirectory sql-server) - sql-server))) - ((eq token 'database) - (list "@" (if (eq type :file) - (file-name-nondirectory sql-database) - sql-database))) - - ((eq token 'password) nil) - (t nil))))))) + (cdr + (apply 'append nil + (sql-for-each-login + (sql-get-product-feature sql-product :sqli-login) + (lambda (token type arg) + (cond + ((eq token 'user) + (unless (string= "" sql-user) + (list "/" sql-user))) + ((eq token 'port) + (unless (= 0 sql-port) + (list ":" sql-port))) + ((eq token 'server) + (unless (string= "" sql-server) + (list "." + (if (eq type :file) + (file-name-nondirectory sql-server) + sql-server)))) + ((eq token 'database) + (when (string= "" sql-database) + (list "@" + (if (eq type :file) + (file-name-nondirectory sql-database) + sql-database)))) + ((eq token 'password) nil) + (t nil)))))))) ;; If there's a connection, use it and the name thus far (if sql-connection @@ -2623,8 +2646,8 @@ server/database name." sql-server) sql-database)) - ;; We've got a name, go with it (without the first punctuation char) - (substring name 1))))) + ;; Use the name we've got + name)))) (defun sql-rename-buffer () "Rename a SQLi buffer." @@ -2702,14 +2725,73 @@ Every newline in STRING will be preceded with a space and a backslash." ;;; Input sender for SQLi buffers +(defvar sql-output-newline-count 0 + "Number of newlines in the input string. + +Allows the suppression of continuation prompts.") + +(defvar sql-output-by-send nil + "Non-nil if the command in the input was generated by `sql-send-string'.") + (defun sql-input-sender (proc string) "Send STRING to PROC after applying filters." (let* ((product (with-current-buffer (process-buffer proc) sql-product)) (filter (sql-get-product-feature product :input-filter))) + ;; Apply filter(s) + (cond + ((not filter) + nil) + ((functionp filter) + (setq string (funcall filter string))) + ((listp filter) + (mapc (lambda (f) (setq string (funcall f string))) filter)) + (t nil)) + + ;; Count how many newlines in the string + (setq sql-output-newline-count 0) + (mapc (lambda (ch) + (when (eq ch ?\n) + (setq sql-output-newline-count (1+ sql-output-newline-count)))) + string) + ;; Send the string - (comint-simple-send proc (if filter (funcall filter string) string)))) + (comint-simple-send proc string))) + +;;; Strip out continuation prompts + +(defun sql-interactive-remove-continuation-prompt (oline) + "Strip out continuation prompts out of the OLINE. + +Added to the `comint-preoutput-filter-functions' hook in a SQL +interactive buffer. If `sql-outut-newline-count' is greater than +zero, then an output line matching the continuation prompt is filtered +out. If the count is one, then the prompt is replaced with a newline +to force the output from the query to appear on a new line." + (if (and sql-prompt-cont-regexp + sql-output-newline-count + (numberp sql-output-newline-count) + (>= sql-output-newline-count 1)) + (progn + (while (and oline + sql-output-newline-count + (> sql-output-newline-count 0) + (string-match sql-prompt-cont-regexp oline)) + + (setq oline + (replace-match (if (and + (= 1 sql-output-newline-count) + sql-output-by-send) + "\n" "") + nil nil oline) + sql-output-newline-count + (1- sql-output-newline-count))) + (if (= sql-output-newline-count 0) + (setq sql-output-newline-count nil)) + (setq sql-output-by-send nil)) + (setq sql-output-newline-count nil)) + oline) ;;; Sending the region to the SQLi buffer. @@ -2717,26 +2799,20 @@ Every newline in STRING will be preceded with a space and a backslash." "Send the string STR to the SQL process." (interactive "sSQL Text: ") - (let (comint-input-sender-no-newline proc) + (let ((comint-input-sender-no-newline nil) + (s (replace-regexp-in-string "[[:space:]\n\r]+\\'" "" str))) (if (buffer-live-p sql-buffer) (progn ;; Ignore the hoping around... (save-excursion - ;; Get the process - (setq proc (get-buffer-process sql-buffer)) - ;; Set product context (with-current-buffer sql-buffer - ;; Send the string - (sql-input-sender proc str) - - ;; Send a newline if there wasn't one on the end of the string - (unless (string-equal "\n" (substring str (1- (length str)))) - (comint-send-string proc "\n")) + ;; Send the string (trim the trailing whitespace) + (sql-input-sender (get-buffer-process sql-buffer) s) ;; Send a command terminator if we must (if sql-send-terminator - (sql-send-magic-terminator sql-buffer str sql-send-terminator)) + (sql-send-magic-terminator sql-buffer s sql-send-terminator)) (message "Sent string to buffer %s." (buffer-name sql-buffer)))) @@ -2771,7 +2847,7 @@ Every newline in STRING will be preceded with a space and a backslash." (defun sql-send-magic-terminator (buf str terminator) "Send TERMINATOR to buffer BUF if its not present in STR." - (let (pat term) + (let (comint-input-sender-no-newline pat term) ;; If flag is merely on(t), get product-specific terminator (if (eq terminator t) (setq terminator (sql-get-product-feature sql-product :terminator))) @@ -2792,8 +2868,13 @@ Every newline in STRING will be preceded with a space and a backslash." ;; Check to see if the pattern is present in the str already sent (unless (and pat term - (string-match (concat pat "\n?\\'") str)) - (comint-send-string buf (concat term "\n"))))) + (string-match (concat pat "\\'") str)) + (comint-simple-send (get-buffer-process buf) term) + (setq sql-output-newline-count + (if sql-output-newline-count + (1+ sql-output-newline-count) + 1))) + (setq sql-output-by-send t))) (defun sql-remove-tabs-filter (str) "Replace tab characters with spaces." @@ -2993,12 +3074,22 @@ you entered, right above the output it created. (sql-get-product-feature sql-product :prompt-regexp)) (set (make-local-variable 'sql-prompt-length) (sql-get-product-feature sql-product :prompt-length)) + (set (make-local-variable 'sql-prompt-cont-regexp) + (sql-get-product-feature sql-product :prompt-cont-regexp)) + (make-local-variable 'sql-output-newline-count) + (make-local-variable 'sql-output-by-send) + (add-hook 'comint-preoutput-filter-functions + 'sql-interactive-remove-continuation-prompt nil t) (make-local-variable 'sql-input-ring-separator) (make-local-variable 'sql-input-ring-file-name) ;; Run the mode hook (along with comint's hooks). (run-mode-hooks 'sql-interactive-mode-hook) ;; Set comint based on user overrides. - (setq comint-prompt-regexp sql-prompt-regexp) + (setq comint-prompt-regexp + (if sql-prompt-cont-regexp + (concat "\\(" sql-prompt-regexp + "\\|" sql-prompt-cont-regexp "\\)") + sql-prompt-regexp)) (setq left-margin sql-prompt-length) ;; Install input sender (set (make-local-variable 'comint-input-sender) 'sql-input-sender)