Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Repl history search #19

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion ccw.core/plugin.properties
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,10 @@ ccw.ui.repl.history.previous.name=Load previous command from REPL's history into
ccw.ui.repl.history.previous.description=Load previous command from REPL's history into REPL input area
ccw.ui.repl.history.next.name=Load next command from REPL's history into REPL input area
ccw.ui.repl.history.next.description=Load next command from REPL's history into REPL input area

ccw.ui.repl.history.search.previous.name=Load previous command starting with the text before the cursor from REPL's history into REPL input area
ccw.ui.repl.history.search.previous.description=Load previous command starting with the text before the cursor from REPL's history into REPL input area
ccw.ui.repl.history.search.next.name=Load next command starting with the text before the cursor from REPL's history into REPL input area
ccw.ui.repl.history.search.next.description=Load next command starting with the text before the cursor from REPL's history into REPL input area

preferencePage.clojure.name=Clojure
preferencePage.general.name=General
Expand Down
100 changes: 100 additions & 0 deletions ccw.core/plugin.xml
Original file line number Diff line number Diff line change
Expand Up @@ -463,6 +463,18 @@
id="ccw.ui.repl.history.next"
name="%ccw.ui.repl.history.next.name">
</command>
<command
categoryId="org.eclipse.ui.category.edit"
description="%ccw.ui.repl.history.search.previous.description"
id="ccw.ui.repl.history.search.previous"
name="%ccw.ui.repl.history.search.previous.name">
</command>
<command
categoryId="org.eclipse.ui.category.edit"
description="%ccw.ui.repl.history.search.next.description"
id="ccw.ui.repl.history.search.next"
name="%ccw.ui.repl.history.search.next.name">
</command>
<command
categoryId="org.eclipse.ui.category.edit"
description="%ccw.ui.edit.text.clojure.comment.toggle.description"
Expand Down Expand Up @@ -836,6 +848,12 @@
schemeId="org.eclipse.ui.defaultAcceleratorConfiguration"
sequence="M4+P">
</key>
<key
commandId="ccw.ui.repl.history.search.previous"
contextId="ccw.ui.context.repl"
schemeId="org.eclipse.ui.defaultAcceleratorConfiguration"
sequence="M1+M3+ARROW_UP">
</key>
<key
commandId="ccw.ui.repl.history.next"
contextId="ccw.ui.context.repl"
Expand Down Expand Up @@ -864,6 +882,12 @@
schemeId="org.eclipse.ui.defaultAcceleratorConfiguration"
sequence="M4+N">
</key>
<key
commandId="ccw.ui.repl.history.search.next"
contextId="ccw.ui.context.repl"
schemeId="org.eclipse.ui.defaultAcceleratorConfiguration"
sequence="M1+M3+ARROW_DOWN">
</key>
<key
commandId="ccw.ui.edit.text.clojure.comment.toggle"
contextId="ccw.ui.clojureEditorScope"
Expand Down Expand Up @@ -2289,6 +2313,82 @@
</with>
</activeWhen>
</handler>
<handler
commandId="ccw.ui.repl.history.search.previous">
<class
class="ccw.util.GenericExecutableExtension">
<parameter
name="factory"
value="ccw.util.factories/handler-factory">
</parameter>
<parameter
name="handler"
value="ccw.repl.view-helpers/history-backward-search">
</parameter>
</class>
<enabledWhen>
<with
variable="activeContexts">
<iterate
ifEmpty="false"
operator="or">
<equals
value="ccw.ui.context.repl">
</equals>
</iterate>
</with>
</enabledWhen>
<activeWhen>
<with
variable="activeContexts">
<iterate
ifEmpty="false"
operator="or">
<equals
value="ccw.ui.context.repl">
</equals>
</iterate>
</with>
</activeWhen>
</handler>
<handler
commandId="ccw.ui.repl.history.search.next">
<class
class="ccw.util.GenericExecutableExtension">
<parameter
name="factory"
value="ccw.util.factories/handler-factory">
</parameter>
<parameter
name="handler"
value="ccw.repl.view-helpers/history-forward-search">
</parameter>
</class>
<enabledWhen>
<with
variable="activeContexts">
<iterate
ifEmpty="false"
operator="or">
<equals
value="ccw.ui.context.repl">
</equals>
</iterate>
</with>
</enabledWhen>
<activeWhen>
<with
variable="activeContexts">
<iterate
ifEmpty="false"
operator="or">
<equals
value="ccw.ui.context.repl">
</equals>
</iterate>
</with>
</activeWhen>
</handler>
<handler
commandId="ccw.ui.edit.text.clojure.comment.toggle">
<class
Expand Down
125 changes: 100 additions & 25 deletions ccw.core/src/clj/ccw/repl/view_helpers.clj
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
(ns ccw.repl.view-helpers
(:require [clojure.tools.nrepl :as repl]
[ccw.repl.cmdhistory :as history])
[ccw.repl.cmdhistory :as history]
[clojure.string :as str])
(:use [clojure.core.incubator :only (-?>)]
[clojure.tools.nrepl.misc :only (uuid)])
(:import ccw.CCWPlugin
Expand Down Expand Up @@ -109,6 +110,79 @@
(CCWPlugin/logError (eval-failure-msg nil expr) t)
(log repl-view log-component (eval-failure-msg nil expr) :err))))


