(module insert-keymap mzscheme
(require (lib "etc.ss")
(lib "list.ss")
(lib "class.ss")
(only (lib "1.ss" "srfi") circular-list)
(lib "framework.ss" "framework")
"rigid-keymap.ss"
"traversal.ss"
"utilities.ss"
"structures.ss"
"gui/choose-paren.ss"
"in-something.ss"
"rope.ss"
"gui/text-rope-mixin.ss"
(prefix action: "actions.ss")
(prefix preferences: "diva-preferences.ss"))
(provide make-insert-mode)
(define-struct Pending (world symbol))
(define (make-insert-mode editor diva-message get-world set-world set-on-focus-lost
set-after-insert-callback set-after-delete-callback
interpret! post-exit-hook cmd edit?)
(define world-at-beginning-of-insert #f)
(define pending-open false)
(define magic-options-lst false)
(define magic-option-base false)
(define left-edge-of-insert (send editor get-start-position))
(define right-edge-of-insert (send editor get-start-position))
(define clear-highlight (lambda () (void)))
(define insert-keymap #f)
(define (initialize!)
(set! insert-keymap (make-insert-keymap))
(send (send editor get-keymap) chain-to-keymap insert-keymap #t)
(set-on-focus-lost consume&exit)
(unset-insert&delete-callbacks)
(if edit? (begin-symbol-edit) (begin-symbol-insertion))
(when cmd (eval-cmd cmd)))
(define (consume-text world pending-open a-rope)
(if pending-open
(interpret! (Pending-world pending-open)
(make-Verb (make-Command (Pending-symbol pending-open))
false
(make-WhatN
(make-Rope-Noun a-rope))))
(interpret! world
(make-Verb (make-InsertRope-Cmd a-rope)
false
false))))
(define (consume-cmd world symbol)
(interpret! world (make-Verb (make-Command symbol) false false)))
(define (insert-color)
(preferences:get 'framework:paren-match-color))
(define (get-text)
(send editor get-text left-edge-of-insert right-edge-of-insert))
(define (get-rope)
(read-subrope-in-text editor left-edge-of-insert
(- right-edge-of-insert left-edge-of-insert)))
(define (get-text-to-cursor)
(send editor get-text left-edge-of-insert (send editor get-start-position)))
(define (set-text text)
(send editor insert text left-edge-of-insert (send editor get-start-position) true))
(define (set-insert&delete-callbacks)
(set-after-insert-callback on-insert)
(set-after-delete-callback on-delete))
(define (unset-insert&delete-callbacks)
(set-after-insert-callback void)
(set-after-delete-callback void))
(define (begin-symbol-insertion/nothing-pending)
(set! pending-open false)
(begin-symbol-insertion))
(define (begin-symbol-edit)
(let* ([world (get-world)]
[stx/false (find-pos-near (World-cursor-position world)
(World-syntax-list world))]
[stx/false (and stx/false
(first (append
(find-all atomic/stx?
(list stx/false))
(list #f))))])
(cond
[stx/false
(let ([original-pos (send editor get-end-position)])
(set-world (action:select/stx world stx/false))
(begin-symbol (send editor get-start-position)
(send editor get-end-position))
(send editor diva:set-selection-position
(clamp original-pos
(send editor get-start-position)
(send editor get-end-position)))
(set-insert&delete-callbacks))]
[else
(begin-symbol-insertion)])))
(define (begin-symbol-insertion)
(define left-point (send editor get-start-position))
(define need-space-before
(and (not (= 0 left-point))
(not (eq? #\space
(send editor get-character (sub1 left-point))))))
(define need-space-after
(or (= (string-length (send editor get-text))
left-point)
(not (eq? #\space
(send editor get-character (add1 left-point))))))
(define (prepare-insertion-point!)
(if need-space-before
(begin-symbol (add1 left-point) (add1 left-point))
(begin-symbol left-point left-point))
(unset-insert&delete-callbacks)
(unless (empty-selection?)
(send editor delete))
(when need-space-before
(send editor insert " "))
(when need-space-after
(send editor insert " ")
(send editor diva:set-selection-position
(max (sub1 (send editor get-end-position)) 0)))
(set-insert&delete-callbacks))
(prepare-insertion-point!)
(fill-highlight!))
(define (begin-symbol start-position end-position)
(diva-message "")
(set! left-edge-of-insert start-position)
(set! right-edge-of-insert end-position)
(fill-highlight!)
(set! world-at-beginning-of-insert (get-world))
(set! magic-options-lst false)
(set! magic-option-base false))
(define (crop n)
(clamp n 0 (string-length (send editor get-text))))
(define (empty-selection?)
(define start (box 0))
(define end (box 0))
(send editor get-position start end)
(= (unbox start) (unbox end)))
(define (clamp x low high)
(min (max x low) high))
(define (snap-to-edges)
(let ([snapped-pos
(clamp (send editor get-start-position)
left-edge-of-insert right-edge-of-insert)])
(unless (= snapped-pos (send editor get-start-position))
(send editor diva:set-selection-position snapped-pos))))
(define (move-up)
(send editor move-position 'up)
(snap-to-edges))
(define (move-down)
(send editor move-position 'down)
(snap-to-edges))
(define (move-left)
(send editor move-position 'left)
(snap-to-edges))
(define (move-right)
(send editor move-position 'right)
(snap-to-edges))
(define (move-left*)
(send editor diva:set-selection-position left-edge-of-insert))
(define (move-right*)
(send editor diva:set-selection-position right-edge-of-insert))
(define (delete-backward)
(cond
[(= left-edge-of-insert (send editor get-start-position))
(void)
(eval-text&cmd 'Younger)
]
[(< left-edge-of-insert
(send editor get-start-position))
(send editor delete)]))
(define (delete-forward)
(when (< (send editor get-start-position)
right-edge-of-insert)
(send editor delete
(send editor get-start-position)
(add1 (send editor get-start-position)))))
(define (kill-word-forward)
(let ([sel-start (send editor get-start-position)]
[sel-end (send editor get-end-position)])
(let ([end-box (box sel-end)])
(send editor find-wordbreak #f end-box 'caret)
(send editor kill
0
sel-start
(min right-edge-of-insert (unbox end-box))))))
(define (kill-word-backward)
(let ([sel-start (send editor get-start-position)]
[sel-end (send editor get-end-position)])
(let ([start-box (box sel-start)])
(send editor find-wordbreak start-box #f 'caret)
(send editor kill
0
(max left-edge-of-insert (unbox start-box))
sel-end))))
(define (fill-highlight!)
(clear-highlight)
(local ((define left-
(cond
[(= (send editor
position-line left-edge-of-insert)
(send editor
position-line (crop (sub1 left-edge-of-insert))))
(crop (sub1 left-edge-of-insert))]
[else
left-edge-of-insert])))
(set! clear-highlight
(send editor highlight-range
left-
(clamp (add1 right-edge-of-insert)
left-
(send editor last-position))
(insert-color)))))
(define (on-insert start length)
(set! right-edge-of-insert (+ right-edge-of-insert length))
(fill-highlight!))
(define (on-delete start length)
(set! right-edge-of-insert (- right-edge-of-insert length))
(fill-highlight!))
(define (invalid-insert? text)
(with-handlers
([exn:fail? (lambda (exn) (exn-message exn))])
(read (open-input-string text))
#f))
(define (eval-text)
(local
((define txt (get-text))
(define closer (in-something? txt))
(define a-rope (get-rope)))
(unless (blank-string? txt)
(local ((define closed-rope
(cond [closer
(rope-append a-rope (string->rope closer))]
[else a-rope])))
(let ([txt (if closer
(format "~a~a" txt closer)
txt)])
(consume-text world-at-beginning-of-insert
pending-open closed-rope)
(begin-symbol-insertion/nothing-pending))))))
(define (eval-cmd symbol)
(consume-cmd world-at-beginning-of-insert symbol)
(if (or (eq? symbol 'Open)
(eq? symbol 'Open-Square))
(begin
(set! pending-open (make-Pending world-at-beginning-of-insert symbol))
(begin-symbol-insertion))
(begin-symbol-insertion/nothing-pending)))
(define (eval-text&cmd symbol)
(cond [(text-already-introduces-open? (get-text))
(eval-text)]
[else
(eval-text)
(eval-cmd symbol)]))
(define (text-already-introduces-open? txt)
(or (string=? txt "#")
(string=? txt "#s")))
(define (magic-expand-insertion-text)
(define quote-prefix "^([\"#'`,@]*)")
(define (get-unmagic-prefix)
(second (regexp-match quote-prefix (get-text))))
(define (get-magic-text)
(regexp-replace quote-prefix (get-text) ""))
(define (consume-magic)
(set-text (string-append (get-unmagic-prefix)
(first magic-options-lst)))
(diva-message "")
(set! magic-option-base (first magic-options-lst))
(set! magic-options-lst (rest magic-options-lst)))
(cond
[(and magic-option-base
(string=? magic-option-base (get-magic-text)))
(consume-magic)]
[else
(let* ([options (action:magic-options
world-at-beginning-of-insert
left-edge-of-insert
(string->symbol (get-magic-text)))])
(set! magic-options-lst (rest (apply circular-list options)))
(cond
[(empty? (rest options))
(diva-message
(format "no completion for ~a" (get-magic-text)))]
[else
(consume-magic)]))]))
(define (revert&exit)
(set-world world-at-beginning-of-insert)
(exit))
(define (consume&exit)
(if (blank-string? (get-text))
(revert&exit)
(begin
(eval-text)
(set-world world-at-beginning-of-insert)
(exit))))
(define (exit)
(send (send editor get-keymap) remove-chained-keymap insert-keymap)
(clear-highlight)
(set-on-focus-lost (lambda () (void)))
(unset-insert&delete-callbacks)
(post-exit-hook))
(define-syntax (wrap-up stx)
(syntax-case stx ()
[(wrap-up fun ...)
(syntax/loc stx
(wrap-up* (lambda () fun) ...))]))
(define (wrap-up* . thunks)
(lambda (any event)
(dynamic-wind
(lambda () (send editor begin-edit-sequence))
(lambda ()
(with-handlers ([voice-exn? (lambda (exn)
(diva-message (voice-exn-message exn))
(exit))])
(for-each (lambda (t) (t)) thunks)))
(lambda () (send editor end-edit-sequence)))))
(define ((open-paren/contextual literal default-cmd) editor evt)
((wrap-up (maybe-literal
literal
(eval-text&cmd (get-contextual-open-cmd editor default-cmd))))
editor evt))
(define-syntax (maybe-literal stx)
(syntax-case stx ()
[(_ c e ...)
(syntax/loc stx
(maybe-literal* c (lambda () e) ...))]))
(define (maybe-literal* c . thunks)
(if (in-something? (get-text-to-cursor))
(send editor insert c)
(for-each (lambda (t) (t)) thunks)))
(define (magic-or-pass)
(if (= (string-length (get-text)) 0)
(eval-text&cmd 'Pass-Wrap)
(magic-expand-insertion-text)))
(define (make-insert-keymap)
(define insert-keymap (make-object keymap:aug-keymap%))
(install-rigid-keymap-bindings! insert-keymap)
(send insert-keymap add-function "diva:exit" (wrap-up (consume&exit)))
(send insert-keymap add-function "diva:cancel" (wrap-up (revert&exit)))
(send insert-keymap add-function "diva:delete-backward" (wrap-up (delete-backward)))
(send insert-keymap add-function "diva:delete-forward" (wrap-up (delete-forward)))
(send insert-keymap add-function "diva:kill-word-forward" (wrap-up (kill-word-forward)))
(send insert-keymap add-function "diva:kill-word-backward" (wrap-up (kill-word-backward)))
(send insert-keymap add-function "diva:space" (wrap-up (maybe-literal #\space (eval-text))))
(send insert-keymap add-function "diva:close" (wrap-up (maybe-literal #\) (eval-text&cmd 'Close))))
(send insert-keymap add-function "diva:close-square" (wrap-up (maybe-literal #\] (eval-text&cmd 'Close))))
(send insert-keymap add-function "diva:close-curly" (wrap-up (maybe-literal #\} (eval-text&cmd 'Close))))
(send insert-keymap add-function "diva:open" (wrap-up (maybe-literal #\( (eval-text&cmd 'Open))))
(send insert-keymap add-function "diva:open-square" (wrap-up (maybe-literal #\[ (eval-text&cmd 'Open))))
(send insert-keymap add-function "diva:open-square/contextual" (open-paren/contextual #\[ 'Open))
(send insert-keymap add-function "diva:open-curly" (wrap-up (maybe-literal #\{ (eval-text&cmd 'Open-Square))))
(send insert-keymap add-function "diva:enter" (wrap-up (maybe-literal #\newline (eval-text&cmd 'Enter))))
(send insert-keymap add-function "diva:indent" (wrap-up (eval-text&cmd 'Indent)))
(send insert-keymap add-function "diva:magic" (wrap-up (magic-expand-insertion-text)))
(send insert-keymap add-function "diva:pass" (wrap-up (magic-or-pass)))
(send insert-keymap add-function "diva:bring" (wrap-up (eval-text&cmd 'Bring)))
(send insert-keymap add-function "diva:up" (wrap-up (move-up)))
(send insert-keymap add-function "diva:down" (wrap-up (move-down)))
(send insert-keymap add-function "diva:left" (wrap-up (move-left)))
(send insert-keymap add-function "diva:right" (wrap-up (move-right)))
(send insert-keymap add-function "diva:left*" (wrap-up (move-left*)))
(send insert-keymap add-function "diva:right*" (wrap-up (move-right*)))
(preferences:install-insert-mode-bindings insert-keymap)
insert-keymap)
(initialize!)))