diff --git a/bundles/basic_modes/lexers/desktop.lua b/bundles/basic_modes/lexers/desktop.lua deleted file mode 100644 index edb943a1e..000000000 --- a/bundles/basic_modes/lexers/desktop.lua +++ /dev/null @@ -1,64 +0,0 @@ --- Copyright 2006-2012 Mitchell mitchell.att.foicica.com. See LICENSE. --- Desktop Entry LPeg lexer. - -local l = lexer -local token, style, color, word_match = l.token, l.style, l.color, l.word_match -local P, R, S = lpeg.P, lpeg.R, lpeg.S - -local M = {_NAME = 'desktop'} - --- Whitespace. -local ws = token(l.WHITESPACE, l.space^1) - --- Comments. -local comment = token(l.COMMENT, '#' * l.nonnewline^0) - --- Strings. -local string = token(l.STRING, l.delimited_range('"', '\\', true)) - --- Group headers. -local group_header = l.starts_line(token(l.STRING, - l.delimited_range('[]', nil, true))) - --- Numbers. -local number = token(l.NUMBER, (l.float + l.integer)) - --- Keywords. -local keyword = token(l.KEYWORD, word_match{'true', 'false'}) - --- Locales. -local locale = token(l.CLASS, l.delimited_range('[]', nil, true)) - --- Keys. -local key = token(l.VARIABLE, word_match{ - 'Type', 'Version', 'Name', 'GenericName', 'NoDisplay', 'Comment', 'Icon', - 'Hidden', 'OnlyShowIn', 'NotShowIn', 'TryExec', 'Exec', 'Exec', 'Path', - 'Terminal', 'MimeType', 'Categories', 'StartupNotify', 'StartupWMClass', - 'URL', 'Actions', 'Keywords' -}) - --- Field codes. -local code = l.token(l.CONSTANT, P('%') * S('fFuUdDnNickvm')) - --- Identifiers. -local identifier = l.token(l.IDENTIFIER, l.alpha * (l.alnum + S('_-'))^0) - --- Operators. -local operator = token(l.OPERATOR, S('=')) - -M._rules = { - {'whitespace', ws}, - {'keyword', keyword}, - {'key', key}, - {'identifier', identifier}, - {'group_header', group_header}, - {'locale', locale}, - {'string', string}, - {'comment', comment}, - {'number', number}, - {'code', code}, - {'operator', operator}, - {'any_char', l.any_char}, -} - -return M diff --git a/bundles/basic_modes/lexers/ini.lua b/bundles/basic_modes/lexers/ini.lua deleted file mode 100644 index c49f3752e..000000000 --- a/bundles/basic_modes/lexers/ini.lua +++ /dev/null @@ -1,54 +0,0 @@ --- Copyright 2006-2012 Mitchell mitchell.att.foicica.com. See LICENSE. --- Ini LPeg lexer. - -local l = lexer -local token, style, color, word_match = l.token, l.style, l.color, l.word_match -local P, R, S = lpeg.P, lpeg.R, lpeg.S - -local M = {_NAME = 'ini'} - --- Whitespace. -local ws = token(l.WHITESPACE, l.space^1) - --- Comments. -local comment = token(l.COMMENT, #S(';#') * l.starts_line(S(';#') * - l.nonnewline^0)) - --- Strings. -local sq_str = l.delimited_range("'", '\\', true) -local dq_str = l.delimited_range('"', '\\', true) -local label = l.delimited_range('[]', nil, true, false, '\n') -local string = token(l.STRING, sq_str + dq_str + label) - --- Numbers. -local dec = l.digit^1 * ('_' * l.digit^1)^0 -local oct_num = '0' * S('01234567_')^1 -local integer = S('+-')^-1 * (l.hex_num + oct_num + dec) -local number = token(l.NUMBER, (l.float + integer)) - --- Keywords. -local keyword = token(l.KEYWORD, word_match{ - 'true', 'false', 'on', 'off', 'yes', 'no' -}) - --- Identifiers. -local word = (l.alpha + '_') * (l.alnum + S('_.'))^0 -local identifier = token(l.IDENTIFIER, word) - --- Operators. -local operator = token(l.OPERATOR, '=') - -M._rules = { - {'whitespace', ws}, - {'keyword', keyword}, - {'identifier', identifier}, - {'string', string}, - {'comment', comment}, - {'number', number}, - {'operator', operator}, - {'any_char', l.any_char}, -} - -M._LEXBYLINE = true - -return M diff --git a/bundles/basic_modes/mode_definitions.moon b/bundles/basic_modes/mode_definitions.moon index 4abe9fd01..f2caaeee5 100644 --- a/bundles/basic_modes/mode_definitions.moon +++ b/bundles/basic_modes/mode_definitions.moon @@ -80,13 +80,6 @@ common_auto_pairs = { auto_pairs: common_auto_pairs parent: 'curly_mode' - desktop: - extensions: 'desktop' - comment_syntax: '#' - auto_pairs: { - '[': ']' - } - diff: extensions: { 'diff', 'patch' } aliases: 'patch' @@ -170,14 +163,6 @@ common_auto_pairs = { comment_syntax: '--' auto_pairs: common_auto_pairs - ini: - extensions: { 'cfg', 'cnf', 'inf', 'ini', 'reg' } - comment_syntax: ';' - auto_pairs: { - '[': ']' - '"': '"' - } - io: extensions: 'io' comment_syntax: '//' diff --git a/bundles/ini/ini_lexer.moon b/bundles/ini/ini_lexer.moon new file mode 100644 index 000000000..d8de5485b --- /dev/null +++ b/bundles/ini/ini_lexer.moon @@ -0,0 +1,269 @@ +-- Copyright 2019 The Howl Developers +-- License: MIT (see LICENSE.md at the top-level directory of the distribution) + +table = _G.table + +(type) -> + howl.util.lpeg_lexer -> + c = capture + + one_line_ws = (space - eol)^0 + + hash_comment = P'#' * scan_until eol + semicolon_comment = P';' * scan_until eol + + comment = c 'comment', + switch type + when 'ini', 'editorconfig' + any { + hash_comment + semicolon_comment + } + when 'xdg', 'systemd' + hash_comment + when 'regedit' + semicolon_comment + + section = sequence { + space^0 + c 'operator', '[' + + switch type + when 'ini' + sequence { + c 'keyword', scan_until(P']'^-1 * one_line_ws * eol) + c 'operator', P']'^-1 + scan_until eol + } + when 'editorconfig' + sequence { + any({ + P { + 'glob' + + glob: any { + c 'error', P'\\' + c 'operator', S'*?/.' + sequence { + c 'operator', P'[' + c 'operator', P'!'^-1 + any({ + V 'glob' + c 'keyword', P(1) - ']' + })^0 + c 'operator', P']'^-1 + } + sequence { + c 'operator', P'{' + any({ + c 'operator', ',' + V 'glob' + c 'keyword', P(1) - S',}' + })^0 + c 'operator', P'}'^-1 + } + } + } + c 'keyword', P(1) - P']' + })^0 + c 'operator', P']'^-1 + one_line_ws + c 'error', scan_until eol + } + else + section_char = { + c 'error', '[' + c 'keyword', P(1) - ']' + } + + if type == 'regedit' + table.insert section_char, 1, c 'operator', P'\\' + + section_name = { + any(section_char)^0 + c 'operator', P']'^-1 + one_line_ws + c 'error', scan_until eol + } + + if type == 'regedit' + table.insert section_name, 1, c 'operator', P'-'^-1 + + sequence section_name + } + + key = switch type + when 'ini', 'editorconfig' + c 'key', (P(1) - S'=:' - eol)^1 + when 'xdg', 'systemd' + sequence { + (-eol * any { + space + c 'key', any { alnum, P'-' } + c 'error', P(1) - (if type == 'xdg' then S'=[' else P'=') + })^1 + if type == 'xdg' + sequence({ + c 'operator', P'[' + c 'special', (P(1) - (S'=]' + eol))^0 + c 'operator', P']'^-1 + })^-1 + else + nil + } + when 'regedit' + c 'key', P'"' * scan_to P'"' + + bool_value = c 'special', word { 'true', 'false' } + decimal_value = c 'number', digit^1 * (P'.' * digit^0)^-1 + string_char = c 'string', P(1) - eol + + operator = c 'operator', switch type + when 'ini', 'editorconfig' + S':=' + else + P'=' + + value = switch type + when 'ini' + any { + bool_value + decimal_value + -- XXX: Substitutions are only highlighted on the first line. + sequence { + any({ + sequence { + c 'operator', '%(' + c 'identifier', scan_until P')' + eol + c 'operator', ')' + c 'special', (P(1) - eol)^-1 + } + sequence { + c 'operator', '${' + c 'identifier', scan_until S':}' + eol + sequence({ + c 'operator', ':' + c 'identifier', scan_until P'}' + eol + })^-1 + c 'operator', '}' + } + string_char + })^0 + c('string', eol * #(space - eol) * scan_through_indented!)^-1 + } + } + + when 'editorconfig' + any { + bool_value + decimal_value + string_char^0 + } + + when 'xdg', 'systemd' + xdg_string_values = if type == 'xdg' + string_char + else + any { + c 'string', P'\\' * eol + sequence { + c 'operator', '$' + any { + sequence { + c 'operator', '{' + c 'identifier', scan_until P'}' + eol + c 'operator', '}' + } + c 'identifier', scan_until space + } + } + c 'string', '%%' + sequence { + c 'special', '%' + any { + c 'special', S'bCEfhHiIjJLmnNpPsStTgGuUvV' + c 'error', P(1) - eol + } + } + c 'error', P'%' + string_char + } + + any({ + bool_value + decimal_value + xdg_string_values^1 + }) + + when 'regedit' + scan_until_colon = scan_until eol + P':' + skip_continuation = sequence { + c 'operator', P'\\' + c 'whitespace', eol + one_line_ws + } + + byte = any { + c 'number', xdigit * xdigit + c 'error', scan_until eol + P',' + } + + any { + c 'operator', '-' + c 'string', P'"' * scan_to P'"' + sequence { + any { + sequence { + c 'type', word { 'dword' } + c 'error', scan_until_colon + c 'operator', P':'^-1 + any({ + c 'number', xdigit + c 'error', P(1) - eol + })^0 + } + + sequence { + c 'type', word { 'hex' } + sequence({ + c 'operator', '(' + any { + c 'number', S'27' + c 'error', P(1) - (eol + S'):') + } + c 'operator', ')' + })^-1 + c 'error', scan_until_colon + c 'operator', P':'^-1 + sequence({ + byte + skip_continuation^0 + sequence({ + skip_continuation^0 + c 'error', scan_until eol + ',' + skip_continuation^0 + c 'operator', P',' + skip_continuation^0 + c 'error', scan_until eol + xdigit + skip_continuation^0 + byte + })^0 + c 'error', scan_until eol + })^-1 + } + } + } + c 'error', scan_until eol + } + + setting = sequence { + key + one_line_ws + (operator * one_line_ws * (value * one_line_ws)^-1)^-1 + } + + any { + comment + section + setting + } diff --git a/bundles/ini/ini_mode.moon b/bundles/ini/ini_mode.moon new file mode 100644 index 000000000..cd88ed89e --- /dev/null +++ b/bundles/ini/ini_mode.moon @@ -0,0 +1,21 @@ +-- Copyright 2019 The Howl Developers +-- License: MIT (see LICENSE.md at the top-level directory of the distribution) + +(type) -> + { + lexer: bundle_load('ini_lexer')(type) + + comment_syntax: if type == 'xdg' or type == 'systemd' then '#' else ';' + word_pattern: r'\\b[A-Za-z0-9_-]+\\b' + + auto_pairs: { + '(': ')' + '[': ']' + '{': '}' + "'": "'" + '"': '"' + } + + structure: (editor) => + [l for l in *editor.buffer.lines when l\match('^%s*%[')] + } diff --git a/bundles/ini/init.moon b/bundles/ini/init.moon new file mode 100644 index 000000000..bb728d6a9 --- /dev/null +++ b/bundles/ini/init.moon @@ -0,0 +1,49 @@ +-- Copyright 2019 The Howl Developers +-- License: MIT (see LICENSE.md at the top-level directory of the distribution) + +modes = { + { + name: 'ini' + extensions: {'cfg', 'cnf', 'inf', 'ini'} + } + + { + name: 'xdg' + extensions: { 'desktop' } + } + + { + name: 'systemd' + extensions: { + 'service', 'socket', 'device', 'mount', 'automount', 'swap', 'target', 'path', + 'timer', 'slice', 'scope', + } + } + + { + name: 'editorconfig' + patterns: { '.editorconfig$' } + extensions: { 'editorconfig' } + } + + { + name: 'regedit' + extensions: { 'reg' } + } +} + +for mode_reg in *modes + mode_reg.create = -> bundle_load('ini_mode')(mode_reg.name) + howl.mode.register mode_reg + +unload = -> + for mode_reg in *modes + howl.mode.unregister mode_reg.name + +return { + info: + author: 'Copyright 2019 The Howl Developers', + description: 'INI file modes', + license: 'MIT', + :unload +} diff --git a/bundles/ini/misc/example.desktop b/bundles/ini/misc/example.desktop new file mode 100644 index 000000000..68db781df --- /dev/null +++ b/bundles/ini/misc/example.desktop @@ -0,0 +1,9 @@ +# This is a comment +; this is an invalid key + +[Section] invalid chars here +key.invalid = true +key[locale] = string \ + multline should not work here +a = 2 +X-Another-Key = 1.2 diff --git a/bundles/ini/misc/example.editorconfig b/bundles/ini/misc/example.editorconfig new file mode 100644 index 000000000..3d260804f --- /dev/null +++ b/bundles/ini/misc/example.editorconfig @@ -0,0 +1,7 @@ +; This is a comment +# as is this + +[*{.py?,[abc],[!def]}] invalid chars here +indent_style = 1 +multi line values = + are not supported here = 2 diff --git a/bundles/ini/misc/example.ini b/bundles/ini/misc/example.ini new file mode 100644 index 000000000..98423ed87 --- /dev/null +++ b/bundles/ini/misc/example.ini @@ -0,0 +1,16 @@ +# Different types +; of comments + +[Section] +bool = true +bool = false +number = 1.2 +string = string +multiline = line1 + line2 + line3 + line4 = 1 +normal key again +python substitutions = some string %(abc)s ${def} ${ghi:jk} more string + +[Nested[Section]]][] diff --git a/bundles/ini/misc/example.reg b/bundles/ini/misc/example.reg new file mode 100644 index 000000000..10d09cc53 --- /dev/null +++ b/bundles/ini/misc/example.reg @@ -0,0 +1,23 @@ +; This is a comment +# this is not a comment + +[-NegativePrefix] + +[\Path\1-2-3\] +"string"="string" +"delete"=- +"dword"=dword:c0fee +"hex"=hex:01,02,ab +"hex2"=hex(2):01,02,03 +"hex7"=hex(7):01,02,03 +"hex-multiline"=hex:12,34,\ + 34,65,46 + +"invalid-basic"=abc +"invalid-type"=type:123 +"invalid-dword-bad-digit"=dword:az3 +"invalid-dword-no-colon"=dword az3 +"invalid-hex-bad-digit"=hex:az,01 +"invalid-hex-extra-comma-digit"=hex:ab,,cd,ef1 +"invalid-hex-no-type-digit"=hex():12 +"invalid-hex-bad-type-digit"=hex(5):12 diff --git a/bundles/ini/misc/example.service b/bundles/ini/misc/example.service new file mode 100644 index 000000000..a4827fd4c --- /dev/null +++ b/bundles/ini/misc/example.service @@ -0,0 +1,8 @@ +# This is a comment +; this is an invalid key + +[Section] invalid chars here +key.invalid=true +locales[are invalid for systemd]=123 +ExecStart=env vars: ${var1} $var2 escaped: %% special: %i invalid: %Z as is: % +ExecStart=