sourcecamGoPlugin::GoPlugin.fan

// History:
//  Jan 30 13 tcolar Creation
//

using camembert
using gfx
using fwt
using util
using syntax
using netColarUtils

**
** GoPlugin
**
const class GoPlugin : BasicPlugin
{
  static const Str _name := "Go"
  const GoDocs docProv := GoDocs()
  const GoCommands cmds

  override const Image icon := Image(`fan://camGoPlugin/res/go.png`)
  override const Str name := _name
  override PluginCommands? commands() {cmds}
  override PluginDocs? docProvider() {docProv}
  override Bool isIndexing() {docProv.isIndexing.val}
  override Type? envType() {GoEnv#}

  const GoFmtCmd fmtCmd

  new make()
  {
    cmds = GoCommands(this)
    syntax := Pod.of(this).file(`/res/syntax-go.fog`)
    addSyntaxRule("go", syntax, ["go"])
    fmtCmd = GoFmtCmd(this)
  }

  override Bool isProject(File dir)
  {
    return isCustomPrj(dir, "Go")
  }

  override Str prjName(File prjDir)
  {
    return prjDir.name
  }

  override Void onFrameReady(Frame frame, Bool initial := true)
  {
    if(initial)
    {
      super.onFrameReady(frame, initial)
      plugins := (frame.menuBar as MenuBar).plugins
      menu := plugins.children.find{it->text == _name}
      menu.add(MenuItem{ it.command = GoIndexCmd(this).asCommand })
      menu.add(MenuItem{ it.command = GoFmtCmd(this).asCommand })
    }
  }

  override Space createSpace(Project prj)
  {
    return GoSpace(Sys.cur.frame, prj.dir.toFile, this.typeof.pod.name, icon.file.uri)
  }

  override Void onFileSaved(File f) {
    e := Event()
    e.data = f
    fmtCmd.invoke(e)
  }

  override Void onInit(File configDir)
  {
    // create go template if not there yet
    go := configDir + `templates/go_file.json`
    if( ! go.exists)
      JsonUtils.save(go.out, Template{it.name="Go file"
        it.extensions=["go"]
        it.text="// History: {date} {user} Creation\n\npackage {folder}\n\nimport ()\n\n"})
  }
}

// Go format command
const class GoFmtCmd : Cmd
{
  const GoPlugin plugin

  new make(GoPlugin plugin)
  {
    this.plugin = plugin
  }

  override const Str name := "GoFmt on current file."
  override Void invoke(Event event)
  {
    f := event.data as File
    if(f == null)
    {
      // this is when called manually from the menu rater than automatically on save
      f = frame.curFile
      // Save the file first before calling goFmt
      frame.save()
    }
    if(!f.exists || f.ext != "go")
      return
    config := PluginManager.cur.conf(GoPlugin._name) as BasicConfig
    if(config == null)
      return
    env := config.curEnv as GoEnv
    if(env == null || ! env.goFmtOnSave)
      return
    distro := env.envHome.toFile
    if( ! distro.exists)
      return
    goFmt := distro + `./bin/gofmt`
    if( ! goFmt.exists)
      return

    opts := env.goFmtOpts
    if(opts.isEmpty)
      opts = [goFmt.osPath, "-w", "{{file}}"] // default
    options := Str[goFmt.osPath]
    opts.each
    {
      options.add(it.replace("{{file}}", f.name))
    }

    // Remember the current location in file
    // Note that it might become "invalid" depending what gofmt does
    item := frame.curSpace.curFileItem

    frame.console.log("Running " + options)
    p := Process(options, f.parent)
    id := Sys.cur.processManager.register(p, "GoFmt")
    try
      p.run().join()
    finally
      Sys.cur.processManager.unregister(id)
    frame.curSpace.refresh
    frame.curView.onGoto(item)
  }
}

const class GoIndexCmd : Cmd
{
  override const Str name := "Re-Index docs"
  const GoPlugin plugin

  new make(GoPlugin plugin)
  {
    this.plugin = plugin
  }

  override Void invoke(Event event)
  {
    plugin.docProv.index
  }
}

const class GoCommands : PluginCommands
{
  override const Cmd run
  override const Cmd runSingle
  override const Cmd test
  override const Cmd testSingle
  override const Cmd build
  override const Cmd buildAndRun
  override const Cmd buildAndRunSingle

  new make(GoPlugin plugin)
  {
    go := "{{env_home}}/bin/go"

    build       = BasicPluginCmd(plugin, "Build", [go, "build"],
                                 ExecCmdInteractive.onetime, goFinder)
    run         = BasicPluginCmd(plugin, "Run", [go, "run"],
                                 ExecCmdInteractive.onetime, goFinder)
    runSingle   = BasicPluginCmd(plugin, "RunSingle", [go, "run", "{{cur_file}}"],
                                 ExecCmdInteractive.always, goFinder)
    test        = BasicPluginCmd(plugin, "Test", ["go", "test"],
                                 ExecCmdInteractive.onetime, goFinder)
    testSingle  = BasicPluginCmd(plugin, "TestSingle", [go, "test", "{{cur_file}}"],
                                 ExecCmdInteractive.always, goFinder)
    buildAndRun = BasicBuildAndRunCmd(plugin)
    buildAndRunSingle = BasicBuildAndRunSingleCmd(plugin)
  }

  static File? goPath()
  {
    config := PluginManager.cur.conf("Go") as BasicConfig
    if(config == null) return null
    env := config.curEnv as GoEnv
    if(env == null) return null
    goPath := env.goPath.toFile
    if( ! goPath.exists) return null
    return goPath
  }

  static File? prj()
  {
    plugin := (GoPlugin) PluginManager.cur.plugins["camGoPlugin"]
    return plugin.findProject(Sys.cur.frame.curFile)
  }

  ** Error:
  **    ./hello.go:7: undefined: dsfdfgd
  ** Log:
  **    2013/10/09 14:50:17 drupsway_test.go:75: a:1:{s:9:"cc_number";b:0;}
  ** Relative path sometimes, absolute paths other times
  static const |Str -> Item?| goFinder := |Str str -> Item?|
  {
    startAt := 0
    error := true
    if(str.size < 4) return null
    if(str.size > 20 && str[4]=='/' && str[7] == '/' && str[13]==':' && str[16]==':')
    {
      startAt = 20
      error = false
    }
    p1 := str.index(":", startAt); if (p1 == null) return null
    c  := str.index(":", p1 + 1); if (c == null) return null
    // Try absolute path
    path := Uri(str[startAt ..< p1].trim)
    file := path.toFile
    if(! file.exists){
    // Try relative to goPath
      file = goPath + path
    }
    if(! file.exists){
      // Try relative to project
      file = prj + path
    }
    if(! file.exists)
      return null
    pos := str[p1 + 1 ..< c]
    line := pos.toInt(10, false) ?: 1
    text := str
    icon := error ? Sys.cur.theme.iconErr: Sys.cur.theme.iconMark
    return FileItem.makeFile(file).setDis(text).setLoc(
          ItemLoc{it.line = line-1; it.col  = col-1}).setIcon(
          icon)
  }
}