001  (ns beowulf.reader.char-reader
002    "Provide sensible line editing, auto completion, and history recall.
003     
004     None of what's needed here is really working yet, and a pull request with
005     a working implementation would be greatly welcomed.
006     
007     ## What's needed (rough specification)
008     
009     1. Carriage return **does not** cause input to be returned, **unless**
010         a. the number of open brackets `(` and closing brackets `)` match; and
011         b. the number of open square brackets `[` and closing square brackets `]` also match;
012     2. <Ctrl-D> aborts editing and returns the string `STOP`;
013     3. <Up-arrow> and <down-arrow> scroll back and forward through history, but ideally I'd like 
014        this to be the Lisp history (i.e. the history of S-Expressions actually read by `READ`, 
015        rather than the strings which were supplied to `READ`);
016     4. <Tab> offers potential auto-completions taken from the value of `(OBLIST)`, ideally the
017        current value, not the value at the time the session started;
018     5. <Back-arrow> and <Forward-arrow> offer movement and editing within the line.
019     
020     TODO: There are multiple problems with JLine; a better solution might be
021     to start from here:
022     https://stackoverflow.com/questions/7931988/how-to-manipulate-control-characters"
023    (:require [beowulf.oblist :refer [*options* oblist]])
024    (:import [org.jline.reader.impl.completer StringsCompleter]
025             [org.jline.reader.impl DefaultParser DefaultParser$Bracket]
026             [org.jline.reader LineReaderBuilder]
027             [org.jline.terminal TerminalBuilder]
028             [org.jline.widget AutopairWidgets AutosuggestionWidgets]))
029  
030  ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
031  ;;;
032  ;;; Copyright (C) 2022-2023 Simon Brooke
033  ;;;
034  ;;; This program is free software; you can redistribute it and/or
035  ;;; modify it under the terms of the GNU General Public License
036  ;;; as published by the Free Software Foundation; either version 2
037  ;;; of the License, or (at your option) any later version.
038  ;;; 
039  ;;; This program is distributed in the hope that it will be useful,
040  ;;; but WITHOUT ANY WARRANTY; without even the implied warranty of
041  ;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
042  ;;; GNU General Public License for more details.
043  ;;; 
044  ;;; You should have received a copy of the GNU General Public License
045  ;;; along with this program; if not, write to the Free Software
046  ;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
047  ;;;
048  ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
049  
050  ;; It looks from the example given [here](https://github.com/jline/jline3/blob/master/demo/src/main/java/org/jline/demo/Repl.java)
051  ;; as though JLine could be used to build a perfect line-reader for Beowulf; but it also
052  ;; looks as though you'd need a DPhil in JLine to write it, and I don't have
053  ;; the time.
054  
055  (defn build-completer
056    "Build a completer which takes tokens from the oblist.
057     
058     This is sort-of working, in as much as hitting <TAB> on a blank line will 
059     show a table of values from the oblist, but hitting <TAB> after you've 
060     started input does not show potential completions for tokens you've started."
061    []
062    (StringsCompleter. (map #(str (first %)) @oblist)))
063  
064  ;; This breaks; it is not correctly resolving the Enum, although I can't work out
065  ;; why not.
066  ;; (defn build-parser
067  ;;   []
068  ;;   (println "Building parser")
069  ;;   (let [parser (DefaultParser.)]
070  ;;     (doall
071  ;;      (.setEofOnUnclosedBracket 
072  ;;       parser DefaultParser$Bracket/ROUND))))
073  
074  (def get-reader
075    "Return a reader, first constructing it if necessary.
076     
077     **NOTE THAT** this is not settled API. The existence and call signature of
078     this function is not guaranteed in future versions."
079    (memoize (fn []
080              (let [term (.build (.system (TerminalBuilder/builder) true))
081                    reader (-> (LineReaderBuilder/builder)
082                                (.terminal  term)
083                                (.completer  (build-completer))
084   ;;       #(.parser % (build-parser))
085                                (.build))
086                    ;; apw (AutopairWidgets. reader false)
087                    ;; asw (AutosuggestionWidgets. reader)
088                    ]
089                ;; (.enable apw)
090                ;; (.enable asw)
091                reader))))
092  
093  (defn read-chars
094    "A drop-in replacement for `clojure.core/read-line`, except that line editing
095     and history should be enabled.
096     
097     **NOTE THAT** this does not fully work yet, but it is in the API because I 
098     hope that it will work later!"
099    [prompt]
100    (let [eddie (get-reader)]
101      (loop [s (.readLine eddie (str prompt " "))]
102        (if (and (= (count (re-seq #"\(" s))
103                    (count (re-seq #"\)" s)))
104                 (= (count (re-seq #"\[]" s))
105                    (count (re-seq #"\]" s))))
106          s
107          (recur (str s " " (.readLine eddie ":: ")))))))