(module actions mzscheme
(require (lib "class.ss")
(lib "struct.ss")
(lib "etc.ss")
(lib "list.ss")
(lib "plt-match.ss")
(lib "mred.ss" "mred")
(lib "framework.ss" "framework")
(only (lib "1.ss" "srfi") last)
(only (lib "13.ss" "srfi") string-prefix?)
"traversal.ss"
"structures.ss"
(all-except "utilities.ss" insert-rope)
"templates.ss"
"tag-state.ss"
"tag-reader.ss"
"gui/text-rope-mixin.ss"
"rope.ss"
"long-prefix.ss"
"cleanup-whitespace.ss"
"gui/clipboard.ss"
"language.ss")
(define voice-debug false)
(define (voice-printf . args)
(when voice-debug
(apply printf args)))
(define (open-paren? achar)
(and (char? achar)
(or (char=? achar #\()
(char=? achar #\[)
(char=? achar #\{))))
(define (select/stx world stx)
(select/pos+len world (syntax-position stx) (syntax-span stx)))
(define (select/pos+len world pos len)
(select/pos (select/len world len) pos))
(define (select/pos world pos)
(copy-struct World world
[World-cursor-position pos]))
(define (select/len world len)
(copy-struct World world
[World-selection-length len]))
(define (set-cursor-position world pos)
(select/pos+len world pos 0))
(define (select/pos+end world start end)
(select/pos+len world start (- end start)))
(define (cursor-at-selection-end world)
(select/pos+len world (World-selection-end-position world) 0))
(define (recompute-selection/insert world pos len)
(let-values ([(new-cursor-index new-selection-length)
(compute-new-selection/insert (World-cursor-index world) (World-selection-length world) (pos->index pos) len)])
(select/pos+len world (index->pos new-cursor-index) new-selection-length)))
(define (recompute-selection/delete world pos len)
(let-values ([(new-cursor-index new-selection-length)
(compute-new-selection/delete (World-cursor-index world) (World-selection-length world) (pos->index pos) len)])
(select/pos+len world (index->pos new-cursor-index) new-selection-length)))
(define (recompute-selection/replace world pos b-len a-len)
(let-values ([(new-cursor-index new-selection-length)
(compute-new-selection/replace (World-cursor-index world) (World-selection-length world) (pos->index pos) b-len a-len)])
(select/pos+len world (index->pos new-cursor-index) new-selection-length)))
(define (shrink-selection world)
(let ([new-world
(select/pos+len world
(add1 (World-cursor-position world))
(sub1 (World-selection-length world)))])
new-world))
(define (mark/stx world stx)
(mark/pos+len world (syntax-position stx) (syntax-span stx)))
(define (mark/pos+len world pos len)
(mark/pos (mark/len world len) pos))
(define (mark/pos world pos)
(copy-struct World world
[World-mark-position pos]))
(define (mark/len world len)
(copy-struct World world
[World-mark-length len]))
(define (set-mark-position world pos)
(mark/pos+len world pos 0))
(define (mark-at-start world)
(mark/len world 0))
(define (mark-at-end world)
(set-mark-position world (World-mark-end-position world)))
(define (unmark world)
(mark-at-start world))
(define (exchange world)
(mark/pos+len
(select/pos+len world (World-mark-position world) (World-mark-length world))
(World-cursor-position world)
(World-selection-length world)))
(define (selection->mark world)
(mark/pos+len (select/len world 0)
(World-cursor-position world)
(World-selection-length world)))
(define (recompute-mark/insert world pos len)
(let-values ([(new-mark-index new-mark-length)
(compute-new-selection/insert (World-mark-index world) (World-mark-length world) (pos->index pos) len)])
(mark/pos+len world (index->pos new-mark-index) new-mark-length)))
(define (recompute-mark/delete world pos len)
(let-values ([(new-mark-index new-mark-length)
(compute-new-selection/delete (World-mark-index world) (World-mark-length world) (pos->index pos) len)])
(mark/pos+len world (index->pos new-mark-index) new-mark-length)))
(define (recompute-mark/replace world pos b-len a-len)
(let-values ([(new-mark-index new-mark-length)
(compute-new-selection/replace (World-mark-index world) (World-mark-length world) (pos->index pos) b-len a-len)])
(mark/pos+len world (index->pos new-mark-index)
new-mark-length)))
(define (indent/pos+len world pos len)
(when (< len 0)
(error 'indent/pos+len))
(let-values ([(world mark) (world-new-marker world pos)])
(queue-imperative-action
world
(lambda (world window update-world-fn update-mred-fn)
(let* ([new-pos (max 0 (sub1 (world-marker-position world mark)))]
[new-end-pos (min (string-length (send window get-text))
(+ new-pos len))])
(send window tabify-selection new-pos new-end-pos)
(world-clear-marker (update-world-fn world) mark))))))
(define (indent/selection world)
(indent/pos+len world
(World-cursor-position world)
(World-selection-length world)))
(define (cleanup-text/pos+len world pos len)
(local
((define (get-line-oriented-start-and-end start-pos end-pos)
(let* ([start-pos
(line-pos (World-rope world) start-pos)]
[start-pos
(cond [(find-pos start-pos (World-syntax-list world))
=> syntax-position]
[else start-pos])]
[end-pos
(line-end-pos (World-rope world) end-pos)]
[end-pos
(cond [(find-pos end-pos (World-syntax-list world))
=> syntax-end-position]
[else end-pos])])
(values start-pos end-pos)))
(define (cleanup-text/between world start-pos end-pos)
(let* ([start-index (pos->index start-pos)]
[end-index (pos->index end-pos)]
[line (subrope (World-rope world) start-index end-index)]
[len (rope-length line)])
(let-values ([(clean-line lst)
(cleanup-whitespace line start-index
(list (World-selection-index world)
(World-selection-end-index world)
(World-mark-index world)
(World-mark-end-index world)))])
(let ([new-world (world-replace-rope world start-index clean-line len)])
(mark/pos+len (select/pos+len new-world
(index->pos (first lst))
(- (second lst) (first lst)))
(index->pos (third lst))
(- (fourth lst) (third lst))))))))
(let*-values ([(start-pos end-pos)
(get-line-oriented-start-and-end pos (+ pos len))]
[(new-world) (cleanup-text/between world start-pos end-pos)])
(indent/pos+len new-world start-pos (- end-pos start-pos)))))
(define (cleanup-text/selection world)
(cleanup-text/pos+len world (World-cursor-position world) (World-selection-length world)))
(define (insert world pos a-rope)
(let ([len (rope-length a-rope)]
[new-world (world-insert-rope world (pos->index pos) a-rope)])
(cleanup-text/pos+len
(recompute-mark/insert
(recompute-selection/insert new-world pos len) pos len) pos len)))
(define (delete/stx world stx)
(delete/pos+len world (syntax-position stx) (syntax-span stx)))
(define (delete/pos+len world pos len)
(if (= len 0)
world
(let ([new-world (world-delete-rope world (pos->index pos) len)])
(cleanup-text/pos+len
(recompute-mark/delete
(recompute-selection/delete
new-world pos len) pos len) pos 0))))
(define (delete/selection world)
(delete/pos+len world
(World-cursor-position world)
(World-selection-length world)))
(define (delete/mark world)
(delete/pos+len world (World-mark-position world) (World-mark-length world)))
(define (replace/selection world a-rope)
(let ([len (rope-length a-rope)]
[new-world
(world-replace-rope world
(World-cursor-index world)
a-rope
(World-selection-length world))])
(cleanup-text/selection (recompute-mark/replace
(select/len new-world len)
(World-cursor-position world)
(World-selection-length world)
len))))
(define (close world)
(define (flash-last-sexp! world window update-world-fn update-mred-fn)
(let ([pos (send window get-backward-sexp (send window get-start-position))])
(queue-callback
(lambda ()
(when (and pos
(send window get-forward-sexp pos)
(open-paren?
(string-ref (send window get-text pos (add1 pos)) 0)))
(send window flash-on pos (send window get-forward-sexp pos))))
#f)
world))
(let*-values
([(stxy) (find-siblings-ellipsis (World-cursor-position world)
(World-syntax-list world))]
[(new-world)
(if (not stxy)
(holder/end world)
(let* ([stx (first stxy)]
[sty (second stxy)]
[world (delete/pos+len world
(syntax-position stx)
(- (syntax-end-position sty)
(syntax-position stx)))])
(holder/end
(if (join-ellipsis/stx? sty)
(anti-join/cursor world)
world))))])
(queue-imperative-action new-world flash-last-sexp!)))
(define (update-ellipsis world stx)
(let ([str/false (update-ellipsis/stx? stx)])
(if str/false
(world-replace-rope world (syntax-index stx) (string->rope str/false) (syntax-span stx))
world)))
(define (dedouble-ellipsis world)
(print-mem
'dedouble-ellipsis
(lambda ()
(with-handlers ([voice-exn? (lambda args world)])
(let ([stxy (find-siblings-ellipsis (World-cursor-position world) (World-syntax-list world))])
(unless stxy (raise (make-voice-exn "unable to find the next placeholder")))
(let* ([stx (first stxy)]
[sty (second stxy)]
[world (update-ellipsis world sty)]
[text
(rope-append (if (enter-ellipsis/stx? sty)
(string->rope " \n ")
(string->rope " "))
(get-subrope/stx (World-rope world) stx))])
(if (and (= 0 (World-selection-length world))
(= (syntax-position stx) (World-cursor-position world)))
world
(insert world (syntax-end-position stx) text))))))))
(define (cursor-position-is-quoted? world)
(and (<= 0
(sub1 (World-cursor-index world))
(sub1 (rope-length (World-rope world))))
(quoting-char?
(rope-ref (World-rope world)
(sub1 (World-cursor-index world))))))
(define (open square? world rope/false pos/false template-number magic-number template/magic-wrap?)
(command world
rope/false
pos/false
true square? template-number
magic-number template/magic-wrap?))
(define (insert-rope world a-rope pos/false template-number magic-number template/magic-wrap?)
(command world
a-rope
pos/false
false false template-number
magic-number template/magic-wrap?))
(define (command world a-rope/false pos/false open?
square? template-number magic-number template/magic-wrap?)
(local
(
(define (apply-template template symbol/false)
(let ([format-string (cond [(and symbol/false
(string-prefix? "#<<" (symbol->string symbol/false)))
" ~a"]
[else
" ~a "])])
(string->rope
(format format-string (if template
(shape-paren (and square? 'Square) template)
symbol/false)))))
(define (get-expanded-text&world)
(cond
[(and a-rope/false (rope-has-special? a-rope/false))
(let ([world (if pos/false
(set-cursor-position world pos/false)
world)])
(values a-rope/false world #f))]
[else
(let* ([world
(if pos/false
(set-cursor-position world pos/false)
world)] [symbol/false
(and a-rope/false
(string->symbol (rope->string a-rope/false)))]
[symbol/false
(and symbol/false
(magic/expand world
(World-cursor-position world)
symbol/false
magic-number
template/magic-wrap?))])
(let-values ([(template number-of-templates)
(lookup-template symbol/false
template-number
open?
template/magic-wrap?)])
(let* ([text (apply-template template symbol/false)]
[text (cond
[(cursor-position-is-quoted? world)
(subrope text 1)]
[else text])]
[world (if (<= number-of-templates 1)
world
(success-message
world
(format "template ~a of ~a"
(add1 (modulo template-number number-of-templates))
number-of-templates)))])
(values text world template))))])))
(let-values ([(text world template)
(get-expanded-text&world)])
(let* ([world
(print-mem*
'command-indent/selection
(indent/selection
(replace/selection
(dedouble-ellipsis world) text)))])
(if template
(holder world)
(step-to-the-right world))))))
(define (step-to-the-right world)
(let ([stx (find-pos-sibling-forward (World-selection-end-position world)
(World-syntax-list world))])
(if (and stx
(placeholder/stx? stx))
(select/stx world stx)
(set-cursor-position world (World-selection-end-position world)))))
(define holder
(opt-lambda (world [base (World-cursor-position world)] [backward? false])
(or (holder-helper world base backward?)
(raise (make-voice-exn "Unable to find the next placeholder.")))))
(define (holder-helper world base backward?)
(let ([stx (find-placeholder backward? base (World-syntax-list world))])
(and stx (select/stx world stx))))
(define (holder/end world)
(define maximum-jump-to-holder 30)
(define (rope-count-nonwhitespace a-rope)
(- (rope-length a-rope) (rope-count-whitespace a-rope)))
(define (too-far-away? old-w new-w)
(let* ([a (World-selection-end-index old-w)]
[b (World-selection-index new-w)]
[small (min a b)]
[big (max a b)])
(<
maximum-jump-to-holder
(rope-count-nonwhitespace (subrope (World-rope world) small big)))))
(define (no-holder)
(let ([stx (find-pos-parent (World-cursor-position world)
(World-syntax-list world))])
(if stx
(set-cursor-position world (syntax-end-position stx))
world)))
(let ([w (holder-helper world (World-cursor-position world) false)])
(if (or (not w) (too-far-away? world w))
(no-holder)
w)))
(define (first/selection world)
(let ([stx/false (find-pos-parent (World-cursor-position world)
(World-syntax-list world))])
(cond
[(not stx/false)
(raise (make-voice-exn "No containing expression."))]
[(empty? (stx->lst stx/false))
(select/stx world stx/false)]
[else
(select/stx world (first (stx->lst stx/false)))])))
(define (last/selection world)
(let ([stx/false (find-pos-parent (World-cursor-position world)
(World-syntax-list world))])
(cond
[(not stx/false)
(raise (make-voice-exn "No containing expression."))]
[(empty? (stx->lst stx/false))
(select/stx world stx/false)]
[else
(select/stx world (last (stx->lst stx/false)))])))
(define (delete world)
(delete/selection (dedouble-ellipsis world)))
(define (bring world)
(define (originally-unmarked?)
(= 0 (World-mark-length world)))
(define (mark-default world)
(match (find-pos-fill-forward (World-selection-end-position world) (World-syntax-list world))
[#f (raise (make-voice-exn "No mark, so nothing to fill with."))]
[stx (mark/stx world stx)]))
(let* ([world (if (originally-unmarked?) (mark-default world) world)]
[world (extend-mark-to-newline world)]
[fill-rope (rope-append* rope-space
(World-mark world)
rope-space)]
[world (id (replace/selection (dedouble-ellipsis (delete/mark world)) fill-rope))]
[world (if (= (World-cursor-position world)
(World-mark-position world))
(set-mark-position world (World-selection-end-position world))
world)]
[stx/false (find-pos-mark-forward (World-mark-position world) (World-syntax-list world))]
[world (if (and (not (originally-unmarked?))
stx/false)
(mark/stx world stx/false)
world)])
(holder/end world)))
(define (push world)
(exchange (bring (exchange world))))
(define (extend-mark-to-newline world)
(let ([matched-whitespace
(regexp-match #rx"^[ \t\n]*\n"
(rope->string
(rope-leading-whitespace
(subrope (World-rope world)
(World-mark-end-index world)))))])
(if matched-whitespace
(mark/pos+len world
(World-mark-position world)
(+ (World-mark-length world)
(string-length (first matched-whitespace))))
world)))
(define (copy world)
(when (World-selection world)
(set-clipboard-content (World-selection world)))
world)
(define (cut world)
(delete/selection (dedouble-ellipsis (copy world))))
(define (paste world)
(if (get-clipboard-content)
(replace/selection (dedouble-ellipsis world)
(rope-append* (string->rope " ")
(get-clipboard-content)
(string->rope " ")))
world))
(define *indentation-overhang* 500)
(define (enter/pos+len world pos len)
(indent/pos+len (insert world pos (string->rope "\n"))
(line-pos (World-rope world) pos)
(+ len *indentation-overhang*)))
(define (enter/selection world)
(enter/pos+len world (World-cursor-position world) (World-selection-length world)))
(define (join/pos+len world pos len)
(let* ([eol-index (line-end-index (World-rope world) (pos->index pos))]
[eof-index (rope-length (World-rope world))]
[line-start (line-pos (World-rope world) pos)])
(when (= eol-index eof-index)
(raise (make-voice-exn "No line to join.")))
(indent/pos+len (delete/pos+len
(world-insert-rope world eol-index (string->rope " "))
(add1 (index->pos eol-index)) 1)
line-start
(+ len (- pos line-start) *indentation-overhang*))))
(define (join/selection world)
(join/pos+len world (World-cursor-position world) (World-selection-length world)))
(define (anti-join/pos+len world pos len)
(let* ([eol-index (sub1 (line-index (World-rope world) (pos->index pos)))]
[line-start (line-pos (World-rope world) pos)])
(when (< eol-index 0)
(raise (make-voice-exn "No line to anti-join.")))
(indent/pos+len (delete/pos+len world (index->pos eol-index) 1)
line-start
(+ len (- pos line-start)))))
(define (anti-join/cursor world)
(anti-join/pos+len world (World-cursor-position world) 0))
(define (default-magic/language world)
(get-language-autocompletes))
(define (find-common-prefix some-strings)
(cond
[(empty? some-strings)
""]
[else
(common-long-prefix-ci some-strings)]))
(define (magic-options world pos symbol)
(let* ([symbols/stx
(find-all-magic symbol/stx? pos
(World-syntax-list world))]
[strs (filter-double
(append
(map (lambda (stx)
(symbol->string (syntax-e stx)))
symbols/stx)
(default-magic/language world)))]
[strs (filter (prefix/string? (symbol->string symbol))
strs)]
[tag-strs (map tag-name
(tag-library-lookup
(get-current-tag-library)
(symbol->string symbol)))]
[strs (append strs tag-strs)]
[strs (filter
(lambda (str)
(< (string-length (symbol->string symbol))
(string-length str)))
strs)]
[common-prefix
(find-common-prefix strs)])
(cond
[(and (> (string-length common-prefix) 0)
(not (string=? common-prefix
(symbol->string symbol))))
(cons
(symbol->string symbol)
(cons common-prefix
(filter-double strs)))]
[else
(cons (symbol->string symbol)
(filter-double strs))])))
(define (magic/expand world pos a-symbol magic-number wrap?)
(let* ([symbols (map string->symbol
(magic-options world pos a-symbol))]
[magic-number
(if wrap?
(modulo magic-number (length symbols))
magic-number)])
(list-ref/safe symbols magic-number)))
(define (magic/completion world pos symbol)
(let* ([symbols (rest (map string->symbol (magic-options world pos symbol)))])
(if (empty? symbols)
symbol
(string->symbol (list->string (list-gcd (map string->list (map symbol->string symbols))))))))
(define (magic-bash world symbol)
(let ([a-rope (string->rope (symbol->string (magic/completion world symbol)))])
(cursor-at-selection-end (replace/selection world a-rope))))
(provide select/stx
select/pos+end
select/pos+len
set-cursor-position
exchange
mark/stx
unmark
selection->mark
first/selection
last/selection
delete
close
dedouble-ellipsis
open
insert-rope
holder
bring
push
copy
cut
paste
enter/selection
join/selection
indent/selection
magic-options
magic-bash)
(define actions%
(class object%
(public select/stx
select/pos+end
set-cursor-position
exchange
mark/stx
unmark
selection->mark
first/selection
last/selection
delete
close
dedouble-ellipsis
open
insert-rope
holder
bring
push
copy
cut
paste
enter/selection
join/selection
transpose
indent/selection
magic-options
magic-bash)
(public select/pos+len
cursor-at-selection-end
recompute-selection/insert
recompute-selection/delete
recompute-selection/replace
mark/pos+len
set-mark-position
mark-at-start
mark-at-end
recompute-mark/insert
recompute-mark/delete
recompute-mark/replace
cleanup-text/pos+len
cleanup-text/selection
insert
delete/pos+len
delete/selection
delete/mark
replace/selection
enter/pos+len
join/pos+len
magic/completion)
(super-instantiate ())
)))