/collects/readline/mzrl.rkt

http://github.com/gmarceau/PLT · Racket · 119 lines · 85 code · 19 blank · 15 comment · 14 complexity · dda48f15a01fb7c34530e497946a2f08 MD5 · raw file

  1. #lang scheme/base
  2. (require mzlib/foreign (only-in '#%foreign ffi-obj)) (unsafe!)
  3. (provide readline readline-bytes
  4. add-history add-history-bytes
  5. history-length history-get history-delete
  6. set-completion-function!)
  7. ;; libtermcap needed on some platforms
  8. (define libtermcap (with-handlers ([exn:fail? void]) (ffi-lib "libtermcap")))
  9. (define libreadline (ffi-lib "libreadline" '("5" "4" "")))
  10. (define make-byte-string ; helper for the two types below
  11. (get-ffi-obj "scheme_make_byte_string" #f (_fun _pointer -> _scheme)))
  12. (define _bytes/eof/free ; register a finalizer on the resulting bytes
  13. (make-ctype _pointer
  14. (lambda (x) (and (not (eof-object? x)) x))
  15. (lambda (x)
  16. (if x
  17. (let ([b (make-byte-string x)])
  18. (register-finalizer b (lambda (_) (free x)))
  19. b)
  20. eof))))
  21. (define _string/eof/free ; make a Scheme str from C str & free immediately
  22. (make-ctype _pointer
  23. (lambda (x) (and (not (eof-object? x)) (string->bytes/utf-8 x)))
  24. (lambda (x)
  25. (if x
  26. (let ([s (bytes->string/utf-8 (make-byte-string x))]) (free x) s)
  27. eof))))
  28. (define readline
  29. (get-ffi-obj "readline" libreadline (_fun _string -> _string/eof/free)))
  30. (define readline-bytes
  31. (get-ffi-obj "readline" libreadline (_fun _bytes -> _bytes/eof/free)))
  32. (define add-history
  33. (get-ffi-obj "add_history" libreadline (_fun _string -> _void)))
  34. (define add-history-bytes
  35. (get-ffi-obj "add_history" libreadline (_fun _bytes -> _void)))
  36. (define history-length
  37. (let ([hl (ffi-obj #"history_length" libreadline)])
  38. (lambda () (ptr-ref hl _int))))
  39. (define history-base
  40. (let ([hb (ffi-obj #"history_base" libreadline)])
  41. (lambda () (ptr-ref hb _int))))
  42. ;; The history library has this great feature: *some* function consume
  43. ;; an index that is relative to history_base, and *some* get a plain
  44. ;; offset. Someone just had so much fun they had to share. This
  45. ;; deals with this absurdity, checks the range of the index, and deals
  46. ;; with negative offsets.
  47. (define (hist-idx who idx base?)
  48. (let* ([len (history-length)]
  49. [idx (cond [(<= 0 idx (sub1 len)) idx]
  50. [(<= (- len) idx -1) (+ len idx)]
  51. [else (error who "index out of history range, -~a - ~a"
  52. len (sub1 len))])])
  53. (if base? (+ idx (history-base)) idx)))
  54. ;; actually, returns a pointer to a struct with the string, but all we
  55. ;; care about is the string...
  56. (define history-get
  57. (get-ffi-obj "history_get" libreadline
  58. (_fun (i) :: (_int = (hist-idx 'history-get i #t)) -> (_ptr o _string))))
  59. (define history-remove ; returns HIST_ENTRY* that history_free() frees
  60. (get-ffi-obj "remove_history" libreadline
  61. (_fun (i) :: (_int = (hist-idx 'history-delete i #f)) -> _pointer)))
  62. (define history-free ; ignore histdata_t return value
  63. (get-ffi-obj "free_history_entry" libreadline (_fun _pointer -> _void)
  64. ;; if not available, use free
  65. (lambda () free)))
  66. (define (history-delete idx)
  67. (history-free (history-remove idx)))
  68. ;; Simple completion: use this with a (string -> (list-of string)) function
  69. ;; that returns the completions for a given string (can be used with other
  70. ;; input string types too, depending on the `type' argument). Use #f to remove
  71. ;; a completion function that was previously set.
  72. (define set-completion-function!
  73. (case-lambda
  74. [(func) (set-completion-function! func _string)]
  75. [(func type)
  76. (if func
  77. (set-ffi-obj! "rl_completion_entry_function" libreadline
  78. (_fun type _int -> _pointer)
  79. (completion-function func))
  80. (set-ffi-obj! "rl_completion_entry_function" libreadline _pointer #f))]))
  81. (define (completion-function func)
  82. (let ([cur '()])
  83. (define (complete str state)
  84. (if (zero? state)
  85. (begin (set! cur (func str)) (complete #f 1))
  86. (and (pair? cur)
  87. (let* ([cur (begin0 (car cur) (set! cur (cdr cur)))]
  88. [cur (if (string? cur) (string->bytes/utf-8 cur) cur)])
  89. (malloc (add1 (bytes-length cur)) cur 'raw)))))
  90. complete))
  91. (set-ffi-obj! "rl_readline_name" libreadline _bytes #"mzscheme")
  92. ;; need to capture the real input port below
  93. (define real-input-port (current-input-port))
  94. (unless (eq? 'stdin (object-name real-input-port))
  95. (log-warning "mzrl warning: could not capture the real input port\n"))
  96. (unless (terminal-port? real-input-port)
  97. (log-warning "mzrl warning: input port is not a terminal\n"))
  98. ;; make it possible to run Scheme threads while waiting for input
  99. (set-ffi-obj! "rl_event_hook" libreadline (_fun -> _int)
  100. (lambda () (sync/enable-break real-input-port) 0))