Skip to content

Commit

Permalink
golang: add codeintel cix support
Browse files Browse the repository at this point in the history
  • Loading branch information
Todd Whiteman committed Oct 8, 2014
1 parent 560c2a7 commit 8ded651
Show file tree
Hide file tree
Showing 2 changed files with 277 additions and 35 deletions.
204 changes: 204 additions & 0 deletions golib/outline.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,204 @@
// Copyright 2013 Chris McGee <[email protected]>. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// Modified by Todd Whiteman <[email protected]> for Komodo purposes.

package main

import (
"os"
"fmt"
"time"
"reflect"
"go/ast"
"go/parser"
"go/token"
"encoding/xml"
"path/filepath"
)

type Node interface {
}

type Import struct {
XMLName xml.Name `xml:"import"`
Module string `xml:"module,attr"`
Name string `xml:"name,attr,omitempty"`
Line int `xml:"line,attr,omitempty"`
LineEnd int `xml:"lineend,attr,omitempty"`
}

type Variable struct {
XMLName xml.Name `xml:"variable"`
Name string `xml:"name,attr"`
Citdl string `xml:"citdl,attr"`
Line int `xml:"line,attr,omitempty"`
LineEnd int `xml:"lineend,attr,omitempty"`
}

type Scope struct {
XMLName xml.Name `xml:"scope"`
Classrefs string `xml:"classrefs,attr,omitempty"`
Ilk string `xml:"ilk,attr"`
Name string `xml:"name,attr"`
Lang string `xml:"lang,attr,omitempty"`
Signature string `xml:"signature,attr,omitempty"`
Line int `xml:"line,attr,omitempty"`
LineEnd int `xml:"lineend,attr,omitempty"`
Nodes []Node
}

type File struct {
XMLName xml.Name `xml:"file"`
Lang string `xml:"lang,attr"`
Path string `xml:"path,attr"`
Mtime int64 `xml:"mtime,attr"`
FileScope *Scope
currentClass *Scope
}

func outlineHandler(path string) File {
now := time.Now()
outline := File{Lang: "Go", Path: path, Mtime: now.Unix()}
filescope := Scope{Lang: "Go", Name: filepath.Base(path), Ilk: "blob"}
outline.FileScope = &filescope
fileset := token.NewFileSet()
file, err := parser.ParseFile(fileset, path, nil, 0)

if err != nil {
fmt.Println("Error parsing go source:", err)
return outline
}

ast.Inspect(file, func(n ast.Node) bool {
switch x := n.(type) {
case *ast.FuncDecl:
if x.Pos().IsValid() {
line := fileset.Position(x.Pos()).Line
lineend := fileset.Position(x.End()).Line
name := x.Name.Name
if name != "" {
signature := name
if x.Recv.NumFields() > 0 {
signature = signature + "(" + fileListStr(x.Recv) + ") "
}

signature = signature + name + "("
if x.Type.Params.NumFields() > 0 {
signature = signature + fileListStr(x.Type.Params)
}
signature = signature + ")"

if x.Type.Results.NumFields() > 0 {
if x.Type.Results.NumFields() == 1 {
signature = signature + " " + fileListStr(x.Type.Results)
} else {
signature = signature + " (" + fileListStr(x.Type.Results) + ")"
}
}

filescope.Nodes = append(filescope.Nodes, Scope{Ilk: "function", Name: name, Line: line, LineEnd: lineend, Signature: signature})
}
}
case *ast.StructType:
scope := outline.currentClass
for _, field := range x.Fields.List {
if (field.Names != nil) {
line := fileset.Position(field.Pos()).Line
name := string(field.Names[0].Name)
citdl := typeStr(field.Type)
scope.Nodes = append(scope.Nodes, Variable{Name: name, Citdl: citdl, Line: line})
}
}
case *ast.GenDecl:
if x.Tok == token.TYPE {
for _, spec := range x.Specs {
if spec.Pos().IsValid() {
typeSpec, ok := spec.(*ast.TypeSpec)
if ok {
line := fileset.Position(spec.Pos()).Line
lineend := fileset.Position(spec.End()).Line
name := typeSpec.Name.Name
outline.currentClass = &(Scope{Ilk: "class", Line: line, LineEnd: lineend, Name: name})
filescope.Nodes = append(filescope.Nodes, outline.currentClass)
}
}
}
} else if x.Tok == token.IMPORT {
for _, spec := range x.Specs {
if spec.Pos().IsValid() {
importSpec, ok := spec.(*ast.ImportSpec)
if ok {
line := fileset.Position(spec.Pos()).Line
name := ""
if (importSpec.Name != nil) {
name = importSpec.Name.Name
}
path := importSpec.Path.Value[1:len(importSpec.Path.Value)-1]
filescope.Nodes = append(filescope.Nodes, Import{Line: line, Name: name, Module: path})
}
}
}
//} else if x.Tok == token.CONST {
// line := strconv.FormatInt(int64(fileset.Position(x.Pos()).Line), 10)
// label := "CONST"
//filescope.Nodes = append(filescope.Nodes, Variable{Line: line, Citdl: citdl})
//filescope.Nodes = append(filescope.Nodes, Entry{Line: line, Label: label})
}
}
return true
})

return outline
}

