Editing Ledger files with Sublime Text or RubyMine

Jan M. Faber edited this page Apr 16, 2015 · 1 revision

This is a plugin to do syntax highlighting for Ledger files on the Sublime Text editor. It is a TextMate-style syntax file so it should work on all editors that understand that: TextMate, Sublime Text and RubyMine all work.

These files are written in XML, but it's easier to write them in YAML as described on this page and use a plugin to translate them to XML.

If you don't want to modify the plugin then you can just download the compiled XML and save it as Ledger.tmLanguage, drop it into the Packages/User directory and then restart the editor. On my Mac, that directory is located here: ~/Library/Application Support/Sublime Text 3/Packages/User. If you want to make changes to the plugin then you can find the YAML from which the plugin is generated below.

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
  <dict>
    <key>fileTypes</key>
    <array>
      <string>ldg</string>
      <string>ledger</string>
      <string>ldgr</string>
    </array>
    <key>name</key>
    <string>Ledger</string>
    <key>patterns</key>
    <array>
      <dict>
        <key>captures</key>
        <dict>
          <key>1</key>
          <dict>
            <key>name</key>
            <string>comment.line</string>
          </dict>
        </dict>
        <key>match</key>
        <string>^(([;#|*%]).*)$</string>
      </dict>
      <dict>
        <key>captures</key>
        <dict>
          <key>1</key>
          <dict>
            <key>name</key>
            <string>string.other.date</string>
          </dict>
          <key>2</key>
          <dict>
            <key>name</key>
            <string>punctuation.separator</string>
          </dict>
          <key>3</key>
          <dict>
            <key>name</key>
            <string>string.other.edate</string>
          </dict>
          <key>4</key>
          <dict>
            <key>name</key>
            <string>constant.other</string>
          </dict>
          <key>5</key>
          <dict>
            <key>name</key>
            <string>comment</string>
          </dict>
          <key>6</key>
          <dict>
            <key>name</key>
            <string>markup.quote</string>
          </dict>
          <key>7</key>
          <dict>
            <key>name</key>
            <string>comment</string>
          </dict>
        </dict>
        <key>match</key>
        <string>^(\d{4}[/-]\d{2}[/-]\d{2})(=)?(\d{4}[/-]\d{2}[/-]\d{2})?(\s+[*!])?(\s+\([^)]+\))?\s+([^;]+)((?:\t|\s\s);.*)?$</string>
      </dict>
      <dict>
        <key>captures</key>
        <dict>
          <key>1</key>
          <dict>
            <key>name</key>
            <string>keyword.other</string>
          </dict>
        </dict>
        <key>match</key>
        <string>^\s+(note|alias|payee|check|assert|eval|default)(?:\s.*)?$</string>
      </dict>
      <dict>
        <key>begin</key>
        <string>^\s+([()\[\]\w\:\s_-]+)\s+(=\s*)?([^;@]+)?(\s*@[^;]+)?</string>
        <key>beginCaptures</key>
        <dict>
          <key>1</key>
          <dict>
            <key>name</key>
            <string>markup.bold.account</string>
          </dict>
          <key>2</key>
          <dict>
            <key>name</key>
            <string>markup.heading</string>
          </dict>
          <key>3</key>
          <dict>
            <key>name</key>
            <string>variable.other.amount</string>
          </dict>
          <key>4</key>
          <dict>
            <key>name</key>
            <string>markup.quote</string>
          </dict>
        </dict>
        <key>end</key>
        <string>$</string>
        <key>patterns</key>
        <array>
          <dict>
            <key>begin</key>
            <string>(?&lt;=\s\s|\t);[^:]*</string>
            <key>end</key>
            <string>$</string>
            <key>name</key>
            <string>comment</string>
            <key>patterns</key>
            <array>
              <dict>
                <key>captures</key>
                <dict>
                  <key>1</key>
                  <dict>
                    <key>name</key>
                    <string>string.other</string>
                  </dict>
                </dict>
                <key>comment</key>
                <string>tag</string>
                <key>match</key>
                <string>(:(?:[^ :]+:)+)</string>
              </dict>
              <dict>
                <key>captures</key>
                <dict>
                  <key>1</key>
                  <dict>
                    <key>name</key>
                    <string>string.other</string>
                  </dict>
                  <key>2</key>
                  <dict>
                    <key>name</key>
                    <string>markup.quote</string>
                  </dict>
                </dict>
                <key>comment</key>
                <string>metadata</string>
                <key>match</key>
                <string>(\w+::?)\s+(.*)</string>
              </dict>
            </array>
          </dict>
        </array>
      </dict>
      <dict>
        <key>begin</key>
        <string>^(?:\s\s+|\t)(;)</string>
        <key>beginCaptures</key>
        <dict>
          <key>1</key>
          <dict>
            <key>name</key>
            <string>comment</string>
          </dict>
        </dict>
        <key>end</key>
        <string>$</string>
        <key>patterns</key>
        <array>
          <dict>
            <key>begin</key>
            <string></string>
            <key>end</key>
            <string>$</string>
            <key>name</key>
            <string>comment</string>
            <key>patterns</key>
            <array>
              <dict>
                <key>captures</key>
                <dict>
                  <key>1</key>
                  <dict>
                    <key>name</key>
                    <string>string.other</string>
                  </dict>
                </dict>
                <key>comment</key>
                <string>tag</string>
                <key>match</key>
                <string>(:(?:[^ :]+:)+)</string>
              </dict>
              <dict>
                <key>captures</key>
                <dict>
                  <key>1</key>
                  <dict>
                    <key>name</key>
                    <string>string.other</string>
                  </dict>
                  <key>2</key>
                  <dict>
                    <key>name</key>
                    <string>markup.quote</string>
                  </dict>
                </dict>
                <key>comment</key>
                <string>metadata</string>
                <key>match</key>
                <string>(\w+::?)\s+(.*)</string>
              </dict>
            </array>
          </dict>
        </array>
      </dict>
      <dict>
        <key>captures</key>
        <dict>
          <key>1</key>
          <dict>
            <key>name</key>
            <string>keyword.control</string>
          </dict>
          <key>2</key>
          <dict>
            <key>name</key>
            <string>string.other.date</string>
          </dict>
          <key>3</key>
          <dict>
            <key>name</key>
            <string>variable.other.amount</string>
          </dict>
          <key>4</key>
          <dict>
            <key>name</key>
            <string>markup.quote</string>
          </dict>
          <key>5</key>
          <dict>
            <key>name</key>
            <string>comment</string>
          </dict>
        </dict>
        <key>match</key>
        <string>^(P)\s+(\d{4}[/-]\d{2}[/-]\d{2}(?:\s\d\d:\d\d:\d\d)?)\s+(\w+)\s([^;]+)((?:\s\s|\t);.*)?$</string>
      </dict>
      <dict>
        <key>captures</key>
        <dict>
          <key>1</key>
          <dict>
            <key>name</key>
            <string>keyword.other</string>
          </dict>
          <key>2</key>
          <dict>
            <key>name</key>
            <string>string.regexp.expression</string>
          </dict>
          <key>3</key>
          <dict>
            <key>name</key>
            <string>comment</string>
          </dict>
        </dict>
        <key>match</key>
        <string>^(~|=)\s(.*?)((?:\s\s|\t);.*)?$</string>
      </dict>
      <dict>
        <key>captures</key>
        <dict>
          <key>1</key>
          <dict>
            <key>name</key>
            <string>keyword.other</string>
          </dict>
        </dict>
        <key>match</key>
        <string>^([a-z]+|[A-Z]).*$</string>
      </dict>
    </array>
    <key>scopeName</key>
    <string>source.ledger</string>
    <key>uuid</key>
    <string>806052d8-c579-43e0-918e-02ba9033b149</string>
  </dict>
</plist>

Here's the YAML source, you only need that if you want to improve it and compile it yourself:

# [PackageDev] target_format: plist, ext: tmLanguage
---
name: Ledger
scopeName: source.ledger
uuid: 806052d8-c579-43e0-918e-02ba9033b149
fileTypes:
- ldg
- ledger
- ldgr
patterns:
- captures:
    '1':
      name: comment.line
  match: "^(([;#|*%]).*)$"
- captures:
    '1':
      name: string.other.date
    '2':
      name: punctuation.separator
    '3':
      name: string.other.edate
    '4':
      name: constant.other
    '5':
      name: comment
    '6':
      name: markup.quote
    '7':
      name: comment
  match: "^(\\d{4}[/-]\\d{2}[/-]\\d{2})(=)?(\\d{4}[/-]\\d{2}[/-]\\d{2})?(\\s+[*!])?(\\s+\\([^)]+\\))?\\s+([^;]+)((?:\\t|\\s\\s);.*)?$"
- captures:
    '1':
      name: keyword.other
  match: "^\\s+(note|alias|payee|check|assert|eval|default)(?:\\s.*)?$"
- begin: "^\\s+([()\\[\\]\\w\\:\\s_-]+)\\s+(=\\s*)?([^;@]+)?(\\s*@[^;]+)?"
  beginCaptures:
    '1':
      name: markup.bold.account
    '2':
      name: markup.heading
    '3':
      name: variable.other.amount
    '4':
      name: markup.quote
  end: "$"
  patterns:
  - begin: "(?<=\\s\\s|\\t);[^:]*"
    # begin should be "(?<=\\s\\s|\\t);" but RubyMine does not play nice
    end: "$"
    name: comment
    patterns:
    - captures:
        '1':
          name: string.other
      comment: tag
      match: "(:(?:[^ :]+:)+)"
    - captures:
        '1':
          name: string.other
        '2':
          name: markup.quote
      comment: metadata
      match: "(\\w+::?)\\s+(.*)"
- begin: "^(?:\\s\\s+|\\t)(;)"
  beginCaptures:
    '1':
      name: comment
  end: "$"
  patterns:
  - begin: ""
    end: "$"
    name: comment
    patterns:
    - captures:
        '1':
          name: string.other
      comment: tag
      match: "(:(?:[^ :]+:)+)"
    - captures:
        '1':
          name: string.other
        '2':
          name: markup.quote
      comment: metadata
      match: "(\\w+::?)\\s+(.*)"
- captures:
    '1':
      name: keyword.control
    '2':
      name: string.other.date
    '3':
      name: variable.other.amount
    '4':
      name: markup.quote
    '5':
      name: comment
  match: "^(P)\\s+(\\d{4}[/-]\\d{2}[/-]\\d{2}(?:\\s\\d\\d:\\d\\d:\\d\\d)?)\\s+(\\w+)\\s([^;]+)((?:\\s\\s|\\t);.*)?$"
- captures:
    '1':
      name: keyword.other
    '2':
      name: string.regexp.expression
    '3':
      name: comment
  match: "^(~|=)\\s(.*?)((?:\\s\\s|\\t);.*)?$"
- captures:
    '1':
      name: keyword.other
  match: "^([a-z]+|[A-Z]).*$"