From 3c6021ee6614d987b7b6bfcb4eab1c049b067732 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ji=C5=99=C3=AD=20Techet?= Date: Mon, 23 Oct 2023 11:59:43 +0200 Subject: [PATCH] Convert Nim to builtin filetype and use Scintilla Nim lexer for it --- data/Makefile.am | 2 +- data/filedefs/filetypes.Nim.conf | 64 --- data/filedefs/filetypes.nim | 71 +++ data/filetype_extensions.conf | 2 +- meson.build | 1 + scintilla/Makefile.am | 1 + scintilla/lexilla/lexers/LexNim.cxx | 814 ++++++++++++++++++++++++++++ scintilla/lexilla/src/Lexilla.cxx | 1 + scintilla/scintilla_changes.patch | 1 + src/filetypes.c | 1 + src/filetypes.h | 1 + src/highlighting.c | 15 + src/highlightingmappings.h | 29 + 13 files changed, 937 insertions(+), 66 deletions(-) delete mode 100644 data/filedefs/filetypes.Nim.conf create mode 100644 data/filedefs/filetypes.nim create mode 100644 scintilla/lexilla/lexers/LexNim.cxx diff --git a/data/Makefile.am b/data/Makefile.am index f01ff04905..f964c6e91b 100644 --- a/data/Makefile.am +++ b/data/Makefile.am @@ -57,7 +57,7 @@ filetypes_dist = \ filedefs/filetypes.markdown \ filedefs/filetypes.matlab \ filedefs/filetypes.Meson.conf \ - filedefs/filetypes.Nim.conf \ + filedefs/filetypes.nim \ filedefs/filetypes.nsis \ filedefs/filetypes.objectivec \ filedefs/filetypes.pascal \ diff --git a/data/filedefs/filetypes.Nim.conf b/data/filedefs/filetypes.Nim.conf deleted file mode 100644 index c790884b03..0000000000 --- a/data/filedefs/filetypes.Nim.conf +++ /dev/null @@ -1,64 +0,0 @@ -# For complete documentation of this file, please see Geany's main documentation - -[keywords] -# all items must be in one line -primary=addr and as asm bind block break case cast concept const continue converter defer discard distinct div do elif else end enum except export finally for from func if import in include interface is isnot iterator let macro method mixin mod nil not notin object of or out proc ptr raise ref return shl shr static template try tuple type using var when while xor yield -# additional keywords, will be highlighted with style "word2" -identifiers=array auto bool byte char int int8 int16 int32 int64 uint uint8 uint16 uint32 uint64 float float32 float64 Natural Ordinal Positive seq string nil true false result echo openArray lent sink assert doAssert spawn parallel - -[lexer_properties] -fold.quotes.nim=1 -lexer.nim.keywords2.no.sub.identifiers=1 - -[settings] -# default extension used when saving files -extension=nim -# MIME type -mime_type=text/x-nim -# single comment char, like # in this file -comment_single=# -comment_use_indent=true -comment_open=#[ -comment_close=]# -tag_parser=Python -lexer_filetype=Python - -[indentation] -width=2 -# 0 is spaces, 1 is tabs, 2 is tab & spaces -type=0 - -[build-menu] -FT_00_LB=nim c -FT_00_CM=nim c "%f" -FT_00_WD=%d -FT_02_LB=_Lint -FT_02_CM=nimpretty --maxLineLen:80 "%f" -EX_01_WD= -error_regex=(.+)\(([0-9]+),\s+([0-9]+)\)\s+Error -FT_01_LB=nim r -FT_01_CM=nim r "%f" -FT_01_WD=%d - -[styling] -# Edit these in the colorscheme .conf file instead -default=default -commentline=comment_line -number=number_1 -string=string_1 -character=character -word=keyword_1 -triple=string_2 -tripledouble=string_2 -classname=type -defname=function -operator=operator -identifier=identifier_1 -commentblock=comment -stringeol=string_eol -word2=keyword_2 -decorator=decorator -fstring=string_1 -fcharacter=character -ftriple=string_2 -ftripledouble=string_2 diff --git a/data/filedefs/filetypes.nim b/data/filedefs/filetypes.nim new file mode 100644 index 0000000000..f9d8b59128 --- /dev/null +++ b/data/filedefs/filetypes.nim @@ -0,0 +1,71 @@ +# For complete documentation of this file, please see Geany's main documentation +[styling] +# Edit these in the colorscheme .conf file instead +default=default +comment=comment +commentdoc=comment_doc +commentline=comment_line +commentlinedoc=comment_doc +number=number_1 +string=string_1 +character=character +word=keyword_1 +triple=string_2 +tripledouble=string_2 +backticks=identifier_1 +funcname=identifier_1 +stringeol=string_eol +numerror=error +operator=operator +identifier=identifier_1 + +[keywords] +# all items must be in one line +keywords=addr and array as asm assert auto bind block bool break byte case cast char concept const continue converter defer discard distinct div do doAssert echo elif else end enum except export false finally float float32 float64 for from func if import in include int int16 int32 int64 int8 interface is isnot iterator lent let macro method mixin mod Natural nil nil not notin object of openArray or Ordinal out parallel Positive proc ptr raise ref result return seq shl shr sink spawn static string template true try tuple type uint uint16 uint32 uint64 uint8 using var when while xor yield + +[lexer_properties] + +[settings] +# default extension used when saving files +extension=nim + +# MIME type +mime_type=text/x-nim + +# these characters define word boundaries when making selections and searching +# using word matching options +#wordchars=_abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789 + +# single comments, like # in this file +comment_single=# +# multiline comments +comment_open=#[ +comment_close=]# + +# set to false if a comment character/string should start at column 0 of a line, true uses any +# indentation of the line, e.g. setting to true causes the following on pressing CTRL+d +# #command_example(); +# setting to false would generate this +# # command_example(); +# This setting works only for single line comments +comment_use_indent=true + +# context action command (please see Geany's main documentation for details) +context_action_cmd= + +[indentation] +#width=4 +# 0 is spaces, 1 is tabs, 2 is tab & spaces +#type=1 + +[build-menu] +FT_00_LB=nim c +FT_00_CM=nim c "%f" +FT_00_WD=%d +FT_02_LB=_Lint +FT_02_CM=nimpretty --maxLineLen:80 "%f" +EX_01_WD= +error_regex=(.+)\(([0-9]+),\s+([0-9]+)\)\s+Error +FT_01_LB=nim r +FT_01_CM=nim r "%f" +FT_01_WD=%d diff --git a/data/filetype_extensions.conf b/data/filetype_extensions.conf index 7f516fdc69..e3dd10f1c0 100644 --- a/data/filetype_extensions.conf +++ b/data/filetype_extensions.conf @@ -88,7 +88,7 @@ None=*; # Note: restarting is required after editing groups [Groups] -Programming=Arduino;Clojure;CUDA;Cython;Genie;Groovy;Kotlin;Nim;Scala;Swift; +Programming=Arduino;Clojure;CUDA;Cython;Genie;Groovy;Kotlin;Scala;Swift; Script=Dockerfile;Graphviz;TypeScript;Meson; Markup= Misc=JSON; diff --git a/meson.build b/meson.build index d3131eac13..fa34466557 100644 --- a/meson.build +++ b/meson.build @@ -272,6 +272,7 @@ lexilla = static_library('lexilla', 'scintilla/lexilla/lexers/LexMake.cxx', 'scintilla/lexilla/lexers/LexMarkdown.cxx', 'scintilla/lexilla/lexers/LexMatlab.cxx', + 'scintilla/lexilla/lexers/LexNim.cxx', 'scintilla/lexilla/lexers/LexNsis.cxx', 'scintilla/lexilla/lexers/LexNull.cxx', 'scintilla/lexilla/lexers/LexPascal.cxx', diff --git a/scintilla/Makefile.am b/scintilla/Makefile.am index ede1b5b921..9db4358f31 100644 --- a/scintilla/Makefile.am +++ b/scintilla/Makefile.am @@ -42,6 +42,7 @@ lexilla/lexers/LexLua.cxx \ lexilla/lexers/LexMake.cxx \ lexilla/lexers/LexMarkdown.cxx \ lexilla/lexers/LexMatlab.cxx \ +lexilla/lexers/LexNim.cxx \ lexilla/lexers/LexNsis.cxx \ lexilla/lexers/LexNull.cxx \ lexilla/lexers/LexPascal.cxx \ diff --git a/scintilla/lexilla/lexers/LexNim.cxx b/scintilla/lexilla/lexers/LexNim.cxx new file mode 100644 index 0000000000..3e8a6e006e --- /dev/null +++ b/scintilla/lexilla/lexers/LexNim.cxx @@ -0,0 +1,814 @@ +// Scintilla source code edit control +/** @file LexNim.cxx +** Lexer for Nim +** Written by Jad Altahan (github.com/xv) +** Nim manual: https://nim-lang.org/docs/manual.html +**/ +// Copyright 1998-2001 by Neil Hodgson +// The License.txt file describes the conditions under which this software may be distributed. + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "ILexer.h" +#include "Scintilla.h" +#include "SciLexer.h" + +#include "StringCopy.h" +#include "WordList.h" +#include "LexAccessor.h" +#include "Accessor.h" +#include "StyleContext.h" +#include "CharacterSet.h" +#include "LexerModule.h" +#include "OptionSet.h" +#include "DefaultLexer.h" + +using namespace Scintilla; +using namespace Lexilla; + +namespace { + // Use an unnamed namespace to protect the functions and classes from name conflicts + +enum NumType { + Binary, + Octal, + Exponent, + Hexadecimal, + Decimal, + FormatError +}; + +int GetNumStyle(const int numType) noexcept { + if (numType == NumType::FormatError) { + return SCE_NIM_NUMERROR; + } + + return SCE_NIM_NUMBER; +} + +constexpr bool IsLetter(const int ch) noexcept { + // 97 to 122 || 65 to 90 + return (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z'); +} + +bool IsAWordChar(const int ch) noexcept { + return ch < 0x80 && (isalnum(ch) || ch == '_' || ch == '.'); +} + +int IsNumHex(const StyleContext &sc) noexcept { + return sc.chNext == 'x' || sc.chNext == 'X'; +} + +int IsNumBinary(const StyleContext &sc) noexcept { + return sc.chNext == 'b' || sc.chNext == 'B'; +} + +int IsNumOctal(const StyleContext &sc) { + return IsADigit(sc.chNext) || sc.chNext == 'o'; +} + +constexpr bool IsNewline(const int ch) noexcept { + return (ch == '\n' || ch == '\r'); +} + +bool IsFuncName(const char *str) noexcept { + const char *identifiers[] = { + "proc", + "func", + "macro", + "method", + "template", + "iterator", + "converter" + }; + + for (const char *id : identifiers) { + if (strcmp(str, id) == 0) { + return true; + } + } + + return false; +} + +constexpr bool IsTripleLiteral(const int style) noexcept { + return style == SCE_NIM_TRIPLE || style == SCE_NIM_TRIPLEDOUBLE; +} + +constexpr bool IsLineComment(const int style) noexcept { + return style == SCE_NIM_COMMENTLINE || style == SCE_NIM_COMMENTLINEDOC; +} + +constexpr bool IsStreamComment(const int style) noexcept { + return style == SCE_NIM_COMMENT || style == SCE_NIM_COMMENTDOC; +} + +// Adopted from Accessor.cxx +int GetIndent(const Sci_Position line, Accessor &styler) { + Sci_Position startPos = styler.LineStart(line); + const Sci_Position eolPos = styler.LineStart(line + 1) - 1; + + char ch = styler[startPos]; + int style = styler.StyleAt(startPos); + + int indent = 0; + bool inPrevPrefix = line > 0; + Sci_Position posPrev = inPrevPrefix ? styler.LineStart(line - 1) : 0; + + // No fold points inside triple literals + while ((IsASpaceOrTab(ch) || IsTripleLiteral(style)) && (startPos < eolPos)) { + if (inPrevPrefix) { + const char chPrev = styler[posPrev++]; + if (chPrev != ' ' && chPrev != '\t') { + inPrevPrefix = false; + } + } + + if (ch == '\t') { + indent = (indent / 8 + 1) * 8; + } else { + indent++; + } + + startPos++; + ch = styler[startPos]; + style = styler.StyleAt(startPos); + } + + // Prevent creating fold lines for comments if indented + if (!(IsStreamComment(style) || IsLineComment(style))) + indent += SC_FOLDLEVELBASE; + + if (styler.LineStart(line) == styler.Length() + || IsASpaceOrTab(ch) + || IsNewline(ch) + || IsStreamComment(style) + || IsLineComment(style)) { + return indent | SC_FOLDLEVELWHITEFLAG; + } else { + return indent; + } +} + +int IndentAmount(const Sci_Position line, Accessor &styler) { + const int indent = GetIndent(line, styler); + const int indentLevel = indent & SC_FOLDLEVELNUMBERMASK; + return indentLevel <= SC_FOLDLEVELBASE ? indent : indentLevel | (indent & ~SC_FOLDLEVELNUMBERMASK); +} + +struct OptionsNim { + bool fold; + bool foldCompact; + bool highlightRawStrIdent; + + OptionsNim() { + fold = true; + foldCompact = true; + highlightRawStrIdent = false; + } +}; + +static const char *const nimWordListDesc[] = { + "Keywords", + nullptr +}; + +struct OptionSetNim : public OptionSet { + OptionSetNim() { + DefineProperty("lexer.nim.raw.strings.highlight.ident", &OptionsNim::highlightRawStrIdent, + "Set to 1 to enable highlighting generalized raw string identifiers. " + "Generalized raw string identifiers are anything other than r (or R)."); + + DefineProperty("fold", &OptionsNim::fold); + DefineProperty("fold.compact", &OptionsNim::foldCompact); + + DefineWordListSets(nimWordListDesc); + } +}; + +LexicalClass lexicalClasses[] = { + // Lexer Nim SCLEX_NIM SCE_NIM_: + 0, "SCE_NIM_DEFAULT", "default", "White space", + 1, "SCE_NIM_COMMENT", "comment block", "Block comment", + 2, "SCE_NIM_COMMENTDOC", "comment block doc", "Block doc comment", + 3, "SCE_NIM_COMMENTLINE", "comment line", "Line comment", + 4, "SCE_NIM_COMMENTLINEDOC", "comment doc", "Line doc comment", + 5, "SCE_NIM_NUMBER", "literal numeric", "Number", + 6, "SCE_NIM_STRING", "literal string", "String", + 7, "SCE_NIM_CHARACTER", "literal string", "Single quoted string", + 8, "SCE_NIM_WORD", "keyword", "Keyword", + 9, "SCE_NIM_TRIPLE", "literal string", "Triple quotes", + 10, "SCE_NIM_TRIPLEDOUBLE", "literal string", "Triple double quotes", + 11, "SCE_NIM_BACKTICKS", "operator definition", "Identifiers", + 12, "SCE_NIM_FUNCNAME", "identifier", "Function name definition", + 13, "SCE_NIM_STRINGEOL", "error literal string", "String is not closed", + 14, "SCE_NIM_NUMERROR", "numeric error", "Numeric format error", + 15, "SCE_NIM_OPERATOR", "operator", "Operators", + 16, "SCE_NIM_IDENTIFIER", "identifier", "Identifiers", +}; + +} + +class LexerNim : public DefaultLexer { + CharacterSet setWord; + WordList keywords; + OptionsNim options; + OptionSetNim osNim; + +public: + LexerNim() : + DefaultLexer("nim", SCLEX_NIM, lexicalClasses, ELEMENTS(lexicalClasses)), + setWord(CharacterSet::setAlphaNum, "_", 0x80, true) { } + + virtual ~LexerNim() { } + + void SCI_METHOD Release() noexcept override { + delete this; + } + + int SCI_METHOD Version() const noexcept override { + return lvRelease5; + } + + const char * SCI_METHOD PropertyNames() override { + return osNim.PropertyNames(); + } + + int SCI_METHOD PropertyType(const char *name) override { + return osNim.PropertyType(name); + } + + const char * SCI_METHOD DescribeProperty(const char *name) override { + return osNim.DescribeProperty(name); + } + + Sci_Position SCI_METHOD PropertySet(const char *key, const char *val) override; + + const char * SCI_METHOD PropertyGet(const char* key) override { + return osNim.PropertyGet(key); + } + + const char * SCI_METHOD DescribeWordListSets() override { + return osNim.DescribeWordListSets(); + } + + Sci_Position SCI_METHOD WordListSet(int n, const char *wl) override; + + void SCI_METHOD Lex(Sci_PositionU startPos, Sci_Position length, int initStyle, IDocument *pAccess) override; + void SCI_METHOD Fold(Sci_PositionU startPos, Sci_Position length, int initStyle, IDocument *pAccess) override; + + void * SCI_METHOD PrivateCall(int, void *) noexcept override { + return nullptr; + } + + int SCI_METHOD LineEndTypesSupported() noexcept override { + return SC_LINE_END_TYPE_UNICODE; + } + + int SCI_METHOD PrimaryStyleFromStyle(int style) noexcept override { + return style; + } + + static ILexer5 *LexerFactoryNim() { + return new LexerNim(); + } +}; + +Sci_Position SCI_METHOD LexerNim::PropertySet(const char *key, const char *val) { + if (osNim.PropertySet(&options, key, val)) { + return 0; + } + + return -1; +} + +Sci_Position SCI_METHOD LexerNim::WordListSet(int n, const char *wl) { + WordList *wordListN = nullptr; + + switch (n) { + case 0: + wordListN = &keywords; + break; + } + + Sci_Position firstModification = -1; + + if (wordListN) { + WordList wlNew; + wlNew.Set(wl); + + if (*wordListN != wlNew) { + wordListN->Set(wl); + firstModification = 0; + } + } + + return firstModification; +} + +void SCI_METHOD LexerNim::Lex(Sci_PositionU startPos, Sci_Position length, + int initStyle, IDocument *pAccess) { + // No one likes a leaky string + if (initStyle == SCE_NIM_STRINGEOL) { + initStyle = SCE_NIM_DEFAULT; + } + + Accessor styler(pAccess, nullptr); + StyleContext sc(startPos, length, initStyle, styler); + + // Nim supports nested block comments! + Sci_Position lineCurrent = styler.GetLine(startPos); + int commentNestLevel = lineCurrent > 0 ? styler.GetLineState(lineCurrent - 1) : 0; + + int numType = NumType::Decimal; + int decimalCount = 0; + + bool funcNameExists = false; + bool isStylingRawString = false; + bool isStylingRawStringIdent = false; + + for (; sc.More(); sc.Forward()) { + if (sc.atLineStart) { + if (sc.state == SCE_NIM_STRING) { + sc.SetState(SCE_NIM_STRING); + } + + lineCurrent = styler.GetLine(sc.currentPos); + styler.SetLineState(lineCurrent, commentNestLevel); + } + + // Handle string line continuation + if (sc.ch == '\\' && (sc.chNext == '\n' || sc.chNext == '\r') && + (sc.state == SCE_NIM_STRING || sc.state == SCE_NIM_CHARACTER) && !isStylingRawString) { + sc.Forward(); + + if (sc.ch == '\r' && sc.chNext == '\n') { + sc.Forward(); + } + + continue; + } + + switch (sc.state) { + case SCE_NIM_OPERATOR: + funcNameExists = false; + sc.SetState(SCE_NIM_DEFAULT); + break; + case SCE_NIM_NUMBER: + // For a type suffix, such as 0x80'u8 + if (sc.ch == '\'') { + if (sc.chNext == 'i' || sc.chNext == 'I' || + sc.chNext == 'u' || sc.chNext == 'U' || + sc.chNext == 'f' || sc.chNext == 'F' || + sc.chNext == 'd' || sc.chNext == 'D') { + sc.Forward(2); + } + } else if (sc.ch == '.') { + if (IsADigit(sc.chNext)) { + sc.Forward(); + } else if (numType <= NumType::Exponent) { + sc.SetState(SCE_NIM_OPERATOR); + break; + } else { + decimalCount++; + + if (numType == NumType::Decimal) { + if (decimalCount <= 1 && !IsAWordChar(sc.chNext)) { + break; + } + } else if (numType == NumType::Hexadecimal) { + if (decimalCount <= 1 && IsADigit(sc.chNext, 16)) { + break; + } + + sc.SetState(SCE_NIM_OPERATOR); + break; + } + } + } else if (sc.ch == '_') { + // Accept only one underscore between digits + if (IsADigit(sc.chNext)) { + sc.Forward(); + } + } else if (numType == NumType::Decimal) { + if (sc.chPrev != '\'' && (sc.ch == 'e' || sc.ch == 'E')) { + numType = NumType::Exponent; + + if (sc.chNext == '-' || sc.chNext == '+') { + sc.Forward(); + } + + break; + } + + if (IsADigit(sc.ch)) { + break; + } + } else if (numType == NumType::Hexadecimal) { + if (IsADigit(sc.ch, 16)) { + break; + } + } else if (IsADigit(sc.ch)) { + if (numType == NumType::Exponent) { + break; + } + + if (numType == NumType::Octal) { + // Accept only 0-7 + if (sc.ch <= '7') { + break; + } + } else if (numType == NumType::Binary) { + // Accept only 0 and 1 + if (sc.ch <= '1') { + break; + } + } + + numType = NumType::FormatError; + break; + } + + sc.ChangeState(GetNumStyle(numType)); + sc.SetState(SCE_NIM_DEFAULT); + break; + case SCE_NIM_IDENTIFIER: + if (sc.ch == '.' || !IsAWordChar(sc.ch)) { + char s[100]; + sc.GetCurrent(s, sizeof(s)); + int style = SCE_NIM_IDENTIFIER; + + if (keywords.InList(s) && !funcNameExists) { + // Prevent styling keywords if they are sub-identifiers + const Sci_Position segStart = styler.GetStartSegment() - 1; + if (segStart < 0 || styler.SafeGetCharAt(segStart, '\0') != '.') { + style = SCE_NIM_WORD; + } + } else if (funcNameExists) { + style = SCE_NIM_FUNCNAME; + } + + sc.ChangeState(style); + sc.SetState(SCE_NIM_DEFAULT); + + if (style == SCE_NIM_WORD) { + funcNameExists = IsFuncName(s); + } else { + funcNameExists = false; + } + } + + if (IsAlphaNumeric(sc.ch) && sc.chNext == '\"') { + isStylingRawStringIdent = true; + + if (options.highlightRawStrIdent) { + if (styler.SafeGetCharAt(sc.currentPos + 2) == '\"' && + styler.SafeGetCharAt(sc.currentPos + 3) == '\"') { + sc.ChangeState(SCE_NIM_TRIPLEDOUBLE); + } else { + sc.ChangeState(SCE_NIM_STRING); + } + } + + sc.ForwardSetState(SCE_NIM_DEFAULT); + } + break; + case SCE_NIM_FUNCNAME: + if (sc.ch == '`') { + funcNameExists = false; + sc.ForwardSetState(SCE_NIM_DEFAULT); + } else if (sc.atLineEnd) { + // Prevent leaking the style to the next line if not closed + funcNameExists = false; + + sc.ChangeState(SCE_NIM_STRINGEOL); + sc.ForwardSetState(SCE_NIM_DEFAULT); + } + break; + case SCE_NIM_COMMENT: + if (sc.Match(']', '#')) { + if (commentNestLevel > 0) { + commentNestLevel--; + } + + lineCurrent = styler.GetLine(sc.currentPos); + styler.SetLineState(lineCurrent, commentNestLevel); + sc.Forward(); + + if (commentNestLevel == 0) { + sc.ForwardSetState(SCE_NIM_DEFAULT); + } + } else if (sc.Match('#', '[')) { + commentNestLevel++; + lineCurrent = styler.GetLine(sc.currentPos); + styler.SetLineState(lineCurrent, commentNestLevel); + } + break; + case SCE_NIM_COMMENTDOC: + if (sc.Match("]##")) { + if (commentNestLevel > 0) { + commentNestLevel--; + } + + lineCurrent = styler.GetLine(sc.currentPos); + styler.SetLineState(lineCurrent, commentNestLevel); + sc.Forward(2); + + if (commentNestLevel == 0) { + sc.ForwardSetState(SCE_NIM_DEFAULT); + } + } else if (sc.Match("##[")) { + commentNestLevel++; + lineCurrent = styler.GetLine(sc.currentPos); + styler.SetLineState(lineCurrent, commentNestLevel); + } + break; + case SCE_NIM_COMMENTLINE: + case SCE_NIM_COMMENTLINEDOC: + if (sc.atLineStart) { + sc.SetState(SCE_NIM_DEFAULT); + } + break; + case SCE_NIM_STRING: + if (!isStylingRawStringIdent && !isStylingRawString && sc.ch == '\\') { + if (sc.chNext == '\"' || sc.chNext == '\'' || sc.chNext == '\\') { + sc.Forward(); + } + } else if (isStylingRawString && sc.ch == '\"' && sc.chNext == '\"') { + // Forward in situations such as r"a""bc\" so that "bc\" wouldn't be + // considered a string of its own + sc.Forward(); + } else if (sc.ch == '\"') { + sc.ForwardSetState(SCE_NIM_DEFAULT); + } else if (sc.atLineEnd) { + sc.ChangeState(SCE_NIM_STRINGEOL); + sc.ForwardSetState(SCE_NIM_DEFAULT); + } + break; + case SCE_NIM_CHARACTER: + if (sc.ch == '\\') { + if (sc.chNext == '\"' || sc.chNext == '\'' || sc.chNext == '\\') { + sc.Forward(); + } + } else if (sc.ch == '\'') { + sc.ForwardSetState(SCE_NIM_DEFAULT); + } else if (sc.atLineEnd) { + sc.ChangeState(SCE_NIM_STRINGEOL); + sc.ForwardSetState(SCE_NIM_DEFAULT); + } + break; + case SCE_NIM_BACKTICKS: + if (sc.ch == '`' ) { + sc.ForwardSetState(SCE_NIM_DEFAULT); + } else if (sc.atLineEnd) { + sc.ChangeState(SCE_NIM_STRINGEOL); + sc.ForwardSetState(SCE_NIM_DEFAULT); + } + break; + case SCE_NIM_TRIPLEDOUBLE: + if (sc.Match(R"(""")")) { + + // Outright forward all " after the closing """ as a triple double + // + // A valid example where this is needed is: """8 double quotes->"""""""" + // You can have as many """ at the end as you wish, as long as the actual + // closing literal is there + while (sc.ch == '"') { + sc.Forward(); + } + + sc.SetState(SCE_NIM_DEFAULT); + } + break; + case SCE_NIM_TRIPLE: + if (sc.Match("'''")) { + sc.Forward(2); + sc.ForwardSetState(SCE_NIM_DEFAULT); + } + break; + } + + if (sc.state == SCE_NIM_DEFAULT) { + // Number + if (IsADigit(sc.ch)) { + sc.SetState(SCE_NIM_NUMBER); + + numType = NumType::Decimal; + decimalCount = 0; + + if (sc.ch == '0') { + if (IsNumHex(sc)) { + numType = NumType::Hexadecimal; + } else if (IsNumBinary(sc)) { + numType = NumType::Binary; + } else if (IsNumOctal(sc)) { + numType = NumType::Octal; + } + + if (numType != NumType::Decimal) { + sc.Forward(); + } + } + } + // Raw string + else if (IsAlphaNumeric(sc.ch) && sc.chNext == '\"') { + isStylingRawString = true; + + // Triple doubles can be raw strings too. How sweet + if (styler.SafeGetCharAt(sc.currentPos + 2) == '\"' && + styler.SafeGetCharAt(sc.currentPos + 3) == '\"') { + sc.SetState(SCE_NIM_TRIPLEDOUBLE); + } else { + sc.SetState(SCE_NIM_STRING); + } + + const int rawStrStyle = options.highlightRawStrIdent ? IsLetter(sc.ch) : + (sc.ch == 'r' || sc.ch == 'R'); + + if (rawStrStyle) { + sc.Forward(); + + if (sc.state == SCE_NIM_TRIPLEDOUBLE) { + sc.Forward(2); + } + } else { + // Anything other than r/R is considered a general raw string identifier + isStylingRawStringIdent = true; + sc.SetState(SCE_NIM_IDENTIFIER); + } + } + // String and triple double literal + else if (sc.ch == '\"') { + isStylingRawString = false; + + if (sc.Match(R"(""")")) { + sc.SetState(SCE_NIM_TRIPLEDOUBLE); + + // Keep forwarding until the total opening literal count is 5 + // A valid example where this is needed is: """""<-5 double quotes""" + while (sc.ch == '"') { + sc.Forward(); + + if (sc.Match(R"(""")")) { + sc.Forward(); + break; + } + } + } else { + sc.SetState(SCE_NIM_STRING); + } + } + // Charecter and triple literal + else if (sc.ch == '\'') { + if (sc.Match("'''")) { + sc.SetState(SCE_NIM_TRIPLE); + } else { + sc.SetState(SCE_NIM_CHARACTER); + } + } + // Operator definition + else if (sc.ch == '`') { + if (funcNameExists) { + sc.SetState(SCE_NIM_FUNCNAME); + } else { + sc.SetState(SCE_NIM_BACKTICKS); + } + } + // Keyword + else if (iswordstart(sc.ch)) { + sc.SetState(SCE_NIM_IDENTIFIER); + } + // Comments + else if (sc.ch == '#') { + if (sc.Match("##[") || sc.Match("#[")) { + commentNestLevel++; + lineCurrent = styler.GetLine(sc.currentPos); + styler.SetLineState(lineCurrent, commentNestLevel); + } + + if (sc.Match("##[")) { + sc.SetState(SCE_NIM_COMMENTDOC); + sc.Forward(); + } else if (sc.Match("#[")) { + sc.SetState(SCE_NIM_COMMENT); + sc.Forward(); + } else if (sc.Match("##")) { + sc.SetState(SCE_NIM_COMMENTLINEDOC); + } else { + sc.SetState(SCE_NIM_COMMENTLINE); + } + } + // Operators + else if (strchr("()[]{}:=;-\\/&%$!+<>|^?,.*~@", sc.ch)) { + sc.SetState(SCE_NIM_OPERATOR); + } + } + + if (sc.atLineEnd) { + funcNameExists = false; + isStylingRawString = false; + isStylingRawStringIdent = false; + } + } + + sc.Complete(); +} + +void SCI_METHOD LexerNim::Fold(Sci_PositionU startPos, Sci_Position length, int, IDocument *pAccess) { + if (!options.fold) { + return; + } + + Accessor styler(pAccess, nullptr); + + const Sci_Position docLines = styler.GetLine(styler.Length()); + const Sci_Position maxPos = startPos + length; + const Sci_Position maxLines = styler.GetLine(maxPos == styler.Length() ? maxPos : maxPos - 1); + + Sci_Position lineCurrent = styler.GetLine(startPos); + int indentCurrent = IndentAmount(lineCurrent, styler); + + while (lineCurrent > 0) { + lineCurrent--; + indentCurrent = IndentAmount(lineCurrent, styler); + + if (!(indentCurrent & SC_FOLDLEVELWHITEFLAG)) { + break; + } + } + + int indentCurrentLevel = indentCurrent & SC_FOLDLEVELNUMBERMASK; + indentCurrent = indentCurrentLevel | (indentCurrent & ~SC_FOLDLEVELNUMBERMASK); + + while (lineCurrent <= docLines && lineCurrent <= maxLines) { + Sci_Position lineNext = lineCurrent + 1; + int indentNext = indentCurrent; + int lev = indentCurrent; + + if (lineNext <= docLines) { + indentNext = IndentAmount(lineNext, styler); + } + + if (indentNext & SC_FOLDLEVELWHITEFLAG) { + indentNext = SC_FOLDLEVELWHITEFLAG | indentCurrentLevel; + } + + while (lineNext < docLines && (indentNext & SC_FOLDLEVELWHITEFLAG)) { + lineNext++; + indentNext = IndentAmount(lineNext, styler); + } + + const int indentNextLevel = indentNext & SC_FOLDLEVELNUMBERMASK; + indentNext = indentNextLevel | (indentNext & ~SC_FOLDLEVELNUMBERMASK); + + const int levelBeforeComments = std::max(indentCurrentLevel, indentNextLevel); + + Sci_Position skipLine = lineNext; + int skipLevel = indentNextLevel; + + while (--skipLine > lineCurrent) { + const int skipLineIndent = IndentAmount(skipLine, styler); + + if (options.foldCompact) { + if ((skipLineIndent & SC_FOLDLEVELNUMBERMASK) > indentNextLevel) { + skipLevel = levelBeforeComments; + } + + const int whiteFlag = skipLineIndent & SC_FOLDLEVELWHITEFLAG; + styler.SetLevel(skipLine, skipLevel | whiteFlag); + } else { + if ((skipLineIndent & SC_FOLDLEVELNUMBERMASK) > indentNextLevel && + !(skipLineIndent & SC_FOLDLEVELWHITEFLAG)) { + skipLevel = levelBeforeComments; + } + + styler.SetLevel(skipLine, skipLevel); + } + } + + if (!(indentCurrent & SC_FOLDLEVELWHITEFLAG)) { + if ((indentCurrent & SC_FOLDLEVELNUMBERMASK) < (indentNext & SC_FOLDLEVELNUMBERMASK)) { + lev |= SC_FOLDLEVELHEADERFLAG; + } + } + + styler.SetLevel(lineCurrent, options.foldCompact ? lev : lev & ~SC_FOLDLEVELWHITEFLAG); + + indentCurrent = indentNext; + indentCurrentLevel = indentNextLevel; + lineCurrent = lineNext; + } +} + +LexerModule lmNim(SCLEX_NIM, LexerNim::LexerFactoryNim, "nim", nimWordListDesc); \ No newline at end of file diff --git a/scintilla/lexilla/src/Lexilla.cxx b/scintilla/lexilla/src/Lexilla.cxx index fee17ecb36..c55111e8f5 100644 --- a/scintilla/lexilla/src/Lexilla.cxx +++ b/scintilla/lexilla/src/Lexilla.cxx @@ -200,6 +200,7 @@ static void AddGeanyLexers() &lmLua, &lmMake, &lmMarkdown, + &lmNim, &lmNsis, &lmNull, &lmOctave, diff --git a/scintilla/scintilla_changes.patch b/scintilla/scintilla_changes.patch index 65f6c8c1db..d79834c6fd 100644 --- a/scintilla/scintilla_changes.patch +++ b/scintilla/scintilla_changes.patch @@ -99,6 +99,7 @@ index cd4b23617..af4a73db4 100644 + &lmLua, + &lmMake, + &lmMarkdown, ++ &lmNim, + &lmNsis, + &lmNull, + &lmOctave, diff --git a/src/filetypes.c b/src/filetypes.c index 05c72f7981..0681c18527 100644 --- a/src/filetypes.c +++ b/src/filetypes.c @@ -192,6 +192,7 @@ static void init_builtin_filetypes(void) FT_INIT( RAKU, RAKU, "Raku", NULL, SOURCE_FILE, SCRIPT ); FT_INIT( CIL, NONE, "CIL", NULL, SOURCE_FILE, COMPILED ); FT_INIT( PROLOG, NONE, "Prolog", NULL, SOURCE_FILE, COMPILED ); + FT_INIT( NIM, NONE, "Nim", NULL, SOURCE_FILE, COMPILED ); } diff --git a/src/filetypes.h b/src/filetypes.h index 9a352ee6df..eea8f893e3 100644 --- a/src/filetypes.h +++ b/src/filetypes.h @@ -112,6 +112,7 @@ typedef enum GEANY_FILETYPES_RAKU, GEANY_FILETYPES_CIL, GEANY_FILETYPES_PROLOG, + GEANY_FILETYPES_NIM, /* ^ append items here */ GEANY_MAX_BUILT_IN_FILETYPES /* Don't use this, use filetypes_array->len instead */ } diff --git a/src/highlighting.c b/src/highlighting.c index 8ed51b6863..bdcd09e194 100644 --- a/src/highlighting.c +++ b/src/highlighting.c @@ -1035,6 +1035,7 @@ void highlighting_init_styles(guint filetype_idx, GKeyFile *config, GKeyFile *co init_styleset_case(MAKE); init_styleset_case(MATLAB); init_styleset_case(MARKDOWN); + init_styleset_case(NIM); init_styleset_case(NSIS); init_styleset_case(OBJECTIVEC); init_styleset_case(PASCAL); @@ -1130,6 +1131,7 @@ void highlighting_set_styles(ScintillaObject *sci, GeanyFiletype *ft) styleset_case(MAKE); styleset_case(MARKDOWN); styleset_case(MATLAB); + styleset_case(NIM); styleset_case(NSIS); styleset_case(OBJECTIVEC); styleset_case(PASCAL); @@ -1676,6 +1678,13 @@ gboolean highlighting_is_string_style(gint lexer, gint style) case SCLEX_AU3: return (style == SCE_AU3_STRING); + + case SCLEX_NIM: + return (style == SCE_NIM_STRING || + style == SCE_NIM_CHARACTER || + style == SCE_NIM_TRIPLE || + style == SCE_NIM_TRIPLEDOUBLE || + style == SCE_NIM_STRINGEOL); } return FALSE; } @@ -1922,6 +1931,12 @@ gboolean highlighting_is_comment_style(gint lexer, gint style) style == SCE_VISUALPROLOG_COMMENT_LINE || style == SCE_VISUALPROLOG_COMMENT_KEY || style == SCE_VISUALPROLOG_COMMENT_KEY_ERROR); + + case SCLEX_NIM: + return (style == SCE_NIM_COMMENT || + style == SCE_NIM_COMMENTDOC || + style == SCE_NIM_COMMENTLINE || + style == SCE_NIM_COMMENTLINEDOC); } return FALSE; } diff --git a/src/highlightingmappings.h b/src/highlightingmappings.h index 7b3972e9b6..f4284baa90 100644 --- a/src/highlightingmappings.h +++ b/src/highlightingmappings.h @@ -1162,6 +1162,35 @@ static const HLKeyword highlighting_keywords_MATLAB[] = #define highlighting_properties_MATLAB EMPTY_PROPERTIES +/* Nim */ +#define highlighting_lexer_NIM SCLEX_NIM +static const HLStyle highlighting_styles_NIM[] = +{ + { SCE_NIM_DEFAULT, "default", FALSE }, + { SCE_NIM_COMMENT, "comment", FALSE }, + { SCE_NIM_COMMENTDOC, "commentdoc", FALSE }, + { SCE_NIM_COMMENTLINE, "commentline", FALSE }, + { SCE_NIM_COMMENTLINEDOC, "commentlinedoc", FALSE }, + { SCE_NIM_NUMBER, "number", FALSE }, + { SCE_NIM_STRING, "string", FALSE }, + { SCE_NIM_CHARACTER, "character", FALSE }, + { SCE_NIM_WORD, "word", FALSE }, + { SCE_NIM_TRIPLE, "triple", FALSE }, + { SCE_NIM_TRIPLEDOUBLE, "tripledouble", FALSE }, + { SCE_NIM_BACKTICKS, "backticks", FALSE }, + { SCE_NIM_FUNCNAME, "funcname", FALSE }, + { SCE_NIM_STRINGEOL, "stringeol", FALSE }, + { SCE_NIM_NUMERROR, "numerror", FALSE }, + { SCE_NIM_OPERATOR, "operator", FALSE }, + { SCE_NIM_IDENTIFIER, "identifier", FALSE } +}; +static const HLKeyword highlighting_keywords_NIM[] = +{ + { 0, "keywords", FALSE }, +}; +#define highlighting_properties_NIM EMPTY_PROPERTIES + + /* NSIS */ #define highlighting_lexer_NSIS SCLEX_NSIS static const HLStyle highlighting_styles_NSIS[] =