diff --git a/src/lang/sunlight.clojure.js b/src/lang/sunlight.clojure.js new file mode 100644 index 0000000..f09d255 --- /dev/null +++ b/src/lang/sunlight.clojure.js @@ -0,0 +1,180 @@ +(function(sunlight, undefined){ + + var specialForms = [ + "def", "defmethod", "defstruct", "defrecord", "definline", "defn-", "defn", "defmacro", "deftype", "definterface", "defmulti", "defonce", "defprotocol", + "if", "do", "let", "quote", "var", "fn", "loop", "recur", "monitor-enter", "monitor-exit", + ".", "new", "set!", ".." + ]; + var exceptionForms = [ + "throw", "try", "catch", "finally" + ]; + var misc = [ + // core dynamic vars + "*compile-path*", "*ns*", "*print-level*", "*allow-unresolved-vars*", "*data-readers*", "*unchecked-math*", + "*math-context*", "*read-eval*", "*compile-files*", "*command-line-args*", "*warn-on-reflection*", + "*flush-on-newline*", "*out*", "*print-length*", "*file*", "*verbose-defrecords*", "*clojure-version*", + "*use-context-classloader*", "*err*", "*default-data-reader-fn*", "*agent*", "*print-dup*", "*print-readably*", + "*fn-loader*", "*print-meta*", "*in*", "*source-path*", "*compiler-options*", "*assert*", "*1", "*2", "*3", "*e", + // constants + "EMPTY_NODE", "char-escape-string", "default-data-readers", "char-name-string", "primitives-classnames" + ]; + var coreForms = [ + // general functions + "sorted-map", "read-line", "re-pattern", "unchecked-inc-int", "val", "find-protocol-impl", + "vector-of", "object-array", "max-key", "list*", "ns-aliases", "booleans", "the-ns", "==", "chunk-buffer", "longs", + "shorts", "format", "empty", "dorun", "remove-method", "gensym", "not=", "unchecked-multiply", + "bit-or", "aset-byte", "send-via", "hash-set", "->Vec", "add-watch", "unchecked-dec", "some", "boolean-array", + "second", "keys", "long-array", "pop-thread-bindings", "error-mode", "bit-set", "spit", "find-protocol-method", + "short-array", "ns-unalias", "ns-publics", "char-array", "all-ns", "long", "init-proxy", "add-classpath", + "await1", "short", "ns-unmap", "repeat", "zipmap", "distinct", "get-in", "bit-xor", + "complement", "get-validator", "ref-max-history", "promise", "set-agent-send-executor!", "-'", "pop!", "derive", + "aset-float", "extend", "-reset-methods", "commute", "partition-by", "rem", + "mapv", "filterv", "proxy-call-with-super", + "ns-interns", "re-matches", "split-with", "munge", "next", "deliver", "symbol", "vals", "->ArrayChunk", + "select-keys", "re-matcher", "rand", "deref", "unchecked-inc", "read", "sequence", "make-hierarchy", "+", + "assoc!", "descendants", "into-array", "last", "some-fn", "unchecked-negate", + "alter", "prn", "with-meta", "floats", "*", "butlast", "-", + "rseq", "send-off", "print", "vary-meta", "agent-error", + "bit-flip", "bit-and", "re-groups", "newline", "replicate", "keep-indexed", + "remove-ns", "xml-seq", "vec", "concat", "update-in", "vector", "with-bindings*", "conj", "bases", + "/", "unchecked-add", "ref-set", "assoc", "unchecked-remainder-int", "seque", "aset-char", "boolean", "read-string", "float-array", + "doubles", "future-call", "remove-watch", "print-str", "ref-history-count", "rsubseq", + "split-at", "chunk-cons", "ns-refers", "create-struct", "int-array", "unchecked-long", "float", + "map", "+'", "double-array", "accessor", "frequencies", "chars", "rand-int", + "aset-short", "unchecked-short", "prn-str", "iterate", "chunk-append", "unchecked-double", "slurp", "restart-agent", + "unchecked-int", "mapcat", "assoc-in", "get-thread-bindings", "ref", "conj!", "find-var", "inc", "future-cancel", + "every-pred", "bound-fn*", "unchecked-subtract", "ns-name", "shuffle", "re-find", + "bit-not", "construct-proxy", "ref-min-history", "destructure", "seq", "intern", "unchecked-multiply-int", "to-array-2d", + "sorted-map-by", "filter", "->VecNode", "alter-meta!", "unchecked-dec-int", + "key", "class", "re-seq", "-cache-protocol-fn", "test", "print-dup", "create-ns", "name", "nthrest", "aset", "nnext", + "doall", "extenders", "macroexpand-1", "resultset-seq", "reductions", "into", + "transient", "ffirst", "bit-clear", "proxy-name", "load-reader", "with-redefs-fn", "hash", "inc'", + "print-ctor", "drop-last", "replace", "parents", "prefers", "numerator", "quot", + "chunk-rest", "unchecked-negate-int", "file-seq", "send", "reverse", "count", "get-proxy-class", + "set", "ex-info", "unchecked-subtract-int", "comp", "nth", "byte", "dissoc!", "constantly", "load", "namespace", "pr-str", + "<", "rationalize", "sort-by", "->VecSeq", "cycle", "peek", "denominator", "reduce", "interleave", "cons", "macroexpand", "var-set", + "str", "aset-boolean", "ns-imports", "remove-all-methods", "first", "bean", "=", "memoize", "var-get", "unchecked-float", "range", + "tree-seq", "set-validator!", "aset-double", "*'", "unchecked-divide-int", "enumeration-seq", "prefer-method", + "partition-all", "ensure", "reduced", "find-ns", "struct-map", ">", "max", "proxy-mappings", "identity", "ints", "fnext", + "biginteger", "min-key", "reset-meta!", "push-thread-bindings", "unchecked-add-int", "subs", "compile", "agent-errors", + "clear-agent-errors", "printf", "ns-resolve", "method-sig", ">=", "shutdown-agents", "reduce-kv", "reset!", + "require", "bit-shift-left", "dec'", "methods", "compare", "sorted-set-by", "cast", + "namespace-munge", "supers", "pcalls", "load-string", "group-by", "get", "<=", "await", "resolve", "bytes", "print-method", "loaded-libs", + "fnil", "force", "partial", "pmap", "comparator", "char", "take-while", "refer", "underive", "in-ns", + "iterator-seq", "ancestors", "hash-combine", "persistent!", "partition", "map-indexed", "update-proxy", + "interpose", "chunk", "aset-int", "load-file", "apply", "swap!", "subvec", + "byte-array", "rest", "keyword", "ns-map", "set-error-mode!", "int", "release-pending-sends", "mod", "bigdec", "nfirst", "nthnext", + "aset-long", "struct", "array-map", "unchecked-char", "bigint", "dec", "println", "aget", "find-keyword", "pr", "drop", "clojure-version", + "eval", "aclone", "pop", "atom", "bit-shift-right", + "num", "disj", "merge-with", "take-nth", "double", + "take-last", "line-seq", "take", "unchecked-byte", "make-array", "rand-nth", "alias", "use", + "juxt", "alength", "chunk-first", "to-array", "hash-map", "bit-and-not", + "compare-and-set!", "type", "repeatedly", "trampoline", "set-error-handler!", "remove", "find", "drop-while", "not-empty", + "flatten", "ex-data", "set-agent-send-off-executor!", "println-str", "list", "chunk-next", "flush", "sort", + "dissoc", "not", "error-handler", "get-method", "agent", "sorted-set", "alter-var-root", "merge", "subseq", "min", + "print-simple", "bit-test", "await-for", "keep", "disj!", "meta", + // Predicates + "keyword?", "chunked-seq?", "instance?", "sequential?", "fn?", "nil?", "string?", "sorted?", "false?", + "true?", "odd?", "symbol?", "thread-bound?", "future-done?", "number?", "integer?", "reduced?", "reversible?", + "seq?", "identical?", "zero?", "char?", "distinct?", "ratio?", "neg?", "isa?", "extends?", "future?", "vector?", + "counted?", "class?", "special-symbol?", "var?", "empty?", "list?", "not-any?", "associative?", "float?", + "decimal?", "map?", "not-every?", "even?", "future-cancelled?", "bound?", "pos?", "contains?", "ifn?", + "delay?", "realized?", "rational?", "set?", "coll?", "every?", "satisfies?", + // Macros + "cond->>", "bound-fn", "doseq", "if-not", "letfn", "dosync", "with-open", "gen-interface", "sync", "dotimes", + "lazy-cat", "with-in-str", "import", "with-out-str", "when-not", "time", "for", "cond", "some->", "->>","refer-clojure", "with-loading-context", "future", "with-redefs","doto", "proxy-super", "assert", "memfn", "when-first", "pvalues", + "comment","ns", "with-precision","extend-type", "or", "as->", "condp", "with-local-vars", "with-bindings", "when-let", "amap", "->", "while", "case", "if-let", "extend-protocol", "and", "declare", "locking", "delay", "proxy", "reify", + "gen-class", "io!", "lazy-seq", "when", "areduce", "some->>", "cond->", "binding" + ]; + + if (sunlight === undefined || sunlight["registerLanguage"] === undefined) { + throw "Include sunlight.js before including language files"; + } + + sunlight.registerLanguage("clojure", { + keywords: specialForms, + scopes: { + string: [ + [ + "\"", + "\"", + ["\\\""] + ] + ], + comment: [ + [ ";", "\n", null, true ] + ], + regex: [ + [ + "#\"", + "\"", + ["\\\""] + ] + ] + }, + + identFirstLetter: /[a-zA-Z\*\+\-!\?_]/, + identAfterFirstLetter: /[^\s,#%&()\[\]{}]/, + + customTokens: { + 'named-ident': { + values: coreForms, + boundary: "[\\s\\(\\),{}\\[\\]]" + }, + constant: { + values: misc, + boundary: "[\\s\\(\\),{}\\[\\]]" + }, + variable: { + values: exceptionForms, + boundary: "[\\s\\(\\),{}\\[\\]]" + }, + boolean: { + values: ["true", "false"], + boundary: "[\\s\\(\\),{}\\[\\]]" + } + }, + customParseRules: [ + // characters + function(context) { + if (context.reader.current() !== "\\") { + return null; + } + + var charNameStrings = ["\\space", "\\backspace", "\\tab", "\\newline", "\\formfeed", "\\return"]; + var map = sunlight.util.createHashMap(charNameStrings, "\\b", false); + var tok = sunlight.util.matchWord(context, map, "char", false); + + if (tok !== null) { + return tok; + } + + var value = "\\" + context.reader.read(1); + var line = context.reader.getLine(); + var column = context.reader.getColumn(); + return context.createToken("char", value, line, column); + }, + + // keywords + function(context) { + if(context.reader.current() !== ":") { + return null; + } + var value = /^:[^\s(){}\[\],]+/.exec(context.reader.substring())[0]; + var tok = context.createToken("symbol", value, context.reader.getLine(), context.reader.getColumn()); + context.reader.read(value.length - 1); + return tok; + } + ], + + caseInsensitive: false, + + doNotParse: /\s/, + + contextItems: { + userDefinedFunctions: [] + }, + + operators: [] + }); +}(this["Sunlight"])); diff --git a/src/sunlight.js b/src/sunlight.js index 2165a1a..e880d75 100644 --- a/src/sunlight.js +++ b/src/sunlight.js @@ -1095,7 +1095,7 @@ languageData.name = languageId; //transform keywords, operators and custom tokens into a hash map - languageData.keywords = createHashMap(languageData.keywords || [], "\\b", languageData.caseInsensitive); + languageData.keywords = createHashMap(languageData.keywords || [], "[\\b\\s]", languageData.caseInsensitive); languageData.operators = createHashMap(languageData.operators || [], "", languageData.caseInsensitive); for (tokenName in languageData.customTokens) { languageData.customTokens[tokenName] = createHashMap( diff --git a/src/themes/sunlight.tomorrow-night.css b/src/themes/sunlight.tomorrow-night.css new file mode 100644 index 0000000..9c3b228 --- /dev/null +++ b/src/themes/sunlight.tomorrow-night.css @@ -0,0 +1,295 @@ +/* global styles */ +.sunlight-container { + clear: both !important; + position: relative !important; + margin: 10px 0 !important; +} +.sunlight-code-container { + clear: both !important; + position: relative !important; + border: none; + border-color: #626262 !important; + background-color: #1d1f21 !important; +} +.sunlight-highlighted, .sunlight-container, .sunlight-container textarea, .sunlight-container pre { + font-family: "Liberation Mono", "DejaVu Sans Mono", Monaco, monospace; + font-size: 12px !important; + line-height: 15px !important; +} +.sunlight-highlighted, .sunlight-container textarea { + color: #FFFFFF !important; + margin: 0 !important; +} +.sunlight-container textarea { + padding-left: 0 !important; + margin-left: 0 !important; + margin-right: 0 !important; + padding-right: 0 !important; +} +.sunlight-code-container > .sunlight-highlighted { + white-space: pre; + overflow-x: auto; + overflow-y: hidden; /* ie requires this wtf? */ +} +.sunlight-highlighted { + z-index: 1; + position: relative; +} +.sunlight-highlighted * { + background: transparent; +} +.sunlight-line-number-margin { + float: left !important; + margin-right: 5px !important; + margin-top: 0 !important; + margin-bottom: 0 !important; + padding: 0 !important; + padding-right: 4px !important; + padding-left: 4px !important; + border-right: 1px solid #9A9A9A !important; + background-color: #3E3E3E !important; + color: #9A9A9A !important; + text-align: right !important; + position: relative; + z-index: 3; +} +.sunlight-highlighted a, .sunlight-line-number-margin a { + border: none !important; + text-decoration: none !important; + font-style: normal !important; + padding: 0 !important; +} +.sunlight-line-number-margin a { + color: inherit !important; +} +.sunlight-line-highlight-overlay { + position: absolute; + top: 0; + left: 0; + width: 100%; + z-index: 0; +} +.sunlight-line-highlight-overlay div { + height: 15px; + width: 100%; +} +.sunlight-line-highlight-overlay .sunlight-line-highlight-active { + background-color: #282a2e; +} + +/* menu */ +.sunlight-menu { + background-color: #FFFFCC; + color: #000000; +} +.sunlight-menu ul { + margin: 0 !important; + padding: 0 !important; + list-style-type: none !important; +} +.sunlight-menu li { + float: right !important; + margin-left: 5px !important; +} +.sunlight-menu a, .sunlight-menu img { + color: #000099 !important; + text-decoration: none !important; + border: none !important; +} + +.sunlight-string, +.sunlight-regex, +.sunlight-heredoc, +.sunlight-heredocDeclaration, +.sunlight-nowdoc, +.sunlight-longString, +.sunlight-rawString, +.sunlight-binaryString, +.sunlight-verbatimString, +.sunlight-rawLongString, +.sunlight-binaryLongString, +.sunlight-diff .sunlight-added, +.sunlight-scala .sunlight-symbolLiteral { + color: #b5bd68 !important; +} +.sunlight-operator, +.sunlight-punctuation, +.sunlight-delimiter { + color: #8abeb7 !important; +} +.sunlight-ident, +.sunlight-diff .sunlight-unchanged { + color: #c5c8c6 !important; +} +.sunlight-comment, +.sunlight-xmlDocCommentContent, +.sunlight-nginx .sunlight-ssiCommand, +.sunlight-sln .sunlight-formatDeclaration, +.sunlight-diff .sunlight-mergeHeader, +.sunlight-diff .sunlight-noNewLine { + color: #969896 !important; +} +.sunlight-number, +.sunlight-char, +.sunlight-boolean, +.sunlight-cdata, +.sunlight-guid, +.sunlight-diff .sunlight-modified { + color: #de935f !important; +} +.sunlight-constant { + color: #f0c674 !important; +} +.sunlight-named-ident, +.sunlight-xml .sunlight-attribute, +.sunlight-javascript .sunlight-globalVariable, +.sunlight-globalObject, +.sunlight-css .sunlight-id, +.sunlight-python .sunlight-attribute, +.sunlight-nginx .sunlight-context, +.sunlight-httpd .sunlight-context, +.sunlight-lisp .sunlight-declarationSpecifier, +.sunlight-erlang .sunlight-userDefinedFunction, +.sunlight-diff .sunlight-removed { + color: #81a2be !important; +} +.sunlight-keyword, +.sunlight-languageConstruct, +.sunlight-specialOperator, +.sunlight-xml .sunlight-tagName, +.sunlight-xml .sunlight-operator, +.sunlight-bash .sunlight-command, +.sunlight-erlang .sunlight-moduleAttribute { + color: #b294bb !important; +} +.sunlight-shortOpenTag, +.sunlight-openTag, +.sunlight-closeTag, +.sunlight-xmlOpenTag, +.sunlight-xmlCloseTag, +.sunlight-aspOpenTag, +.sunlight-aspCloseTag, +.sunlight-label, +.sunlight-css .sunlight-importantFlag { + background-color: #7373C1 !important; +} +.sunlight-content { + color: #FFFFFF !important; + font-weight: bold !important; +} +.sunlight-function, +.sunlight-globalFunction, +.sunlight-objective-c .sunlight-messageDestination, +.sunlight-ruby .sunlight-specialFunction, +.sunlight-clojure .sunlight-specialVariable, +.sunlight-6502asm .sunlight-illegalOpcode, +.sunlight-powershell .sunlight-switch, +.sunlight-lisp .sunlight-macro, +.sunlight-lisp .sunlight-specialForm, +.sunlight-lisp .sunlight-type, +.sunlight-sln .sunlight-sectionName, +.sunlight-diff .sunlight-header { + color: #f0c674 !important; +} +.sunlight-variable, +.sunlight-environmentVariable, +.sunlight-specialVariable, +.sunlight-objective-c .sunlight-messageArgumentName, +.sunlight-lisp .sunlight-globalVariable, +.sunlight-ruby .sunlight-globalVariable, +.sunlight-ruby .sunlight-instanceVariable { + color: #cc6666 !important; +} +.sunlight-regexLiteral, +.sunlight-lisp .sunlight-operator, +.sunlight-6502asm .sunlight-pseudoOp, +.sunlight-erlang .sunlight-macro, +.sunlight-diff .sunlight-rangeInfo { + color: #8abeb7 !important; +} +.sunlight-specialVariable { + font-style: italic !important; + font-weight: bold !important; +} +.sunlight-csharp .sunlight-pragma, +.sunlight-preprocessorDirective, +.sunlight-vb .sunlight-compilerDirective { + color: #666363 !important; + font-style: italic !important; +} +.sunlight-xmlDocCommentMeta, +.sunlight-java .sunlight-annotation, +.sunlight-scala .sunlight-annotation, +.sunlight-docComment { + color: #666363 !important; +} +.sunlight-quotedIdent, +.sunlight-ruby .sunlight-subshellCommand, +.sunlight-lisp .sunlight-keywordArgument, +.sunlight-haskell .sunlight-infixOperator, +.sunlight-erlang .sunlight-quotedAtom { + color: #F8CA16 !important; +} + + + + +/* html/xml */ +.sunlight-xml .sunlight-tagName, +.sunlight-xml .sunlight-operator, +.sunlight-xml .sunlight-attribute { + font-weight: normal !important; +} +.sunlight-doctype { + color: #DEB9B2 !important; + font-style: italic !important; +} +.sunlight-xml .sunlight-entity { + background-color: #E6E585 !important; + color: #000000 !important; +} + +/* javascript */ +.sunlight-javascript .sunlight-reservedWord { + font-style: italic !important; +} + +/* css */ +.sunlight-css .sunlight-element { + color: #E9EE97 !important; +} +.sunlight-css .sunlight-microsoftFilterPrefix { + color: #C9FF9F !important; +} +.sunlight-css .sunlight-rule { + color: #0099FF !important; +} +.sunlight-css .sunlight-class { + color: #E78282 !important; +} +.sunlight-css .sunlight-pseudoClass, .sunlight-css .sunlight-pseudoElement { + color: #73D693 !important; +} + +/* bash */ +.sunlight-bash .sunlight-hashBang { + color: #FFFF00 !important; +} + +.sunlight-bash .sunlight-verbatimCommand { + color: #BBA4EE !important; +} +.sunlight-bash .sunlight-variable, +.sunlight-bash .sunlight-specialVariable { + color: #ED8585 !important; +} + +/* python */ +.sunlight-python .sunlight-specialMethod { + font-weight: bold !important; + color: #B0A3C2; +} + +.sunlight-symbol { + color: #b5bd68 !important; +} diff --git a/tests/test-clojure.html b/tests/test-clojure.html new file mode 100644 index 0000000..f088f9e --- /dev/null +++ b/tests/test-clojure.html @@ -0,0 +1,329 @@ + + + + + + + + + +
+; https://github.com/LightTable/LightTable/blob/master/src/lt/plugins/doc.cljs
+(ns lt.plugins.doc
+  (:require [lt.object :as object]
+            [lt.objs.context :as ctx]
+            [lt.objs.clients :as clients]
+            [lt.objs.notifos :as notifos]
+            [lt.objs.editor :as editor]
+            [lt.objs.editor.pool :as pool]
+            [lt.objs.sidebar :as sidebar]
+            [lt.util.dom :as dom]
+            [lt.util.cljs :refer [str-contains?]]
+            [clojure.set :as set]
+            [lt.objs.command :as cmd])
+  (:require-macros [lt.macros :refer [behavior defui]]))
+
+(defn doc-on-line? [editor line]
+  (let [line (editor/line-handle editor line)]
+    (get-in @editor [:widgets [line :underline]])))
+
+(defn remove! [editor cur]
+  (object/update! editor [:widgets] dissoc [(:line @cur) :underline])
+  (object/raise cur :clear!))
+
+(behavior ::clear
+          :triggers #{:clear}
+          :reaction (fn [this]
+                      (object/update! (:ed @this) [:widgets] dissoc [(:line @this) :underline])))
+
+(defn inline-doc [this res opts loc]
+  (let [ed (:ed @this)
+        type :underline
+        line (editor/line-handle ed (:line loc))
+        res-obj (object/create :lt.objs.eval/underline-result {:ed this
+                                                               :class (name type)
+                                                               :opts opts
+                                                               :result res
+                                                               :loc loc
+                                                               :line line})]
+    (object/add-tags res-obj [:inline.doc])
+    (object/update! this [:widgets] assoc [line :underline] res-obj)
+    res-obj))
+
+(behavior ::doc-menu+
+          :triggers #{:menu+}
+          :reaction (fn [this items]
+                      (conj items
+                            {:label "Show docs"
+                             :order 0.1
+                             :enabled (not (editor/selection? this))
+                             :click (fn []
+                                      (cmd/exec! :editor.doc.toggle))}
+                            {:type "separator"
+                             :order 0.2}
+                            )))
+
+
+(cmd/command {:command :editor.doc.toggle
+              :desc "Docs: Toggle documentation at cursor"
+              :exec (fn []
+                      (when-let [ed (pool/last-active)]
+                        (let [loc (editor/->cursor ed)]
+                          (if-let [cur (doc-on-line? ed (:line loc))]
+                            (remove! ed cur)
+                            (object/raise ed :editor.doc)
+                            )))
+                      )})
+
+(defui doc-ui [doc]
+  [:div.inline-doc
+   [:h1 (:name doc)]
+   [:h2 (:ns doc)]
+   (when (and (:args doc)
+              (not= (:args doc) "nil"))
+     [:h3 (:args doc)])
+   (when (and (:doc doc)
+              (not= (:doc doc) "nil"))
+     [:pre (:doc doc)])])
+
+(behavior ::editor.doc.show!
+          :triggers #{:editor.doc.show!}
+          :reaction (fn [editor doc]
+                      (when (not= (:name doc) "")
+                        (inline-doc editor (doc-ui doc) {} (:loc doc)))
+                      ))
+
+(defui search-item [item]
+  [:li
+   [:h2 (:name item)]
+   [:h3 (:ns item)]
+   [:pre (str (:args item))]
+   [:pre (:doc item)]])
+
+(defui search-input [this]
+  [:input.search {:type "text" :placeholder "search docs"}]
+  :focus (fn []
+           (ctx/in! :sidebar.doc.search.input this))
+  :blur (fn []
+          (ctx/out! :sidebar.doc.search.input)))
+
+(defui type-item [this i]
+  [:li (:label i)]
+  :click (fn []
+           (object/raise this :set-item! i)))
+
+(defui type-list [this]
+  (let [types (object/raise-reduce this :types+ [])
+        cur (or (:cur @this) (first types))]
+    [:div.types
+     [:span (:label cur)]
+     [:ul.types
+      (map (partial type-item this) types)]]))
+
+(defui connect-button []
+  [:button "Connect"]
+  :click (fn []
+           (cmd/exec! :show-add-connection)))
+
+(defui no-client-ui [this]
+  [:div.no-client
+   [:p "There's no client for us to use to search for these kinds of docs. "]
+   [:p (connect-button) " to one."]])
+
+(defn try-trigger [this cur v]
+  (let [cs (clients/discover* (:trigger cur))]
+    (if-not (seq cs)
+      (object/raise this :no-client)
+      (do
+        (when-let [nc (dom/$ :.no-client (object/->content this))]
+          (dom/remove nc))
+        (notifos/set-msg! "Searching for docs...")
+        (doseq [c cs]
+          (notifos/working)
+          (clients/send c (:trigger cur) {:search v} :only this))))))
+
+(defn ->val [this]
+  (dom/val (dom/$ :.search (object/->content this))))
+
+(defn grouped-items [results v prev]
+  (let [normal (dom/fragment [])
+        exact (dom/fragment [])]
+    (doseq [r results
+            :when (not (prev r))]
+      (if (str-contains? (str (:name r)) v)
+        (dom/append exact (search-item r))
+        (dom/append normal (search-item r))))
+    {:normal normal
+     :exact exact}))
+
+(behavior ::set-item
+          :triggers #{:set-item!}
+          :reaction (fn [this i]
+                      (object/merge! this {:cur i})
+                      (object/raise this :clear!)
+                      (dom/replace-with (dom/$ :.types (object/->content this)) (type-list this))
+                      (object/raise this :focus!)))
+
+(behavior ::clear!
+          :triggers #{:clear!}
+          :reaction (fn [this]
+                      (dom/empty (dom/$ :.results (object/->content this)))
+                      (object/merge! this {:results #{}})))
+
+(behavior ::no-client
+          :triggers #{:no-client}
+          :reaction (fn [this]
+                      (when-not (dom/$ :.no-client (object/->content this))
+                        (dom/before (dom/$ :.results (object/->content this)) (no-client-ui this)))))
+
+(behavior ::cur-from-last-editor
+          :triggers #{:show}
+          :reaction (fn [this]
+                      (when-let [ed (pool/last-active)]
+                        (let [ed-type (-> @ed :info :type-name)]
+                          (when-not (-> @this :cur :file-types (get ed-type))
+                            (when-let [neue (first (filter #(-> % :file-types (get ed-type))
+                                                           (object/raise-reduce this :types+ [])))]
+                              (object/raise this :set-item! neue)
+                              ))))))
+
+(behavior ::sidebar.doc.search.exec
+          :triggers #{:sidebar.doc.search.exec}
+          :reaction (fn [this]
+                      (let [v (->val this)
+                            trigger (-> @this :cur :trigger)]
+                        (object/raise this :clear!)
+                        (when-not (empty? v)
+                          (if (fn? trigger)
+                            (trigger v)
+                            (try-trigger this (:cur @this) v))))))
+
+(behavior ::doc.search.results
+          :triggers #{:doc.search.results}
+          :reaction (fn [this results]
+                      (let [v (->val this)
+                            {:keys [normal exact]} (grouped-items results v (:results @this))
+                            old (dom/$ :.results (object/->content this))]
+                        (object/merge! this {:results (into (:results @this) results)})
+                        (notifos/done-working (str "Found " (count (:results @this)) " doc results."))
+                        (dom/prepend old exact)
+                        (dom/append old normal))))
+
+(behavior ::focus!
+          :triggers #{:focus!}
+          :reaction (fn [this]
+                      (dom/focus (dom/$ :input (object/->content this)))))
+
+(object/object* ::sidebar.doc.search
+                :tags #{:sidebar.docs.search}
+                :label "Doc search"
+                :init (fn [this]
+                        (object/merge! this {:cur (first (object/raise-reduce this :types+ []))})
+                        [:div.docs-search.filter-list
+                         (search-input this)
+                         (type-list this)
+                         [:ul.results
+                          ]]
+                        ))
+
+(cmd/command {:command :docs.search.exec
+              :desc "Docs: Execute sidebar search"
+              :hidden true
+              :exec (fn []
+                      (when doc-search
+                        (object/raise doc-search :sidebar.doc.search.exec))
+                      )})
+
+(cmd/command {:command :docs.search.show
+              :desc "Docs: Search language docs"
+              :exec (fn [force?]
+                      (when doc-search
+                        (object/raise sidebar/rightbar :toggle doc-search {:force? force?})
+                        (object/raise doc-search :focus!))
+                      )})
+
+(cmd/command {:command :docs.search.hide
+              :desc "Docs: hide language docs"
+              :hidden true
+              :exec (fn [force?]
+                      (when doc-search
+                        (object/raise sidebar/rightbar :close! doc-search))
+                      )})
+
+(behavior ::init-doc-search
+          :triggers #{:init}
+          :reaction (fn [this]
+                      (set! doc-search (object/create ::sidebar.doc.search))
+                      (sidebar/add-item sidebar/rightbar doc-search)
+                      ))
+
+(def doc-search nil)
+
+; snippet from https://github.com/LightTable/LightTable/blob/master/src/lt/plugins/auto_paren.cljs
+(def pairs {\( \)
+            \{ \}
+            \[ \]
+            \" \"
+            \< \>})
+
+(def word-char #"[^\s\)\}\]\(\{\[]")
+(defn- meh)
+
+ + + + + \ No newline at end of file