;;;;; ;;;;; $Id$ ;;;;; Copyright (C) 1991 Lysator Academic Computer Association. ;;;;; ;;;;; This file is part of the LysKOM server. ;;;;; ;;;;; LysKOM is free software; you can redistribute it and/or modify it ;;;;; under the terms of the GNU General Public License as published by ;;;;; the Free Software Foundation; either version 1, or (at your option) ;;;;; any later version. ;;;;; ;;;;; LysKOM is distributed in the hope that it will be useful, but WITHOUT ;;;;; ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ;;;;; FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License ;;;;; for more details. ;;;;; ;;;;; You should have received a copy of the GNU General Public License ;;;;; along with LysKOM; see the file COPYING. If not, write to ;;;;; Lysator, c/o ISY, Linkoping University, S-581 83 Linkoping, SWEDEN, ;;;;; or the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, ;;;;; MA 02139, USA. ;;;;; ;;;;; Please mail bug reports to bug-lyskom@lysator.liu.se. ;;;;; ;;;; ================================================================ ;;;; ================================================================ ;;;; ;;;; File: completing-read.el ;;;; Author: David Byers ;;;; ;;;; This file implements functions for reading a conference name ;;;; or a person name with completion and other help. ;;;; (setq lyskom-clientversion-long (concat lyskom-clientversion-long "$Id$\n")) (defvar lyskom-name-hist nil) ;;; ============================================================ ;;; ;;; Name lookup caches ;;; (defvar lyskom-completing-who-info-cache nil "Temporary cache of who-info data") (defvar lyskom-completing-lookup-name-cache nil "Temporary cache of server queries") (defun lyskom-completing-clear-cache () (setq lyskom-completing-who-info-cache nil) (setq lyskom-completing-lookup-name-cache nil)) (defun lyskom-completing-who-is-on () "Get information about who is on, first checking the cache. Returns what \(blocking-do 'who-is-on\) would, but as a list, not a vector" (if lyskom-completing-who-info-cache lyskom-completing-who-info-cache (setq lyskom-completing-who-info-cache (listify-vector (blocking-do 'who-is-on))))) (defun lyskom-completing-cache-completion (string data) (let* ((downs (downcase string)) (tmp (assoc downs lyskom-completing-lookup-name-cache))) (if (null tmp) (setq lyskom-completing-lookup-name-cache (cons (cons downs data) lyskom-completing-lookup-name-cache))) string)) (defun lyskom-completing-lookup-z-name (string want-conf want-pers) "Look up STRING as a name. Same as \(blocking-do 'lookup-z-name ...\) but first checks a cache." (let* ((downs (downcase string)) (tmp (assoc downs lyskom-completing-lookup-name-cache))) (if tmp (cdr tmp) (progn (setq tmp (blocking-do 'lookup-z-name string want-conf want-pers)) (setq lyskom-completing-lookup-name-cache (cons (cons downs tmp) lyskom-completing-lookup-name-cache)) tmp)))) ;;; ============================================================ ;;; ;;; Keymaps ;;; (defvar lyskom-minibuffer-local-completion-map (let ((map (copy-keymap minibuffer-local-completion-map))) (define-key map " " nil) map) "Keymap used for reading LysKOM names.") (defvar lyskom-minibuffer-local-must-match-map (let ((map (copy-keymap minibuffer-local-must-match-map))) (define-key map " " nil) map) "Keymap used for reading LysKOM names.") (defun lyskom-read-conf-no (prompt type &optional empty initial mustmatch) "Read a conference name from the minibuffer with completion and return its number or zero if nothing was matched. See lyskom-read-conf for a description of the parameters." (let ((conf-z-info (lyskom-read-conf prompt type empty initial mustmatch))) (cond ((null conf-z-info) 0) ((stringp conf-z-info) 0) ((lyskom-conf-stat-p conf-z-info) (conf-stat->conf-no conf-z-info)) (t (conf-z-info->conf-no conf-z-info))))) (defun lyskom-read-conf-stat (prompt type &optional empty initial mustmatch) "Read a conference name from the minibuffer with completion and return its conf-stat or nil if nothing was matched. See lyskom-read-conf for a description of the parameters." (let ((conf-z-info (lyskom-read-conf prompt type empty initial mustmatch))) (cond ((null conf-z-info) nil) ((stringp conf-z-info) nil) ((lyskom-conf-stat-p conf-z-info) conf-z-info) (t (blocking-do 'get-conf-stat (conf-z-info->conf-no conf-z-info)))))) (defun lyskom-read-conf-name (prompt type &optional empty initial mustmatch) "Read a conference name from the minibuffer with completion and return its name. See lyskom-read-conf for a description of the parameters." (let ((conf-z-info (lyskom-read-conf prompt type empty initial mustmatch))) (cond ((null conf-z-info) "") ((stringp conf-z-info) conf-z-info) ((lyskom-conf-stat-p conf-z-info) (conf-stat->name conf-z-info)) (t (conf-z-info->name conf-z-info))))) (defun lyskom-read-conf (prompt type &optional empty initial mustmatch) "Completing read a conference or person from the minibuffer. PROMPT is the prompt type type. TYPE is the type of conferences to return. It is a list of one or more of the following: all Return any conference, conf Return conferences (not letterboxes), pers Return persons (letterboxes), login Return persons who are also logged-in, and none Return names that do not match anything in the database. Optional arguments EMPTY allow nothing to be entered. INITIAL initial contents of the minibuffer MUSTMATCH if non-nil, the user must enter a valid name. The return value may be one of A conf-z-info: The conf-z-info associated with the name entered, nil: Nothing was entered, or A string: A name that matched nothing in the database." (lyskom-completing-clear-cache) (let* ((completion-ignore-case t) (lyskom-blocking-process lyskom-proc) (minibuffer-local-completion-map lyskom-minibuffer-local-completion-map) (minibuffer-local-must-match-map lyskom-minibuffer-local-must-match-map) (read-string nil) (result nil) (keep-going t)) (while keep-going (setq read-string (completing-read prompt 'lyskom-read-conf-internal type mustmatch initial 'lyskom-name-hist)) (setq result (cond ((null read-string) nil) ((string= "" read-string) nil) (t (lyskom-lookup-conf-by-name read-string type)))) (setq keep-going (and (not empty) (null result)))) result)) (defun lyskom-read-conf-get-logins () "Used internally by lyskom-read-conf-internal to get a list of persons who are logged on." (mapcar (function (lambda (el) (who-info->pers-no el))) (lyskom-completing-who-is-on))) (defun lyskom-read-conf-expand-specials (string predicate login-list x-list) "Used internally by lyskom-read-conf-internal to expand person and conference number specifications to something useful." (cond ((string-match (lyskom-get-string 'person-or-conf-no-regexp) string) (let* ((no (string-to-int (match-string 1 string))) (cs (blocking-do 'get-conf-stat no))) (if (and cs (lyskom-read-conf-internal-verify-type (conf-stat->conf-no cs) (conf-stat->conf-type cs) predicate login-list x-list)) (list string)))))) (defun lyskom-read-conf-lookup-specials (string predicate login-list x-list) "Used internally by lyskom-read-conf-internal to look up conf-stats from person and conference number specifications." (if (string-match (lyskom-get-string 'person-or-conf-no-regexp) string) (progn (let* ((no (string-to-int (match-string 1 string))) (cs (blocking-do 'get-conf-stat no))) (if (and cs (lyskom-read-conf-internal-verify-type (conf-stat->conf-no cs) (conf-stat->conf-type cs) predicate login-list x-list)) cs))))) (defun lyskom-lookup-conf-by-name (string predicate) "Return the conf-z-info associated with STRING that also satisfies PREDICATE or nil if no name matches. See lyskom-read-conf-internal for a documentation of PREDICATE." (let ((lyskom-blocking-process (or lyskom-blocking-process lyskom-proc))) (if (string= string "") nil (lyskom-read-conf-internal string predicate 'lyskom-lookup)))) (defun lyskom-read-conf-internal (string predicate all) "Complete the name STRING according to PREDICATE and ALL. STRING is a string to complete. PREDICATE is a list of name types to return. Valid types are all Any existing name may be returned, pers Names of persons may be returned, conf Names of conferences may be returned, login Names of logged-in persons may be returned, and none Names that match nothing may be returned. ALL is set by try-completion and all-completions. See the Emacs lisp manual for a description. Special value 'lyskom-lookup makes the function work as a name-to-conf-stat translator." ;; ;; Catch some degenerate cases that can cause...problems. This ;; won't solve all the...problems, but should speed things up a ;; little bit. ;; (cond ((and (null all) (string= string "")) "") ((and (eq all 'lyskom-lookup) (string= string "")) nil) ((and (eq all 'lambda) (string= string "")) nil) (t (let* ((login-list (and (memq 'login predicate) (lyskom-read-conf-get-logins))) (x-list (lyskom-completing-lookup-z-name string 1 1)) (candidate-list (and x-list (listify-vector (conf-z-info-list->conf-z-infos x-list)))) (lyskom-read-conf-internal-result-list nil) (result-list nil)) ;; ;; login-list now contains a list of logins, IF the predicate ;; includes 'login ;; ;; candidate-list contains a list of conf-nos, with the ;; corresponding conf-types in candidate-type-list. ;; ;; Now set result-list to the conf-z-infos that fulfill the ;; predicate, fetching the conf-stats asynchronously. ;; (lyskom-traverse el candidate-list (if (lyskom-read-conf-internal-verify-type (conf-z-info->conf-no el) (conf-z-info->conf-type el) predicate login-list candidate-list) (setq result-list (cons el result-list)))) ;; ;; Now the matching conf-z-infos are in result-list ;; (cond ((eq all 'lyskom-lookup) (let ((names (mapcar 'conf-z-info->name result-list)) (specials (lyskom-read-conf-expand-specials string predicate login-list candidate-list))) (cond ((= (length result-list) 1) (car result-list)) ((and (> (length result-list) 1) (lyskom-completing-member string names)) (elt result-list (- (length result-list) (length (lyskom-completing-member string names))))) (specials (lyskom-read-conf-lookup-specials string predicate login-list candidate-list)) ((string-match (lyskom-get-string 'person-or-conf-no-regexp) string) nil) ((lyskom-read-conf-internal-verify-type nil nil predicate login-list candidate-list) string)))) ;; ;; Check for exact match. We have an exact match in the server ;; when there was a single match OR when there was no match, and ;; no match is valid according to predicate ;; ((eq all 'lambda) (let ((specials (lyskom-read-conf-expand-specials string predicate login-list candidate-list))) (cond ((= (length result-list) 1) t) (result-list nil) ((= (length specials) 1) t) (specials nil) ((string-match (lyskom-get-string 'person-or-conf-no-regexp) string) nil) (t (lyskom-read-conf-internal-verify-type nil nil predicate login-list candidate-list))))) ;; ;; Called from all-completions. Return a list of all possible ;; completions, in this case all names in the result list plus, ;; if the input string is a person or conf number specification, ;; the input string, PROVIDED, the requested conference matches ;; the predicate. If there were no matches, return the input ;; string if no matches satisfies the predicate. ;; (all (let ((names (mapcar 'conf-z-info->name result-list)) (specials (lyskom-read-conf-expand-specials string predicate login-list candidate-list))) (cond (specials (append specials names)) (names names) ((string-match (lyskom-get-string 'person-or-conf-no-regexp) string) nil) ((lyskom-read-conf-internal-verify-type nil nil predicate login-list candidate-list) (list string)) (t nil)))) ;; ;; Called from try-completion, and there were no matches. Try to ;; expand the input string as a person or conf number ;; specification or return something sensible if the predicate ;; is satisfied by no matches. ;; ((null result-list) (let ((specials (lyskom-read-conf-expand-specials string predicate login-list candidate-list))) (cond (specials specials) ((string-match (lyskom-get-string 'person-or-conf-no-regexp) string) nil) ((lyskom-read-conf-internal-verify-type nil nil predicate login-list candidate-list) (list string)) (t nil)))) ;; ;; Called from try-completion, and there were matches in the ;; server. Return t if the string is an exact match to any ;; string returned from the server. Otherwise, expand the string ;; as far as possible and return that ;; (t (let ((name-list (mapcar 'conf-z-info->name result-list)) (specials (lyskom-read-conf-expand-specials string predicate login-list candidate-list)) (found nil)) (if specials (setq name-list (nconc specials name-list))) (cond ((lyskom-completing-member string name-list) t) ; Exact match ((= (length name-list) 1) (car name-list)) ((string-match (lyskom-get-string 'person-or-conf-no-regexp) string) nil) (t (or (lyskom-completing-cache-completion (lyskom-complete-string name-list) x-list) (and (lyskom-read-conf-internal-verify-type nil nil predicate login-list candidate-list) (list string)))))))))))) (defun lyskom-completing-member (string list) "Check case-insensitively if STRING is a member of LIST" (let (result) (while (and list (not result)) (if (string= (downcase string) (downcase (car list))) (setq result list) (setq list (cdr list)))) result)) (defun lyskom-read-conf-internal-verify-type (conf-no conf-type predicate logins x-list) (or (and (memq 'all predicate) conf-no) (and (memq 'conf predicate) conf-type (not (conf-type->letterbox conf-type))) (and (memq 'pers predicate) conf-type (conf-type->letterbox conf-type)) (and (memq 'login predicate) conf-type (memq conf-no logins)) (and (memq 'none predicate) (and (null conf-no) (null x-list))))) (defun lyskom-complete-string (string-list) "Find the longest common prefix of all strings in STRING-LIST according to the LysKOM rules of string matching." (let ((main-state 'start-of-string) (tmp-state nil) (current-state 'main-state) (main-accumulator nil) (tmp-accumulator nil) (current-accumulator 'main-accumulator) (done nil) (next-char-start nil) (paren-depth 0) (data-list (lyskom-complete-string-munge-input string-list)) (next-char-state (vector nil nil))) (while (not done) (lyskom-complete-string-next-char next-char-state data-list) (cond ;; ;; Case one, a match of two non-special characters. ;; Accumulate one character and advance the lists ;; ((eq (aref next-char-state 0) 'match) (if (and (eq (aref next-char-state 1) ?\ ) (or (eq (symbol-value current-state) 'start-of-word) (eq (symbol-value current-state) 'start-of-string))) (lyskom-complete-string-advance data-list) (progn (lyskom-complete-string-accumulate current-accumulator (aref next-char-state 1)) (if (eq (aref next-char-state 1) ?\ ) (set current-state 'start-of-word) (set current-state 'in-a-word)) (lyskom-complete-string-advance data-list)))) ;; ;; Case two, a match of two open-paren expressions Increase ;; paren depth and accumulate a character. First set ;; current-accumulator to the temporary if paren-depth is zero ;; to start with. ;; ((eq (aref next-char-state 0) 'open-paren-match) (if (zerop paren-depth) (progn (setq current-accumulator 'tmp-accumulator) (setq current-state 'tmp-state) (setq tmp-state main-state) (setq tmp-accumulator nil))) (setq paren-depth (1+ paren-depth)) (lyskom-complete-string-accumulate current-accumulator (aref next-char-state 1)) (lyskom-complete-string-advance data-list)) ;; ;; Case three, a match of two close-paren expressions ;; Accumulate a character. If paren-depth is postitive, ;; decrease it. If it ends up zero, add the temporary ;; accumulator to the main accumulator and set the current ;; accumulator to the main accumulator. ;; ((eq (aref next-char-state 0) 'close-paren-match) (lyskom-complete-string-accumulate current-accumulator (aref next-char-state 1)) (if (> paren-depth 0) (progn (setq paren-depth (1- paren-depth)) (if (zerop paren-depth) (progn (setq main-accumulator (nconc tmp-accumulator main-accumulator)) (setq main-state tmp-state) (setq current-state 'main-state) (setq current-accumulator 'main-accumulator))))) (lyskom-complete-string-advance data-list)) ;; ;; Case two, a mismatch of any kind in a paren expression ;; ((and (> paren-depth 0) (or (eq (aref next-char-state 0) 'mismatch) (eq (aref next-char-state 0) 'space-mismatch) (eq (aref next-char-state 0) 'open-paren-mismatch))) (setq tmp-accumulator nil) (setq tmp-state nil) (setq current-state 'main-state) (setq current-accumulator 'main-accumulator) (lyskom-complete-string-close-parens data-list paren-depth) (setq paren-depth 0)) ;; ;; Case two and a half or so, a space mismatch. This is ignored ;; if we're still at the start of the string ;; ((and (eq (aref next-char-state 0) 'space-mismatch) (eq (symbol-value current-state) 'start-of-string)) (lyskom-complete-string-skip-whitespace data-list)) ;; ;; Case three, a mismatch of regular characters outside a paren ;; Advance to the end of the current word ;; ((and (or (eq (aref next-char-state 0) 'mismatch) (eq (aref next-char-state 0) 'space-mismatch)) (zerop paren-depth)) (if (or (eq (symbol-value current-state) 'start-of-word) (eq (symbol-value current-state) 'start-of-string)) (setq done t) (progn (lyskom-complete-string-advance-to-end-of-word data-list) (set current-state 'in-a-word)))) ;; ;; Case four, a mistmatch where one character is an open-paren ;; ((eq (aref next-char-state 0) 'open-paren-mismatch) (lyskom-complete-string-skip-parens data-list)) ;; ;; Case five, eof ;; ((eq (aref next-char-state 0) 'eof) (setq done t)) ;; ;; Case six, can't happen ;; (t (error "This can't happen: %S" next-char-state)))) ;; ;; Build the result by reversing the result list and making a ;; string out of it. ;; (let ((tmp (make-string (length main-accumulator) 0)) (index 0)) (lyskom-traverse el (nreverse main-accumulator) (aset tmp index el) (setq index (1+ index))) tmp))) (defun lyskom-complete-string-accumulate (accumulator char) (set accumulator (cons char (symbol-value accumulator)))) (defun lyskom-complete-string-munge-input (string-list) (mapcar (function (lambda (x) (vector 0 (length x) x))) string-list)) ;;; ;;; Advance one regular character or multiple whitespaces ;;; (defun lyskom-complete-string-advance (data-list) (lyskom-traverse el data-list (string-match "\\(\\s-+\\|\\S-\\|$\\)" (aref el 2) (aref el 0)) (aset el 0 (match-end 0)))) (defun lyskom-complete-string-skip-whitespace (data-list) (lyskom-traverse el data-list (string-match "\\s-*" (aref el 2) (aref el 0)) (aset el 0 (match-end 0)))) ;;; ;;; Advance to the end of the current word ;;; (defun lyskom-complete-string-advance-to-end-of-word (data-list) (lyskom-traverse el data-list (aset el 0 (string-match "\\(\\s-\\|$\\)" (aref el 2) (aref el 0))))) ;;; ;;; Unwind a number of parens ;;; (defun lyskom-complete-string-skip-parens (data-list) (lyskom-traverse el data-list (if (eq ?\( (aref (aref el 2) (aref el 0))) (progn (aset el 0 (1+ (aref el 0))) (lyskom-complete-string-close-parens-2 el 1))))) (defun lyskom-complete-string-close-parens (data-list depth) (lyskom-traverse el data-list (lyskom-complete-string-close-parens-2 el depth))) (defun lyskom-complete-string-close-parens-2 (el depth) (let ((tmp nil)) (while (> depth 0) (setq tmp (string-match "[^(]*)" (aref el 2) (aref el 0))) (if tmp (progn (aset el 0 (match-end 0)) (setq depth (1- depth))) (progn (setq tmp (string-match "[^)]*(" (aref el 2) (aref el 0))) (if tmp (progn (aset el 0 (match-end 0)) (setq depth (1+ depth))) (aset el 0 (aref el 1)) (setq depth 0))))))) ;;; ;;; Check what's happenin' next ;;; (defun lyskom-complete-string-next-char (state data-list) (let ((eofp nil) (open-paren-p nil) (close-paren-p nil) (matchp t) (spacep nil) (char nil) (xchar nil)) (mapcar (function (lambda (x) (cond ((>= (aref x 0) (aref x 1)) (setq eofp t) (setq matchp nil)) ((eq (aref (aref x 2) (aref x 0)) ?\() (setq open-paren-p t)) ((eq (aref (aref x 2) (aref x 0)) ?\)) (setq close-paren-p t)) ((eq (aref (aref x 2) (aref x 0)) ?\ ) (setq spacep t))) (setq matchp (and matchp (if (null char) (progn (setq xchar (aref (aref x 2) (aref x 0))) (setq char (downcase xchar))) (eq char (downcase (aref (aref x 2) (aref x 0))))))))) data-list) (aset state 1 xchar) (cond (eofp (aset state 0 'eof)) ((and matchp open-paren-p) (aset state 0 'open-paren-match)) ((and matchp close-paren-p) (aset state 0 'close-paren-match)) (matchp (aset state 0 'match)) (spacep (aset state 0 'space-mismatch)) (open-paren-p (aset state 0 'open-paren-mismatch)) (t (aset state 0 'mismatch)))) state) ;;; ============================================================ ;;; ;;; Session reading ;;; ;;; (defun lyskom-read-session-no (prompt &optional empty initial only-one) "Returns a list of session numbers of a session by reading either the number of the session or a name. The question is prompted with PROMPT. If EMPTY is non-nil then the empty string is allowed (returns 0). INITIAL is the initial contents of the input field. If ONLY-ONE is non-nil only one session number will be returned." (lyskom-completing-clear-cache) (let (result data done (lyskom-blocking-process lyskom-proc)) (while (not done) (setq data (lyskom-read-session-no-aux prompt t initial)) (cond ((and (string= data "") (not empty))) ((string= data "") (setq done t result nil)) (t (setq result (lyskom-read-session-internal data '(login) 'lyskom-lookup) done t)))) (if (and only-one (> (length result) 1)) (setq result (lyskom-read-session-resolve-ambiguity result))) result)) (defun lyskom-read-session-resolve-ambiguity (sessions) (lyskom-insert "\n") (let* ((s-width (1+ (apply 'max (mapcar (function (lambda (x) (length (int-to-string x)))) sessions)))) (format-string-s (lyskom-info-line-format-string s-width "s" "s")) (format-string-p (lyskom-info-line-format-string s-width "P" "M"))) (lyskom-format-insert format-string-s "" (lyskom-get-string 'lyskom-name) (lyskom-get-string 'is-in-conf)) (lyskom-format-insert format-string-s "" (lyskom-get-string 'from-machine) (lyskom-get-string 'is-doing)) (lyskom-insert (concat (make-string (- (lyskom-window-width) 2) ?-) "\n")) (let ((result nil) (who-info (mapcar (function (lambda (el) (let* ((info (blocking-do 'get-session-info el)) (confconfstat (blocking-do 'get-conf-stat (session-info->working-conf info)))) (lyskom-format-insert format-string-p (format "%d%s" (session-info->connection info) (if (eq (session-info->connection info) lyskom-session-no) "*" " ")) (session-info->pers-no info) (if (conf-stat->name confconfstat) confconfstat (lyskom-get-string 'not-present-anywhere))) (lyskom-format-insert format-string-p "" (lyskom-return-username info) (concat "(" (session-info->doing info) ")")) (cons (number-to-string (session-info->connection info)) info)))) (sort sessions '<)))) (lyskom-insert (concat (make-string (- (lyskom-window-width) 2) ?-) "\n")) (lyskom-insert (lyskom-format 'total-users (length who-info))) (lyskom-scroll) (while (string= "" (setq result (completing-read (lyskom-get-string 'resolve-session) who-info nil t (car (car who-info)) nil)))) (list (session-info->connection (cdr (assoc result who-info))))))) (defun lyskom-read-session-no-aux (prompt &optional mustmatch initial) "Read a LysKOM name or session number, prompting with PROMPT. The third argument MUSTMATCH makes the function always return the conf-no and never the read string. The fourth argument INITIAL is the initial contents of the input-buffer. Returns the name." (lyskom-completing-clear-cache) (let* ((completion-ignore-case t) ; When lyskom-read-conf-name-internal is called the current-buffer ; is the minibuffer and the buffer-local variable lyskom-proc is not ; correct. Then the variable lyskom-blocking-process must be set ; instead. It is not buffer-local but scopes the let. (lyskom-blocking-process lyskom-proc) (minibuffer-local-completion-map lyskom-minibuffer-local-completion-map) (minibuffer-local-must-match-map lyskom-minibuffer-local-must-match-map)) (completing-read prompt 'lyskom-read-session-internal '(login) mustmatch initial 'lyskom-name-hist))) (defun lyskom-read-session-internal (string predicate all) (let* ((result nil) (partial (lyskom-read-conf-internal string predicate all)) (who-list (if (or (null partial) (eq all 'lyskom-lookup)) (mapcar (function (lambda (el) (cons (number-to-string (who-info->connection el)) el))) (lyskom-completing-who-is-on)))) (result (cond ((and (null who-list) (not (eq 'lyskom-lookup all))) nil) ((eq all nil) ; try-completion (try-completion string who-list nil)) ((eq all t) ; all-completions (all-completions string who-list nil)) ((eq all 'lambda) ; exact match (and (assoc string who-list) t)) ((eq all 'lyskom-lookup) ; get number (car-safe (assoc string who-list)))))) (cond ((eq all 'lyskom-lookup) (if partial (let* ((output nil) (list who-list) (num (string-to-number string)) (conf-no (if (= 0 num) ; Dont lookup unless necessary (conf-z-info->conf-no partial)))) (while list (if (or (eq conf-no (who-info->pers-no (cdr (car list)))) (eq num (who-info->connection (cdr (car list))))) (setq output (cons (who-info->connection (cdr (car list))) output))) (setq list (cdr list))) output) (list (string-to-number result)))) (t (or partial result)))))