func fileListStr(t *ast.FieldList) string {
label := ""

for argIdx, arg := range t.List {
for nameIdx, name := range arg.Names {
label = label + name.Name

if nameIdx != len(arg.Names)-1 {
label = label + ","
}
}

if len(arg.Names) != 0 {
label = label + " "
}

label = label + typeStr(arg.Type)

if argIdx != len(t.List)-1 {
label = label + ", "
}
}

return label
}

func typeStr(t ast.Expr) string {
switch e := t.(type) {
case *ast.StarExpr:
return "*" + typeStr(e.X)
case *ast.Ident:
return e.Name
case *ast.SelectorExpr:
return typeStr(e.X) + "." + e.Sel.Name
case *ast.ArrayType:
return "[]" + typeStr(e.Elt)
default:
return "<" + reflect.TypeOf(t).String() + ">"
}
}

func main() {
outline := outlineHandler(os.Args[1])
output, err := xml.MarshalIndent(outline, " ", " ")
if err != nil {
fmt.Printf("error: %v\n", err)
}
fmt.Println(string(output))
}
108 changes: 73 additions & 35 deletions pylib/lang_go.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,15 @@
import logging
import process
import time

try:
from zope.cachedescriptors.property import LazyClassAttribute
except ImportError:
import warnings
warnings.warn("Unable to import zope.cachedescriptors.property")
# Fallback to regular properties.
LazyClassAttribute = property

import ciElementTree as ET
import which

Expand All @@ -16,8 +25,17 @@
from SilverCity import ScintillaConstants
from codeintel2.accessor import AccessorCache
from codeintel2.citadel import CitadelLangIntel, CitadelBuffer
from codeintel2.common import Trigger, TRG_FORM_CALLTIP, TRG_FORM_CPLN, CILEDriver, Definition
from codeintel2.common import Trigger, TRG_FORM_CALLTIP, TRG_FORM_CPLN, CILEDriver, Definition, CodeIntelError
from codeintel2.langintel import ParenStyleCalltipIntelMixin, ProgLangTriggerIntelMixin, PythonCITDLExtractorMixin
from codeintel2.udl import UDLBuffer
from codeintel2.tree import tree_from_cix


try:
from xpcom.server import UnwrapObject
_xpcom_ = True
except ImportError:
_xpcom_ = False


#---- globals
Expand Down Expand Up @@ -166,16 +184,12 @@ def lookup_defn(self, buf, trg, ctlr):
godef_path = 'godef'
cmd = [godef_path, '-i=true', '-t=true', '-f=%s' % buf.path, '-o=%s' % trg.pos]
log.debug("running [%s]", cmd)
try:
p = process.ProcessOpen(cmd, env=env.get_all_envvars())
except OSError, e:
log.error("Error executing '%s': %s", cmd[0], e)
return
p = process.ProcessOpen(cmd, env=buf.env.get_all_envvars())

