diff --git a/.gitignore b/.gitignore index 233ad94..a130817 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,4 @@ java/descriptors/descriptors.go java/signatures/signatures.go clang/parser/parser.go util/expression/expression.go +*.pyc diff --git a/3rdparty/jsonrpc.py b/3rdparty/jsonrpc.py index 4ce38ee..9032f1f 100644 --- a/3rdparty/jsonrpc.py +++ b/3rdparty/jsonrpc.py @@ -44,7 +44,7 @@ close ('127.0.0.1', 31415) Client with JsonRPC2.0 and an abstract Unix Domain Socket:: - + >>> proxy = ServerProxy( JsonRpc20(), TransportUnixSocket(addr="\\x00.rpcsocket") ) >>> proxy.hi( message="hello" ) #named parameters u'hi there' @@ -56,7 +56,7 @@ u'hello world' Server with JsonRPC2.0 and abstract Unix Domain Socket with a logfile:: - + >>> server = Server( JsonRpc20(), TransportUnixSocket(addr="\\x00.rpcsocket", logfunc=log_file("mylog.txt")) ) >>> def echo( s ): ... return s @@ -168,7 +168,7 @@ PERMISSION_DENIED : "Permission denied.", INVALID_PARAM_VALUES: "Invalid parameter values." } - + #---------------------- # exceptions @@ -183,7 +183,7 @@ class RPCTimeoutError(RPCTransportError): class RPCFault(RPCError): """RPC error/fault package received. - + This exception can also be used as a class, to generate a RPC-error/fault message. @@ -254,8 +254,11 @@ def __init__(self, error_data=None): try: import simplejson except ImportError, err: - print "FATAL: json-module 'simplejson' is missing (%s)" % (err) - sys.exit(1) + try: + import json as simplejson + except ImportError, err: + print "FATAL: json-module 'simplejson' is missing (%s)" % (err) + sys.exit(1) #---------------------- # @@ -304,7 +307,7 @@ def dumps_request( self, method, params=(), id=0 ): - id: if id=None, this results in a Notification :Returns: | {"method": "...", "params": ..., "id": ...} | "method", "params" and "id" are always in this order. - :Raises: TypeError if method/params is of wrong type or + :Raises: TypeError if method/params is of wrong type or not JSON-serializable """ if not isinstance(method, (str, unicode)): @@ -346,7 +349,7 @@ def dumps_error( self, error, id=None ): Since JSON-RPC 1.0 does not define an error-object, this uses the JSON-RPC 2.0 error-object. - + :Parameters: - error: a RPCFault instance :Returns: | {"result": null, "error": {"code": error_code, "message": error_message, "data": error_data}, "id": ...} @@ -481,7 +484,7 @@ def dumps_request( self, method, params=(), id=0 ): :Returns: | {"jsonrpc": "2.0", "method": "...", "params": ..., "id": ...} | "jsonrpc", "method", "params" and "id" are always in this order. | "params" is omitted if empty - :Raises: TypeError if method/params is of wrong type or + :Raises: TypeError if method/params is of wrong type or not JSON-serializable """ if not isinstance(method, (str, unicode)): @@ -528,7 +531,7 @@ def dumps_response( self, result, id=None ): def dumps_error( self, error, id=None ): """serialize a JSON-RPC-Response-error - + :Parameters: - error: a RPCFault instance :Returns: | {"jsonrpc": "2.0", "error": {"code": error_code, "message": error_message, "data": error_data}, "id": ...} @@ -676,7 +679,7 @@ def logfile( message ): class Transport: """generic Transport-interface. - + This class, and especially its methods and docstrings, define the Transport-Interface. """ @@ -696,7 +699,7 @@ def sendrecv( self, string ): return self.recv() def serve( self, handler, n=None ): """serve (forever or for n communicaions). - + - receive data - call result = handler(data) - send back result if not None @@ -737,7 +740,7 @@ def recv(self): import socket, select class TransportSocket(Transport): """Transport via socket. - + :SeeAlso: python-module socket :TODO: - documentation @@ -772,7 +775,7 @@ def close( self ): self.s = None def __repr__(self): return "" % repr(self.addr) - + def send( self, string ): if self.s is None: self.connect() @@ -799,7 +802,7 @@ def sendrecv( self, string ): self.close() def serve(self, handler, n=None): """open socket, wait for incoming connections and handle them. - + :Parameters: - n: serve n requests, None=forever """ @@ -829,7 +832,7 @@ def serve(self, handler, n=None): if hasattr(socket, 'AF_UNIX'): - + class TransportUnixSocket(TransportSocket): """Transport via Unix Domain Socket. """ @@ -941,7 +944,7 @@ def __call__(self, *args, **kwargs): class Server: """RPC-server. - It works with different data/serializers and + It works with different data/serializers and with different transports. :Example: @@ -982,7 +985,7 @@ def log(self, message): def register_instance(self, myinst, name=None): """Add all functions of a class-instance to the RPC-services. - + All entries of the instance which do not begin with '_' are added. :Parameters: @@ -1002,7 +1005,7 @@ def register_instance(self, myinst, name=None): self.register_function( getattr(myinst, e), name="%s.%s" % (name, e) ) def register_function(self, function, name=None): """Add a function to the RPC-services. - + :Parameters: - function: function to add - name: RPC-name for the function. If omitted/None, the original @@ -1012,7 +1015,7 @@ def register_function(self, function, name=None): self.funcs[function.__name__] = function else: self.funcs[name] = function - + def handle(self, rpcstr): """Handle a RPC-Request. @@ -1066,7 +1069,7 @@ def handle(self, rpcstr): def serve(self, n=None): """serve (forever or for n communicaions). - + :See: Transport """ self.__transport.serve( self.handle, n ) diff --git a/README.md b/README.md index b4f3265..0c8400a 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,5 @@ +# Goals + This project aims to implement an editor and language agnostic backend with the initial scope of supporting: @@ -21,6 +23,8 @@ Collaborators wanting to aid the project are welcome to submit pull requests and open up a new issue number for discussing implementation details not already discussed in the [existing issues](https://github.com/quarnster/completion/issues?state=open) list. +# License + The license of the project is the 2-clause BSD license: ``` @@ -47,3 +51,78 @@ ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ``` + +# Third party libraries used: + +[Roland Koebler's json.py](http://www.simple-is-better.org/rpc/) (slightly modified, see [commit history](https://github.com/quarnster/completion/commits/master/3rdparty/jsonrpc.py)) +``` +Copyright (c) 2007-2008 by Roland Koebler (rk(at)simple-is-better.org) + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be included +in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +``` + +[Kyle Lemons' log4go](https://code.google.com/p/log4go/) +``` +Copyright (c) 2010, Kyle Lemons . All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +``` + +[fsnotify](https://github.com/howeyc/fsnotify) +``` +Copyright (c) 2012 The Go Authors. All rights reserved. +Copyright (c) 2012 fsnotify Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +``` diff --git a/build/build.go b/build/build.go index 6c7efc2..60cff90 100644 --- a/build/build.go +++ b/build/build.go @@ -11,7 +11,7 @@ import ( "runtime" ) -var ignore = regexp.MustCompile(`\.git|build|testdata`) +var ignore = regexp.MustCompile(`\.git|build|testdata|3rdparty`) var verbose bool func adddirs(pkg, path string, dirs []string) []string { @@ -103,7 +103,6 @@ func main() { if verbose { tests = append(tests, "-v") } - tests = append(tests, "github.com/quarnster/completion") tests = adddirs("github.com/quarnster/completion", "..", tests) c = exec.Command("go", tests...) r, err := c.StdoutPipe() diff --git a/clang/clang.go b/clang/clang.go index cc00d14..27f8472 100644 --- a/clang/clang.go +++ b/clang/clang.go @@ -1,6 +1,7 @@ package clang import ( + "code.google.com/p/log4go" "fmt" cp "github.com/quarnster/completion/clang/parser" "github.com/quarnster/completion/content" @@ -10,7 +11,7 @@ import ( func RunClang(args ...string) ([]byte, error) { cmd := exec.Command("clang", args...) - fmt.Println("cmd:", cmd.Args) + log4go.Trace("Running clang command: %v", cmd) return cmd.CombinedOutput() } @@ -70,12 +71,20 @@ func parseresult(in string) (ret content.CompletionResult, err error) { return } -func CompleteAt(args []string, loc content.SourceLocation) (ret content.CompletionResult, err error) { - args = append([]string{"-fsyntax-only", "-Xclang", fmt.Sprintf("-code-completion-at=%s:%d:%d", loc.File.Name, loc.Line, loc.Column)}, args...) - args = append(args, loc.File.Name) +type Clang struct { +} + +func (c *Clang) CompleteAt(a *content.CompleteAtArgs, ret *content.CompletionResult) error { + args, _ := a.Settings().Get("compiler_flags").([]string) + + args = append([]string{"-fsyntax-only", "-Xclang", fmt.Sprintf("-code-completion-at=%s:%d:%d", a.Location.File.Name, a.Location.Line, a.Location.Column)}, args...) + args = append(args, a.Location.File.Name) if out, err := RunClang(args...); err != nil { - return ret, err + return err + } else if r, err := parseresult(string(out)); err != nil { + return err } else { - return parseresult(string(out)) + *ret = r + return nil } } diff --git a/clang/clang_test.go b/clang/clang_test.go index c342994..7a4144a 100644 --- a/clang/clang_test.go +++ b/clang/clang_test.go @@ -16,11 +16,20 @@ func TestClang(t *testing.T) { if _, err := RunClang("-v"); err != nil { t.Skipf("Couldn't launch clang: %s", err) } - loc := content.SourceLocation{} - loc.Column = 1 - loc.Line = 10 - loc.File.Name = "testdata/hello.cpp" - t.Log(CompleteAt([]string{}, loc)) + var ( + a content.CompleteAtArgs + b content.CompletionResult + c Clang + ) + + a.Location.Column = 1 + a.Location.Line = 4 + a.Location.File.Name = "testdata/hello.cpp" + if err := c.CompleteAt(&a, &b); err != nil { + t.Error(err) + } else { + t.Log(b) + } } func TestParseResult(t *testing.T) { diff --git a/clang/complete_intent.go b/clang/complete_intent.go deleted file mode 100644 index 11f4d75..0000000 --- a/clang/complete_intent.go +++ /dev/null @@ -1,33 +0,0 @@ -package clang - -import ( - "github.com/quarnster/completion/content" - "regexp" -) - -var extre = regexp.MustCompile(`\.(h|hpp|c|cc|cpp|m|mm)$`) - -type CompletionHandler struct { -} - -func (c *CompletionHandler) Handle(it *content.Intent) *content.Response { - loc := it.Data.Get("loc").(content.SourceLocation) - args, _ := it.Settings().Get("compiler_flags").([]string) - if cmp, err := CompleteAt(args, loc); err != nil { - return content.NewErrorResponse(err.Error()) - } else { - r := content.NewResponse() - r.Data.Set("completions", cmp) - return &r - } -} - -func (c *CompletionHandler) CanHandle(it *content.Intent) bool { - if it.Operation != content.CompleteSourceLocation { - return false - } else if loc, ok := it.Data.Get("loc").(content.SourceLocation); !ok { - return false - } else { - return extre.MatchString(loc.File.Name) - } -} diff --git a/clang/testdata/hello.cpp b/clang/testdata/hello.cpp new file mode 100644 index 0000000..5439d9b --- /dev/null +++ b/clang/testdata/hello.cpp @@ -0,0 +1,6 @@ +#include + +int main(int argc, char** argv) { + printf("Hello world!\n"); + return 0; +} diff --git a/content/args.go b/content/args.go new file mode 100644 index 0000000..e69407d --- /dev/null +++ b/content/args.go @@ -0,0 +1,47 @@ +package content + +type ( + Args struct { + SessionId string + SessionOverrides Settings + } + + CompleteAtArgs struct { + Args + Location SourceLocation + } + + CompleteArgs struct { + Args + Location FullyQualifiedName + } + + Session struct { + Settings Settings + } +) + +// Returns the Session object associated with this Args or nil. +func (a *Args) Session() *Session { + if a.SessionId != "" { + // TODO actually look up session and return it + } + return nil +} + +// If a Session is associated with this Args, then the Session's +// Settings is cloned and the Args's Settings are merged into the +// cloned Settings object. This to allow Argss to be "slim", i.e. +// only contain the keys of the specific settings it wants to override. +// +// If there's no Session nor Args Settings, the empty Settings object +// is returned. +func (it *Args) Settings() *Settings { + if session := it.Session(); session != nil { + set := session.Settings.Clone() + set.Merge(&it.SessionOverrides) + return set + } else { + return &it.SessionOverrides + } +} diff --git a/content/completion.go b/content/completion.go index 0cef7d8..fe8779a 100644 --- a/content/completion.go +++ b/content/completion.go @@ -95,4 +95,12 @@ type ( Extends []Type `protocol:"optional" json:",omitempty"` Implements []Type `protocol:"optional" json:",omitempty"` } + + Complete interface { + Complete(*CompleteArgs, *CompletionResult) error + } + + CompleteAt interface { + CompleteAt(*CompleteAtArgs, *CompletionResult) error + } ) diff --git a/content/error.go b/content/error.go deleted file mode 100644 index a7cc85e..0000000 --- a/content/error.go +++ /dev/null @@ -1,8 +0,0 @@ -package content - -// A common function for creating error responses. -func NewErrorResponse(s string) *Response { - r := NewResponse() - r.Data.Set("error", s) - return &r -} diff --git a/content/intent.go b/content/intent.go deleted file mode 100644 index 0050d26..0000000 --- a/content/intent.go +++ /dev/null @@ -1,91 +0,0 @@ -package content - -import ( - "errors" - "fmt" - "io" -) - -var handlers = map[string]Handler{} - -type ( - IntentHandler interface { - CanHandle(it *Intent) bool - Handle(it *Intent) *Response - } - // TODO(@dskinner): Replace Handler with IntentHandler or something else? - Handler func(io.Writer, Intent) - - Intent struct { - Version int64 - Operation string - Data Settings - } - - Response struct { - Version int64 `json:"version"` - Data Settings `json:"data"` - } - - Session struct { - Settings Settings - } -) - -func NewIntent(key string) (ret Intent) { - ret.Operation = key - ret.Data = *NewSettings() - return -} - -func NewResponse() (ret Response) { - ret.Data = *NewSettings() - return -} - -// Returns the Session object associated with this Intent or nil. -func (it *Intent) Session() *Session { - // TODO(someone): Intent.Session not implemented yet - if sessionid, ok := it.Data.Get("sessionid").(int); ok { - _ = sessionid // TODO actually look up session and return it - } - return nil -} - -// If a Session is associated with this Intent, then the Session's -// Settings is cloned and the intent's Settings are merged into the -// cloned Settings object. This to allow Intents to be "slim", i.e. -// only contain the keys of the specific settings it wants to override. -// -// If there's no Session nor Intent Settings, the empty Settings object -// is returned. -func (it *Intent) Settings() *Settings { - if session := it.Session(); session != nil { - set := session.Settings.Clone() - if settings, ok := it.Data.Get("settings").(Settings); ok { - set.Merge(&settings) - } - return set - } else if settings, ok := it.Data.Get("settings").(Settings); ok { - return &settings - } - return NewSettings() -} - -func AddHandler(name string, h Handler) { - if _, ok := handlers[name]; ok { - // immediately exit to prevent double-register which should - // come up during development & testing. - panic(fmt.Sprintf("Handler already registered for %s", name)) - } - handlers[name] = h -} - -func Handle(conn io.Writer, intent Intent) error { - if handler, ok := handlers[intent.Operation]; !ok { - return errors.New(fmt.Sprintf("Failed to locate handler for operation %s", intent.Operation)) - } else { - handler(conn, intent) - } - return nil -} diff --git a/content/json_test.go b/content/json_test.go index 6d18bb4..b9ee27b 100644 --- a/content/json_test.go +++ b/content/json_test.go @@ -69,44 +69,3 @@ func TestJson(t *testing.T) { t.Error("It shouldn't be possible to marshal a type that was registered as a struct, but is now a pointer to a struct") } } - -func TestJson2(t *testing.T) { - it := NewIntent("completion.complete.location") - it.Data.Set("location", SourceLocation{File{Name: "hello.cpp"}, 10, 2}) - it.Data.Set("sessionid", 1337) - settings := NewSettings() - settings.Set("compilation_options", []string{"-Iincludepath1", "-Iincludepath2"}) - it.Data.Set("settings", *settings) - - if d, err := json.MarshalIndent(it, "", "\t"); err != nil { - t.Error(err) - } else { - t.Log(string(d)) - } - - // obviously the proper action would be taken in the "real" code - if cmp, err := loadData(); err != nil { - t.Error(err) - } else { - cmp.Methods = cmp.Methods[:3] // Just to make the result smaller in this mock up use case - cmp.Fields = cmp.Fields[:3] // Just to make the result smaller in this mock up use case - r := NewResponse() - r.Data.Set("completions", cmp) - if d, err := json.MarshalIndent(r, "", "\t"); err != nil { - t.Error(err) - } else { - t.Log(string(d)) - } - } - - // and in the case of an error - { - r := NewResponse() - r.Data.Set("error", "something") // Could include a stacktrace etc - if d, err := json.MarshalIndent(r, "", "\t"); err != nil { - t.Error(err) - } else { - t.Log(string(d)) - } - } -} diff --git a/content/standard_intents.go b/content/standard_intents.go deleted file mode 100644 index 8f21e2d..0000000 --- a/content/standard_intents.go +++ /dev/null @@ -1,11 +0,0 @@ -package content - -var ( - // The CompleteFullyQualifiedName operation expects a "fqn" key in the intent's data field - // pointing to the FullyQualifiedName of the type we want to complete - CompleteFullyQualifiedName = "completion.complete.fqn" - - // The CompleteSourceLocation operation expects a "loc" key in the intent's data field - // pointing to the SourceLocation we want to complete - CompleteSourceLocation = "complete.loc" -) diff --git a/java/completion_intent_test.go b/java/completion_intent_test.go deleted file mode 100644 index fea8075..0000000 --- a/java/completion_intent_test.go +++ /dev/null @@ -1,24 +0,0 @@ -package java - -import ( - "github.com/quarnster/completion/content" - "testing" -) - -func TestCompletionIntent(t *testing.T) { - it := content.NewIntent("completion.complete.fqn") - it.Data.Set("fqn", content.FullyQualifiedName{Absolute: "java://type/java/util/jar/JarEntry"}) - c := CompletionHandler{} - if !c.CanHandle(&it) { - t.Fatal("Can't handle the intent") - } else { - r := c.Handle(&it) - if err, ok := r.Data.Get("error").(string); ok { - t.Error(err) - } else if cmp, ok := r.Data.Get("completions").(content.CompletionResult); !ok { - t.Error("No completions entry") - } else { - t.Log(cmp) - } - } -} diff --git a/java/completion_intent.go b/java/java.go similarity index 51% rename from java/completion_intent.go rename to java/java.go index 374e31f..7b773be 100644 --- a/java/completion_intent.go +++ b/java/java.go @@ -7,59 +7,38 @@ import ( "strings" ) -type CompletionHandler struct { +type Java struct { } func fqnToClassname(fqn content.FullyQualifiedName) Classname { return Classname(strings.Replace(fqn.Absolute[len("java://type/"):], "/", ".", -1)) } -func (c *CompletionHandler) Complete(archive Archive, fqn content.FullyQualifiedName) (cmp content.CompletionResult, err error) { - if data, err := archive.LoadClass(fqnToClassname(fqn)); err != nil { - return cmp, err - } else if class, err := NewClass(bytes.NewReader(data)); err != nil { - return cmp, err - } else if ct, err := class.ToContentType(); err != nil { - return cmp, err - } else { - // TODO: inherited fields and methods - cmp.Fields = ct.Fields - cmp.Types = ct.Types - cmp.Methods = ct.Methods - } - return -} - -func (c *CompletionHandler) Handle(it *content.Intent) *content.Response { +func (c *Java) Complete(args *content.CompleteArgs, cmp *content.CompletionResult) error { cp, err := DefaultClasspath() if err != nil { // We don't really care about not being able to get the default classpath as it could be provided manually by the user log4go.Warn("Couldn't get the default classpath: %s", err) } - settings := it.Settings() + settings := args.Settings() if cp2, ok := settings.Get("classpath").([]string); ok { // TODO: do we ever want to override rather than append to the classpath? cp = append(cp, cp2...) } // TODO: this should probably be cached in the session... if archive, err := NewCompositeArchive(cp); err != nil { - return content.NewErrorResponse(err.Error()) + return err + } else if data, err := archive.LoadClass(fqnToClassname(args.Location)); err != nil { + return err + } else if class, err := NewClass(bytes.NewReader(data)); err != nil { + return err + } else if ct, err := class.ToContentType(); err != nil { + return err } else { - if cmp, err := c.Complete(archive, it.Data.Get("fqn").(content.FullyQualifiedName)); err != nil { - return content.NewErrorResponse(err.Error()) - } else { - r := content.NewResponse() - r.Data.Set("completions", cmp) - return &r - } - } -} - -func (c *CompletionHandler) CanHandle(it *content.Intent) bool { - // TODO - if it.Operation != content.CompleteFullyQualifiedName { - return false + // TODO: inherited fields and methods + cmp.Fields = ct.Fields + cmp.Types = ct.Types + cmp.Methods = ct.Methods } - fqn, ok := it.Data.Get("fqn").(content.FullyQualifiedName) - return ok && strings.HasPrefix(fqn.Absolute, "java://type/") + return nil } diff --git a/java/java_test.go b/java/java_test.go new file mode 100644 index 0000000..702d782 --- /dev/null +++ b/java/java_test.go @@ -0,0 +1,18 @@ +package java + +import ( + "github.com/quarnster/completion/content" + "testing" +) + +func TestJavaComplete(t *testing.T) { + var a content.CompleteArgs + var cmp content.CompletionResult + a.Location.Absolute = "java://type/java/util/jar/JarEntry" + c := Java{} + if err := c.Complete(&a, &cmp); err != nil { + t.Error(err) + } else { + t.Log(cmp) + } +} diff --git a/main.go b/main.go deleted file mode 100644 index a69f72f..0000000 --- a/main.go +++ /dev/null @@ -1,17 +0,0 @@ -package main - -import ( - "flag" -) - -var ( - flagAddr = flag.String("addr", ":8888", "Local address to listen on.") -) - -func init() { - flag.Parse() -} - -func main() { - startServer() -} diff --git a/rpc/main.go b/rpc/main.go new file mode 100644 index 0000000..5bb03c9 --- /dev/null +++ b/rpc/main.go @@ -0,0 +1,53 @@ +package main + +import ( + "code.google.com/p/log4go" + "flag" + "github.com/quarnster/completion/clang" + "github.com/quarnster/completion/java" + "net" + "net/rpc" + "net/rpc/jsonrpc" + "runtime/debug" +) + +func main() { + var ( + port = ":12345" + ifs = []interface{}{ + &clang.Clang{}, + &java.Java{}, + } + ) + flag.StringVar(&port, "port", port, "TCP port the server will listen on") + flag.Parse() + + server := rpc.NewServer() + + for _, i := range ifs { + if err := server.Register(i); err != nil { + log4go.Crash(err) + } + } + + l, err := net.Listen("tcp", port) + if err != nil { + log4go.Crashf("Could not open port: %s", err) + } + for { + if conn, err := l.Accept(); err != nil { + log4go.Error("Error accepting connection: %s", err) + } else { + go func() { + codec := jsonrpc.NewServerCodec(conn) + defer func() { + codec.Close() + if r := recover(); r != nil { + log4go.Error("Recovered from panic: %v, stack: %s", r, string(debug.Stack())) + } + }() + server.ServeRequest(codec) + }() + } + } +} diff --git a/server.go b/server.go deleted file mode 100644 index e5a6b91..0000000 --- a/server.go +++ /dev/null @@ -1,72 +0,0 @@ -package main - -import ( - "bufio" - "encoding/json" - "fmt" - "github.com/quarnster/completion/content" - "log" - "net" - "strconv" - "time" -) - -var ( - ln net.Listener -) - -func handleConnection(conn net.Conn) { - defer conn.Close() - conn.SetReadDeadline(time.Now().Add(time.Second * 3)) - - r := bufio.NewReader(conn) - - size, err := r.ReadString(';') - if err != nil { - log.Println(err) - fmt.Fprintf(conn, "Failed to read content length: %v\n", err) - return - } - - size = size[:len(size)-1] - i, err := strconv.Atoi(size) - if err != nil { - log.Println(err) - fmt.Fprintf(conn, "Failed to get content length from %s: %v\n", size, err) - return - } - - b := make([]byte, i) - r.Read(b) - - intent := content.Intent{} - if err := json.Unmarshal(b, &intent); err != nil { - log.Println(err) - fmt.Fprintf(conn, "Failed to decode request: %v", err) - } - - content.Handle(conn, intent) -} - -func listen() { - l, err := net.Listen("tcp", *flagAddr) - if err != nil { - log.Fatal(err) - } - ln = l -} - -func accept() { - for { - if conn, err := ln.Accept(); err != nil { - log.Print(err) - } else { - go handleConnection(conn) - } - } -} - -func startServer() { - listen() - accept() -} diff --git a/server_test.go b/server_test.go deleted file mode 100644 index c580795..0000000 --- a/server_test.go +++ /dev/null @@ -1,52 +0,0 @@ -package main - -import ( - "encoding/json" - "fmt" - "github.com/quarnster/completion/content" - "io" - "io/ioutil" - "log" - "net" - "testing" -) - -func init() { - listen() - go accept() - - content.AddHandler("test", handler) -} - -func send(s string) (string, error) { - conn, err := net.Dial("tcp", ":8888") - if err != nil { - return "", err - } - fmt.Fprintf(conn, "%v;%s", len(s), s) - if reply, err := ioutil.ReadAll(conn); err != nil { - return "", err - } else { - return string(reply), nil - } -} - -func handler(w io.Writer, intent content.Intent) { - log.Println(intent) - r := content.NewResponse() - if b, err := json.Marshal(r); err != nil { - log.Println(err) - fmt.Fprintf(w, "Failed to create response: %v", err) - } else { - w.Write(b) - } -} - -func TestHandler(t *testing.T) { - s := `{"version": 1, "operation": "test"}` - if reply, err := send(s); err != nil { - t.Fatal(err) - } else { - log.Println(reply) - } -}