Permalink
Browse files

Editor has documentation hover for clojure vars.

Also, refactoring of shared code between context information (proposal completion) and documentation hover
  • Loading branch information...
1 parent a2fa303 commit 29e85136f12308f77185e49c5933b9b9810d3bea @laurentpetit laurentpetit committed Dec 24, 2012
@@ -7,10 +7,12 @@
[clojure.zip :as z]
[ccw.util.doc-utils :as doc]
[ccw.debug.serverrepl :as serverrepl]
- [ccw.core.trace :as trace])
+ [ccw.core.trace :as trace]
+ [ccw.editors.clojure.editor-common :as common])
(:use [clojure.core.incubator :only [-?>]])
(:import [org.eclipse.jface.viewers StyledString
StyledString$Styler]
+ [org.eclipse.jface.text ITextViewer]
[org.eclipse.jface.text.contentassist
IContentAssistProcessor
ContentAssistant
@@ -22,7 +24,8 @@
IContextInformationValidator]
[org.eclipse.jdt.core JavaCore
IMethod
- IType]))
+ IType]
+ [ccw.editors.clojure IClojureEditor]))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; parse tree manipulation functions
@@ -43,7 +46,7 @@
(defn call-context-loc
"for viewer, at offset 12, return the loc containing the encapsulating
call, or nil"
- [viewer offset]
+ [^IClojureEditor viewer offset]
(when (pos? offset)
(let [loc (lu/loc-containing-offset
(-> viewer .getParseState :parse-tree lu/parsed-root-loc)
@@ -60,7 +63,7 @@
;; TODO find the prefix via the editor's parse tree !
(defn prefix-info
- [ns offset prefix]
+ [ns offset ^String prefix]
(let [[n1 n2] (s/split prefix #"/")
[n1 n2] (cond
(and (nil? n2)
@@ -73,7 +76,7 @@
:prefix-name n2}))
;; TODO find the prefix via the editor's parse tree !
-(defn invalid-symbol-char? [c]
+(defn invalid-symbol-char? [^Character c]
(let [invalid-chars
#{ \(, \), \[, \], \{, \}, \', \@,
\~, \^, \`, \#, \" }]
@@ -89,7 +92,7 @@
;; TODO find the prefix via the editor's parse tree !
(defn compute-prefix-offset
- [string offset]
+ [^String string offset]
(if-let [start (some
#(when (invalid-symbol-char? (.charAt string %)) %)
(range (dec offset) -1 -1))]
@@ -179,69 +182,36 @@
namespace
completion-limit))
-(defn send-message
- "Send the command over the nrepl connection."
- [connection command]
- (try
- (-> (.client connection)
- (repl/message {"op" "eval"
- "code" command})
- repl/response-values)
- (catch Exception e
- (ccw.CCWPlugin/logError (str "exception while sending command " command " to connection " connection) e)
- nil)))
-
(defn find-var-metadata
- [current-namespace editor var]
+ [current-namespace ^IClojureEditor editor var]
(when-let [repl (.getCorrespondingREPL editor)]
(let [connection (.getToolingConnection repl)
command (format (str "(ccw.debug.serverrepl/var-info "
"(clojure.core/ns-resolve "
"(clojure.core/the-ns '%s) '%s))")
current-namespace
var)
- response (send-message connection command)]
+ response (common/send-message connection command)]
;(println "response:" response)
(first response))))
(defn find-suggestions
"For the given prefix, inside the current editor and in the current namespace,
query the remote REPL for code completions list"
- [current-namespace prefix editor find-only-public]
+ [current-namespace prefix ^IClojureEditor editor find-only-public]
(cond
(nil? namespace) []
(s/blank? prefix) []
:else (when-let [repl (.getCorrespondingREPL editor)]
(let [connection (.getToolingConnection repl)
command (complete-command current-namespace prefix false)
- response (send-message connection command)]
+ response (common/send-message connection command)]
(first response)))))
-(defn safe-split-lines [s]
- (when s (s/split-lines s)))
-
-(defn slim-doc [s]
- (let [lines (safe-split-lines s)
- nb-display-lines 2
- lines (if (> (count lines) nb-display-lines)
- (concat (take (dec nb-display-lines) lines)
- [(str (nth lines nb-display-lines) " ...")])
- lines)]
- (s/join \newline (map s/trim lines))))
-
-(defn context-message
- "Creates the context message"
- [callee-name callee-metadata]
- (when (some #{:arglists :doc} (keys callee-metadata))
- (format "%s: %s\n%s"
- callee-name
- (or (:arglists callee-metadata) "")
- (slim-doc (:doc callee-metadata)))))
-
(defn context-info-data
"Create a context-information from the data"
[callee-name cursor-offset callee-metadata]
- (when-let [message (context-message callee-name callee-metadata)]
+ (when-let [message (common/context-message callee-name callee-metadata)]
(context-information
message
nil
@@ -252,7 +222,7 @@
(def ca (atom nil))
(defn find-hippie-suggestions
- [prefix offset parse-state]
+ [^String prefix offset parse-state]
(let [buffer-wo-prefix (p/edit-buffer (parse-state :buffer)
(- offset (count prefix))
(count prefix) "")
@@ -309,8 +279,8 @@
(defn compute-completion-proposals
"Return the list of java completion objects to the Completion framework."
- [editor content-assistant
- text-viewer offset]
+ [^IClojureEditor editor content-assistant
+ ^ITextViewer text-viewer offset]
(reset! ca content-assistant)
(let [prefix-offset (compute-prefix-offset
(-> text-viewer .getDocument .get)
@@ -360,7 +330,7 @@
[\. \- \? \!]))
(defn compute-context-information
- [editor
+ [^IClojureEditor editor
text-viewer new-offset]
(into-array
IContextInformation
@@ -385,15 +355,13 @@
The problem is that Eclipse framework then triggers wrongly code completion
feature.
This predicate is there to restore the truth :-)"
- [editor offset]
- (def ed editor)
- (def of offset)
- (let [parse-tree ((-> ed .getParseState :parse-tree :abstract-node)
+ [^IClojureEditor editor offset]
+ (let [parse-tree ((-> editor .getParseState :parse-tree :abstract-node)
p/parse-tree-view)
last-token-tag (-> parse-tree :content peek :tag)]
(not= last-token-tag :comment)))
-(defn make-process [editor content-assistant]
+(defn make-process [editor ^ContentAssistant content-assistant]
(reify IContentAssistProcessor
(computeCompletionProposals
[this text-viewer offset]
@@ -0,0 +1,43 @@
+(ns ccw.editors.clojure.clojure-text-hover
+ "Supports documentation hovers for Clojure Editor"
+ (:import [org.eclipse.jface.text Region ITextHover]
+ [ccw.editors.clojure IClojureEditor])
+ (:require [ccw.core.trace :as trace]
+ [paredit.loc-utils :as lu]
+ [ccw.editors.clojure.editor-common :as common]
+ [ccw.util.doc-utils :as doc-utils]))
+
+(defn hover-info
+ "Return the documentation hover text to be displayed at offset offset for
+ editor. The text can be composed of a subset of html (e.g. <pre>, <i>, etc.)"
+ [^IClojureEditor editor offset]
+ (let [loc (common/offset-loc editor offset)
+ parse-symbol (common/parse-symbol? loc)]
+ (when parse-symbol
+ (let [m (common/find-var-metadata
+ (.findDeclaringNamespace editor)
+ editor
+ parse-symbol)]
+ (doc-utils/var-doc-info-html m)))))
+
+(defn hover-region
+ "For editor, given the character offset, return a vector of [offset length]
+ representing a region of the editor (containing offset).
+ The idea is that for every offset in that region, the same documentation
+ hover as the one computed for offset will be used.
+ This is a function for optimizing the number of times the hover-info
+ function is called."
+ [editor offset]
+ (let [loc (offset-loc editor offset)]
+ [(lu/start-offset loc) (lu/loc-count loc)]))
+
+(defn make-TextHover
+ "Factory function for creating an ITextHover instance for the editor."
+ [editor]
+ (reify ITextHover
+ (getHoverInfo [this text-viewer region]
+ (hover-info editor (.getOffset region)))
+
+ (getHoverRegion [this text-viewer offset]
+ (let [[offset length] (hover-region editor offset)]
+ (Region. offset length)))))
@@ -0,0 +1,93 @@
+(ns ccw.editors.clojure.editor-common
+ (:require [clojure.string :as s]
+ [clojure.test :as test]
+ [clojure.tools.nrepl :as repl]
+ [paredit.parser :as p]
+ [paredit.loc-utils :as lu]
+ [clojure.zip :as z]
+ [ccw.util.doc-utils :as doc]
+ [ccw.debug.serverrepl :as serverrepl]
+ [ccw.core.trace :as trace]
+ [ccw.editors.clojure.editor-support :as editor]
+ [ccw.util.doc-utils :as doc-utils])
+ (:use [clojure.core.incubator :only [-?>]])
+ (:import [org.eclipse.jface.viewers StyledString
+ StyledString$Styler]
+ [org.eclipse.jface.text.contentassist
+ IContentAssistProcessor
+ ContentAssistant
+ CompletionProposal
+ ICompletionProposal
+ ICompletionProposalExtension6
+ IContextInformation
+ IContextInformationExtension
+ IContextInformationValidator]
+ [org.eclipse.jdt.core JavaCore
+ IMethod
+ IType]
+ [ccw.editors.clojure IClojureEditor]
+ [clojure.tools.nrepl Connection]))
+
+(defn offset-loc
+ "Return the zip loc for offset in editor"
+ [^IClojureEditor editor offset]
+ (let [rloc (-> editor .getParseState (editor/getParseTree) lu/parsed-root-loc)]
+ (lu/loc-for-offset rloc offset)))
+
+(defn send-message*
+ "Send the command over the nrepl connection. This version is \"bare\", ie it
+ calls into the REPL without timeout protection. If you want to protect the
+ IDE to freeze if e.g. the REPL never times out, call send-message instead."
+ [^Connection connection command]
+ (try
+ (-> (.client connection)
+ (repl/message {"op" "eval"
+ "code" command})
+ repl/response-values)
+ (catch Exception e
+ (ccw.CCWPlugin/logError (str "exception while sending command " command " to connection " connection) e)
+ nil)))
+
+(defn send-message
+ "Same as send-message*, but guarded by a client timeout
+ so that Eclipse cannot hang forever.
+ timeout in milliseconds"
+ [connection command & {:keys [timeout] :or {timeout 4000}}]
+ (let [timeout-val (Object.)
+ secure-call (future (send-message* connection command))
+ result (deref secure-call timeout timeout-val)]
+ (if (not= result timeout-val)
+ result
+ (ccw.CCWPlugin/logError (str
+ "timeout while calling command send-message* for command "
+ command)))))
+
+(defn parse-symbol?
+ "If loc's node is a symbol, return the symbol String. Otherwise, return nil."
+ [loc]
+ (when (= :symbol (:tag (z/node loc)))
+ (symbol (lu/loc-text loc))))
+
+(defn find-var-metadata
+ "Given editor, and an already resolved current-namespace, makes a call to
+ the editor REPL to find metadata associated to var. If there's currently
+ no REPL, just return nil."
+ [current-namespace ^IClojureEditor editor var]
+ (when-let [repl (.getCorrespondingREPL editor)]
+ (let [connection (.getToolingConnection repl)
+ command (format (str "(ccw.debug.serverrepl/var-info "
+ "(clojure.core/ns-resolve "
+ "(clojure.core/the-ns '%s) '%s))")
+ current-namespace
+ var)
+ response (send-message connection command)]
+ (first response))))
+
+(defn context-message
+ "Creates the context message"
+ [callee-name callee-metadata]
+ (when (some #{:arglists :doc} (keys callee-metadata))
+ (format "%s: %s\n%s"
+ callee-name
+ (or (:arglists callee-metadata) "")
+ (doc-utils/slim-doc (:doc callee-metadata)))))
@@ -23,6 +23,10 @@
import org.eclipse.jface.text.IDocumentExtension3;
import org.eclipse.jface.text.IInformationControl;
import org.eclipse.jface.text.IInformationControlCreator;
+import org.eclipse.jface.text.IRegion;
+import org.eclipse.jface.text.ITextHover;
+import org.eclipse.jface.text.ITextViewer;
+import org.eclipse.jface.text.Region;
import org.eclipse.jface.text.contentassist.ContentAssistEvent;
import org.eclipse.jface.text.contentassist.ContentAssistant;
import org.eclipse.jface.text.contentassist.ICompletionListener;
@@ -52,6 +56,9 @@
private static final ClojureInvoker proposalProcessor = ClojureInvoker.newInvoker(
CCWPlugin.getDefault(),
"ccw.editors.clojure.clojure-proposal-processor");
+ private static final ClojureInvoker textHover = ClojureInvoker.newInvoker(
+ CCWPlugin.getDefault(),
+ "ccw.editors.clojure.clojure-text-hover");
public ClojureSourceViewerConfiguration(IPreferenceStore preferenceStore,
IClojureEditor editor) {
@@ -169,6 +176,12 @@ public IReconciler getReconciler(ISourceViewer sourceViewer) {
// and code, and spell check based on these partitions
return null;
}
+
+ @Override
+ public ITextHover getTextHover(ISourceViewer sourceViewer,
+ String contentType) {
+ return (ITextHover) textHover._("make-TextHover", editor);
+ }
@Override
public IAutoEditStrategy[] getAutoEditStrategies(
@@ -79,4 +79,22 @@
(var-doc-info :html m))
(defn var-doc-info-text [m]
- (var-doc-info :text m))
+ (var-doc-info :text m))
+
+(defn safe-split-lines
+ "Same as clojure.string/split-lines but accepts a nil input and returns nil
+ instead of throwing an exception."
+ [s]
+ (when s (str/split-lines s)))
+
+(defn slim-doc
+ "Summary of the documentation (arglist + start of the doc) taking up to 3
+ lines. Used e.g. for context information."
+ [s]
+ (let [lines (safe-split-lines s)
+ nb-display-lines 2
+ lines (if (> (count lines) nb-display-lines)
+ (concat (take (dec nb-display-lines) lines)
+ [(str (nth lines nb-display-lines) " ...")])
+ lines)]
+ (str/join \newline (map str/trim lines))))

0 comments on commit 29e8513

Please sign in to comment.