sourcecamembert::TextView.fan

//
// Copyright (c) 2012, Brian Frank
// Licensed under the Academic Free License version 3.0
//
// History:
//   25 Apr 12  Brian Frank  Creation
//

using gfx
using fwt
using syntax
using petanque

**
** TextView
**
class TextView : View
{
  new make(Frame frame, File file) : super(frame, file)
  {
    this.fileTimeAtLoad = file.modified

    // read document into memory, if we fail with the
    // configured charset, then fallback to ISO 8859-1
    // which will always "work" since it is byte based
    lines := readAllLines
    if (lines == null)
    {
      this.charset = Charset.fromStr("ISO-8859-1")
      lines = readAllLines
    }

    // get rules for ext or first line
    rules := SyntaxRules.loadForFile(file, lines.first)
    if (rules == null) rules = SyntaxRules {}

    // construct and load editor
    editor = Editor
    {
      it.rules = rules
      it.options.bg = Sys.cur.theme.edBg
      it.options.font = Sys.cur.theme.edFont
      it.options.bgCurLine = Sys.cur.theme.edCurLineBg
      it.options.highlight = Sys.cur.theme.edSelectBg
      it.options.showCols = Sys.cur.theme.edCols
      it.options.showColColor = Sys.cur.theme.edColsColor
      it.options.bracket = Sys.cur.theme.edBracket
      it.options.bracketMatch = Sys.cur.theme.edBracketMatch
      it.options.comment = Sys.cur.theme.edComment
      it.options.keyword = Sys.cur.theme.edKeyword
      it.options.numLiteral = Sys.cur.theme.edNum
      it.options.strLiteral = Sys.cur.theme.edStr
      it.options.text = Sys.cur.theme.edText
      it.options.lineNumberColor = Sys.cur.theme.lineNumberColor
      it.options.caretColor = Sys.cur.theme.caretColor
      it.options.scrollFg  = Sys.cur.theme.scrollFg
      it.options.scrollBg  = Sys.cur.theme.scrollBg
      it.gutterColor =  Sys.cur.theme.scrollBg
      it.thumbColor = Sys.cur.theme.scrollFg
    }
    editor.onFocus.add |e| { onFocusCheckFileTime }
    editor.onModify.add |e| { this.dirty = true }
    editor.onCaret.add |e| { frame.updateStatus }
    editor.loadLines(lines)
    editor.onKeyDown.add |e|
    {
      if (!e.consumed) frame.trapKeyDown(e)
      if (!e.consumed) editorKeyDown(e)
    }

    this.content = editor
  }

  private Str[]? readAllLines()
  {
    in := file.in { it.charset = this.charset }
    try
      return in.readAllLines
    catch
      return null
    finally
      in.close
  }

  Editor editor

  ** Current selected string or empty
  override Str curSelection()
  {
    span := editor.selection
    if (span == null) return ""
    return editor.textForSpan(span)
  }

  override Pos curPos() { editor.caret }

  override Void onReady() { editor.focus }

  override Str curStatus()
  {
    pos := curPos
    return "$charset   ${pos.line+1}:${pos.col+1}"
  }


  override Void onGoto(Item item)
  {
    editor.selection = item.loc.span
    editor.goto(item.pos)
    editor.focus
  }

  override Void onSave()
  {
    out := file.out
    try
      editor.save(out)
    finally
      out.close
    fileTimeAtLoad = file.modified
  }

  private Void onFocusCheckFileTime ()
  {
    if (file.modified == fileTimeAtLoad) return
    fileTimeAtLoad = file.modified

    // prompt user to reload
    r := Dialog.openQuestion(editor.window,
          "Another application has updated file:
           $file.osPath
           Reload it?", Dialog.yesNo)
    if (r == Dialog.yes) frame.curSpace.refresh
  }

  private Void editorKeyDown(Event event)
  {
    if (editor.ro) return

    switch (event.key.toStr)
    {
      case Sys.cur.shortcuts.insertCommentSection : event.consume; insertSection
      case Sys.cur.shortcuts.toggleComment        : event.consume; toggleCommentBlock
    }
  }

  private Void toggleCommentBlock()
  {
    curLine := editor.caret.line
    lines := curLine..curLine
    sel := editor.selection
    if (sel != null)
    {
      if (sel.end.line > sel.start.line && sel.end.col == 0)
        lines = sel.start.line .. sel.end.line-1
      else
        lines = sel.start.line .. sel.end.line
    }
    lines.each |linei|
    {
      line := editor.line(linei)
      if (line.startsWith("// "))
        editor.modify(Span(linei, 0, linei, 3), "")
      else
        editor.modify(Pos(linei, 0).toSpan, "// ")
    }
  }

  private Void insertSection()
  {
    linei := editor.caret.line

    previ := linei-1
    while (previ > 0 && editor.line(previ).trim.isEmpty) previ--
    prev := editor.line(previ)

    nexti := linei
    while (nexti < editor.lineCount && editor.line(nexti).trim.isEmpty) nexti++

    char := prev.startsWith("}") ? "*" : "/"

    s := StrBuf()
    gotoi := linei+1
    if (previ == linei - 1) { s.add("\n"); gotoi++ }
    74.times { s.add(char) }
    s.add("\n").add(char).add(char).add(" ").add("\n")
    74.times { s.add(char) }
    s.add("\n")
    if (nexti == linei) s.add("\n")

    editor.modify(Pos(linei, 0).toSpan, s.toStr)
    editor.goto(Pos(gotoi, 3))
  }

  const Charset charset := Charset.utf8
  private DateTime fileTimeAtLoad

}