output, error = p.communicate(buf.accessor.text)
if error:
log.debug("'godef' (%s) stderr: [%s]", godef_path, error)
return
log.debug("'gocode' stderr: [%s]", error)
raise CodeIntelError(error)

lines = output.splitlines()
log.debug(output)
Expand Down Expand Up @@ -313,6 +327,37 @@ def trg_from_pos(self, pos, implicit=True):

class GoCILEDriver(CILEDriver):
lang = lang
_gooutline_executable_and_error = None

@LazyClassAttribute
def golib_dir(self):
ext_path = os.path.abspath(os.path.dirname(os.path.dirname(__file__)))
return os.path.join(ext_path, "golib")

def compile_gooutline(self, env=None):
if self._gooutline_executable_and_error is None:
self._gooutline_executable_and_error = (None, "Unknown Error")
outline_src = os.path.join(self.golib_dir, "outline.go")
# XXX - "go" should be an interpreter preference.
cmd = ["go", "build", outline_src]
cwd = self.golib_dir
try:
# Compile the executable.
p = process.ProcessOpen(cmd, cwd=cwd, env=env, stdin=None)
output, error = p.communicate()
if error:
log.warn("'%s' stderr: [%s]", cmd, error)
outline_exe = outline_src.rstrip(".go")
if sys.platform.startswith("win"):
outline_exe += ".exe"
# Remember the executable.
self._gooutline_executable_and_error = (outline_exe, None)
except Exception, ex:
error_message = "Unable to compile 'outline.go'" + str(ex)
self._gooutline_executable_and_error = (None, error_message)
if self._gooutline_executable_and_error[0]:
return self._gooutline_executable_and_error[0]
raise CodeIntelError(self._gooutline_executable_and_error[1])

def scan_purelang(self, buf, mtime=None, lang="Go"):
"""Scan the given GoBuffer return an ElementTree (conforming
Expand Down Expand Up @@ -342,34 +387,27 @@ def scan_purelang(self, buf, mtime=None, lang="Go"):
else:
path = buf.path

tree = ET.Element("codeintel", version="2.0",
xmlns="urn:activestate:cix:2.0")
file = ET.SubElement(tree, "file", lang=lang, mtime=str(mtime))
blob = ET.SubElement(file, "scope", ilk="blob", lang=lang,
name=os.path.basename(path))

#TODO:
# - A 'package' -> 'blob'. Problem is a single go package can be from
# multiple files... so really would want `lib.get_blobs(name)` instead
# of `lib.get_blob(name)` in the codeintel API. How does Ruby deal with
# this? Perl?
# - How do the multi-platform stdlib syscall_linux.go et al fit together?

# Dev Note:
# This is where you process the Go content and add CIX elements
# to 'blob' as per the CIX schema (cix-2.0.rng). Use the
# "buf.accessor" API (see class Accessor in codeintel2.accessor) to
# analyze. For example:
# - A token stream of the content is available via:
# buf.accessor.gen_tokens()
# Use the "codeintel html -b <example-Go-file>" command as
# a debugging tool.
# - "buf.accessor.text" is the whole content of the file. If you have
# a separate tokenizer/scanner tool for Go content, you may
# want to use it.

return tree
env = buf.env.get_all_envvars()
try:
gooutline_exe_path = self.compile_gooutline(env)
except Exception, e:
log.error("Error compiling outline: %s", e)
raise

cmd = [gooutline_exe_path, buf.path]
log.debug("running [%s]", cmd)
try:
p = process.ProcessOpen(cmd, env=env)
except OSError, e:
log.error("Error executing '%s': %s", cmd, e)
return

output, error = p.communicate()
if error:
log.warn("'%s' stderr: [%s]", cmd[0], error)

xml = '<codeintel version="2.0">\n' + output + '</codeintel>'
return tree_from_cix(xml)


#---- registration
Expand Down

0 comments on commit 8ded651

Please sign in to comment.