Skip to content
Chris McGee edited this page Feb 17, 2014 · 11 revisions

Introduction

With each sprint GoDev has gained new features making it a richer IDE. For the most part these features have made use of existing tools written by others (e.g. godef, oracle, gocode, godbg). In many cases the authors of these tools intend for them to be plugged into text editors such as vim and emacs by providing configuration files that the user can install. This extensibility allows third parties to integrate interesting features independently.

Moving forward, one of the concerns with GoDev is that the standard install becomes too bloated with features making it harder to understand, slower to run and more complicated to navigate. Extensions allow the user to choose what features they care about and install only those pieces. GoDev should retain a basic set of functionality that most users will expect.

Requirements

  1. Extensions shall be trivial to install
  2. Extensions shall be easy to write (documented, examples provided, minimal boilerplate)

Design

Installing a new extension to GoDev should be like adding any other Go library using "go get." Go developers quickly become accustomed to getting libraries and Go commands this way. It gives the user the code in case they want to read, tweak or customize it. It works with different VCS systems (git, hg, svn). Finally, it can calculate dependency closures to retrieve everything you need. This is an obvious choice to retrieve GoDev extensions.

$ go get github.com/sirnewton01/mygodev-extension

Features in GoDev usually have two aspects to them. There is a UI that is plugged in via Orion services (page, edit command) leveraging browser-side capabilities in HTML and Javascript. There is an http service written in Go to leverage such facilities as the Go standard library, third party Go libraries or a command-line tool (maybe a Go command, maybe something like git or hg). The UI portion of the extension often calls back to the http service using XMLHttpRequest or similar. Any design will need to support extensions that have either aspect or both.

UI Bundle

