- Preparation
- Usage
- implementation
- helm-q buffer class
- how to display a q instance in helm buffer
- setup instance list
- initialize helm-q-source
- get one instance by its candidate display string
- match funtion
- helm-q-source-filtered-candidate-transformer
- password management
- buffer name for Q-Shell
- actions
- The interactive command
- test connecting of qcon
- select an instance when run =q-evail-*=
- Release current library
https://github.com/emacs-helm/helm/wiki/Developing
(require 's)
(require 'cl-lib)
(require 'helm)
(require 'q-mode)
;;; Code:
(defgroup helm-q nil
"Helm mode for managing kdb+/q instances."
:group 'helm)
(defcustom helm-q-config-directory "~/.helm-q/"
"The directory containing kdb connection files."
:group 'helm-q
:type 'string)
(defcustom helm-q-password-storage nil ; Change to 'pass will enable password storage via the standard unix password manager.
"The default storage method to use."
:group 'helm-q
:options '(nil pass)
:type 'symbol)
(defcustom helm-q-qcon-buffer-name-pattern '("." (service env region))
"The name matching pattern used to build the Q-Shell buffer name.
The first item is the concatenator to join the fields in second item.
These fields in second item can be found in file `instances-meta.json'."
:group 'helm-q
:type 'list)
- Put some JSON files with the same format as it is in ./instances-meta.json to directory
~/.helm-q
. - Load file
helm-q.el
(for example invoking commandload-~file
). Now you can execute interactive commandhelm-q
by press keyAlt-x
. ifM-x helm-q
was invoked withC-u
prefix argument, and after instance was selected from helm-q buffer, We will prompt for new user and then for password in the minibuffer. andqcon
will attempt to connect with the supplied user:password. - (Optional) If you want to load
helm-q.el
after loadingq-mode
, please add following initialization code in your.emacs
;; Please be sure that `helm-q.el' can be found in `load-path'.
(eval-after-load "q-mode" (require 'helm-q))
We use the standard unix password manager to store password in local encrypted file by default.
So when connecting to a q process, helm-q
will query password from it.
We should have a GPG key to encrypt password to file and decrypt it back, to obtain this GPG key, please read a tutorial of GPG(for example this one). After install the standard unix password manager, you have to set up it with a GPG key, for example:
pass init AA224F33CCCCCCE40E6C1C2E8A48C70000022222
Here AA224F33CCCCCCE40E6C1C2E8A48C70000022222
is the ID of your GPG key(in the output of the shell command gpg -k
).
Let’s run a test to check the setup.
# insert a password
pass insert test
# If no error message for above command then it is ok, now let's retrieve the password.
pass test
To manage password in helm-q
, please execute command helm-q
and press key tab
on a candidate, which will list available actions.
If you want to prompt for new user, please invoke M-x helm-q
with C-u prefix argument.
helm-q
manages the Q-Shell buffer name via variable helm-q-qcon-buffer-name-pattern
,
which is ("." (service env region))
by default.
The first item in this list is the concatenator to join the fields in second item.
The fields in second item can be found in file instances-meta.json
.
For example, if file instances-meta.json
contains instance with attributes:
service:"TAQ.HDB", env:"prod", region:"nam"
Then the Q-Shell buffer name for this instance will be *qcon-TAQ.HDB.prod.nam*
, which contains
- A prefix
*qcon-
and - a string joining fields
service
,env
,region
with a dot(.
), and - a suffix
*
.
We define a new helm source for helm-q
, which describes the additional and overwritten attributes of helm-q
.
(defclass helm-q-source (helm-source-sync)
((instance-list
:initarg :instance-list
:initform #'helm-q-instance-list-from-config-directory
:custom function
:documentation
" A function with no arguments to create instance list.")
(candidate-columns
:initform '(address service env region)
:documentation "The columns used to display each candidate.")
(candidate-columns-width-hash
:initform (make-hash-table :test 'equal)
:documentation "The width of each column in candidate-columns, key is the column symbol and value is the width of it.")
(init :initform 'helm-q-source-list--init)
(multimatch :initform t)
(multiline :initform t)
(match :initform 'helm-q-source-match-function)
(action :initform
'(("Connect to a pre-existing q process" . helm-q-source-action-qcon)
("Display username/password for current instance" . helm-q-source-action-show-password)
("Add username/password for current instance" . helm-q-source-action-add-password)
("Update username/password for current instance" . helm-q-source-action-update-password)
))
(filtered-candidate-transformer :initform 'helm-q-source-filtered-candidate-transformer)
(migemo :initform 'nomultimatch)
(volatile :initform t)
(nohighlight :initform nil)
))
we will calculate the maximum width of each column to make sure each column will display with same width. That is, each column will have a width which is the maximum one in all the instances.
(defun helm-q-calculate-columns-width (instances)
"Calculate columns width.
Argument INSTANCES: the instance list."
(cl-loop with width-hash = (helm-attr 'candidate-columns-width-hash)
for column in (helm-attr 'candidate-columns)
do (cl-loop for instance in instances
for width = (length (cdr (assoc column instance)))
if (or (null (gethash column width-hash))
(> width (gethash column width-hash)))
do (setf (gethash column width-hash) width))))
Now we can build a display string with fixed size.
(defun helm-q-instance-display-string (instance)
"Argument INSTANCE: one instance."
(let ((first-row (s-join helm-buffers-column-separator
(cl-loop for column in (helm-attr 'candidate-columns)
collect (helm-substring-by-width (format "%s" (cdr (assoc column instance)))
(gethash column (helm-attr 'candidate-columns-width-hash))))))
(context-matched-columns (helm-q-context-matched-columns instance)))
(propertize
(if (null context-matched-columns)
(propertize first-row 'face 'bold)
(concat (propertize first-row 'face 'bold) "\n"
(s-join helm-buffers-column-separator
(cons helm-buffers-column-separator
context-matched-columns))))
'instance instance)))
Normally the setup will be used by class helm-q-source
to load instances from file instances-meta.json
,
but you can overwrite the slot instance-list
to load instances as you want, so we write a routine to setup for incoming instances:
(defun helm-q-instance-list (instances)
"Load source for instances.
Argument INSTANCES: the incoming list of instance."
(helm-q-calculate-columns-width instances)
;; a list whose members are `(DISPLAY . REAL)' pairs.
(cl-loop for instance in instances
collect (cons (helm-q-instance-display-string instance) instance)))
(defun helm-q-instance-list-from-config-directory ()
"Load source from json files in a directory."
(require 'json)
(helm-q-instance-list (cl-loop for file in (directory-files helm-q-config-directory t ".json$")
append (cl-loop for instance across (json-read-file file)
collect instance))))
(defun helm-q-source-list--init ()
"Initialize helm-q-source."
(helm-attrset 'candidates (funcall (helm-attr 'instance-list))))
(defun helm-q-get-instance-by-display (display-str)
"Get an instance by its display string.
Argument DISPLAY-STR: the display string."
(cl-loop with candidates = (helm-attr 'candidates)
for candidate in candidates
when (string= display-str (car candidate))
return (cdr candidate)))
When match, we will test some columns that are not in candidate-columns
, which will not display by default.
For them, if it can match, we will return them so then can be added as additional lines for display.
(defun helm-q-context-matched-columns (instance)
"Return a list of string for matched columns.
Argument INSTANCE: one instance."
(unless (s-blank? helm-pattern)
(let ((word-patterns (split-string helm-pattern)))
(append
(cl-loop for table-columns in (cdr (assoc 'tablescolumns instance))
for tab-name = (format "%s" (car table-columns))
append (append (if (loop for pattern in word-patterns
thereis (helm-buffer--match-pattern pattern tab-name nil))
(list (format "Table:'%s'" tab-name)))
(cl-loop for column-name across (cdr table-columns)
if (loop for pattern in word-patterns
thereis (helm-buffer--match-pattern pattern column-name nil))
collect (format "Column:'%s.%s'" tab-name column-name))))
(cl-loop for (function) in (cdr (assoc 'functions instance))
for function-name = (format "%s" function)
if (loop for pattern in word-patterns
thereis (helm-buffer--match-pattern pattern function-name nil))
collect (format "Function:'%s'" function-name))
(cl-loop for variable-name across (cdr (assoc 'variables instance))
if (loop for pattern in word-patterns
thereis (helm-buffer--match-pattern pattern variable-name nil))
collect (format "Var:'%s'" variable-name))))))
The helm match function will combine candidate columns and these additional columns.
(defun helm-q-source-match-function (candidate)
"Default function to match buffers.
Argument CANDIDATE: one helm candidate."
(let ((instance (helm-q-get-instance-by-display candidate))
(helm-buffers-fuzzy-matching t))
(or
(cl-loop for slot in (helm-attr 'candidate-columns)
for slot-value = (cdr (assoc slot instance))
thereis (helm-buffer--match-pattern helm-pattern slot-value nil))
(helm-q-context-matched-columns instance))))
Rebuild the candidate string after a search.
(defun helm-q-source-filtered-candidate-transformer (candidates source)
"Filter candidates by context match.
Argument CANDIDATES: the candidate list.
Argument SOURCE: the source."
(cl-loop for (nil . instance) in candidates
collect (cons (helm-q-instance-display-string instance) instance)))
We use a custom format string as the entry, that is, “helm-q/{host}/{user}” to distinguish them from other entries.
(defvar helm-q-pass-prefix "helm-q")
So to get a path for an host
(defun helm-q-pass-path-of-host (host)
"Get the path for an host.
Argument HOST: the host of an instance."
(format "%s/%s/" helm-q-pass-prefix host))
And the path for an user under an host.
(defun helm-q-pass-path-of-host-user (host user)
"Get the path for an host.
Argument HOST: the host of an instance.
Argument USER: the user for the host."
(format "%s/%s/%s" helm-q-pass-prefix host user))
If we use pass
as the storage, the stored password files just like the following file structure:
$ pass show helm-q helm-q ├── host.domain.com:5000 │ ├── user1 │ └── user2 └── host.domain.com:5001 └── user1
We supply different password storage implementation, for each implementation, it should implement the following interfaces.
(cl-defgeneric helm-q-pass-users-of-host (storage host)
"Get a list of users by its host.
Argument STORAGE: a valid storage method.
Argument HOST: a host.")
(cl-defgeneric helm-q-get-pass (storage host user)
"Get pass by its host and user.
Argument STORAGE: a valid storage method.
Argument HOST: a host.
Argument USER: an user name.")
(cl-defgeneric helm-q-update-pass (storage host user &optional password)
"Update user and pass to local encrypted storage file.
Argument STORAGE: a valid storage method.
Argument HOST: the host of an instance.
Argument USER: the user for the instance.
Argument PASSWORD: the optional password for the instance.")
This case happens when variable helm-q-password-storage
is nil
.
That is, we will not store any password in file and will notify user when such action is invoked.
In this case, there are no users.
(cl-defmethod helm-q-pass-users-of-host ((storage (eql nil)) host)
"Get a list of users by its host.
Argument STORAGE: should be 'pass
Argument HOST:"
nil)
In this case, no password.
(cl-defmethod helm-q-get-pass ((storage (eql nil)) host user)
"Get pass by its host and user.
Argument STORAGE: should be 'pass
Argument HOST:
Argument USER:"
nil)
In this case, we should notify user an error message.
(cl-defmethod helm-q-update-pass ((storage (eql nil)) host user &optional password)
"Update user and pass to local pass storage file.
Argument STORAGE: should be 'pass
Argument HOST: the host of an instance.
Argument USER: the user for the instance.
Argument PASSWORD: the optional password for the instance."
(message "You can't save password because this feature is disabled by Emacs lisp variable 'helm-q-password-storage'."))
It will return a cons whose car
is true if it runs successfully, and the cdr
is the result string.
(defun helm-q-run-pass (infile &rest args)
"Run pass with args.
Argument INFILE: input file for pass process.
Argument ARGS: additional arguments for pass."
(with-temp-buffer
(let* ((exit-code (apply 'call-process "pass" infile (current-buffer) t args))
(result (string-trim (buffer-string))))
(cons (= 0 exit-code) result))))
(cl-defmethod helm-q-pass-users-of-host ((storage (eql pass)) host)
"Get a list of users by its host.
Argument STORAGE: should be 'pass
Argument HOST:"
(cl-destructuring-bind (succ-p . result)
(helm-q-run-pass nil "ls" (helm-q-pass-path-of-host host))
(when succ-p
(let ((words (split-string result)))
;; th words list has the format `("helm-q/host.domain.com:5000" "├──" "user1" "└──" "user2")' .
(cl-loop for user-list on (cdr words) by 'cddr
collect (second user-list))))))
(cl-defmethod helm-q-get-pass ((storage (eql pass)) host user)
"Get pass by its host and user.
Argument STORAGE: should be 'pass
Argument HOST:
Argument USER:"
(cl-destructuring-bind (succ-p . entry)
(helm-q-run-pass nil "show" (helm-q-pass-path-of-host-user host user))
(when succ-p
entry)))
(cl-defmethod helm-q-update-pass ((storage (eql pass)) host user &optional password)
"Update user and pass to local pass storage file.
Argument STORAGE: should be 'pass
Argument HOST: the host of an instance.
Argument USER: the user for the instance.
Argument PASSWORD: the optional password for the instance."
(let* ((pass (or password (read-passwd (format "Password for %s@%s: " user host) t)))
(in-file (make-temp-file "helm-q-")))
;; when insert a password in pass, it will ask for password, `call-process' will let pass read it from this input file.
(with-temp-file in-file
(insert pass "\n" pass "\n\n"))
(unwind-protect
(cl-destructuring-bind (succ-p . entry)
(helm-q-run-pass in-file "insert" "-f" (helm-q-pass-path-of-host-user host user))
succ-p)
(delete-file in-file); delete this file to avoid potential security leak.
nil)))
(defun helm-q-user (users)
"Select a user in Helm.
Argument USERS: a user list."
(let ((prompt "Please select an user:")
(user "")
(helm-source
`((name . "helm-q-user-list")
(candidates . ,users)
(action . (lambda (candidate) (setf user candidate)))))
(helm :sources '(helm-source) :prompt prompt)
user)))
The buffer id is a string based on user configuration without prefix and suffix string. And the buffer name will surround buffer id with prefix ”qcon-” and suffix ””.
(defun helm-q-shell-buffer-id (instance)
"Build Q-Shell buffer id based on user configuration.
Argument INSTANCE: the instance."
(string-join (cl-loop for pattern in (second helm-q-qcon-buffer-name-pattern)
collect (cdr (assoc pattern instance)))
(first helm-q-qcon-buffer-name-pattern)))
(defun helm-q-shell-buffer-name (buffer-id)
"Build Q-Shell buffer name based on user configuration.
Argument BUFFER-ID: the buffer id."
(concat "*qcon-" buffer-id "*"))
if M-x helm-q
was invoked with C-u
prefix argument, and after instance was selected from helm-q buffer,
prompt for new user and then for password in the minibuffer. Attempt to connect with the supplied user:password.
In above condition, we will use a special variable to indicate the switch.
(defvar helm-q-pass-required-p nil "Switch it on when helm-q was invoked with prefix argument.")
The action routine:
(defun helm-q-source-action-qcon (candidate)
"Argument CANDIDATE: selected candidate."
(let* ((instance candidate)
(host (cdr (assoc 'address instance)))
(host-port (split-string host ":"))
(q-qcon-server (car host-port))
(q-qcon-port (or (second host-port) q-qcon-port))
(users (helm-q-pass-users-of-host helm-q-password-storage host))
(q-qcon-user (if helm-q-pass-required-p
(read-string "Please enter a new user name: " (car users))
(case (length users)
(0 "")
(1 (car users))
(2 (helm-q-user users)))))
(q-qcon-password (when q-qcon-user
(if helm-q-pass-required-p
(read-passwd (format "Password for %s@%s: " q-qcon-user host))
(helm-q-get-pass helm-q-password-storage host q-qcon-user))))
;; KLUDGE: q-mode should supply a function to build buffer name.
(q-buffer-name (format "*%s*" (format "qcon-%s" (q-qcon-default-args))))
(helm-q-buffer-name (helm-q-shell-buffer-name (helm-q-shell-buffer-id instance)))
(q-buffer (get-buffer q-buffer-name)))
(if (and helm-q-buffer-name
(process-live-p (get-buffer-process helm-q-buffer-name)))
;; activate this buffer if the instance has already been connected.
(q-activate-buffer helm-q-buffer-name)
(when (helm-q-test-active-connection host)
(q-qcon (q-qcon-default-args))
(rename-buffer helm-q-buffer-name)
(q-activate-buffer helm-q-buffer-name)))))
(defun helm-q-source-action-show-password (candidate)
"Show password for current instance.
Argument CANDIDATE: selected candidate."
(if (null helm-q-password-storage)
(message "This feature is disabled by Emacs lisp variable 'helm-q-password-storage'.")
(let* ((instance candidate)
(host (cdr (assoc 'address instance)))
(users (helm-q-pass-users-of-host helm-q-password-storage host)))
(case (length users)
(0 (message "No username/password for host %s" host))
(1 (message "%s@%s's password is '%s'" (car users) host (helm-q-get-pass helm-q-password-storage host (car users))))
(t (let ((user (helm-q-user users)))
(when user
(message "%s@%s's password is '%s'" user host (helm-q-get-pass helm-q-password-storage host user)))))))))
(defun helm-q-source-action-add-password (candidate)
"Add password for current instance.
Argument CANDIDATE: selected candidate."
(if (null helm-q-password-storage)
(message "This feature is disabled by Emacs lisp variable 'helm-q-password-storage'.")
(let* ((instance candidate)
(host (cdr (assoc 'address instance)))
(user (read-string "Please enter the user name: ")))
(if (s-blank? user)
(message "Please input a valid user name!")
(helm-q-update-pass helm-q-password-storage host user)))))
(defun helm-q-source-action-update-password (candidate)
"Update password for current instance.
Argument CANDIDATE: selected candidate."
(if (null helm-q-password-storage)
(message "This feature is disabled by Emacs lisp variable 'helm-q-password-storage'.")
(let* ((instance candidate)
(host (cdr (assoc 'address instance)))
(users (helm-q-pass-users-of-host helm-q-password-storage host)))
(case (length users)
(0 (message "No username/password for host %s" host))
(1 (helm-q-update-pass helm-q-password-storage host (car users)))
(t (let ((user (helm-q-user users)))
(when user
(helm-q-update-pass helm-q-password-storage host user))))))))
;;;###autoload
(defun helm-q (arg)
"Select data source in helm.
Argument ARG: prefix argument."
(interactive "P")
(let ((helm-candidate-separator " ")
(helm-q-bringing-q-actite-buffer-front-p t)
(helm-q-pass-required-p (and arg t)))
(helm :sources (list (helm-make-source "helm-running-q" 'helm-q-running-source)
(helm-make-source "helm-q" 'helm-q-source))
:buffer "*helm q*")))
We will try to send a command after connecting via q
or qcon
in Emacs, and execute different actions based on the test.
The current behavior of qcon for a command likes this.
$ qcon 192.168.0.100:5000 # a normal successful connection without password.
192.168.0.100:5000>1+1
2
192.168.0.100:5000>\\
$ qcon 192.168.0.100:5010:admin:password # a normal successful connection with password.
192.168.0.100:5010>2
2
192.168.0.100:5010>\\
$ qcon 192.168.0.100:5010:admin:badpassword # an invalid connection with bad password.
192.168.0.100:5010>2
192.168.0.100:5010>\\
$ qcon 192.168.0.100:5010:baduser:badpassword # an invalid connection with bad user.
192.168.0.100:5010>2
192.168.0.100:5010>\\
$ qcon 192.168.0.100:5020 # no process on the port
192.168.0.100:5020>2
conn: Connection refused
$ qcon 192.168.1.111:5000 # bad host
192.168.1.111:5000>2
conn: No route to host
So the actions we will do based on a test command are.
- if after a command and the process is still alive,
- if there is a response, we will treat it as a successful connection and store the username and password based on current storage method.
- if there is no response, we will treat it as a failed connection and ask for new username and password for it and connect again.
- if the process is dead,
- we will do nothing with it.
We will not do this test in comint
buffer directly because it’s an interactive buffer for user.
Instead, we will create a new process of qcon
and send test commands to it in a temp buffer.
(defun helm-q-test-active-connection (host)
"Test connection of qcon, return true if connection is ok.
Argument HOST: the host of current instance."
(message "Test connection...")
(let ((in-file (make-temp-file "helm-q-"))
(test-message "Test Connection."))
;; prepare test commands in input file.
(with-temp-file in-file
(insert
;; echo a test message.
"\"" test-message "\"" "\n"
;; quit from this process.
"\\\\" "\n\n"))
(with-temp-buffer
(let* ((exit-code (apply 'call-process q-qcon-program in-file (current-buffer) t
(list (q-qcon-default-args))))
(result (string-trim (buffer-string))))
(delete-file in-file); remove temp file after use.
(if (/= 0 exit-code)
;; if failed to connect, report the result as error message.
(progn (message "connection failed: %s" result)
nil)
(if (ignore-errors
(goto-char (point-min))
;; The test message should occur in the output.
(search-forward test-message nil nil 1))
(progn
;; connection is ok, save password for this connection if it is from user input.
(when helm-q-pass-required-p
(helm-q-update-pass helm-q-password-storage host q-qcon-user q-qcon-password))
t)
(progn
;; invalid user/pass, ask for a new username and password.
(message "connection is not responding: %s" result)
(if (s-blank? q-qcon-user)
(progn
;; Prompting for user and password in case of unsuccessful passwordless connection attempt.
(setf q-qcon-user (read-string "Please enter the user name: " q-qcon-user))
(setf q-qcon-password (read-passwd "Please enter the password: "))
;; test connection with new username and password.
(let ((helm-q-pass-required-p t)); save the password if it is ok.
(helm-q-test-active-connection host)))
(progn
;; Prompting for new password in case of failed authentication.
(setf q-qcon-password (read-passwd "Please enter the password: "))
;; test connection with new username and password.
(let ((helm-q-pass-required-p t)); save the password if it is ok.
(helm-q-test-active-connection host)))))))))))
In q-mode, q-eval-*
sends string (q-send-string
) from Q-Script buffer to whichever Q-Shell comint buffer that
is marked as q-active-buffer
. This is to be extended with the ability to interactively select which buffer/instance
should the string be sent to, skipping the need to manually q-activate-buffer
each time a different destination is desired.
Extend behavior of q-eval-*
so when it’s called with prefix argument C-u
, it brings up a helm buffer and
wait for the selection of an instance. This special helm buffer consists of two sections of candidates,
just like M-x list-buffers
consists of three sections: Buffers
, Recentf
and Create buffer
. The two sections are:
Buffers
: existing Q-Shell buffer candidates - searchable by buffer nameInstances
:helm-q
qcon instance candidates searchable by attributes like tables, columns, functions etc..
Note that the candidates can be overlapping, when an instance listed in Instances
section is an already existing Q-Shell buffer
listed in Buffers section. Selecting such an instance should not create a duplicate Q-Shell buffer.
On selection, the selected Q-Shell buffer is marked as q-active-buffer
and the string is sent to it (q-send-string
) as usual.
However, the cursor stays and never leaves the initial Q-Script buffer.
When any of the q-eval-*
commands are called with double prefix argument C-u C-u
,
invoke helm-q
with single prefix argument to prompt for user
and password
.
(defclass helm-q-running-source (helm-source-sync)
((buffer-list
:initarg :buffer-list
:initform #'helm-q-running-buffer-list
:custom function
:documentation
" A function with no arguments to get running buffer list.")
(init :initform 'helm-q-running-source-list--init)
(multimatch :initform nil)
(multiline :initform nil)
(action :initform
'(("Select a pre-existing q process" . helm-q-running-source-action-select-an-instance)))
(migemo :initform 'nomultimatch)
(volatile :initform t)
(nohighlight :initform nil)))
There are two conditions to execute this action:
- update active buffer before
q-send-string
, in this condition, we just active the selection buffer. - invoked by
helm-q
, in this condition, we not only active the selection buffer, but also bring it to front.
We will use a special variable tow distinguish these two conditions.
(defvar helm-q-bringing-q-actite-buffer-front-p nil)
The implementation of the action.
(defun helm-q-running-source-action-select-an-instance (candidate)
"Select an running instance.
Argument CANDIDATE: the selected candidate."
(q-activate-buffer candidate)
(when helm-q-bringing-q-actite-buffer-front-p
(pop-to-buffer q-active-buffer)))
(defun helm-q-running-buffer-list ()
"Get running Q buffers."
(loop with q-active-buffer-name = (if (bufferp q-active-buffer)
(buffer-name q-active-buffer)
q-active-buffer)
for buffer in (buffer-list)
if (with-current-buffer buffer
(equal 'q-shell-mode major-mode))
collect (let ((buffer-name (buffer-name buffer)))
(if (string= buffer-name q-active-buffer-name)
(propertize buffer-name 'face 'bold)
buffer-name))))
(defun helm-q-running-source-list--init ()
"Initialize helm-q-running-source."
(helm-attrset 'candidates (funcall (helm-attr 'buffer-list))))
We add a before
advice to function q-send-string
, to detect the prefix argument for current command, to
- change the
q-active-buffer
if necessary; - prompt for
user
andpassword
inhelm-q
. - display
q-active-buffer
in another window.
(defun helm-q-update-active-buffer (&rest args)
"An advice function for `q-send-string'.
To update active buffer based on prefix argument.
Argument ARGS: the argument for original function."
(let ((update-active-buffer-p nil)
(helm-q-pass-required-p helm-q-pass-required-p))
(case (prefix-numeric-value current-prefix-arg)
(4 ; prefix C-u
(setf update-active-buffer-p t))
(16 ; prefix C-u C-u
(setf update-active-buffer-p t
helm-q-pass-required-p t)))
(when update-active-buffer-p
(let ((another-win (if (one-window-p)
(if (> (window-width) 100)
(split-window-horizontally)
(split-window-vertically))
(next-window))))
(helm :sources (list (helm-make-source "helm-running-q" 'helm-q-running-source)
(helm-make-source "helm-q" 'helm-q-source))
:buffer "*helm q*")
(set-window-buffer another-win q-active-buffer)))))
(advice-add 'q-send-string :before #'helm-q-update-active-buffer)
And when a new version of ./helm-q.el can release from this file, the following code should execute.
(literate-elisp-tangle
"helm-q.org"
:header ";;; helm-q.el --- A library to manage remote q sessions with Helm and q-mode -*- lexical-binding: t; -*-
;; URL: https://github.com/emacs-q/helm-q.el
;; Package-Requires: ((emacs \"26.1\") (cl-lib \"0.6\") (helm \"1.9.4\") (s \"1.10.0\") (q-mode \"0.1\") (cl-lib \"1.0\"))
;;; Commentary:
;; helm-q is an Emacs Lisp library to manage remote q sessions with Helm and q-mode.
"
:tail "(provide 'helm-q)
;;; helm-q.el ends here
")