From 9cdcfdee5127028544b842868415822bbabce72b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gunnar=20V=C3=B6lkel?= Date: Mon, 1 Oct 2012 20:53:00 +0200 Subject: [PATCH 1/6] Implemented REPL history search for text before cursor. Stepping through and searching REPL history share the same implementation. --- ccw.core/src/clj/ccw/repl/view_helpers.clj | 99 ++++++++++++++++------ 1 file changed, 75 insertions(+), 24 deletions(-) diff --git a/ccw.core/src/clj/ccw/repl/view_helpers.clj b/ccw.core/src/clj/ccw/repl/view_helpers.clj index 28e55010..d124b46d 100644 --- a/ccw.core/src/clj/ccw/repl/view_helpers.clj +++ b/ccw.core/src/clj/ccw/repl/view_helpers.clj @@ -109,6 +109,59 @@ (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 (if (< position -1) -1 position), + position (if (>= position cnt) (dec cnt) position), + 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) + (concat + (map vector (range (dec position) -1 -1) (subvec history (- (count history) position))) + [[-1 retained-input]])))] + (first (filter #(filter-pred cursor-split-text (second %)) entries)))) + + +(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 search-history + [history history-position ^StyledText input-widget retained-input backward? modify-cursor filter-pred] + (when-not @retained-input + (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 @@ -118,28 +171,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 @@ -148,7 +184,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 @@ -159,9 +195,24 @@ (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? + [[text-begin] ^String history-entry] + (when 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?)) From df2089bd13d115cdaafe8c1722bddf2e41f852da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gunnar=20V=C3=B6lkel?= Date: Sun, 7 Oct 2012 13:49:28 +0200 Subject: [PATCH 2/6] Added command, handler, keys for REPL history search. --- ccw.core/plugin.properties | 5 +- ccw.core/plugin.xml | 130 +++++++++++++++++++++++++++++++++++++ 2 files changed, 134 insertions(+), 1 deletion(-) diff --git a/ccw.core/plugin.properties b/ccw.core/plugin.properties index 0f407c1d..3f71a03f 100644 --- a/ccw.core/plugin.properties +++ b/ccw.core/plugin.properties @@ -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 diff --git a/ccw.core/plugin.xml b/ccw.core/plugin.xml index f50be8a8..98631334 100644 --- a/ccw.core/plugin.xml +++ b/ccw.core/plugin.xml @@ -463,6 +463,18 @@ id="ccw.ui.repl.history.next" name="%ccw.ui.repl.history.next.name"> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Date: Sun, 4 Nov 2012 14:01:11 +0100 Subject: [PATCH 3/6] Changes default keybindings for repl history search. Default keybings are Ctrl+Alt+Up and Ctrl+Alt+Down. --- ccw.core/plugin.xml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/ccw.core/plugin.xml b/ccw.core/plugin.xml index 98631334..285f57a6 100644 --- a/ccw.core/plugin.xml +++ b/ccw.core/plugin.xml @@ -853,21 +853,21 @@ contextId="ccw.ui.context.repl" platform="win32" schemeId="org.eclipse.ui.defaultAcceleratorConfiguration" - sequence="M1+ARROW_RIGHT"> + sequence="M1+M3+ARROW_UP"> + sequence="M1+M3+ARROW_UP"> + sequence="M1+M3+ARROW_UP"> + sequence="M1+M3+ARROW_DOWN"> + sequence="M1+M3+ARROW_DOWN"> + sequence="M1+M3+ARROW_DOWN"> Date: Sun, 4 Nov 2012 15:38:14 +0100 Subject: [PATCH 4/6] Retained input handling + empty prefix search. - Retained input is always last history entry in forward history search. - History search with empty prefix (nil or "") steps through history entries. --- ccw.core/src/clj/ccw/repl/view_helpers.clj | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/ccw.core/src/clj/ccw/repl/view_helpers.clj b/ccw.core/src/clj/ccw/repl/view_helpers.clj index d124b46d..90ff7656 100644 --- a/ccw.core/src/clj/ccw/repl/view_helpers.clj +++ b/ccw.core/src/clj/ccw/repl/view_helpers.clj @@ -124,11 +124,15 @@ 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) - (concat - (map vector (range (dec position) -1 -1) (subvec history (- (count history) position))) - [[-1 retained-input]])))] - (first (filter #(filter-pred cursor-split-text (second %)) entries)))) + (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 @@ -211,7 +215,10 @@ (defn text-begin-matches? [[text-begin] ^String history-entry] - (when text-begin + ; if text-begin is nil or empty (not blank), match everything (= classic stepping through history) + (or + (nil? text-begin) + (= text-begin "") (.startsWith history-entry text-begin))) (defn history-backward-search [_ event] (load-history event true restore-cursor text-begin-matches?)) From f6a48a0ddfbe10104c15f2b3df1becd5ad225fff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gunnar=20V=C3=B6lkel?= Date: Sat, 15 Dec 2012 17:47:11 +0100 Subject: [PATCH 5/6] Input modificaton resets history search. --- ccw.core/src/clj/ccw/repl/view_helpers.clj | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/ccw.core/src/clj/ccw/repl/view_helpers.clj b/ccw.core/src/clj/ccw/repl/view_helpers.clj index 90ff7656..ab3a1429 100644 --- a/ccw.core/src/clj/ccw/repl/view_helpers.clj +++ b/ccw.core/src/clj/ccw/repl/view_helpers.clj @@ -149,9 +149,23 @@ :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))] From 5b59025209506437fa2fe26661ad9cd937fde341 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gunnar=20V=C3=B6lkel?= Date: Sun, 13 Jan 2013 15:59:51 +0100 Subject: [PATCH 6/6] Changes for pull request comments. * Only one key binding definition instead of 3 redundant ones. * Implements suggestions for several code changes. --- ccw.core/plugin.xml | 30 ---------------------- ccw.core/src/clj/ccw/repl/view_helpers.clj | 15 ++++++----- 2 files changed, 9 insertions(+), 36 deletions(-) diff --git a/ccw.core/plugin.xml b/ccw.core/plugin.xml index 285f57a6..831a863c 100644 --- a/ccw.core/plugin.xml +++ b/ccw.core/plugin.xml @@ -851,21 +851,6 @@ - - - - @@ -900,21 +885,6 @@ - - - - diff --git a/ccw.core/src/clj/ccw/repl/view_helpers.clj b/ccw.core/src/clj/ccw/repl/view_helpers.clj index ab3a1429..9c783f13 100644 --- a/ccw.core/src/clj/ccw/repl/view_helpers.clj +++ b/ccw.core/src/clj/ccw/repl/view_helpers.clj @@ -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 @@ -114,8 +115,10 @@ [history position retained-input backward? filter-pred cursor-split-text] (let [cnt (count history), ; -1 <= position < count - position (if (< position -1) -1 position), - position (if (>= position cnt) (dec cnt) position), + position (condp < position + -1 -1 + cnt position + (dec cnt)), entries (if backward? ; seq of history entries backward from current position (map vector @@ -228,11 +231,11 @@ (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] - ; if text-begin is nil or empty (not blank), match everything (= classic stepping through history) (or - (nil? text-begin) - (= text-begin "") + (str/blank? text-begin) (.startsWith history-entry text-begin))) (defn history-backward-search [_ event] (load-history event true restore-cursor text-begin-matches?))