(defn next-history-entry
[history position retained-input backward? filter-pred cursor-split-text]
(let [cnt (count history),
; -1 <= position < count
position (condp < position
-1 -1
cnt position
(dec cnt)),
entries (if backward?
; seq of history entries backward from current position
(map vector
(range (inc position) cnt)
(rseq (if (= position -1)
history
(subvec history 0 (dec (- (count history) position))))))
; seq of history entries forward from current position with retained input as last
(when-not (neg? position)
(map vector
(range (dec position) -1 -1)
(subvec history (- (count history) position)))))]
(or
(first (filter #(filter-pred cursor-split-text (second %)) entries))
; when not backward in history, then ensure retained-input as last match
(when-not backward?
[-1 retained-input]))))


(defn get-text-split-by-cursor
[^StyledText input-widget]
(let [cursor-pos (.getCaretOffset input-widget)
length (.getCharCount input-widget)]
(cond
(zero? length)
nil
(zero? cursor-pos)
[nil (.getText input-widget)]
(= cursor-pos length)
[(.getText input-widget) nil]
:else
[(.getText input-widget 0 (dec cursor-pos)) (.getText input-widget cursor-pos (dec length))])))

(defn history-entry
[history, history-pos]
(@history (- (count @history) @history-pos 1)))

(defn search-history
[history history-position ^StyledText input-widget retained-input backward? modify-cursor filter-pred]
; when there was a previous search and the history-entry was altered in the REPL, ...
(when (and @retained-input
(<= 0 @history-position)
(not= (history-entry history, history-position) (.getText input-widget)))
; ... then reset the search by retaining the current input ...
(reset! retained-input (.getText input-widget))
; ... and reset the history position to start search from the end of the history
(reset! history-position -1))
; if no retained input, ...
(when-not @retained-input
; ... search is starting now and the current input needs to be retained
(reset! retained-input (.getText input-widget)))
(if-let [[next-position, entry] (next-history-entry @history @history-position @retained-input
backward? filter-pred (get-text-split-by-cursor input-widget))]
(do
(reset! history-position next-position)
(when (= @retained-input entry)
(reset! retained-input nil))
(let [cursor-pos (.getCaretOffset input-widget)]
(doto input-widget
(.setText entry)
(modify-cursor cursor-pos))))
(beep)))


(defn configure-repl-view
[repl-view log-panel repl-client session-id]
(let [[history retain-expr-fn] (history/get-history (-?> repl-view
Expand All @@ -118,28 +192,11 @@
; a bunch of atoms are just fine, since access to them is already
; serialized via the SWT event thread
history (atom history)
current-step (atom -1)
history-position (atom -1)
retained-input (atom nil)
history-action-fn
(fn [history-shift]
(swap! current-step history-shift)
(cond
(>= @current-step (count @history)) (do (swap! current-step dec) (beep))
(neg? @current-step) (do (reset! current-step -1)
(when @retained-input
(doto input-widget
(.setText @retained-input)
cursor-at-end)
(reset! retained-input nil)))
:else (do
(when-not @retained-input
(reset! retained-input (.getText input-widget)))
(doto input-widget
(.setText (@history (dec (- (count @history) @current-step))))
cursor-at-end))))
session-client (repl/client-session repl-client :session session-id)
responses-promise (promise)]
(.setHistoryActionFn repl-view history-action-fn)
(.setHistoryActionFn repl-view (partial search-history history history-position input-widget retained-input))

;; TODO need to make client-session accept a single arg to avoid
;; dummy message sends
Expand All @@ -148,7 +205,7 @@
(comp (partial eval-expression repl-view log-panel session-client)
(fn [expr add-to-log?]
(reset! retained-input nil)
(reset! current-step -1)
(reset! history-position -1)
(when add-to-log?
(swap! history #(subvec
; don't add duplicate expressions to the history
Expand All @@ -159,9 +216,27 @@
(retain-expr-fn expr))
expr))))

(defn- load-history [event history-shift]
(defn- load-history [event backward? modify-cursor filter-pred]
(let [repl-view (HandlerUtil/getActivePartChecked event)]
((.getHistoryActionFn repl-view) history-shift)))
((.getHistoryActionFn repl-view) backward? modify-cursor filter-pred)))


(defn move-cursor-to-end [input-widget _] (cursor-at-end input-widget))

(defn restore-cursor [^StyledText input-widget prev-cursor-pos] (.setCaretOffset input-widget prev-cursor-pos))


(defn history-previous [_ event] (load-history event true move-cursor-to-end (constantly true)))
(defn history-next [_ event] (load-history event false move-cursor-to-end (constantly true)))


(defn text-begin-matches?
"Check whether the text begin matches the history entry.
If the text begin is blank, every history entry is matched (= classic stepping through history)."
[[text-begin] ^String history-entry]
(or
(str/blank? text-begin)
(.startsWith history-entry text-begin)))

(defn history-previous [_ event] (load-history event inc))
(defn history-next [_ event] (load-history event dec))
(defn history-backward-search [_ event] (load-history event true restore-cursor text-begin-matches?))
(defn history-forward-search [_ event] (load-history event false restore-cursor text-begin-matches?))