From e14a07638393019d38b36bdbf70fcd17fb54a867 Mon Sep 17 00:00:00 2001 From: Skif-off Date: Sat, 27 Jan 2018 06:54:02 +0400 Subject: [PATCH] Add AutoIt syntax highlighting and Ctags parser for AutoIt --- ctags/Makefile.am | 1 + ctags/parsers/autoit.c | 319 ++++++++++ data/Makefile.am | 1 + data/filedefs/filetypes.autoit | 69 +++ data/filetype_extensions.conf | 1 + meson.build | 2 + scintilla/Makefile.am | 1 + scintilla/lexilla/lexers/LexAU3.cxx | 911 ++++++++++++++++++++++++++++ scintilla/lexilla/src/Lexilla.cxx | 1 + scintilla/scintilla_changes.patch | 3 +- src/filetypes.c | 1 + src/filetypes.h | 1 + src/highlighting.c | 9 + src/highlightingmappings.h | 35 ++ src/tagmanager/tm_parser.c | 14 + src/tagmanager/tm_parser.h | 1 + src/tagmanager/tm_parsers.h | 3 +- tests/ctags/Makefile.am | 1 + tests/ctags/simple.au3 | 34 ++ tests/ctags/simple.au3.tags | 8 + tests/meson.build | 1 + 21 files changed, 1415 insertions(+), 2 deletions(-) create mode 100644 ctags/parsers/autoit.c create mode 100644 data/filedefs/filetypes.autoit create mode 100644 scintilla/lexilla/lexers/LexAU3.cxx create mode 100644 tests/ctags/simple.au3 create mode 100644 tests/ctags/simple.au3.tags diff --git a/ctags/Makefile.am b/ctags/Makefile.am index 4cb2d5c5c0..fe980d27f1 100644 --- a/ctags/Makefile.am +++ b/ctags/Makefile.am @@ -48,6 +48,7 @@ parsers = \ parsers/ada.c \ parsers/asciidoc.c \ parsers/asm.c \ + parsers/autoit.c \ parsers/basic.c \ parsers/bibtex.c \ parsers/geany_c.c \ diff --git a/ctags/parsers/autoit.c b/ctags/parsers/autoit.c new file mode 100644 index 0000000000..2d7d03e80f --- /dev/null +++ b/ctags/parsers/autoit.c @@ -0,0 +1,319 @@ +/* +* Copyright (c) 2017, Alexey Olenich +* Copyright (c) 2018, Colomban Wendling +* +* This source code is released for free distribution under the terms of the +* GNU General Public License version 2 or (at your option) any later version. +* +* This module contains functions for generating tags for AutoIt functions. +* Homepage https://www.autoitscript.com/site/autoit/ +* Online Documentation https://www.autoitscript.com/autoit3/docs/ +*/ + +/* +* INCLUDE FILES +*/ +#include "general.h" /* must always come first */ + +#include + +#include "parse.h" +#include "entry.h" +#include "nestlevel.h" +#include "read.h" +#include "routines.h" +#include "vstring.h" + +/* +* DATA DEFINITIONS +*/ +typedef enum { + K_FUNCTION, + K_REGION, + K_GLOBALVAR, + K_LOCALVAR, + K_SCRIPT, +} AutoItKind; + +typedef enum { + R_INCLUDE_SYSTEM, + R_INCLUDE_LOCAL, +} AutoItIncludeRole; + +typedef enum { + F_PROPERTIES, +} AutoItField; + +static roleDefinition AutoItIncludeRoles [] = { + { true, "system", "system include" }, + { true, "local", "local include" }, +}; + +static kindDefinition AutoItKinds [] = { + { true, 'f', "func", "functions" }, + { true, 'r', "region", "regions" }, + { true, 'g', "global", "global variables" }, + { true, 'l', "local", "local variables" }, + { true, 'S', "script", "included scripts", + .referenceOnly = true, ATTACH_ROLES (AutoItIncludeRoles) }, +}; + +static fieldDefinition AutoItFields [] = { + { + .name = "properties", + .description = "properties (static, volatile, ...)", + .enabled = false, + }, +}; + +/* +* FUNCTION DEFINITIONS +*/ + +/* it's unclear what *is* an identifier character, so maybe we're too strict */ +static bool isIdentChar (int c) +{ + return isalnum (c) || c == '_'; +} + +static bool match (const unsigned char *line, const char *word, + const unsigned char **positionAfter) +{ + size_t len = strlen (word); + bool matched = (strncasecmp ((const char*) line, word, len) == 0 && + ! isIdentChar (line[len])); + + if (matched && positionAfter) + *positionAfter = line + len; + + return matched; +} + +static int makeAutoItTag (const NestingLevels *const nls, + const vString* const name, + const int kindIndex, + const vString *const signature) +{ + int r = CORK_NIL; + + if (isInputLanguageKindEnabled(kindIndex) && name != NULL && vStringLength (name) > 0) + { + NestingLevel *nl = nestingLevelsGetCurrent (nls); + tagEntryInfo e; + + initTagEntry (&e, vStringValue (name), kindIndex); + + if (nl) + e.extensionFields.scopeIndex = nl->corkIndex; + if (signature) + e.extensionFields.signature = vStringValue (signature); + + r = makeTagEntry (&e); + } + + return r; +} + +static int makeSimpleAutoItTag (const NestingLevels *const nls, + const vString* const name, + const int kindIndex) +{ + return makeAutoItTag (nls, name, kindIndex, NULL); +} + +static void setEndLine (const NestingLevels *const nls) +{ + NestingLevel *nl = nestingLevelsGetCurrent (nls); + tagEntryInfo *entry; + + if (nl && (entry = getEntryInCorkQueue (nl->corkIndex)) != NULL) + entry->extensionFields.endLine = getInputLineNumber (); +} + +static void skipSpaces (const unsigned char **p) +{ + while (isspace ((int) **p)) + ++(*p); +} + +/* parses after a "func" keyword */ +static int parseFunc (const unsigned char *p, NestingLevels *nls) +{ + int k = CORK_NIL; + vString *name = vStringNew (); + + skipSpaces (&p); + while (isIdentChar ((int) *p)) + { + vStringPut (name, (int) *p); + ++p; + } + skipSpaces (&p); + if (*p == '(' && (vStringLength (name) > 0)) + { + vString *signature = vStringNew (); + + do + vStringPut (signature, (int) *p); + while (*p != ')' && *p++); + + k = makeAutoItTag (nls, name, K_FUNCTION, signature); + nestingLevelsPush (nls, k); + vStringDelete (signature); + } + + vStringDelete (name); + + return k; +} + +static void findAutoItTags (void) +{ + vString *name = vStringNew (); + const unsigned char *line; + NestingLevels *nls = nestingLevelsNew (0); + unsigned int commentDepth = 0; + + while ((line = readLineFromInputFile ()) != NULL) + { + const unsigned char* p = line; + if (p [0] == '#') + { + p++; + if (match (p, "cs", NULL) || match (p, "comments-start", NULL)) + commentDepth++; + else if (commentDepth > 0) + { + if (match (p, "ce", NULL) || match (p, "comments-end", NULL)) + commentDepth--; + } + else if (match (p, "region", &p)) + { + skipSpaces (&p); + while (*p != '\0') + { + vStringPut (name, (int) *p); + ++p; + } + + if (vStringLength(name) > 0) + { + int k = makeSimpleAutoItTag (nls, name, K_REGION); + nestingLevelsPush (nls, k); + vStringClear (name); + } + } + else if (nls->n > 0 && match (p, "endregion", NULL)) + { + setEndLine (nls); + nestingLevelsPop (nls); + } + else if (match (p, "include", &p)) + { + skipSpaces (&p); + if (*p == '<' || *p == '"') + { + const AutoItIncludeRole role = (*p == '<') + ? R_INCLUDE_SYSTEM + : R_INCLUDE_LOCAL; + + ++p; + while (*p != '\0' && *p != '>' && *p != '"') + { + vStringPut (name, (int) *p); + ++p; + } + if (vStringLength(name) > 0) + { + makeSimpleRefTag (name, K_SCRIPT, role); + vStringClear (name); + } + } + } + } + else if (commentDepth == 0) + { + bool isGlobal = false; + bool isStatic = false; + + /* skip white space */ + skipSpaces (&p); + if (*p == ';') + /* ignore single-line comments */; + else if (match (p, "volatile", &p)) + { + skipSpaces (&p); + if (match (p, "func", &p)) + { + int k = parseFunc (p, nls); + if (k != CORK_NIL) + attachParserFieldToCorkEntry (k, AutoItFields[F_PROPERTIES].ftype, "volatile"); + } + } + else if (match (p, "func", &p)) + parseFunc (p, nls); + else if (nls->n > 0 && match (p, "endfunc", NULL)) + { + setEndLine (nls); + nestingLevelsPop (nls); + } + else if ((isStatic = match (p, "static", &p)) || + (isGlobal = match (p, "global", &p)) || + match (p, "local", &p)) + { + /* + * variable-identifier ::= "$[a-zA-Z_0-9]+" + * scope-modifier ::= "Local" | "Global" + * variable-declaration ::= "Static" [scope-modifier] variable-identifier + * scope-modifier ["Static" | "Const"] variable-identifier + */ + skipSpaces (&p); + if (isStatic) + { + /* skip optional modifiers */ + if ((isGlobal = match (p, "global", &p)) || + match (p, "local", &p)) + { + skipSpaces (&p); + } + } + else if ((isStatic = match (p, "static", &p))) + skipSpaces (&p); + else if (match (p, "const", &p)) + skipSpaces (&p); + if (*p == '$') + { + vStringPut (name, (int) *p++); + while (isIdentChar ((int) *p)) + { + vStringPut (name, (int) *p); + ++p; + } + if (vStringLength(name) > 0) + { + int k = makeSimpleAutoItTag (nls, name, isGlobal ? K_GLOBALVAR : K_LOCALVAR); + if (k != CORK_NIL && isStatic) + attachParserFieldToCorkEntry (k, AutoItFields[F_PROPERTIES].ftype, "static"); + } + vStringClear (name); + } + } + } + } + vStringDelete (name); + nestingLevelsFree (nls); +} + +parserDefinition *AutoItParser (void) +{ + static char const *extensions[] = { "au3", "AU3", "aU3", "Au3", NULL }; + parserDefinition* def = parserNew ("AutoIt"); + def->kindTable = AutoItKinds; + def->kindCount = ARRAY_SIZE (AutoItKinds); + def->fieldTable = AutoItFields; + def->fieldCount = ARRAY_SIZE (AutoItFields); + def->extensions = extensions; + def->parser = findAutoItTags; + def->useCork = CORK_QUEUE; + return def; +} diff --git a/data/Makefile.am b/data/Makefile.am index b7c633751e..6d19397d8e 100644 --- a/data/Makefile.am +++ b/data/Makefile.am @@ -10,6 +10,7 @@ filetypes_dist = \ filedefs/filetypes.Arduino.conf \ filedefs/filetypes.asciidoc \ filedefs/filetypes.asm \ + filedefs/filetypes.autoit \ filedefs/filetypes.batch \ filedefs/filetypes.bibtex \ filedefs/filetypes.c \ diff --git a/data/filedefs/filetypes.autoit b/data/filedefs/filetypes.autoit new file mode 100644 index 0000000000..10a31152d0 --- /dev/null +++ b/data/filedefs/filetypes.autoit @@ -0,0 +1,69 @@ +# For complete documentation of this file, please see Geany's main documentation +# AutoIt 3.3.16.1 +[styling] +# Edit these in the colorscheme .conf file instead +default=default +comment=comment_line +commentblock=comment +number=number +function=function +keyword=keyword +macro=preprocessor +string=string +operator=operator +variable=keyword_2 +sent=other +preprocessor=preprocessor +special=type +expand=default +comobj=keyword_1 +udf=class + +[keywords] +keywords=and byref case const continuecase continueloop default dim do else elseif endfunc endif endselect endswitch endwith enum exit exitloop false for func global if in local next not null or redim return select static step switch then to true until volatile wend while with +functions=abs acos adlibregister adlibunregister asc ascw asin assign atan autoitsetoption autoitwingettitle autoitwinsettitle beep binary binarylen binarymid binarytostring bitand bitnot bitor bitrotate bitshift bitxor blockinput break call cdtray ceiling chr chrw clipget clipput consoleread consolewrite consolewriteerror controlclick controlcommand controldisable controlenable controlfocus controlgetfocus controlgethandle controlgetpos controlgettext controlhide controllistview controlmove controlsend controlsettext controlshow controltreeview cos dec dircopy dircreate dirgetsize dirmove dirremove dllcall dllcalladdress dllcallbackfree dllcallbackgetptr dllcallbackregister dllclose dllopen dllstructcreate dllstructgetdata dllstructgetptr dllstructgetsize dllstructsetdata drivegetdrive drivegetfilesystem drivegetlabel drivegetserial drivegettype drivemapadd drivemapdel drivemapget drivesetlabel drivespacefree drivespacetotal drivestatus envget envset envupdate eval execute exp filechangedir fileclose filecopy filecreatentfslink filecreateshortcut filedelete fileexists filefindfirstfile filefindnextfile fileflush filegetattrib filegetencoding filegetlongname filegetpos filegetshortcut filegetshortname filegetsize filegettime filegetversion fileinstall filemove fileopen fileopendialog fileread filereadline filereadtoarray filerecycle filerecycleempty filesavedialog fileselectfolder filesetattrib filesetend filesetpos filesettime filewrite filewriteline floor ftpsetproxy funcname guicreate guictrlcreateavi guictrlcreatebutton guictrlcreatecheckbox guictrlcreatecombo guictrlcreatecontextmenu guictrlcreatedate guictrlcreatedummy guictrlcreateedit guictrlcreategraphic guictrlcreategroup guictrlcreateicon guictrlcreateinput guictrlcreatelabel guictrlcreatelist guictrlcreatelistview guictrlcreatelistviewitem guictrlcreatemenu guictrlcreatemenuitem guictrlcreatemonthcal guictrlcreateobj guictrlcreatepic guictrlcreateprogress guictrlcreateradio guictrlcreateslider guictrlcreatetab guictrlcreatetabitem guictrlcreatetreeview guictrlcreatetreeviewitem guictrlcreateupdown guictrldelete guictrlgethandle guictrlgetstate guictrlread guictrlrecvmsg guictrlregisterlistviewsort guictrlsendmsg guictrlsendtodummy guictrlsetbkcolor guictrlsetcolor guictrlsetcursor guictrlsetdata guictrlsetdefbkcolor guictrlsetdefcolor guictrlsetfont guictrlsetgraphic guictrlsetimage guictrlsetlimit guictrlsetonevent guictrlsetpos guictrlsetresizing guictrlsetstate guictrlsetstyle guictrlsettip guidelete guigetcursorinfo guigetmsg guigetstyle guiregistermsg guisetaccelerators guisetbkcolor guisetcoord guisetcursor guisetfont guisethelp guiseticon guisetonevent guisetstate guisetstyle guistartgroup guiswitch hex hotkeyset httpsetproxy httpsetuseragent hwnd inetclose inetget inetgetinfo inetgetsize inetread inidelete iniread inireadsection inireadsectionnames inirenamesection iniwrite iniwritesection inputbox int isadmin isarray isbinary isbool isdeclared isdllstruct isfloat isfunc ishwnd isint iskeyword isnumber isobj isptr isstring log mapappend mapexists mapkeys mapremove memgetstats mod mouseclick mouseclickdrag mousedown mousegetcursor mousegetpos mousemove mouseup mousewheel msgbox number objcreate objcreateinterface objevent objevent objget objname onautoitexitregister onautoitexitunregister opt ping pixelchecksum pixelgetcolor pixelsearch processclose processexists processgetstats processlist processsetpriority processwait processwaitclose progressoff progresson progressset ptr random regdelete regenumkey regenumval regread regwrite round run runas runaswait runwait send sendkeepactive seterror setextended shellexecute shellexecutewait shutdown sin sleep soundplay soundsetwavevolume splashimageon splashoff splashtexton sqrt srandom statusbargettext stderrread stdinwrite stdioclose stdoutread string stringaddcr stringcompare stringformat stringfromasciiarray stringinstr stringisalnum stringisalpha stringisascii stringisdigit stringisfloat stringisint stringislower stringisspace stringisupper stringisxdigit stringleft stringlen stringlower stringmid stringregexp stringregexpreplace stringreplace stringreverse stringright stringsplit stringstripcr stringstripws stringtoasciiarray stringtobinary stringtrimleft stringtrimright stringupper tan tcpaccept tcpclosesocket tcpconnect tcplisten tcpnametoip tcprecv tcpsend tcpshutdown tcpstartup timerdiff timerinit tooltip traycreateitem traycreatemenu traygetmsg trayitemdelete trayitemgethandle trayitemgetstate trayitemgettext trayitemsetonevent trayitemsetstate trayitemsettext traysetclick trayseticon traysetonevent traysetpauseicon traysetstate traysettooltip traytip ubound udpbind udpclosesocket udpopen udprecv udpsend udpshutdown udpstartup vargettype winactivate winactive winclose winexists winflash wingetcaretpos wingetclasslist wingetclientsize wingethandle wingetpos wingetprocess wingetstate wingettext wingettitle winkill winlist winmenuselectitem winminimizeall winminimizeallundo winmove winsetontop winsetstate winsettitle winsettrans winwait winwaitactive winwaitclose winwaitnotactive +macros=@appdatacommondir @appdatadir @autoitexe @autoitpid @autoitversion @autoitx64 @com_eventobj @commonfilesdir @compiled @computername @comspec @cpuarch @cr @crlf @desktopcommondir @desktopdepth @desktopdir @desktopheight @desktoprefresh @desktopwidth @documentscommondir @error @exitcode @exitmethod @extended @favoritescommondir @favoritesdir @gui_ctrlhandle @gui_ctrlid @gui_dragfile @gui_dragid @gui_dropid @gui_winhandle @homedrive @homepath @homeshare @hotkeypressed @hour @ipaddress1 @ipaddress2 @ipaddress3 @ipaddress4 @kblayout @lf @localappdatadir @logondnsdomain @logondomain @logonserver @mday @min @mon @msec @muilang @mydocumentsdir @numparams @osarch @osbuild @oslang @osservicepack @ostype @osversion @programfilesdir @programscommondir @programsdir @scriptdir @scriptfullpath @scriptlinenumber @scriptname @sec @startmenucommondir @startmenudir @startupcommondir @startupdir @sw_disable @sw_enable @sw_hide @sw_lock @sw_maximize @sw_minimize @sw_restore @sw_show @sw_showdefault @sw_showmaximized @sw_showminimized @sw_showminnoactive @sw_showna @sw_shownoactivate @sw_shownormal @sw_unlock @systemdir @tab @tempdir @tray_id @trayiconflashing @trayiconvisible @username @userprofiledir @wday @windowsdir @workingdir @yday @year +sent={!} {#} {^} {{} {}} {+} {alt} {altdown} {altup} {appskey} {asc} {backspace} {break} {browser_back} {browser_favorites} {browser_forward} {browser_home} {browser_refresh} {browser_search} {browser_stop} {bs} {capslock} {ctrldown} {ctrlup} {del} {delete} {down} {end} {enter} {esc} {escape} {f1} {f10} {f11} {f12} {f2} {f3} {f4} {f5} {f6} {f7} {f8} {f9} {home} {ins} {insert} {lalt} {launch_app1} {launch_app2} {launch_mail} {launch_media} {lctrl} {left} {lshift} {lwin} {lwindown} {lwinup} {media_next} {media_play_pause} {media_prev} {media_stop} {numlock} {numpad0} {numpad1} {numpad2} {numpad3} {numpad4} {numpad5} {numpad6} {numpad7} {numpad8} {numpad9} {numpadadd} {numpaddiv} {numpaddot} {numpadenter} {numpadmult} {numpadsub} {oem_102} {pause} {pgdn} {pgup} {printscreen} {ralt} {rctrl} {right} {rshift} {rwin} {rwindown} {rwinup} {scrolllock} {shiftdown} {shiftup} {sleep} {space} {tab} {up} {volume_down} {volume_mute} {volume_up} +preprocessor=#ce #comments-end #comments-start #cs #include #include-once #notrayicon #onautoitstartregister #requireadmin +special=#endregion #forcedef #forceref #ignorefunc #pragma #region +expand= +udf= + +[settings] +# default extension used when saving files +extension=au3 + +# 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=#cs +comment_close=#ce +# or alternatively +#comment_open=#comments-start +#comment_close=#comments-end + +# 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_settings] +# %f will be replaced by the complete filename +# %e will be replaced by the filename without extension +compiler=Aut2exe.exe /in "%f" +run_cmd=AutoIt3.exe "%f" diff --git a/data/filetype_extensions.conf b/data/filetype_extensions.conf index d534eb74bc..7c2f74f961 100644 --- a/data/filetype_extensions.conf +++ b/data/filetype_extensions.conf @@ -9,6 +9,7 @@ Ada=*.adb;*.ads; Arduino=*.ino;*.pde; Asciidoc=*.asciidoc;*.adoc; ASM=*.asm;*.asm51;*.a51;*.s;*.S;*.sx; +AutoIt=*.au3; Batch=*.bat;*.cmd;*.nt; BibTeX=*.bib; CAML=*.ml;*.mli; diff --git a/meson.build b/meson.build index 6944a423b4..15a37704ea 100644 --- a/meson.build +++ b/meson.build @@ -253,6 +253,7 @@ lexilla = static_library('lexilla', 'scintilla/lexilla/lexers/LexAda.cxx', 'scintilla/lexilla/lexers/LexAsciidoc.cxx', 'scintilla/lexilla/lexers/LexAsm.cxx', + 'scintilla/lexilla/lexers/LexAU3.cxx', 'scintilla/lexilla/lexers/LexBash.cxx', 'scintilla/lexilla/lexers/LexBasic.cxx', 'scintilla/lexilla/lexers/LexBatch.cxx', @@ -594,6 +595,7 @@ ctags = static_library('ctags', 'ctags/parsers/ada.c', 'ctags/parsers/asciidoc.c', 'ctags/parsers/asm.c', + 'ctags/parsers/autoit.c', 'ctags/parsers/basic.c', 'ctags/parsers/bibtex.c', 'ctags/parsers/clojure.c', diff --git a/scintilla/Makefile.am b/scintilla/Makefile.am index cf13dd69a9..198ec596fb 100644 --- a/scintilla/Makefile.am +++ b/scintilla/Makefile.am @@ -16,6 +16,7 @@ lexilla/lexers/LexAbaqus.cxx \ lexilla/lexers/LexAda.cxx \ lexilla/lexers/LexAsciidoc.cxx \ lexilla/lexers/LexAsm.cxx \ +lexilla/lexers/LexAU3.cxx \ lexilla/lexers/LexBash.cxx \ lexilla/lexers/LexBasic.cxx \ lexilla/lexers/LexBatch.cxx \ diff --git a/scintilla/lexilla/lexers/LexAU3.cxx b/scintilla/lexilla/lexers/LexAU3.cxx new file mode 100644 index 0000000000..897375459f --- /dev/null +++ b/scintilla/lexilla/lexers/LexAU3.cxx @@ -0,0 +1,911 @@ +// Scintilla source code edit control +// @file LexAU3.cxx +// Lexer for AutoIt3 https://www.autoitscript.com/site/ +// by Jos van der Zande, jvdzande@yahoo.com +// +// Changes: +// March 28, 2004 - Added the standard Folding code +// April 21, 2004 - Added Preprosessor Table + Syntax Highlighting +// Fixed Number highlighting +// Changed default isoperator to IsAOperator to have a better match to AutoIt3 +// Fixed "#comments_start" -> "#comments-start" +// Fixed "#comments_end" -> "#comments-end" +// Fixed Sendkeys in Strings when not terminated with } +// Added support for Sendkey strings that have second parameter e.g. {UP 5} or {a down} +// April 26, 2004 - Fixed # pre-processor statement inside of comment block would invalidly change the color. +// Added logic for #include to treat the <> as string +// Added underscore to IsAOperator. +// May 17, 2004 - Changed the folding logic from indent to keyword folding. +// Added Folding logic for blocks of single-commentlines or commentblock. +// triggered by: fold.comment=1 +// Added Folding logic for preprocessor blocks triggered by fold.preprocessor=1 +// Added Special for #region - #endregion syntax highlight and folding. +// May 30, 2004 - Fixed issue with continuation lines on If statements. +// June 5, 2004 - Added comma to Operators for better readability. +// Added fold.compact support set with fold.compact=1 +// Changed folding inside of #cs-#ce. Default is no keyword folding inside comment blocks when fold.comment=1 +// it will now only happen when fold.comment=2. +// Sep 5, 2004 - Added logic to handle colourizing words on the last line. +// Typed Characters now show as "default" till they match any table. +// Oct 10, 2004 - Added logic to show Comments in "Special" directives. +// Nov 1, 2004 - Added better testing for Numbers supporting x and e notation. +// Nov 28, 2004 - Added logic to handle continuation lines for syntax highlighting. +// Jan 10, 2005 - Added Abbreviations Keyword used for expansion +// Mar 24, 2005 - Updated Abbreviations Keywords to fix when followed by Operator. +// Apr 18, 2005 - Updated #CE/#Comment-End logic to take a linecomment ";" into account +// - Added folding support for With...EndWith +// - Added support for a DOT in variable names +// - Fixed Underscore in CommentBlock +// May 23, 2005 - Fixed the SentKey lexing in case of a missing } +// Aug 11, 2005 - Fixed possible bug with s_save length > 100. +// Aug 23, 2005 - Added Switch/endswitch support to the folding logic. +// Sep 27, 2005 - Fixed the SentKey lexing logic in case of multiple sentkeys. +// Mar 12, 2006 - Fixed issue with <> coloring as String in stead of Operator in rare occasions. +// Apr 8, 2006 - Added support for AutoIt3 Standard UDF library (SCE_AU3_UDF) +// Mar 9, 2007 - Fixed bug with + following a String getting the wrong Color. +// Jun 20, 2007 - Fixed Commentblock issue when LF's are used as EOL. +// Jul 26, 2007 - Fixed #endregion undetected bug. +// +// Copyright for Scintilla: 1998-2001 by Neil Hodgson +// The License.txt file describes the conditions under which this software may be distributed. +// Scintilla source code edit control + +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "ILexer.h" +#include "Scintilla.h" +#include "SciLexer.h" + +#include "WordList.h" +#include "LexAccessor.h" +#include "Accessor.h" +#include "StyleContext.h" +#include "CharacterSet.h" +#include "LexerModule.h" + +using namespace Lexilla; + +static inline bool IsTypeCharacter(const int ch) +{ + return ch == '$'; +} +static inline bool IsAWordChar(const int ch) +{ + return (ch < 0x80) && (isalnum(ch) || ch == '_'); +} + +static inline bool IsAWordStart(const int ch) +{ + return (ch < 0x80) && (isalnum(ch) || ch == '_' || ch == '@' || ch == '#' || ch == '$' || ch == '.'); +} + +static inline bool IsAOperator(char ch) { + if (IsASCII(ch) && isalnum(ch)) + return false; + if (ch == '+' || ch == '-' || ch == '*' || ch == '/' || + ch == '&' || ch == '^' || ch == '=' || ch == '<' || ch == '>' || + ch == '(' || ch == ')' || ch == '[' || ch == ']' || ch == ',' ) + return true; + return false; +} + +/////////////////////////////////////////////////////////////////////////////// +// GetSendKey() filters the portion before and after a/multiple space(s) +// and return the first portion to be looked-up in the table +// also check if the second portion is valid... (up,down.on.off,toggle or a number) +/////////////////////////////////////////////////////////////////////////////// + +static int GetSendKey(const char *szLine, char *szKey) +{ + int nFlag = 0; + int nStartFound = 0; + int nKeyPos = 0; + int nSpecPos= 0; + int nSpecNum= 1; + int nPos = 0; + char cTemp; + char szSpecial[100]; + + // split the portion of the sendkey in the part before and after the spaces + while ( ( (cTemp = szLine[nPos]) != '\0')) + { + // skip leading Ctrl/Shift/Alt state + if (cTemp == '{') { + nStartFound = 1; + } + // + if (nStartFound == 1) { + if ((cTemp == ' ') && (nFlag == 0) ) // get the stuff till first space + { + nFlag = 1; + // Add } to the end of the first bit for table lookup later. + szKey[nKeyPos++] = '}'; + } + else if (cTemp == ' ') + { + // skip other spaces + } + else if (nFlag == 0) + { + // save first portion into var till space or } is hit + szKey[nKeyPos++] = cTemp; + } + else if ((nFlag == 1) && (cTemp != '}')) + { + // Save second portion into var... + szSpecial[nSpecPos++] = cTemp; + // check if Second portion is all numbers for repeat fuction + if (isdigit(cTemp) == false) {nSpecNum = 0;} + } + } + nPos++; // skip to next char + + } // End While + + + // Check if the second portion is either a number or one of these keywords + szKey[nKeyPos] = '\0'; + szSpecial[nSpecPos] = '\0'; + if (strcmp(szSpecial,"down")== 0 || strcmp(szSpecial,"up")== 0 || + strcmp(szSpecial,"on")== 0 || strcmp(szSpecial,"off")== 0 || + strcmp(szSpecial,"toggle")== 0 || nSpecNum == 1 ) + { + nFlag = 0; + } + else + { + nFlag = 1; + } + return nFlag; // 1 is bad, 0 is good + +} // GetSendKey() + +// +// Routine to check the last "none comment" character on a line to see if its a continuation +// +static bool IsContinuationLine(Sci_PositionU szLine, Accessor &styler) +{ + Sci_Position nsPos = styler.LineStart(szLine); + Sci_Position nePos = styler.LineStart(szLine+1) - 2; + //int stylech = styler.StyleAt(nsPos); + while (nsPos < nePos) + { + //stylech = styler.StyleAt(nePos); + int stylech = styler.StyleAt(nsPos); + if (!(stylech == SCE_AU3_COMMENT)) { + char ch = styler.SafeGetCharAt(nePos); + if (!isspacechar(ch)) { + if (ch == '_') + return true; + else + return false; + } + } + nePos--; // skip to next char + } // End While + return false; +} // IsContinuationLine() + +// +// syntax highlighting logic +static void ColouriseAU3Doc(Sci_PositionU startPos, + Sci_Position length, int initStyle, + WordList *keywordlists[], + Accessor &styler) { + + WordList &keywords = *keywordlists[0]; + WordList &keywords2 = *keywordlists[1]; + WordList &keywords3 = *keywordlists[2]; + WordList &keywords4 = *keywordlists[3]; + WordList &keywords5 = *keywordlists[4]; + WordList &keywords6 = *keywordlists[5]; + WordList &keywords7 = *keywordlists[6]; + WordList &keywords8 = *keywordlists[7]; + // find the first previous line without continuation character at the end + Sci_Position lineCurrent = styler.GetLine(startPos); + Sci_Position s_startPos = startPos; + // When not inside a Block comment: find First line without _ + if (!(initStyle==SCE_AU3_COMMENTBLOCK)) { + while ((lineCurrent > 0 && IsContinuationLine(lineCurrent,styler)) || + (lineCurrent > 1 && IsContinuationLine(lineCurrent-1,styler))) { + lineCurrent--; + startPos = styler.LineStart(lineCurrent); // get start position + initStyle = 0; // reset the start style to 0 + } + } + // Set the new length to include it from the start and set the start position + length = length + s_startPos - startPos; // correct the total length to process + styler.StartAt(startPos); + + StyleContext sc(startPos, length, initStyle, styler); + char si; // string indicator "=1 '=2 + char ni; // Numeric indicator error=9 normal=0 normal+dec=1 hex=2 Enot=3 + char ci; // comment indicator 0=not linecomment(;) + char s_save[100] = ""; + si=0; + ni=0; + ci=0; + //$$$ + for (; sc.More(); sc.Forward()) { + char s[100]; + sc.GetCurrentLowered(s, sizeof(s)); + // ********************************************** + // save the total current word for eof processing + if (IsAWordChar(sc.ch) || sc.ch == '}') + { + strcpy(s_save,s); + int tp = static_cast(strlen(s_save)); + if (tp < 99) { + s_save[tp] = static_cast(tolower(sc.ch)); + s_save[tp+1] = '\0'; + } + } + // ********************************************** + // + switch (sc.state) + { + case SCE_AU3_COMMENTBLOCK: + { + //Reset at line end + if (sc.atLineEnd) { + ci=0; + if (strcmp(s, "#ce")== 0 || strcmp(s, "#comments-end")== 0) { + if (sc.atLineEnd) + sc.SetState(SCE_AU3_DEFAULT); + else + sc.SetState(SCE_AU3_COMMENTBLOCK); + } + break; + } + //skip rest of line when a ; is encountered + if (sc.chPrev == ';') { + ci=2; + sc.SetState(SCE_AU3_COMMENTBLOCK); + } + // skip rest of the line + if (ci==2) + break; + // check when first character is detected on the line + if (ci==0) { + if (IsAWordStart(static_cast(sc.ch)) || IsAOperator(static_cast(sc.ch))) { + ci=1; + sc.SetState(SCE_AU3_COMMENTBLOCK); + } + break; + } + if (!(IsAWordChar(sc.ch) || (sc.ch == '-' && strcmp(s, "#comments") == 0))) { + if ((strcmp(s, "#ce")== 0 || strcmp(s, "#comments-end")== 0)) + sc.SetState(SCE_AU3_COMMENT); // set to comment line for the rest of the line + else + ci=2; // line doesn't begin with #CE so skip the rest of the line + } + break; + } + case SCE_AU3_COMMENT: + { + if (sc.atLineEnd) {sc.SetState(SCE_AU3_DEFAULT);} + break; + } + case SCE_AU3_OPERATOR: + { + // check if its a COMobject + if (sc.chPrev == '.' && IsAWordChar(sc.ch)) { + sc.SetState(SCE_AU3_COMOBJ); + } + else { + sc.SetState(SCE_AU3_DEFAULT); + } + break; + } + case SCE_AU3_SPECIAL: + { + if (sc.ch == ';') {sc.SetState(SCE_AU3_COMMENT);} + if (sc.atLineEnd) {sc.SetState(SCE_AU3_DEFAULT);} + break; + } + case SCE_AU3_KEYWORD: + { + if (!(IsAWordChar(sc.ch) || (sc.ch == '-' && (strcmp(s, "#comments") == 0 || strcmp(s, "#include") == 0)))) + { + if (!IsTypeCharacter(sc.ch)) + { + if (strcmp(s, "#cs")== 0 || strcmp(s, "#comments-start")== 0 ) + { + sc.ChangeState(SCE_AU3_COMMENTBLOCK); + sc.SetState(SCE_AU3_COMMENTBLOCK); + break; + } + else if (keywords.InList(s)) { + sc.ChangeState(SCE_AU3_KEYWORD); + sc.SetState(SCE_AU3_DEFAULT); + } + else if (keywords2.InList(s)) { + sc.ChangeState(SCE_AU3_FUNCTION); + sc.SetState(SCE_AU3_DEFAULT); + } + else if (keywords3.InList(s)) { + sc.ChangeState(SCE_AU3_MACRO); + sc.SetState(SCE_AU3_DEFAULT); + } + else if (keywords5.InList(s)) { + sc.ChangeState(SCE_AU3_PREPROCESSOR); + sc.SetState(SCE_AU3_DEFAULT); + if (strcmp(s, "#include")== 0) + { + si = 3; // use to determine string start for #inlude <> + } + } + else if (keywords6.InList(s)) { + sc.ChangeState(SCE_AU3_SPECIAL); + sc.SetState(SCE_AU3_SPECIAL); + } + else if ((keywords7.InList(s)) && (!IsAOperator(static_cast(sc.ch)))) { + sc.ChangeState(SCE_AU3_EXPAND); + sc.SetState(SCE_AU3_DEFAULT); + } + else if (keywords8.InList(s)) { + sc.ChangeState(SCE_AU3_UDF); + sc.SetState(SCE_AU3_DEFAULT); + } + else if (strcmp(s, "_") == 0) { + sc.ChangeState(SCE_AU3_OPERATOR); + sc.SetState(SCE_AU3_DEFAULT); + } + else if (!IsAWordChar(sc.ch)) { + sc.ChangeState(SCE_AU3_DEFAULT); + sc.SetState(SCE_AU3_DEFAULT); + } + } + } + if (sc.atLineEnd) { + sc.SetState(SCE_AU3_DEFAULT);} + break; + } + case SCE_AU3_NUMBER: + { + // Numeric indicator error=9 normal=0 normal+dec=1 hex=2 E-not=3 + // + // test for Hex notation + if (strcmp(s, "0") == 0 && (sc.ch == 'x' || sc.ch == 'X') && ni == 0) + { + ni = 2; + break; + } + // test for E notation + if (IsADigit(sc.chPrev) && (sc.ch == 'e' || sc.ch == 'E') && ni <= 1) + { + ni = 3; + break; + } + // Allow Hex characters inside hex numeric strings + if ((ni == 2) && + (sc.ch == 'a' || sc.ch == 'b' || sc.ch == 'c' || sc.ch == 'd' || sc.ch == 'e' || sc.ch == 'f' || + sc.ch == 'A' || sc.ch == 'B' || sc.ch == 'C' || sc.ch == 'D' || sc.ch == 'E' || sc.ch == 'F' )) + { + break; + } + // test for 1 dec point only + if (sc.ch == '.') + { + if (ni==0) + { + ni=1; + } + else + { + ni=9; + } + break; + } + // end of numeric string ? + if (!(IsADigit(sc.ch))) + { + if (ni==9) + { + sc.ChangeState(SCE_AU3_DEFAULT); + } + sc.SetState(SCE_AU3_DEFAULT); + } + break; + } + case SCE_AU3_VARIABLE: + { + // Check if its a COMObject + if (sc.ch == '.' && !IsADigit(sc.chNext)) { + sc.SetState(SCE_AU3_OPERATOR); + } + else if (!IsAWordChar(sc.ch)) { + sc.SetState(SCE_AU3_DEFAULT); + } + break; + } + case SCE_AU3_COMOBJ: + { + if (!(IsAWordChar(sc.ch))) { + sc.SetState(SCE_AU3_DEFAULT); + } + break; + } + case SCE_AU3_STRING: + { + // check for " to end a double qouted string or + // check for ' to end a single qouted string + if ((si == 1 && sc.ch == '\"') || (si == 2 && sc.ch == '\'') || (si == 3 && sc.ch == '>')) + { + sc.ForwardSetState(SCE_AU3_DEFAULT); + si=0; + break; + } + if (sc.atLineEnd) + { + si=0; + // at line end and not found a continuation char then reset to default + Sci_Position lineCurrent = styler.GetLine(sc.currentPos); + if (!IsContinuationLine(lineCurrent,styler)) + { + sc.SetState(SCE_AU3_DEFAULT); + break; + } + } + // find Sendkeys in a STRING + if (sc.ch == '{' || sc.ch == '+' || sc.ch == '!' || sc.ch == '^' || sc.ch == '#' ) { + sc.SetState(SCE_AU3_SENT);} + break; + } + + case SCE_AU3_SENT: + { + // Send key string ended + if (sc.chPrev == '}' && sc.ch != '}') + { + // set color to SENDKEY when valid sendkey .. else set back to regular string + char sk[100]; + // split {111 222} and return {111} and check if 222 is valid. + // if return code = 1 then invalid 222 so must be string + if (GetSendKey(s,sk)) + { + sc.ChangeState(SCE_AU3_STRING); + } + // if single char between {?} then its ok as sendkey for a single character + else if (strlen(sk) == 3) + { + sc.ChangeState(SCE_AU3_SENT); + } + // if sendkey {111} is in table then ok as sendkey + else if (keywords4.InList(sk)) + { + sc.ChangeState(SCE_AU3_SENT); + } + else + { + sc.ChangeState(SCE_AU3_STRING); + } + sc.SetState(SCE_AU3_STRING); + } + else + { + // check if the start is a valid SendKey start + Sci_Position nPos = 0; + int nState = 1; + char cTemp; + while (!(nState == 2) && ((cTemp = s[nPos]) != '\0')) + { + if (cTemp == '{' && nState == 1) + { + nState = 2; + } + if (nState == 1 && !(cTemp == '+' || cTemp == '!' || cTemp == '^' || cTemp == '#' )) + { + nState = 0; + } + nPos++; + } + //Verify characters infront of { ... if not assume regular string + if (nState == 1 && (!(sc.ch == '{' || sc.ch == '+' || sc.ch == '!' || sc.ch == '^' || sc.ch == '#' ))) { + sc.ChangeState(SCE_AU3_STRING); + sc.SetState(SCE_AU3_STRING); + } + // If invalid character found then assume its a regular string + if (nState == 0) { + sc.ChangeState(SCE_AU3_STRING); + sc.SetState(SCE_AU3_STRING); + } + } + // check if next portion is again a sendkey + if (sc.atLineEnd) + { + sc.ChangeState(SCE_AU3_STRING); + sc.SetState(SCE_AU3_DEFAULT); + si = 0; // reset string indicator + } + //* check in next characters following a sentkey are again a sent key + // Need this test incase of 2 sentkeys like {F1}{ENTER} but not detect {{} + if (sc.state == SCE_AU3_STRING && (sc.ch == '{' || sc.ch == '+' || sc.ch == '!' || sc.ch == '^' || sc.ch == '#' )) { + sc.SetState(SCE_AU3_SENT);} + // check to see if the string ended... + // Sendkey string isn't complete but the string ended.... + if ((si == 1 && sc.ch == '\"') || (si == 2 && sc.ch == '\'')) + { + sc.ChangeState(SCE_AU3_STRING); + sc.ForwardSetState(SCE_AU3_DEFAULT); + } + break; + } + } //switch (sc.state) + + // Determine if a new state should be entered: + + if (sc.state == SCE_AU3_DEFAULT) + { + if (sc.ch == ';') {sc.SetState(SCE_AU3_COMMENT);} + else if (sc.ch == '#') {sc.SetState(SCE_AU3_KEYWORD);} + else if (sc.ch == '$') {sc.SetState(SCE_AU3_VARIABLE);} + else if (sc.ch == '.' && !IsADigit(sc.chNext)) {sc.SetState(SCE_AU3_OPERATOR);} + else if (sc.ch == '@') {sc.SetState(SCE_AU3_KEYWORD);} + //else if (sc.ch == '_') {sc.SetState(SCE_AU3_KEYWORD);} + else if (sc.ch == '<' && si==3) {sc.SetState(SCE_AU3_STRING);} // string after #include + else if (sc.ch == '\"') { + sc.SetState(SCE_AU3_STRING); + si = 1; } + else if (sc.ch == '\'') { + sc.SetState(SCE_AU3_STRING); + si = 2; } + else if (IsADigit(sc.ch) || (sc.ch == '.' && IsADigit(sc.chNext))) + { + sc.SetState(SCE_AU3_NUMBER); + ni = 0; + } + else if (IsAWordStart(sc.ch)) {sc.SetState(SCE_AU3_KEYWORD);} + else if (IsAOperator(static_cast(sc.ch))) {sc.SetState(SCE_AU3_OPERATOR);} + else if (sc.atLineEnd) {sc.SetState(SCE_AU3_DEFAULT);} + } + } //for (; sc.More(); sc.Forward()) + + //************************************* + // Colourize the last word correctly + //************************************* + if (sc.state == SCE_AU3_KEYWORD) + { + if (strcmp(s_save, "#cs")== 0 || strcmp(s_save, "#comments-start")== 0 ) + { + sc.ChangeState(SCE_AU3_COMMENTBLOCK); + sc.SetState(SCE_AU3_COMMENTBLOCK); + } + else if (keywords.InList(s_save)) { + sc.ChangeState(SCE_AU3_KEYWORD); + sc.SetState(SCE_AU3_KEYWORD); + } + else if (keywords2.InList(s_save)) { + sc.ChangeState(SCE_AU3_FUNCTION); + sc.SetState(SCE_AU3_FUNCTION); + } + else if (keywords3.InList(s_save)) { + sc.ChangeState(SCE_AU3_MACRO); + sc.SetState(SCE_AU3_MACRO); + } + else if (keywords5.InList(s_save)) { + sc.ChangeState(SCE_AU3_PREPROCESSOR); + sc.SetState(SCE_AU3_PREPROCESSOR); + } + else if (keywords6.InList(s_save)) { + sc.ChangeState(SCE_AU3_SPECIAL); + sc.SetState(SCE_AU3_SPECIAL); + } + else if (keywords7.InList(s_save) && sc.atLineEnd) { + sc.ChangeState(SCE_AU3_EXPAND); + sc.SetState(SCE_AU3_EXPAND); + } + else if (keywords8.InList(s_save)) { + sc.ChangeState(SCE_AU3_UDF); + sc.SetState(SCE_AU3_UDF); + } + else { + sc.ChangeState(SCE_AU3_DEFAULT); + sc.SetState(SCE_AU3_DEFAULT); + } + } + if (sc.state == SCE_AU3_SENT) + { + // Send key string ended + if (sc.chPrev == '}' && sc.ch != '}') + { + // set color to SENDKEY when valid sendkey .. else set back to regular string + char sk[100]; + // split {111 222} and return {111} and check if 222 is valid. + // if return code = 1 then invalid 222 so must be string + if (GetSendKey(s_save,sk)) + { + sc.ChangeState(SCE_AU3_STRING); + } + // if single char between {?} then its ok as sendkey for a single character + else if (strlen(sk) == 3) + { + sc.ChangeState(SCE_AU3_SENT); + } + // if sendkey {111} is in table then ok as sendkey + else if (keywords4.InList(sk)) + { + sc.ChangeState(SCE_AU3_SENT); + } + else + { + sc.ChangeState(SCE_AU3_STRING); + } + sc.SetState(SCE_AU3_STRING); + } + // check if next portion is again a sendkey + if (sc.atLineEnd) + { + sc.ChangeState(SCE_AU3_STRING); + sc.SetState(SCE_AU3_DEFAULT); + } + } + //************************************* + sc.Complete(); +} + +// +static bool IsStreamCommentStyle(int style) { + return style == SCE_AU3_COMMENT || style == SCE_AU3_COMMENTBLOCK; +} + +// +// Routine to find first none space on the current line and return its Style +// needed for comment lines not starting on pos 1 +static int GetStyleFirstWord(Sci_PositionU szLine, Accessor &styler) +{ + Sci_Position nsPos = styler.LineStart(szLine); + Sci_Position nePos = styler.LineStart(szLine+1) - 1; + while (isspacechar(styler.SafeGetCharAt(nsPos)) && nsPos < nePos) + { + nsPos++; // skip to next char + + } // End While + return styler.StyleAt(nsPos); + +} // GetStyleFirstWord() + + +// +static void FoldAU3Doc(Sci_PositionU startPos, Sci_Position length, int, WordList *[], Accessor &styler) +{ + Sci_Position endPos = startPos + length; + // get settings from the config files for folding comments and preprocessor lines + bool foldComment = styler.GetPropertyInt("fold.comment") != 0; + bool foldInComment = styler.GetPropertyInt("fold.comment") == 2; + bool foldCompact = styler.GetPropertyInt("fold.compact", 1) != 0; + bool foldpreprocessor = styler.GetPropertyInt("fold.preprocessor") != 0; + // Backtrack to previous line in case need to fix its fold status + Sci_Position lineCurrent = styler.GetLine(startPos); + if (startPos > 0) { + if (lineCurrent > 0) { + lineCurrent--; + startPos = styler.LineStart(lineCurrent); + } + } + // vars for style of previous/current/next lines + int style = GetStyleFirstWord(lineCurrent,styler); + int stylePrev = 0; + // find the first previous line without continuation character at the end + while ((lineCurrent > 0 && IsContinuationLine(lineCurrent,styler)) || + (lineCurrent > 1 && IsContinuationLine(lineCurrent-1,styler))) { + lineCurrent--; + startPos = styler.LineStart(lineCurrent); + } + if (lineCurrent > 0) { + stylePrev = GetStyleFirstWord(lineCurrent-1,styler); + } + // vars for getting first word to check for keywords + bool FirstWordStart = false; + bool FirstWordEnd = false; + char szKeyword[11]=""; + int szKeywordlen = 0; + char szThen[5]=""; + int szThenlen = 0; + bool ThenFoundLast = false; + // var for indentlevel + int levelCurrent = SC_FOLDLEVELBASE; + if (lineCurrent > 0) + levelCurrent = styler.LevelAt(lineCurrent-1) >> 16; + int levelNext = levelCurrent; + // + int visibleChars = 0; + char chNext = styler.SafeGetCharAt(startPos); + char chPrev = ' '; + // + for (Sci_Position i = startPos; i < endPos; i++) { + char ch = chNext; + chNext = styler.SafeGetCharAt(i + 1); + if (IsAWordChar(ch)) { + visibleChars++; + } + // get the syle for the current character neede to check in comment + int stylech = styler.StyleAt(i); + // get first word for the line for indent check max 9 characters + if (FirstWordStart && (!(FirstWordEnd))) { + if (!IsAWordChar(ch)) { + FirstWordEnd = true; + szKeyword[szKeywordlen] = '\0'; + } + else { + if (szKeywordlen < 10) { + szKeyword[szKeywordlen++] = static_cast(tolower(ch)); + } + } + } + // start the capture of the first word + if (!(FirstWordStart)) { + if (IsAWordChar(ch) || IsAWordStart(ch) || ch == ';') { + FirstWordStart = true; + szKeyword[szKeywordlen++] = static_cast(tolower(ch)); + } + } + // only process this logic when not in comment section + if (!(stylech == SCE_AU3_COMMENT)) { + if (ThenFoundLast) { + if (IsAWordChar(ch)) { + ThenFoundLast = false; + } + } + // find out if the word "then" is the last on a "if" line + if (FirstWordEnd && strcmp(szKeyword,"if") == 0) { + if (szThenlen == 4) { + szThen[0] = szThen[1]; + szThen[1] = szThen[2]; + szThen[2] = szThen[3]; + szThen[3] = static_cast(tolower(ch)); + if (strcmp(szThen,"then") == 0 ) { + ThenFoundLast = true; + } + } + else { + szThen[szThenlen++] = static_cast(tolower(ch)); + if (szThenlen == 5) { + szThen[4] = '\0'; + } + } + } + } + // End of Line found so process the information + if ((ch == '\r' && chNext != '\n') || (ch == '\n') || (i == endPos)) { + // ************************** + // Folding logic for Keywords + // ************************** + // if a keyword is found on the current line and the line doesn't end with _ (continuation) + // and we are not inside a commentblock. + if (szKeywordlen > 0 && (!(chPrev == '_')) && + ((!(IsStreamCommentStyle(style)) || foldInComment)) ) { + szKeyword[szKeywordlen] = '\0'; + // only fold "if" last keyword is "then" (else its a one line if) + if (strcmp(szKeyword,"if") == 0 && ThenFoundLast) { + levelNext++; + } + // create new fold for these words + if (strcmp(szKeyword,"do") == 0 || strcmp(szKeyword,"for") == 0 || + strcmp(szKeyword,"func") == 0 || strcmp(szKeyword,"while") == 0|| + strcmp(szKeyword,"with") == 0 || strcmp(szKeyword,"#region") == 0 ) { + levelNext++; + } + // create double Fold for select&switch because Case will subtract one of the current level + if (strcmp(szKeyword,"select") == 0 || strcmp(szKeyword,"switch") == 0) { + levelNext++; + levelNext++; + } + // end the fold for these words before the current line + if (strcmp(szKeyword,"endfunc") == 0 || strcmp(szKeyword,"endif") == 0 || + strcmp(szKeyword,"next") == 0 || strcmp(szKeyword,"until") == 0 || + strcmp(szKeyword,"endwith") == 0 ||strcmp(szKeyword,"wend") == 0){ + levelNext--; + levelCurrent--; + } + // end the fold for these words before the current line and Start new fold + if (strcmp(szKeyword,"case") == 0 || strcmp(szKeyword,"else") == 0 || + strcmp(szKeyword,"elseif") == 0 ) { + levelCurrent--; + } + // end the double fold for this word before the current line + if (strcmp(szKeyword,"endselect") == 0 || strcmp(szKeyword,"endswitch") == 0 ) { + levelNext--; + levelNext--; + levelCurrent--; + levelCurrent--; + } + // end the fold for these words on the current line + if (strcmp(szKeyword,"#endregion") == 0 ) { + levelNext--; + } + } + // Preprocessor and Comment folding + int styleNext = GetStyleFirstWord(lineCurrent + 1,styler); + // ************************************* + // Folding logic for preprocessor blocks + // ************************************* + // process preprosessor line + if (foldpreprocessor && style == SCE_AU3_PREPROCESSOR) { + if (!(stylePrev == SCE_AU3_PREPROCESSOR) && (styleNext == SCE_AU3_PREPROCESSOR)) { + levelNext++; + } + // fold till the last line for normal comment lines + else if (stylePrev == SCE_AU3_PREPROCESSOR && !(styleNext == SCE_AU3_PREPROCESSOR)) { + levelNext--; + } + } + // ********************************* + // Folding logic for Comment blocks + // ********************************* + if (foldComment && IsStreamCommentStyle(style)) { + // Start of a comment block + if (!(stylePrev==style) && IsStreamCommentStyle(styleNext) && styleNext==style) { + levelNext++; + } + // fold till the last line for normal comment lines + else if (IsStreamCommentStyle(stylePrev) + && !(styleNext == SCE_AU3_COMMENT) + && stylePrev == SCE_AU3_COMMENT + && style == SCE_AU3_COMMENT) { + levelNext--; + } + // fold till the one but last line for Blockcomment lines + else if (IsStreamCommentStyle(stylePrev) + && !(styleNext == SCE_AU3_COMMENTBLOCK) + && style == SCE_AU3_COMMENTBLOCK) { + levelNext--; + levelCurrent--; + } + } + int levelUse = levelCurrent; + int lev = levelUse | levelNext << 16; + if (visibleChars == 0 && foldCompact) + lev |= SC_FOLDLEVELWHITEFLAG; + if (levelUse < levelNext) { + lev |= SC_FOLDLEVELHEADERFLAG; + } + if (lev != styler.LevelAt(lineCurrent)) { + styler.SetLevel(lineCurrent, lev); + } + // reset values for the next line + lineCurrent++; + stylePrev = style; + style = styleNext; + levelCurrent = levelNext; + visibleChars = 0; + // if the last character is an Underscore then don't reset since the line continues on the next line. + if (!(chPrev == '_')) { + szKeywordlen = 0; + szThenlen = 0; + FirstWordStart = false; + FirstWordEnd = false; + ThenFoundLast = false; + } + } + // save the last processed character + if (!isspacechar(ch)) { + chPrev = ch; + visibleChars++; + } + } +} + + +// + +static const char * const AU3WordLists[] = { + "#autoit keywords", + "#autoit functions", + "#autoit macros", + "#autoit Sent keys", + "#autoit Pre-processors", + "#autoit Special", + "#autoit Expand", + "#autoit UDF", + 0 +}; +LexerModule lmAU3(SCLEX_AU3, ColouriseAU3Doc, "au3", FoldAU3Doc , AU3WordLists); diff --git a/scintilla/lexilla/src/Lexilla.cxx b/scintilla/lexilla/src/Lexilla.cxx index fb58a4c718..7a3fe9be96 100644 --- a/scintilla/lexilla/src/Lexilla.cxx +++ b/scintilla/lexilla/src/Lexilla.cxx @@ -174,6 +174,7 @@ static void AddGeanyLexers() &lmAda, &lmAsm, &lmAsciidoc, + &lmAU3, &lmBash, &lmBatch, &lmCaml, diff --git a/scintilla/scintilla_changes.patch b/scintilla/scintilla_changes.patch index e28d19aac4..6368340c7e 100644 --- a/scintilla/scintilla_changes.patch +++ b/scintilla/scintilla_changes.patch @@ -62,7 +62,7 @@ diff --git scintilla/lexilla/src/Lexilla.cxx scintilla/lexilla/src/Lexilla.cxx index cd4b23617..af4a73db4 100644 --- scintilla/lexilla/src/Lexilla.cxx +++ scintilla/lexilla/src/Lexilla.cxx -@@ -165,12 +165,68 @@ namespace { +@@ -165,12 +165,69 @@ namespace { CatalogueModules catalogueLexilla; @@ -72,6 +72,7 @@ index cd4b23617..af4a73db4 100644 + &lmAbaqus, + &lmAda, + &lmAsm, ++ &lmAU3, + &lmBash, + &lmBatch, + &lmCaml, diff --git a/src/filetypes.c b/src/filetypes.c index b5e8f6593f..6e2917a96e 100644 --- a/src/filetypes.c +++ b/src/filetypes.c @@ -188,6 +188,7 @@ static void init_builtin_filetypes(void) FT_INIT( ZEPHIR, ZEPHIR, "Zephir", NULL, SOURCE_FILE, COMPILED ); FT_INIT( SMALLTALK, NONE, "Smalltalk", NULL, SOURCE_FILE, SCRIPT ); FT_INIT( JULIA, JULIA, "Julia", NULL, SOURCE_FILE, SCRIPT ); + FT_INIT( AU3, AUTOIT, "AutoIt", NULL, SCRIPT, SCRIPT ); } diff --git a/src/filetypes.h b/src/filetypes.h index 83d1203fed..77d6c78b76 100644 --- a/src/filetypes.h +++ b/src/filetypes.h @@ -108,6 +108,7 @@ typedef enum GEANY_FILETYPES_BIBTEX, GEANY_FILETYPES_SMALLTALK, GEANY_FILETYPES_JULIA, + GEANY_FILETYPES_AU3, /* ^ 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 a43e3a7d79..9cc5bf2b2c 100644 --- a/src/highlighting.c +++ b/src/highlighting.c @@ -1002,6 +1002,7 @@ void highlighting_init_styles(guint filetype_idx, GKeyFile *config, GKeyFile *co init_styleset_case(ADA); init_styleset_case(ASCIIDOC); init_styleset_case(ASM); + init_styleset_case(AU3); init_styleset_case(BASIC); init_styleset_case(BATCH); init_styleset_case(C); @@ -1093,6 +1094,7 @@ void highlighting_set_styles(ScintillaObject *sci, GeanyFiletype *ft) styleset_case(ADA); styleset_case(ASCIIDOC); styleset_case(ASM); + styleset_case(AU3); styleset_case(BASIC); styleset_case(BATCH); styleset_case(C); @@ -1642,6 +1644,9 @@ gboolean highlighting_is_string_style(gint lexer, gint style) case SCLEX_YAML: /* there is no string type in those lexers, listing here just for completeness */ return FALSE; + + case SCLEX_AU3: + return (style == SCE_AU3_STRING); } return FALSE; } @@ -1868,6 +1873,10 @@ gboolean highlighting_is_comment_style(gint lexer, gint style) case SCLEX_GDSCRIPT: return (style == SCE_GD_COMMENTLINE || style == SCE_GD_COMMENTBLOCK); + + case SCLEX_AU3: + return (style == SCE_AU3_COMMENT || + style == SCE_AU3_COMMENTBLOCK); } return FALSE; } diff --git a/src/highlightingmappings.h b/src/highlightingmappings.h index 003bb83337..b85d7ede10 100644 --- a/src/highlightingmappings.h +++ b/src/highlightingmappings.h @@ -1740,6 +1740,41 @@ static const HLKeyword highlighting_keywords_YAML[] = #define highlighting_keywords_ZEPHIR highlighting_keywords_PHP #define highlighting_properties_ZEPHIR highlighting_properties_PHP + +/* AutoIt */ +#define highlighting_lexer_AU3 SCLEX_AU3 +static const HLStyle highlighting_styles_AU3[] = +{ + { SCE_AU3_DEFAULT, "default", FALSE}, + { SCE_AU3_COMMENT, "comment", FALSE}, + { SCE_AU3_COMMENTBLOCK, "commentblock", FALSE}, + { SCE_AU3_NUMBER, "number", FALSE}, + { SCE_AU3_FUNCTION, "function", FALSE}, + { SCE_AU3_KEYWORD, "keyword", FALSE}, + { SCE_AU3_MACRO, "macro", FALSE}, + { SCE_AU3_STRING, "string", FALSE}, + { SCE_AU3_OPERATOR, "operator", FALSE}, + { SCE_AU3_VARIABLE, "variable", FALSE}, + { SCE_AU3_SENT, "sent", FALSE}, + { SCE_AU3_PREPROCESSOR, "preprocessor", FALSE}, + { SCE_AU3_SPECIAL, "special", FALSE}, + { SCE_AU3_EXPAND, "expand", FALSE}, + { SCE_AU3_COMOBJ, "comobj", FALSE}, + { SCE_AU3_UDF, "udf", FALSE} +}; +static const HLKeyword highlighting_keywords_AU3[] = +{ + { 0, "keywords", FALSE }, + { 1, "functions", FALSE }, + { 2, "macros", FALSE }, + { 3, "sent", FALSE }, + { 4, "preprocessor", FALSE }, + { 5, "special", FALSE }, + { 6, "expand", FALSE }, + { 7, "udf", FALSE } +}; +#define highlighting_properties_AU3 EMPTY_PROPERTIES + G_END_DECLS #endif /* GEANY_HIGHLIGHTING_MAPPINGS_H */ diff --git a/src/tagmanager/tm_parser.c b/src/tagmanager/tm_parser.c index 9f3be4fcd0..f0c41edb0d 100644 --- a/src/tagmanager/tm_parser.c +++ b/src/tagmanager/tm_parser.c @@ -1080,6 +1080,18 @@ static TMParserMapGroup group_BATCH[] = { {N_("Variables"), TM_ICON_VAR, tm_tag_variable_t}, }; +static TMParserMapEntry map_AUTOIT[] = { + {'f', tm_tag_function_t}, + {'r', tm_tag_other_t}, + {'g', tm_tag_undef_t}, + {'l', tm_tag_undef_t}, + {'S', tm_tag_undef_t}, +}; +static TMParserMapGroup group_AUTOIT[] = { + {N_("Functions"), TM_ICON_METHOD, tm_tag_function_t}, + {N_("Regions"), TM_ICON_OTHER, tm_tag_other_t}, +}; + typedef struct { TMParserMapEntry *entries; @@ -1151,6 +1163,7 @@ static TMParserMap parser_map[] = { MAP_ENTRY(LISP), MAP_ENTRY(TYPESCRIPT), MAP_ENTRY(BATCH), + MAP_ENTRY(AUTOIT), }; /* make sure the parser map is consistent and complete */ G_STATIC_ASSERT(G_N_ELEMENTS(parser_map) == TM_PARSER_COUNT); @@ -1666,6 +1679,7 @@ gboolean tm_parser_has_full_scope(TMParserType lang) case TM_PARSER_VHDL: case TM_PARSER_VERILOG: case TM_PARSER_ZEPHIR: + case TM_PARSER_AUTOIT: return TRUE; /* These make use of the scope, but don't include nested hierarchy diff --git a/src/tagmanager/tm_parser.h b/src/tagmanager/tm_parser.h index 42d47c328a..0d6e9f5524 100644 --- a/src/tagmanager/tm_parser.h +++ b/src/tagmanager/tm_parser.h @@ -118,6 +118,7 @@ enum TM_PARSER_LISP, TM_PARSER_TYPESCRIPT, TM_PARSER_BATCH, + TM_PARSER_AUTOIT, TM_PARSER_COUNT }; diff --git a/src/tagmanager/tm_parsers.h b/src/tagmanager/tm_parsers.h index 215f1ec0b5..e7b89f0ff7 100644 --- a/src/tagmanager/tm_parsers.h +++ b/src/tagmanager/tm_parsers.h @@ -72,6 +72,7 @@ ClojureParser, \ LispParser, \ TypeScriptParser, \ - DosBatchParser + DosBatchParser, \ + AutoItParser #endif diff --git a/tests/ctags/Makefile.am b/tests/ctags/Makefile.am index 0e5efc6abe..a973eadd03 100644 --- a/tests/ctags/Makefile.am +++ b/tests/ctags/Makefile.am @@ -294,6 +294,7 @@ test_sources = \ signature.cpp \ simple.abc \ simple.asciidoc \ + simple.au3 \ simple.bas \ simple.bat \ simple.clj \ diff --git a/tests/ctags/simple.au3 b/tests/ctags/simple.au3 new file mode 100644 index 0000000000..ea788142f6 --- /dev/null +++ b/tests/ctags/simple.au3 @@ -0,0 +1,34 @@ +; Taken from https://www.autoitscript.com/autoit3/docs/intro/lang_functions.htm + +#include +#include +#include "WindowsConstants.au3" + +Global $iMagic = 0x377abcaf271c + +Local $iNumber = 10 +Local $iDoubled = 0 + +For $i = 1 To 10 + $iDoubled = MyDouble($iNumber) + MsgBox($MB_OK, "", $iNumber & " doubled is " & $iDoubled) + $iNumber = $iDoubled +Next +Exit + +#Region All functions +Func MyDouble($iValue) + $iValue = $iValue * 2 + Return $iValue +EndFunc ;==>MyDouble + +func MyDouble0($iValue) + $iValue = $iValue * 2 + Return $iValue +EndFunc ;==>MyDouble + + FUNC MyDouble1($iValue) + $iValue = $iValue * 2 + Return $iValue +EndFunc +#EndRegion All functions diff --git a/tests/ctags/simple.au3.tags b/tests/ctags/simple.au3.tags new file mode 100644 index 0000000000..7eea426621 --- /dev/null +++ b/tests/ctags/simple.au3.tags @@ -0,0 +1,8 @@ +All functionsÌ524288Ö0 +other: All functions +MyDoubleÌ16Í($iValue)ÎAll functionsÖ0 +function: All functions :: MyDouble($iValue) +MyDouble0Ì16Í($iValue)ÎAll functionsÖ0 +function: All functions :: MyDouble0($iValue) +MyDouble1Ì16Í($iValue)ÎAll functionsÖ0 +function: All functions :: MyDouble1($iValue) diff --git a/tests/meson.build b/tests/meson.build index fafd32b1ae..bd975327ea 100644 --- a/tests/meson.build +++ b/tests/meson.build @@ -291,6 +291,7 @@ ctags_tests = files([ 'ctags/signature.cpp.tags', 'ctags/simple.abc.tags', 'ctags/simple.asciidoc.tags', + 'ctags/simple.au3.tags', 'ctags/simple.bas.tags', 'ctags/simple.bat.tags', 'ctags/simple.clj.tags',