For the UI portion, GoDev is built on top of the Eclipse Orion project (http://www.eclipse.org/orion/). There are well-defined extensions to allow you to contribute new UI's. For example, there is a way to plug in your own "page," which appears in the top-left menu. See the "Go Doc" and "Debug" pages for examples. Also, there are "edit commands" to contribute buttons on the top of the editor. The "Go Doc" and "Format" buttons are examples of edit commands. The GoDev server provides a special JSON object at a predefined location to tell Orion what extension should be loaded in the browser. GoDev actively scans your GOPATH looking for godev extensions and provides up-to-date list of extensions to the browser. With this mechanism in place you can simply "go get" a new extension and reload your web browser page to see the new pages and buttons.

src/github.com/sirnewton01/mygodev-extension
                                             /godev-bundle
                                                           mygodev-extension/
                                                                             bundle.html
                                                                             bundle.js
                                                                             coolhtmlpage.html

The file structure above represents a typical Go package except it has some special files and folders. The "godev-bundle" folder is there to tell the godev server that this is a UI bundle. The "mygodev-extension" folder underneath provides a unique namespace to distinguish web resources in your bundle from others. The bundle.html and bundle.js are known to the godev server to be your UI bundle's initializers, which declare the pages, edit commands and Orion extension that you want to define (see http://wiki.eclipse.org/Orion/Documentation/Developer_Guide/Plugging_into_the_editor and http://wiki.eclipse.org/Orion/Documentation/Developer_Guide/Plugging_into_Orion_pages). Finally, you can put whatever web content you want inside your "mygodev-extension" folder such as HTML pages, images, css, javascript. Your bundle will reference these things using the URL prefix "/mygodev-extension" (e.g. "/mygodev-extension/coolhtmlpage.html").

Service Bundle

Ideally, extensions would only require UI bundles as above. Go get will complain that there are no Go sources to be compiled but then you can always put a dummy.go file in your project, which should squelch the error. It is often the case that you want to integrate your UI with something that is only available outside of the web browser such as your own Go code, modules in the Go standard library, third party libraries or some system command. To bridge the gap between your HTML UI and the browser you need an http service.

For better or for worse, Go has no dynamic linking built into the language. As a result of this, godev can't dynamically load a service and register it with its http server without statically importing, recompiling and restarting. It will be impossible to do all of this within a simple "go get." Restarting godev currently forces you to re-authenticate, which is not great for the workflow either.

If Go does not support dynamic linking then an alternative is to use a form of RPC (Remote Process Communication). One platform independent way of doing this is to launch the program and establish a TCP port to communicate. Since godev will control the lifecycle of the service we could launch it and use stdin/stdout. We could make stdin and stdout send a dump of http requests and responses. In other words, we are essentially re-inventing CGI (Common Gateway Interface) from the days of yore. So, why not use CGI itself? It turns out that Go has a standard library for both client and server.

GoDev registers a handler for any requests with the "/go/bundle-cgi" prefix, take the command name in the third segment (e.g. "mygodev-extension" in "/go/bundle-cgi/mygodev-extension") and search for it in the GOPATH. If it finds it, it will run the command with the "-godev" argument and begin a CGI protocol with your command. The "-godev" argument is provided in case your command is not just a godev extension but also serves other purposes. The idea is that any command-line tool could optionally hook into GoDev as an extension.

import (
    "fmt"
    "net/http"
    "net/http/cgi"
    "strings"
)

func main() {
    // TODO Check for the "-godev" parameter
    req, err := cgi.Request()

    if err != nil {
        fmt.Printf("Error trying to set up the CGI request", err.Error())
        return
    }

    if strings.HasSuffix(req.URL.Path, "/greeting") {
        fmt.Printf("Status: 200 OK\r\n\r\n{Greeting: 'Hello World!'}")
    } else {
        fmt.Printf("Status: 404 Unrecognized request\r\n\r\n")
    }
}

CGI does have its limitations. Every time you make a request the CGI routine will launch the Go command, which has some overhead. Luckily, Go programs start very quickly. We don't anticipate that GoDev extension services will be called rapidly in the UI since it is a single user environment at the moment. Extensions for a multi-user environment will have different requirements and may need a very different design.

Accessing GoDev services

At times extensions need to be able to interact with the base GoDev services. For example, an extension can provide a link so that the user can click to open the file in the GoDev editor. In other cases, the package view or the godoc view would be opened. Much of GoDev and its services use logical paths, which are described in the next section.

Logical paths

GoDev provides a uniform view of the source code in your GOPATHs and GOROOT. It does this in order to filter out files that are either not Go related or not source code (e.g. /home/cmcgee/Downloads). Paths always use the '/' separator in order to make them friendlier in URL's.

As an example, file bar.txt with an OS path "$GOPATH/src/foo/bar.txt" will have a logical path "/foo/bar.txt." If you are using Windows and bar.txt is in "$GOPATH\src\test\bar.txt" the logical path is "/test/bar.txt." GOPATH can contain multiple directories on your system. The same rules apply to paths in any of your GOPATHs.

You might be wondering what happens with source code in the GOROOT. Godev's design anticipates most of your development activity to be in your GOPATHs. Go has such a rich set of packages that they easily overrun the top levels of the navigator making it difficult to find your source code. The result is that any files in your GOROOT have a special prefix "/GOROOT." For example, the bufio package has the logical path "/GOROOT/bufio."

Editor URLs

If your extension needs to navigate and/or provide hyperlinks to the editor on a file or folder you can construct a logical path and use it in the fragment of the edit page. Open a file with a logical path "/test/foo.txt" using relative URL "/edit/edit.html#/file/test/foo.txt." If you want to navigate directly to a particular line in the file you can add the ",line=" parameter to the end like this: "/edit/edit.html#/file/test/foo.txt,line=254."

Although, folders are not normally edited like a file you can use the same kinds of URLs to open up the folder view for a Go package. Opening the folder view for the bufio package is done with the URL "/edit/edit.html#/file/GOROOT/bufio." The line number parameter doesn't work on folders.

Next steps

Over the next few sprints GoDev will be restructuring itself to pull out some of the current features into separate github projects. This will help with the current bloat as well as provide examples of how to write extensions. Where possible, we will attempt to provide pull requests to other tool authors to add integrations.

GoDev has built-in javascript, html and css editing capabilities making it a good choice for writing extensions.