alexander-yakushev / compliment Goto Github PK
View Code? Open in Web Editor NEWClojure completion library that you deserve
License: Eclipse Public License 1.0
Clojure completion library that you deserve
License: Eclipse Public License 1.0
Need fully fuzzy completion like in slime for Common Lisp. Current fuzzyness is too simple.
I've written a failing test for you. This issue was raised on CIDER
But vars with semicolons colons in them can be completed when the text doesn't include them. for instance,
(fact "handles vars with semicolons in them"
(def foo:bar 1)
(def foo:baz 2)
(src/candidates "foo" *ns* nil)
=> (contains #{"foo:bar" "foo:baz"} :gaps-ok) ;; passes
(src/candidates "foo:" *ns* nil)
=> (contains #{"foo:bar" "foo:baz"} :gaps-ok) ;; fails
(src/candidates "foo:b" *ns* nil)
=> (contains #{"foo:bar" "foo:baz"} :gaps-ok)) ;; fails
Don't filter out aliases based on periods.
(ns foo
(:import [java.time █]))
Only candidates from ::namespaces-and-classes source make sense in this context but e.g. ::ns-mappings offers loads of candidates which hide the interesting results.
Had a bug report in #cider on clojure slack.
If you have a namespace that you require with an alias as user
there will be no completions found. The bug is from
(defn resolve-namespace
"Tries to resolve a namespace from the given symbol, either from a
fully qualified name or an alias in the given namespace."
[sym ns]
(or (find-ns sym) ((ns-aliases ns) sym)))
as the prefix 'user
comes in and this resolves to the single segment namespace user
.
Note this happens on the special single-segment ns user but could happen on anything that is single segment ns and has the same name as an alias.
The fix seems easy in that the aliases should be consulted first and then single segment namespaces after. This works because if the person has imported something as user
, this is what they would prefer. And would seem to make referring to the single segment namespace impossible so the completions should mimic.
cider.nrepl.middleware.complete> (jvm-complete/documentation "")
StringIndexOutOfBoundsException String index out of range: 0 java.lang.String.charAt (String.java:646)
Ideally this should return "" (what happens with unknown symbols).
If I do ::my-ns-alias/
then keywords in my-ns-alias
are listed correctly, but the there's no completion suggestion while I'm typing ::my-ns-alias
When you have a Clojure type like
(deftype Foo [x-y] ...)
then (.x
completes to (.x_y
instead of (.x-y
. That's because for a deftype a class is generated, and the field names of the class are the "munged" names of the deftype's fields, i.e., hypens are replaced with underscores.
In Clojure on the JVM, (.x_y foo)
actually works but it relies on this very implementation detail and is not portable to the CLR or ClojureScript. Thus, it's better to use (.x-y foo)
as this is the documented way to access fields of a deftype.
So it would be nice if CIDER could directly suggest the correct version when completing. The fact that can be exploited is that all classes (and only those) generated for a deftype implement clojure.lang.IType
(see https://github.com/clojure/clojure/blob/master/src/clj/clojure/core_deftype.clj#L396). Therefore, I suggest to "unmunge" field names of classes implementing IType
.
It'd be nice if the documentation function returned the complete javadoc for a class/member. That way we'll be able to create a documentation middleware that we'll behave similarly for Clojure vars and Java classes/members. It would also be good if we could reduce the ambiguity and return just a single documentation entry (that way the short documentation could used to implement eldoc
-like functionality for Java stuff). That would require us to know the type of the receiver, but we'll know that most of the time (at least for already evaluated code).
If context is set e.g. following doesn't offer any candidates:
(ns foo
(:import java.util.█))
Maybe this would be fixed if context aware completion is used only if prefix doesn't have any dots?
Emacs has a feature that allows you to annotate a completion candidate with addition information - e.g. the type of the candidate (function
, macro
, interface
, etc). You can see the feature in action for Emacs Lisp completions. For this to effectively used, however, it'd be beneficial if one could retrieve a completion candidate together with some metadata regarding it (perhaps the type and the ns/package of the candidate, maybe others make sense as well), so avoid having to do one of two things:
Obviously this is not a must-have feature, but it'd be nice to have something like this in tools like CIDER. I'm also not quite sure if packages like compliment should handle this or their clients, but I wanted to start a discussion on the subject.
Hi!
For the following example:
(defn thing [^String s]
(. s|))
|
being the cursor, ideally compliment would infer that s
is a String, offering accurate completions accordingly.
(bonus points: also accurately complete at (-> s .|)
)
My thinking is that type hinting could be a more extended practice if it resulted in accurate completions. Which would yield faster code and a more pleasant dev UX, so why not? :)
I assume the feature isn't there because I that's not the behavior I can observe (or find in https://github.com/alexander-yakushev/compliment/wiki/Examples).
Is this something you have attempted in the past? Or willing to give a shot someday?
I could try it myself. Would appreciate any info that might aid an implementation.
Cheers - Victor
It'd be nice if there was a source for resource completions. Those completions should be contextual - e.g. triggered only in functions known to accept resource paths.
We can also reuse this functionality to implement find resource
functionality in CIDER.
Related ticket clojure-emacs/cider#1062
completions
accepts context as a param, but I cannot understand from the docstring what exactly this context is supposed to be. Maybe you can extend the documentation about this or even add a few tests showcasing its work.
The two following pieces of code should get the same completions, but they don't:
(defn x [^String arg]
(__prefix__ arg)) ;; for a prefix value of ".", String completions will be offered
(defn x [^String arg]
(-> arg __prefix__)) ;; for a prefix value of ".", Object completions will be offered
Perform certain macroexpansions as an early step in compliment.context/parse-context
.
The following POC worked fine for me:
(ns compliment.sources.poc
(:require
[compliment.sources.class-members :as src]
[compliment.context :as ctx]
[compliment.t-helpers :refer :all]
[clojure.walk :as walk]))
(defn macroexpanded-context [form]
(->> form
(walk/postwalk (fn [x]
(if (and (list? x)
(-> x first #{'-> '->>}))
(macroexpand-1 x)
x)))
ctx/parse-context))
(strip-tags (src/members-candidates "." *ns* (macroexpanded-context '(defn x [^String arg]
(-> arg __prefix__)))))
WDYT?
Cheers - Victor
In doseq
and for
you can use :let
to create new bindings. Afaict compliment isn't aware of this context.
It'd be nice if you got completion candidates when you did something like (import java.net. TAB
- all the classes in the package, even the unimported ones.
;; set! is missing
user> (compliment.core/completions "set" nil)
("set" "set?" "set-validator!" "set-error-mode!" "set-error-handler!" "set-agent-send-executor!" "set-agent-send-off-executor!")
;; var is missing
user> (compliment.core/completions "va" nil)
("val" "vals" "var?" "var-get" "var-set" "vary-meta")
Pretty sure all special forms are missing.
I noticed that by default candidates don't seem to be sorted alphatebically, which is a bit odd:
user> (compliment.core/completions "map" nil)
("mapv" "map" "mapcat" "map?" "map-indexed")
user> (complete.core/completions "map")
("map" "map-indexed" "map?" "mapcat" "mapv")
Any particular reason about this? The current ordering seems confusing to me.
In this scenario, we get no candidates,
(use 'compliment.sources.class-members)
(def blob (javax.sql.rowset.serial.SerialBlob. (byte-array 1)))
(map :candidate (members-candidates "." (find-ns 'user) (ctx/parse-context '(__prefix__ blob)) ))
()
so no candidates are found.
expected behavior: all and only members of class SerialBlob are returned
found behavior: no members are returned
It does work, when class is imported first:
(require '[compliment.context :as ctx])
(use 'compliment.sources.class-members)
(import javax.sql.rowset.serial.SerialBlob)
(def blob (SerialBlob. (byte-array 1)))
(map :candidate (members-candidates "." (find-ns 'user) (ctx/parse-context '(__prefix__ blob)) ))
(".getBinaryStream" ".setBytes" ".free" ".setBinaryStream" ".getBytes" ".position" ".length" ".truncate" ".clone" ".hashCode" ".equals")
e.g.
user> (compliment.core/completions "Thre" {:tag-candidates true})
returns
({:ns "user", :type :class, :candidate "Thread"}
{:ns "user", :type :class, :candidate "ThreadDeath"}
{:ns "user", :type :class, :candidate "ThreadGroup"}
{:ns "user", :type :class, :candidate "ThreadLocal"}
{:ns "user", :type :class, :candidate "Thread$State"}
{:ns "user", :type :class, :candidate "Thread$UncaughtExceptionHandler"})
To handle situations like this:
(.. (java.util.Date.) |toString (|endsWith "2019"))
Sister issue: clojure-emacs/cider#2612.
Hi,
while playing around with compliment (0.0.3) I discovered the following:
(require 'compliment.core)
(require '[clojure.string :as s])
(compliment.core/completions "s/cap" nil) => ()
;; fresh repl
(require '[clojure.string :as str])
(compliment.core/completions "str/cap" nil) => ("str/capitalize")
Not sure if this is an issue of compliment or that of the underlying data source.
ERROR: Unhandled REPL handler exception processing message {:op complete, :session 5eefc359-1330-474e-bd86-9df453b135af, :ns refactor-nrepl.rename-file-or-dir, :symbol jav, :context :same, :id 503}
java.lang.StackOverflowError
at clojure.lang.LazySeq.sval(LazySeq.java:42)
at clojure.lang.LazySeq.seq(LazySeq.java:60)
at clojure.lang.RT.seq(RT.java:484)
at clojure.core$seq.invoke(core.clj:133)
at clojure.core$concat$fn__3923.invoke(core.clj:678)
at clojure.lang.LazySeq.sval(LazySeq.java:42)
at clojure.lang.LazySeq.seq(LazySeq.java:60)
at clojure.lang.RT.seq(RT.java:484)
at clojure.core$seq.invoke(core.clj:133)
at clojure.core$concat$fn__3923.invoke(core.clj:678)
Reminded me of this blog post
Hopefully this is enough to go on, the stacktrace is entirely without reference to any of your code :/
I can't seem to get non static member completion to work. If I do the following:
(def foo "test")
(.c foo)
I get all kind of completions unrelated to String, like .checkSystemClipboardAccess
.
Same with this one:
(def bar (HashMap.))
(.p bar)
I expect to see only HashMap methods starting with p, as stated in the examples wiki: https://github.com/alexander-yakushev/compliment/wiki/Examples.
Regards
Hi @alexander-yakushev I hope my #62 PR is not super scary but I have a new requirement for the :candidates
function that plugs in defsource
: basically ClojureScript completions require the compiler map to be passed through all the functions, because that's where the information we need lives.
I was wondering how to manage that, because it could potentially be breaking.
What do you think? What should we do?
I recently started using compliment
in CIDER as a replacement for clojure-complete
. While it works great I noticed there are neither tests nor integration documentation. Sure, the public API is two functions, but I think you'll make developer lives easier if they had a formal specification in the form of tests.
Hi, I extended compliment to support clojurescript completion as part of replique. Is that something you would be interested in ?
user> (->> (compliment.core/completions "" {:tag-candidates true})
(filter #(= (:type %) :special-form)))
({:type :special-form, :candidate "do"}
{:type :special-form, :candidate "if"}
{:type :special-form, :candidate "def"}
{:type :special-form, :candidate "new"}
{:type :special-form, :candidate "try"}
{:type :special-form, :candidate "var"}
{:type :special-form, :candidate "set!"}
{:type :special-form, :candidate "catch"}
{:type :special-form, :candidate "quote"}
{:type :special-form, :candidate "recur"}
{:type :special-form, :candidate "throw"}
{:type :special-form, :candidate "monitor-exit"}
{:type :special-form, :candidate "monitor-enter"})
user>
Should these have an :ns
of clojure.core
? Technically, you can't invoke any of them with a namespace prefix (i.e. (clojure.core/try 1)
does not work), but I think they're generally considered to belong to that namespace.
Just pointing it out, in case you consider it an omission. I have no opinion either way :-).
As discussed here.
Suggestion is that the following forms be handled by compliment and the rest by auto-complete/company by drawing on words in the same buffer:
Hi,
I'm currently writing an emacs mode for clojure development. I would love to embed compliment in order to provide auto completion, though I can't because of GPL/EPL license incompatibility. Would you consider dual licensing with a GPL compatible license?
This should enable locals completion for those who don't use Paredit, inlcuding rebel-readline (see bhauman/rebel-readline#28).
Got an issue popping up in CIDER with compliment throwing a null pointer exception by just typing ::/
and the autocomplete functionality throwing:
I've created the following test that "tests" it to demonstrate the error programatically and I'm starting to look into the codebase but i'm not too familiar with it.
(fact "keyword reading does not throw an error"
(do (src/candidates "::/" *ns* nil)
=> (strip-tags (just #{""}))))
Very often I'm rewriting something from Java or going through a tutorial, and I have to use a Java class I don't know full package name of. So I have either to Google it, or search somewhere else, then type in its full name into :import
clause.
Solution: Context-check if we are inside :import
and then allow completing full classnames by a simple name.
e.g.
user> (compliment.core/completions ".getDe" {:tag-candidates true})
returns
({:type :method, :candidate ".getDeclaredField"}
{:type :method, :candidate ".getDeclaredFields"}
{:type :method, :candidate ".getDeclaredMethod"}
{:type :method, :candidate ".getDeclaringClass"}
{:type :method, :candidate ".getDeclaredClasses"}
{:type :method, :candidate ".getDeclaredMethods"}
{:type :method, :candidate ".getDeclaredAnnotation"}
{:type :method, :candidate ".getDeclaredAnnotations"}
{:type :method, :candidate ".getDeclaredConstructor"}
{:type :method, :candidate ".getDeclaredConstructors"}
{:type :method, :candidate ".getDeclaredAnnotationsByType"})
but
user> (compliment.core/completions ".getDe" {:ns 'user :tag-candidates true})
returns
({:type :field, :candidate ".getDeclaredField"}
{:type :field, :candidate ".getDeclaredFields"}
{:type :field, :candidate ".getDeclaredMethod"}
{:type :field, :candidate ".getDeclaringClass"}
{:type :field, :candidate ".getDeclaredClasses"}
{:type :field, :candidate ".getDeclaredMethods"}
{:type :field, :candidate ".getDeclaredAnnotation"}
{:type :field, :candidate ".getDeclaredAnnotations"}
{:type :field, :candidate ".getDeclaredConstructor"}
{:type :field, :candidate ".getDeclaredConstructors"}
{:type :field, :candidate ".getDeclaredAnnotationsByType"})
~/.lein/profiles.clj
Hi,
I've been using compliment via company-mode in cider, and I keep running into an issue where typing a boolean literal autocompletes to the associated predicate function (true?
instead of true
/ false?
instead of false
). If you add the strings "true"
and "false"
to the special-forms
set in compliment.sources.special-forms
, then this problem goes away.
Thanks for the great package!
Sanjay
I tried adding JDK9 to travis CI for testing and there were some failures as below :
Hi, thanks for the work on :tag-candidates
- it's looking great! Here's a small issue:
user> (compliment.core/completions "clojure.core.async.la" {:tag-candidates true})
({:type :class, :candidate "clojure.core.async.lab"})
user> (require 'clojure.core.async.lab)
nil
user> (compliment.core/completions "clojure.core.async.la" {:tag-candidates true})
({:type :namespace, :candidate "clojure.core.async.lab"})
Note that the candidate has a :type
of :class
until the namespace has been required - then :namespace
is correctly returned.
java.lang.NullPointerException: null
at java.util.concurrent.ConcurrentHashMap.get (ConcurrentHashMap.java:936)
clojure.lang.Namespace.find (Namespace.java:188)
clojure.core$find_ns.invoke (core.clj:3976)
clojure.core$the_ns.invoke (core.clj:4008)
clojure.core$ns_map.invoke (core.clj:4022)
cider.inlined_deps.compliment.v0v2v7.compliment.sources.ns_mappings$candidates.invoke (ns_mappings.clj:53)
clojure.lang.Var.invoke (Var.java:388)
cider.inlined_deps.compliment.v0v2v7.compliment.core$eval52698$completions__52701$iter__52703__52707$fn__52708.invoke (core.clj:95)
clojure.lang.LazySeq.sval (LazySeq.java:40)
clojure.lang.LazySeq.seq (LazySeq.java:49)
clojure.lang.RT.seq (RT.java:507)
clojure.core/seq (core.clj:137)
clojure.core$tree_seq$walk__5045$fn__5046.invoke (core.clj:4739)
clojure.lang.LazySeq.sval (LazySeq.java:40)
clojure.lang.LazySeq.seq (LazySeq.java:49)
clojure.lang.LazySeq.more (LazySeq.java:85)
clojure.lang.RT.more (RT.java:683)
clojure.core/rest (core.clj:73)
clojure.core$flatten.invoke (core.clj:6851)
cider.inlined_deps.compliment.v0v2v7.compliment.core$eval52698$completions__52701.invoke (core.clj:91)
cider.nrepl.middleware.complete$complete.invoke (complete.clj:19)
cider.nrepl.middleware.complete$complete_reply.invoke (complete.clj:34)
cider.nrepl.middleware.complete$wrap_complete$fn__53008.invoke (complete.clj:55)
clojure.tools.nrepl.middleware$wrap_conj_descriptor$fn__40282.invoke (middleware.clj:22)
cider.nrepl.middleware.ns$wrap_ns$fn__51752.invoke (ns.clj:110)
clojure.tools.nrepl.middleware$wrap_conj_descriptor$fn__40282.invoke (middleware.clj:22)
cider.nrepl.middleware.trace$wrap_trace$fn__59014.invoke (trace.clj:65)
clojure.tools.nrepl.middleware$wrap_conj_descriptor$fn__40282.invoke (middleware.clj:22)
cider.nrepl.middleware.macroexpand$wrap_macroexpand$fn__58340.invoke (macroexpand.clj:220)
clojure.tools.nrepl.middleware$wrap_conj_descriptor$fn__40282.invoke (middleware.clj:22)
cider.nrepl.middleware.apropos$wrap_apropos$fn__51782.invoke (apropos.clj:100)
clojure.tools.nrepl.middleware$wrap_conj_descriptor$fn__40282.invoke (middleware.clj:22)
refactor_nrepl.middleware$wrap_refactor$fn__68899.invoke (middleware.clj:135)
clojure.tools.nrepl.middleware$wrap_conj_descriptor$fn__40282.invoke (middleware.clj:22)
cider.nrepl.middleware.classpath$wrap_classpath$fn__51806.invoke (classpath.clj:30)
clojure.tools.nrepl.middleware$wrap_conj_descriptor$fn__40282.invoke (middleware.clj:22)
clojure.tools.nrepl.middleware.interruptible_eval$interruptible_eval$fn__40547.invoke (interruptible_eval.clj:247)
clojure.tools.nrepl.middleware$wrap_conj_descriptor$fn__40282.invoke (middleware.clj:22)
cider.nrepl.middleware.inspect$wrap_inspect$fn__53217.invoke (inspect.clj:108)
clojure.tools.nrepl.middleware$wrap_conj_descriptor$fn__40282.invoke (middleware.clj:22)
cider.nrepl.middleware.debug$wrap_debug$fn__54863.invoke (debug.clj:461)
clojure.tools.nrepl.middleware$wrap_conj_descriptor$fn__40282.invoke (middleware.clj:22)
clojure.tools.nrepl.middleware.load_file$wrap_load_file$fn__40667.invoke (load_file.clj:79)
clojure.tools.nrepl.middleware$wrap_conj_descriptor$fn__40282.invoke (middleware.clj:22)
user$eval72797$fn__72798$fn__72800.invoke (form-init6400695034319109191.clj:1)
cider.nrepl.middleware.enlighten$wrap_enlighten$fn__54905.invoke (enlighten.clj:86)
clojure.tools.nrepl.middleware$wrap_conj_descriptor$fn__40282.invoke (middleware.clj:22)
clojure.tools.nrepl.middleware.session$add_stdin$fn__40626.invoke (session.clj:238)
clojure.tools.nrepl.middleware$wrap_conj_descriptor$fn__40282.invoke (middleware.clj:22)
cider.nrepl.middleware.pprint$wrap_pprint$fn__54554.invoke (pprint.clj:106)
clojure.tools.nrepl.middleware$wrap_conj_descriptor$fn__40282.invoke (middleware.clj:22)
cider.nrepl.middleware.track_state$wrap_tracker$fn__59277.invoke (track_state.clj:200)
clojure.tools.nrepl.middleware$wrap_conj_descriptor$fn__40282.invoke (middleware.clj:22)
clojure.tools.nrepl.middleware.pr_values$pr_values$fn__40465.invoke (pr_values.clj:22)
clojure.tools.nrepl.middleware$wrap_conj_descriptor$fn__40282.invoke (middleware.clj:22)
cider.nrepl.middleware.test$wrap_test$fn__58741.invoke (test.clj:286)
clojure.tools.nrepl.middleware$wrap_conj_descriptor$fn__40282.invoke (middleware.clj:22)
cider.nrepl.middleware.resource$wrap_resource$fn__58596.invoke (resource.clj:49)
clojure.tools.nrepl.middleware$wrap_conj_descriptor$fn__40282.invoke (middleware.clj:22)
cider.nrepl.middleware.stacktrace$wrap_stacktrace$fn__54632.invoke (stacktrace.clj:175)
clojure.tools.nrepl.middleware$wrap_conj_descriptor$fn__40282.invoke (middleware.clj:22)
cider.nrepl.middleware.pprint$wrap_pprint_fn$fn__54528.invoke (pprint.clj:53)
clojure.tools.nrepl.middleware$wrap_conj_descriptor$fn__40282.invoke (middleware.clj:22)
cider.nrepl.middleware.info$wrap_info$fn__58225.invoke (info.clj:304)
clojure.tools.nrepl.middleware$wrap_conj_descriptor$fn__40282.invoke (middleware.clj:22)
cider.nrepl.middleware.undef$wrap_undef$fn__59304.invoke (undef.clj:30)
clojure.tools.nrepl.middleware$wrap_conj_descriptor$fn__40282.invoke (middleware.clj:22)
cider.nrepl.middleware.out$wrap_out$fn__58426.invoke (out.clj:96)
clojure.tools.nrepl.middleware$wrap_conj_descriptor$fn__40282.invoke (middleware.clj:22)
clojure.tools.nrepl.middleware.session$session$fn__40611.invoke (session.clj:192)
clojure.tools.nrepl.middleware$wrap_conj_descriptor$fn__40282.invoke (middleware.clj:22)
clojure.tools.nrepl.server$handle_STAR_.invoke (server.clj:19)
clojure.tools.nrepl.server$handle$fn__40682.invoke (server.clj:28)
clojure.core$binding_conveyor_fn$fn__4444.invoke (core.clj:1916)
clojure.lang.AFn.call (AFn.java:18)
java.util.concurrent.FutureTask.run (FutureTask.java:266)
java.util.concurrent.ThreadPoolExecutor.runWorker (ThreadPoolExecutor.java:1142)
java.util.concurrent.ThreadPoolExecutor$Worker.run (ThreadPoolExecutor.java:617)
java.lang.Thread.run (Thread.java:745)
Functions bound using letfn
are currently not provided as completion candidates, but it would be nice if they were :)
By leveraging context complete defn
arguments and local bindings introduced by let
.
Moved from clojure-emacs/cider#660.
This requires parsing the jars and classfolders on the classpath. It is exclusive to JVM as Dalvik doesn't have classes freely lying around.
Moved from trptcolin/reply#153.
Additional dependencies that get sideloaded into a running REPL (e.g. via Boot's set-env!
) are not suggested during completion because of the stale classname cache.
Trying to complete "/" with (co/completions "/" nil)
generates a NPE:
java.lang.NullPointerException
at clojure.lang.Symbol.intern(Symbol.java:60)
at clojure.core$symbol.invoke(core.clj:546)
at compliment.sources.class_members$static_members_candidates.invoke(class_members.clj:213)
at clojure.lang.Var.invoke(Var.java:388)
at compliment.core$completions$iter__3762__3766$fn__3767.invoke(core.clj:51)
...
in the class-members source. I don't think there's anything sensible to offer as a completion for "/", so it might be best just to guard against it in the class-members source.
Similarly, "/a" generates an exception, this time a StringIndexOutOfBoundsException
.
I'm happy to send a PR if you think checking explicitly for strings starting with "/" is the right way to go.
Is there any interest in using core.typed to drive completion? Particularly interesting would be to complete java interop forms:
(.. "Hello World" toLow<tab>)
==> (.. "Hello World" toLowerCase)
The cf
macro or something similar in core.typed could do a lot of the work here. It would seem you could go a long way with that and a bit of reflection. Of course, this would be jvm clojure only.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.