diff --git a/data/filetype_extensions.conf b/data/filetype_extensions.conf index 3965b46789..6ec67e1e4f 100644 --- a/data/filetype_extensions.conf +++ b/data/filetype_extensions.conf @@ -53,7 +53,7 @@ Python=*.py;*.pyw;SConstruct;SConscript;wscript; PowerShell=*.ps1;*.psm1; reStructuredText=*.rest;*.reST;*.rst; R=*.R;*.r; -Rust=*.rs +Rust=*.rs; Ruby=*.rb;*.rhtml;*.ruby;*.gemspec;Gemfile;rakefile;Rakefile; Scala=*.scala;*.scl; Sh=*.sh;configure;configure.in;configure.in.in;configure.ac;*.ksh;*.mksh;*.zsh;*.ash;*.bash;*.m4;PKGBUILD;*profile; diff --git a/data/filetypes.Rust.conf b/data/filetypes.Rust.conf deleted file mode 100644 index ceb37863d8..0000000000 --- a/data/filetypes.Rust.conf +++ /dev/null @@ -1,61 +0,0 @@ -# For complete documentation of this file, please see Geany's main documentation -[styling=C] - -[keywords] -# all items must be in one line -primary=as break const copy do drop else enum extern false fn for if impl let log loop match mod mut priv pub pure ref return self static struct super true trait type unsafe use while -secondary= -# these are the Doxygen keywords -docComment=a addindex addtogroup anchor arg attention author authors b brief bug c callergraph callgraph category cite class code cond copybrief copydetails copydoc copyright date def defgroup deprecated details dir dontinclude dot dotfile e else elseif em endcode endcond enddot endhtmlonly endif endinternal endlatexonly endlink endmanonly endmsc endrtfonly endverbatim endxmlonly enum example exception extends file fn headerfile hideinitializer htmlinclude htmlonly if ifnot image implements include includelineno ingroup interface internal invariant latexonly li line link mainpage manonly memberof msc mscfile n name namespace nosubgrouping note overload p package page par paragraph param post pre private privatesection property protected protectedsection protocol public publicsection ref related relatedalso relates relatesalso remark remarks result return returns retval rtfonly sa section see short showinitializer since skip skipline snippet struct subpage subsection subsubsection tableofcontents test throw throws todo tparam typedef union until var verbatim verbinclude version warning weakgroup xmlonly xrefitem - -[lexer_properties] -# preprocessor properties - possibly useful for tweaking #[attribute] highlighting -#styling.within.preprocessor=1 -#lexer.cpp.track.preprocessor=0 -#preprocessor.symbol.$(file.patterns.cpp)=# -#preprocessor.start.$(file.patterns.cpp)=if ifdef ifndef -#preprocessor.middle.$(file.patterns.cpp)=else elif -#preprocessor.end.$(file.patterns.cpp)=endif - -[settings] -# default extension used when saving files -extension=rs -lexer_filetype=C - -# the following characters are these which a "word" can contains, see documentation -#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=_Compile -FT_00_CM=rustc -c "%f" -FT_00_WD= -FT_01_LB=_Build -FT_01_CM=rustc "%f" -FT_01_WD= -EX_00_LB=_Run -EX_00_CM="./%e" -EX_00_WD= - - diff --git a/data/filetypes.rust b/data/filetypes.rust new file mode 100644 index 0000000000..45a2b02864 --- /dev/null +++ b/data/filetypes.rust @@ -0,0 +1,66 @@ +# For complete documentation of this file, please see Geany's main documentation +[styling] +# Edit these in the colorscheme .conf file instead +default=default +commentblock=comment +commentline=comment_line +commentblockdoc=comment_doc +commentlinedoc=comment_doc +number=number_1 +word=keyword_1 +word2=keyword_2 +word3=keyword_3 +word4=type +string=string_1 +stringraw=string_2 +character=character +operator=operator +identifier=identifier_1 +lifetime=parameter +macro=preprocessor +lexerror=error + +[keywords] +# all items must be in one line +primary=alignof as be box break const continue do else enum extern false fn for if impl in let __log_level loop match mod mut offsetof once priv proc pub pure ref return self sizeof static struct super trait true type typeof unsafe unsized use while yield +secondary=bool char f32 f64 i16 i32 i64 i8 int str u16 u32 u64 u8 uint +tertiary=Self + +[lexer_properties] +styling.within.preprocessor=1 +lexer.cpp.track.preprocessor=0 + +[settings] +# default extension used when saving files +extension=rs + +# 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_settings] +# %f will be replaced by the complete filename +# %e will be replaced by the filename without extension +# (use only one of it at one time) +compiler=rustc "%f" +linker=rustc -o "%e" "%f" +run_cmd="./%e" + diff --git a/scintilla/Makefile.am b/scintilla/Makefile.am index d35411516a..a0673ce9ce 100644 --- a/scintilla/Makefile.am +++ b/scintilla/Makefile.am @@ -36,6 +36,7 @@ lexers/LexPython.cxx \ lexers/LexPO.cxx \ lexers/LexR.cxx \ lexers/LexRuby.cxx \ +lexers/LexRust.cxx \ lexers/LexSQL.cxx \ lexers/LexTCL.cxx \ lexers/LexTxt2tags.cxx \ diff --git a/scintilla/lexers/LexRust.cxx b/scintilla/lexers/LexRust.cxx new file mode 100644 index 0000000000..bf59933afb --- /dev/null +++ b/scintilla/lexers/LexRust.cxx @@ -0,0 +1,781 @@ +/** @file LexRust.cxx + ** Lexer for Rust. + ** + ** Copyright (c) 2013 by SiegeLord + ** Converted to lexer object and added further folding features/properties by "Udo Lechner" + **/ +// Copyright 1998-2005 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 "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" +#include "OptionSet.h" +#include "PropSetSimple.h" + +#ifdef SCI_NAMESPACE +using namespace Scintilla; +#endif + +static const int NUM_RUST_KEYWORD_LISTS = 7; +static const int MAX_RUST_IDENT_CHARS = 1023; + +static bool IsStreamCommentStyle(int style) { + return style == SCE_RUST_COMMENTBLOCK || + style == SCE_RUST_COMMENTBLOCKDOC; +} + +// Options used for LexerRust +struct OptionsRust { + bool fold; + bool foldSyntaxBased; + bool foldComment; + bool foldCommentMultiline; + bool foldCommentExplicit; + std::string foldExplicitStart; + std::string foldExplicitEnd; + bool foldExplicitAnywhere; + bool foldCompact; + int foldAtElseInt; + bool foldAtElse; + OptionsRust() { + fold = false; + foldSyntaxBased = true; + foldComment = false; + foldCommentMultiline = true; + foldCommentExplicit = true; + foldExplicitStart = ""; + foldExplicitEnd = ""; + foldExplicitAnywhere = false; + foldCompact = true; + foldAtElseInt = -1; + foldAtElse = false; + } +}; + +static const char * const rustWordLists[NUM_RUST_KEYWORD_LISTS + 1] = { + "Primary keywords and identifiers", + "Built in types", + "Other keywords", + "Keywords 4", + "Keywords 5", + "Keywords 6", + "Keywords 7", + 0, + }; + +struct OptionSetRust : public OptionSet { + OptionSetRust() { + DefineProperty("fold", &OptionsRust::fold); + + DefineProperty("fold.comment", &OptionsRust::foldComment); + + DefineProperty("fold.compact", &OptionsRust::foldCompact); + + DefineProperty("fold.at.else", &OptionsRust::foldAtElse); + + DefineProperty("fold.rust.syntax.based", &OptionsRust::foldSyntaxBased, + "Set this property to 0 to disable syntax based folding."); + + DefineProperty("fold.rust.comment.multiline", &OptionsRust::foldCommentMultiline, + "Set this property to 0 to disable folding multi-line comments when fold.comment=1."); + + DefineProperty("fold.rust.comment.explicit", &OptionsRust::foldCommentExplicit, + "Set this property to 0 to disable folding explicit fold points when fold.comment=1."); + + DefineProperty("fold.rust.explicit.start", &OptionsRust::foldExplicitStart, + "The string to use for explicit fold start points, replacing the standard //{."); + + DefineProperty("fold.rust.explicit.end", &OptionsRust::foldExplicitEnd, + "The string to use for explicit fold end points, replacing the standard //}."); + + DefineProperty("fold.rust.explicit.anywhere", &OptionsRust::foldExplicitAnywhere, + "Set this property to 1 to enable explicit fold points anywhere, not just in line comments."); + + DefineProperty("lexer.rust.fold.at.else", &OptionsRust::foldAtElseInt, + "This option enables Rust folding on a \"} else {\" line of an if statement."); + + DefineWordListSets(rustWordLists); + } +}; + +class LexerRust : public ILexer { + WordList keywords[NUM_RUST_KEYWORD_LISTS]; + OptionsRust options; + OptionSetRust osRust; +public: + virtual ~LexerRust() { + } + void SCI_METHOD Release() { + delete this; + } + int SCI_METHOD Version() const { + return lvOriginal; + } + const char * SCI_METHOD PropertyNames() { + return osRust.PropertyNames(); + } + int SCI_METHOD PropertyType(const char *name) { + return osRust.PropertyType(name); + } + const char * SCI_METHOD DescribeProperty(const char *name) { + return osRust.DescribeProperty(name); + } + int SCI_METHOD PropertySet(const char *key, const char *val); + const char * SCI_METHOD DescribeWordListSets() { + return osRust.DescribeWordListSets(); + } + int SCI_METHOD WordListSet(int n, const char *wl); + void SCI_METHOD Lex(unsigned int startPos, int length, int initStyle, IDocument *pAccess); + void SCI_METHOD Fold(unsigned int startPos, int length, int initStyle, IDocument *pAccess); + void * SCI_METHOD PrivateCall(int, void *) { + return 0; + } + static ILexer *LexerFactoryRust() { + return new LexerRust(); + } +}; + +int SCI_METHOD LexerRust::PropertySet(const char *key, const char *val) { + if (osRust.PropertySet(&options, key, val)) { + return 0; + } + return -1; +} + +int SCI_METHOD LexerRust::WordListSet(int n, const char *wl) { + int firstModification = -1; + if (n < NUM_RUST_KEYWORD_LISTS) { + WordList *wordListN = &keywords[n]; + WordList wlNew; + wlNew.Set(wl); + if (*wordListN != wlNew) { + wordListN->Set(wl); + firstModification = 0; + } + } + return firstModification; +} + +static bool IsWhitespace(int c) { + return c == ' ' || c == '\t' || c == '\r' || c == '\n'; +} + +/* This isn't quite right for Unicode identifiers */ +static bool IsIdentifierStart(int ch) { + return (IsASCII(ch) && (isalpha(ch) || ch == '_')) || !IsASCII(ch); +} + +/* This isn't quite right for Unicode identifiers */ +static bool IsIdentifierContinue(int ch) { + return (IsASCII(ch) && (isalnum(ch) || ch == '_')) || !IsASCII(ch); +} + +static void ScanWhitespace(Accessor& styler, int& pos, int max) { + while (IsWhitespace(styler.SafeGetCharAt(pos, '\0')) && pos < max) { + if (pos == styler.LineEnd(styler.GetLine(pos))) + styler.SetLineState(styler.GetLine(pos), 0); + pos++; + } + styler.ColourTo(pos-1, SCE_RUST_DEFAULT); +} + +static void GrabString(char* s, Accessor& styler, int start, int len) { + for (int ii = 0; ii < len; ii++) + s[ii] = styler[ii + start]; + s[len] = '\0'; +} + +static void ScanIdentifier(Accessor& styler, int& pos, WordList *keywords) { + int start = pos; + while (IsIdentifierContinue(styler.SafeGetCharAt(pos, '\0'))) + pos++; + + if (styler.SafeGetCharAt(pos, '\0') == '!') { + pos++; + styler.ColourTo(pos - 1, SCE_RUST_MACRO); + } else { + char s[MAX_RUST_IDENT_CHARS + 1]; + int len = pos - start; + len = len > MAX_RUST_IDENT_CHARS ? MAX_RUST_IDENT_CHARS : len; + GrabString(s, styler, start, len); + bool keyword = false; + for (int ii = 0; ii < NUM_RUST_KEYWORD_LISTS; ii++) { + if (keywords[ii].InList(s)) { + styler.ColourTo(pos - 1, SCE_RUST_WORD + ii); + keyword = true; + break; + } + } + if (!keyword) { + styler.ColourTo(pos - 1, SCE_RUST_IDENTIFIER); + } + } +} + +static void ScanDigits(Accessor& styler, int& pos, int base) { + for (;;) { + int c = styler.SafeGetCharAt(pos, '\0'); + if (IsADigit(c, base) || c == '_') + pos++; + else + break; + } +} + +static void ScanNumber(Accessor& styler, int& pos) { + int base = 10; + int c = styler.SafeGetCharAt(pos, '\0'); + int n = styler.SafeGetCharAt(pos + 1, '\0'); + bool error = false; + if (c == '0' && n == 'x') { + pos += 2; + base = 16; + } else if (c == '0' && n == 'b') { + pos += 2; + base = 2; + } else if (c == '0' && n == 'o') { + pos += 2; + base = 8; + } + int old_pos = pos; + ScanDigits(styler, pos, base); + c = styler.SafeGetCharAt(pos, '\0'); + if (c == 'u' || c == 'i') { + pos++; + c = styler.SafeGetCharAt(pos, '\0'); + n = styler.SafeGetCharAt(pos + 1, '\0'); + if (c == '8') { + pos++; + } else if (c == '1' && n == '6') { + pos += 2; + } else if (c == '3' && n == '2') { + pos += 2; + } else if (c == '6' && n == '4') { + pos += 2; + } + } else { + n = styler.SafeGetCharAt(pos + 1, '\0'); + if (c == '.' && !(IsIdentifierStart(n) || n == '.')) { + error |= base != 10; + pos++; + ScanDigits(styler, pos, 10); + } + + c = styler.SafeGetCharAt(pos, '\0'); + if (c == 'e' || c == 'E') { + error |= base != 10; + pos++; + c = styler.SafeGetCharAt(pos, '\0'); + if (c == '-' || c == '+') + pos++; + int old_pos = pos; + ScanDigits(styler, pos, 10); + if (old_pos == pos) { + error = true; + } + } + + c = styler.SafeGetCharAt(pos, '\0'); + if (c == 'f') { + error |= base != 10; + pos++; + c = styler.SafeGetCharAt(pos, '\0'); + n = styler.SafeGetCharAt(pos + 1, '\0'); + if (c == '3' && n == '2') { + pos += 2; + } else if (c == '6' && n == '4') { + pos += 2; + } else { + error = true; + } + } + } + if (old_pos == pos) { + error = true; + } + if (error) + styler.ColourTo(pos - 1, SCE_RUST_LEXERROR); + else + styler.ColourTo(pos - 1, SCE_RUST_NUMBER); +} + +static bool IsOneCharOperator(int c) { + return c == ';' || c == ',' || c == '(' || c == ')' + || c == '{' || c == '}' || c == '[' || c == ']' + || c == '@' || c == '#' || c == '~' || c == '+' + || c == '*' || c == '/' || c == '^' || c == '%' + || c == '.' || c == ':' || c == '!' || c == '<' + || c == '>' || c == '=' || c == '-' || c == '&' + || c == '|' || c == '$'; +} + +static bool IsTwoCharOperator(int c, int n) { + return (c == '.' && n == '.') || (c == ':' && n == ':') + || (c == '!' && n == '=') || (c == '<' && n == '<') + || (c == '<' && n == '=') || (c == '>' && n == '>') + || (c == '>' && n == '=') || (c == '=' && n == '=') + || (c == '=' && n == '>') || (c == '-' && n == '>') + || (c == '&' && n == '&') || (c == '|' && n == '|') + || (c == '-' && n == '=') || (c == '&' && n == '=') + || (c == '|' && n == '=') || (c == '+' && n == '=') + || (c == '*' && n == '=') || (c == '/' && n == '=') + || (c == '^' && n == '=') || (c == '%' && n == '='); +} + +static bool IsThreeCharOperator(int c, int n, int n2) { + return (c == '<' && n == '<' && n2 == '=') + || (c == '>' && n == '>' && n2 == '='); +} + +static bool IsValidCharacterEscape(int c) { + return c == 'n' || c == 'r' || c == 't' || c == '\\' + || c == '\'' || c == '"' || c == '0'; +} + +static bool IsValidStringEscape(int c) { + return IsValidCharacterEscape(c) || c == '\n'; +} + +static bool ScanNumericEscape(Accessor &styler, int& pos, int num_digits, bool stop_asap) { + for (;;) { + int c = styler.SafeGetCharAt(pos, '\0'); + if (!IsADigit(c, 16)) + break; + num_digits--; + pos++; + if (num_digits == 0 && stop_asap) + return true; + } + if (num_digits == 0) { + return true; + } else { + return false; + } +} + +/* This is overly permissive for character literals in order to accept UTF-8 encoded + * character literals. */ +static void ScanCharacterLiteralOrLifetime(Accessor &styler, int& pos) { + pos++; + int c = styler.SafeGetCharAt(pos, '\0'); + int n = styler.SafeGetCharAt(pos + 1, '\0'); + bool done = false; + bool valid_lifetime = IsIdentifierStart(c); + bool valid_char = true; + bool first = true; + while (!done) { + switch (c) { + case '\\': + done = true; + if (IsValidCharacterEscape(n)) { + pos += 2; + } else if (n == 'x') { + pos += 2; + valid_char = ScanNumericEscape(styler, pos, 2, false); + } else if (n == 'u') { + pos += 2; + valid_char = ScanNumericEscape(styler, pos, 4, false); + } else if (n == 'U') { + pos += 2; + valid_char = ScanNumericEscape(styler, pos, 8, false); + } else { + valid_char = false; + } + break; + case '\'': + valid_char = !first; + done = true; + break; + case '\t': + case '\n': + case '\r': + case '\0': + valid_char = false; + done = true; + break; + default: + if (!IsIdentifierContinue(c) && !first) { + done = true; + } else { + pos++; + } + break; + } + c = styler.SafeGetCharAt(pos, '\0'); + n = styler.SafeGetCharAt(pos + 1, '\0'); + + first = false; + } + if (styler.SafeGetCharAt(pos, '\0') == '\'') { + valid_lifetime = false; + } else { + valid_char = false; + } + if (valid_lifetime) { + styler.ColourTo(pos - 1, SCE_RUST_LIFETIME); + } else if (valid_char) { + pos++; + styler.ColourTo(pos - 1, SCE_RUST_CHARACTER); + } else { + styler.ColourTo(pos - 1, SCE_RUST_LEXERROR); + } +} + +enum CommentState { + UnknownComment, + DocComment, + NotDocComment +}; + +/* + * The rule for block-doc comments is as follows: /xxN and /x! (where x is an asterisk, N is a non-asterisk) start doc comments. + * Otherwise it's a regular comment. + */ +static void ResumeBlockComment(Accessor &styler, int& pos, int max, CommentState state, int level) { + int c = styler.SafeGetCharAt(pos, '\0'); + bool maybe_doc_comment = false; + if (c == '*') { + int n = styler.SafeGetCharAt(pos + 1, '\0'); + if (n != '*' && n != '/') { + maybe_doc_comment = true; + } + } else if (c == '!') { + maybe_doc_comment = true; + } + + for (;;) { + int n = styler.SafeGetCharAt(pos + 1, '\0'); + if (pos == styler.LineEnd(styler.GetLine(pos))) + styler.SetLineState(styler.GetLine(pos), level); + if (c == '*') { + pos++; + if (n == '/') { + pos++; + level--; + if (level == 0) { + styler.SetLineState(styler.GetLine(pos), 0); + if (state == DocComment || (state == UnknownComment && maybe_doc_comment)) + styler.ColourTo(pos - 1, SCE_RUST_COMMENTBLOCKDOC); + else + styler.ColourTo(pos - 1, SCE_RUST_COMMENTBLOCK); + break; + } + } + } else if (c == '/') { + pos++; + if (n == '*') { + pos++; + level++; + } + } + else { + pos++; + } + if (pos >= max) { + if (state == DocComment || (state == UnknownComment && maybe_doc_comment)) + styler.ColourTo(pos - 1, SCE_RUST_COMMENTBLOCKDOC); + else + styler.ColourTo(pos - 1, SCE_RUST_COMMENTBLOCK); + break; + } + c = styler.SafeGetCharAt(pos, '\0'); + } +} + +/* + * The rule for line-doc comments is as follows... ///N and //! (where N is a non slash) start doc comments. + * Otherwise it's a normal line comment. + */ +static void ResumeLineComment(Accessor &styler, int& pos, int max, CommentState state) { + bool maybe_doc_comment = false; + int c = styler.SafeGetCharAt(pos, '\0'); + if (c == '/') { + if (pos < max) { + pos++; + c = styler.SafeGetCharAt(pos, '\0'); + if (c != '/') { + maybe_doc_comment = true; + } + } + } else if (c == '!') { + maybe_doc_comment = true; + } + + while (pos < max && c != '\n') { + if (pos == styler.LineEnd(styler.GetLine(pos))) + styler.SetLineState(styler.GetLine(pos), 0); + pos++; + c = styler.SafeGetCharAt(pos, '\0'); + } + + if (state == DocComment || (state == UnknownComment && maybe_doc_comment)) + styler.ColourTo(pos - 1, SCE_RUST_COMMENTLINEDOC); + else + styler.ColourTo(pos - 1, SCE_RUST_COMMENTLINE); +} + +static void ScanComments(Accessor &styler, int& pos, int max) { + pos++; + int c = styler.SafeGetCharAt(pos, '\0'); + pos++; + if (c == '/') + ResumeLineComment(styler, pos, max, UnknownComment); + else if (c == '*') + ResumeBlockComment(styler, pos, max, UnknownComment, 1); +} + +static void ResumeString(Accessor &styler, int& pos, int max) { + int c = styler.SafeGetCharAt(pos, '\0'); + bool error = false; + while (c != '"' && !error) { + if (pos >= max) { + error = true; + break; + } + if (pos == styler.LineEnd(styler.GetLine(pos))) + styler.SetLineState(styler.GetLine(pos), 0); + if (c == '\\') { + int n = styler.SafeGetCharAt(pos + 1, '\0'); + if (IsValidStringEscape(n)) { + pos += 2; + } else if (n == 'x') { + pos += 2; + error = !ScanNumericEscape(styler, pos, 2, true); + } else if (n == 'u') { + pos += 2; + error = !ScanNumericEscape(styler, pos, 4, true); + } else if (n == 'U') { + pos += 2; + error = !ScanNumericEscape(styler, pos, 8, true); + } else { + pos += 1; + error = true; + } + } else { + pos++; + } + c = styler.SafeGetCharAt(pos, '\0'); + } + if (!error) + pos++; + styler.ColourTo(pos - 1, SCE_RUST_STRING); +} + +static void ResumeRawString(Accessor &styler, int& pos, int max, int num_hashes) { + for (;;) { + if (pos == styler.LineEnd(styler.GetLine(pos))) + styler.SetLineState(styler.GetLine(pos), num_hashes); + + int c = styler.SafeGetCharAt(pos, '\0'); + if (c == '"') { + pos++; + int trailing_num_hashes = 0; + while (styler.SafeGetCharAt(pos, '\0') == '#' && trailing_num_hashes < num_hashes) { + trailing_num_hashes++; + pos++; + } + if (trailing_num_hashes == num_hashes) { + styler.SetLineState(styler.GetLine(pos), 0); + styler.ColourTo(pos - 1, SCE_RUST_STRINGR); + break; + } + } else if (pos >= max) { + styler.ColourTo(pos - 1, SCE_RUST_STRINGR); + break; + } else { + pos++; + } + } +} + +static void ScanRawString(Accessor &styler, int& pos, int max) { + pos++; + int num_hashes = 0; + while (styler.SafeGetCharAt(pos, '\0') == '#') { + num_hashes++; + pos++; + } + if (styler.SafeGetCharAt(pos, '\0') != '"') { + styler.ColourTo(pos - 1, SCE_RUST_LEXERROR); + } else { + pos++; + ResumeRawString(styler, pos, max, num_hashes); + } +} + +void SCI_METHOD LexerRust::Lex(unsigned int startPos, int length, int initStyle, IDocument *pAccess) { + PropSetSimple props; + Accessor styler(pAccess, &props); + int pos = startPos; + int max = pos + length; + + styler.StartAt(pos); + styler.StartSegment(pos); + + if (initStyle == SCE_RUST_COMMENTBLOCK || initStyle == SCE_RUST_COMMENTBLOCKDOC) { + ResumeBlockComment(styler, pos, max, initStyle == SCE_RUST_COMMENTBLOCKDOC ? DocComment : NotDocComment, styler.GetLineState(styler.GetLine(pos) - 1)); + } else if (initStyle == SCE_RUST_COMMENTLINE || initStyle == SCE_RUST_COMMENTLINEDOC) { + ResumeLineComment(styler, pos, max, initStyle == SCE_RUST_COMMENTLINEDOC ? DocComment : NotDocComment); + } else if (initStyle == SCE_RUST_STRING) { + ResumeString(styler, pos, max); + } else if (initStyle == SCE_RUST_STRINGR) { + ResumeRawString(styler, pos, max, styler.GetLineState(styler.GetLine(pos) - 1)); + } + + while (pos < max) { + int c = styler.SafeGetCharAt(pos, '\0'); + int n = styler.SafeGetCharAt(pos + 1, '\0'); + int n2 = styler.SafeGetCharAt(pos + 2, '\0'); + + if (pos == 0 && c == '#' && n == '!') { + pos += 2; + ResumeLineComment(styler, pos, max, NotDocComment); + } else if (IsWhitespace(c)) { + ScanWhitespace(styler, pos, max); + } else if (c == '/' && (n == '/' || n == '*')) { + ScanComments(styler, pos, max); + } else if (c == 'r' && (n == '#' || n == '"')) { + ScanRawString(styler, pos, max); + } else if (IsIdentifierStart(c)) { + ScanIdentifier(styler, pos, keywords); + } else if (IsADigit(c)) { + ScanNumber(styler, pos); + } else if (IsThreeCharOperator(c, n, n2)) { + pos += 3; + styler.ColourTo(pos - 1, SCE_RUST_OPERATOR); + } else if (IsTwoCharOperator(c, n)) { + pos += 2; + styler.ColourTo(pos - 1, SCE_RUST_OPERATOR); + } else if (IsOneCharOperator(c)) { + pos++; + styler.ColourTo(pos - 1, SCE_RUST_OPERATOR); + } else if (c == '\'') { + ScanCharacterLiteralOrLifetime(styler, pos); + } else if (c == '"') { + pos++; + ResumeString(styler, pos, max); + } else { + pos++; + styler.ColourTo(pos - 1, SCE_RUST_LEXERROR); + } + } + styler.ColourTo(pos - 1, SCE_RUST_DEFAULT); + styler.Flush(); +} + +void SCI_METHOD LexerRust::Fold(unsigned int startPos, int length, int initStyle, IDocument *pAccess) { + + if (!options.fold) + return; + + LexAccessor styler(pAccess); + + unsigned int endPos = startPos + length; + int visibleChars = 0; + bool inLineComment = false; + int lineCurrent = styler.GetLine(startPos); + int levelCurrent = SC_FOLDLEVELBASE; + if (lineCurrent > 0) + levelCurrent = styler.LevelAt(lineCurrent-1) >> 16; + unsigned int lineStartNext = styler.LineStart(lineCurrent+1); + int levelMinCurrent = levelCurrent; + int levelNext = levelCurrent; + char chNext = styler[startPos]; + int styleNext = styler.StyleAt(startPos); + int style = initStyle; + const bool userDefinedFoldMarkers = !options.foldExplicitStart.empty() && !options.foldExplicitEnd.empty(); + for (unsigned int i = startPos; i < endPos; i++) { + char ch = chNext; + chNext = styler.SafeGetCharAt(i + 1); + int stylePrev = style; + style = styleNext; + styleNext = styler.StyleAt(i + 1); + bool atEOL = i == (lineStartNext-1); + if ((style == SCE_RUST_COMMENTLINE) || (style == SCE_RUST_COMMENTLINEDOC)) + inLineComment = true; + if (options.foldComment && options.foldCommentMultiline && IsStreamCommentStyle(style) && !inLineComment) { + if (!IsStreamCommentStyle(stylePrev)) { + levelNext++; + } else if (!IsStreamCommentStyle(styleNext) && !atEOL) { + // Comments don't end at end of line and the next character may be unstyled. + levelNext--; + } + } + if (options.foldComment && options.foldCommentExplicit && ((style == SCE_RUST_COMMENTLINE) || options.foldExplicitAnywhere)) { + if (userDefinedFoldMarkers) { + if (styler.Match(i, options.foldExplicitStart.c_str())) { + levelNext++; + } else if (styler.Match(i, options.foldExplicitEnd.c_str())) { + levelNext--; + } + } else { + if ((ch == '/') && (chNext == '/')) { + char chNext2 = styler.SafeGetCharAt(i + 2); + if (chNext2 == '{') { + levelNext++; + } else if (chNext2 == '}') { + levelNext--; + } + } + } + } + if (options.foldSyntaxBased && (style == SCE_RUST_OPERATOR)) { + if (ch == '{') { + // Measure the minimum before a '{' to allow + // folding on "} else {" + if (levelMinCurrent > levelNext) { + levelMinCurrent = levelNext; + } + levelNext++; + } else if (ch == '}') { + levelNext--; + } + } + if (!IsASpace(ch)) + visibleChars++; + if (atEOL || (i == endPos-1)) { + int levelUse = levelCurrent; + if (options.foldSyntaxBased && options.foldAtElse) { + levelUse = levelMinCurrent; + } + int lev = levelUse | levelNext << 16; + if (visibleChars == 0 && options.foldCompact) + lev |= SC_FOLDLEVELWHITEFLAG; + if (levelUse < levelNext) + lev |= SC_FOLDLEVELHEADERFLAG; + if (lev != styler.LevelAt(lineCurrent)) { + styler.SetLevel(lineCurrent, lev); + } + lineCurrent++; + lineStartNext = styler.LineStart(lineCurrent+1); + levelCurrent = levelNext; + levelMinCurrent = levelCurrent; + if (atEOL && (i == static_cast(styler.Length()-1))) { + // There is an empty line at end of file so give it same level and empty + styler.SetLevel(lineCurrent, (levelCurrent | levelCurrent << 16) | SC_FOLDLEVELWHITEFLAG); + } + visibleChars = 0; + inLineComment = false; + } + } +} + +LexerModule lmRust(SCLEX_RUST, LexerRust::LexerFactoryRust, "rust", rustWordLists); diff --git a/scintilla/makefile.win32 b/scintilla/makefile.win32 index 5cbed06a70..6daac9b63e 100644 --- a/scintilla/makefile.win32 +++ b/scintilla/makefile.win32 @@ -96,6 +96,7 @@ LexLua.o \ LexHaskell.o \ LexBasic.o \ LexR.o \ +LexRust.o \ LexYAML.o \ LexCmake.o \ LexNsis.o diff --git a/scintilla/scintilla_changes.patch b/scintilla/scintilla_changes.patch index fdc8d467c4..3b754f82f6 100644 --- a/scintilla/scintilla_changes.patch +++ b/scintilla/scintilla_changes.patch @@ -31,7 +31,7 @@ diff --git b/scintilla/src/Catalogue.cxx a/scintilla/src/Catalogue.cxx index e728f34..85116a5 100644 +++ scintilla/src/Catalogue.cxx --- scintilla/src/Catalogue.cxx -@@ -76,112 +76,47 @@ int Scintilla_LinkLexers() { +@@ -76,112 +76,48 @@ int Scintilla_LinkLexers() { //++Autogenerated -- run scripts/LexGen.py to regenerate //**\(\tLINK_LEXER(\*);\n\) @@ -121,7 +121,7 @@ index e728f34..85116a5 100644 LINK_LEXER(lmR); - LINK_LEXER(lmREBOL); LINK_LEXER(lmRuby); -- LINK_LEXER(lmRust); + LINK_LEXER(lmRust); - LINK_LEXER(lmScriptol); - LINK_LEXER(lmSmalltalk); - LINK_LEXER(lmSML); diff --git a/scintilla/src/Catalogue.cxx b/scintilla/src/Catalogue.cxx index 85116a5fb4..70ce3bc701 100644 --- a/scintilla/src/Catalogue.cxx +++ b/scintilla/src/Catalogue.cxx @@ -112,6 +112,7 @@ int Scintilla_LinkLexers() { LINK_LEXER(lmPython); LINK_LEXER(lmR); LINK_LEXER(lmRuby); + LINK_LEXER(lmRust); LINK_LEXER(lmSQL); LINK_LEXER(lmTCL); LINK_LEXER(lmTxt2tags); diff --git a/src/document.c b/src/document.c index 37dbe97b1b..14b7643e22 100644 --- a/src/document.c +++ b/src/document.c @@ -2317,6 +2317,7 @@ void document_highlight_tags(GeanyDocument *doc) case GEANY_FILETYPES_JAVA: case GEANY_FILETYPES_OBJECTIVEC: case GEANY_FILETYPES_VALA: + case GEANY_FILETYPES_RUST: { /* index of the keyword set in the Scintilla lexer, for diff --git a/src/editor.c b/src/editor.c index 1d6676ee54..0db81c475e 100644 --- a/src/editor.c +++ b/src/editor.c @@ -1257,6 +1257,7 @@ static gboolean lexer_has_braces(ScintillaObject *sci) case SCLEX_PERL: case SCLEX_TCL: case SCLEX_R: + case SCLEX_RUST: return TRUE; default: return FALSE; @@ -2893,6 +2894,7 @@ static gint get_multiline_comment_style(GeanyEditor *editor, gint line_start) case SCLEX_CAML: style_comment = SCE_CAML_COMMENT; break; case SCLEX_D: style_comment = SCE_D_COMMENT; break; case SCLEX_PASCAL: style_comment = SCE_PAS_COMMENT; break; + case SCLEX_RUST: style_comment = SCE_RUST_COMMENTBLOCK; break; default: style_comment = SCE_C_COMMENT; } @@ -3415,6 +3417,10 @@ static gboolean in_block_comment(gint lexer, gint style) case SCLEX_CSS: return (style == SCE_CSS_COMMENT); + case SCLEX_RUST: + return (style == SCE_RUST_COMMENTBLOCK || + style == SCE_RUST_COMMENTBLOCKDOC); + default: return FALSE; } @@ -4951,6 +4957,7 @@ void editor_set_indentation_guides(GeanyEditor *editor) case SCLEX_FREEBASIC: case SCLEX_D: case SCLEX_OCTAVE: + case SCLEX_RUST: mode = SC_IV_LOOKBOTH; break; diff --git a/src/filetypes.c b/src/filetypes.c index a971a8165b..0048b345f1 100644 --- a/src/filetypes.c +++ b/src/filetypes.c @@ -522,6 +522,15 @@ static void init_builtin_filetypes(void) ft->name = g_strdup("PowerShell"); filetype_make_title(ft, TITLE_SOURCE_FILE); ft->group = GEANY_FILETYPE_GROUP_SCRIPT; + +#define RUST + ft = filetypes[GEANY_FILETYPES_RUST]; + ft->lang = 45; + ft->name = g_strdup("Rust"); + filetype_make_title(ft, TITLE_SOURCE_FILE); + ft->mime_type = g_strdup("text/x-rustsrc"); + ft->group = GEANY_FILETYPE_GROUP_COMPILED; + } @@ -937,7 +946,8 @@ static GeanyFiletype *find_shebang(const gchar *utf8_filename, const gchar *line { "ash", GEANY_FILETYPES_SH }, { "dmd", GEANY_FILETYPES_D }, { "wish", GEANY_FILETYPES_TCL }, - { "node", GEANY_FILETYPES_JS } + { "node", GEANY_FILETYPES_JS }, + { "rust", GEANY_FILETYPES_RUST } }; gchar *tmp = g_path_get_basename(line + 2); gchar *basename_interpreter = tmp; diff --git a/src/filetypes.h b/src/filetypes.h index 49fff2fef5..2161f8b055 100644 --- a/src/filetypes.h +++ b/src/filetypes.h @@ -94,6 +94,7 @@ typedef enum GEANY_FILETYPES_ABAQUS, GEANY_FILETYPES_BATCH, GEANY_FILETYPES_POWERSHELL, + GEANY_FILETYPES_RUST, /* ^ 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 d84b489336..073acef58c 100644 --- a/src/highlighting.c +++ b/src/highlighting.c @@ -1049,6 +1049,7 @@ void highlighting_init_styles(guint filetype_idx, GKeyFile *config, GKeyFile *co init_styleset_case(PYTHON); init_styleset_case(R); init_styleset_case(RUBY); + init_styleset_case(RUST); init_styleset_case(SH); init_styleset_case(SQL); init_styleset_case(TCL); @@ -1131,6 +1132,7 @@ void highlighting_set_styles(ScintillaObject *sci, GeanyFiletype *ft) styleset_case(PYTHON); styleset_case(R); styleset_case(RUBY); + styleset_case(RUST); styleset_case(SH); styleset_case(SQL); styleset_case(TCL); @@ -1543,6 +1545,12 @@ gboolean highlighting_is_string_style(gint lexer, gint style) case SCLEX_ABAQUS: return (style == SCE_ABAQUS_STRING); + + case SCLEX_RUST: + return (style == SCE_RUST_CHARACTER || + style == SCE_RUST_STRING || + style == SCE_RUST_STRINGR || + style == SCE_RUST_LEXERROR); } return FALSE; } @@ -1692,6 +1700,12 @@ gboolean highlighting_is_comment_style(gint lexer, gint style) return (style == SCE_ASM_COMMENT || style == SCE_ASM_COMMENTBLOCK || style == SCE_ASM_COMMENTDIRECTIVE); + + case SCLEX_RUST: + return (style == SCE_RUST_COMMENTBLOCK || + style == SCE_RUST_COMMENTLINE || + style == SCE_RUST_COMMENTBLOCKDOC || + style == SCE_RUST_COMMENTLINEDOC); } return FALSE; } diff --git a/src/highlightingmappings.h b/src/highlightingmappings.h index 1eba579a51..8c34d5303e 100644 --- a/src/highlightingmappings.h +++ b/src/highlightingmappings.h @@ -1290,6 +1290,41 @@ static const HLKeyword highlighting_keywords_RUBY[] = }; #define highlighting_properties_RUBY EMPTY_PROPERTIES +/* Rust */ +#define highlighting_lexer_RUST SCLEX_RUST +static const HLStyle highlighting_styles_RUST[] = +{ + { SCE_RUST_DEFAULT, "default", FALSE }, + { SCE_RUST_COMMENTBLOCK, "commentblock", FALSE }, + { SCE_RUST_COMMENTLINE, "commentline", FALSE }, + { SCE_RUST_COMMENTBLOCKDOC, "commentblockdoc", FALSE }, + { SCE_RUST_COMMENTLINEDOC, "commentlinedoc", FALSE }, + { SCE_RUST_NUMBER, "number", FALSE }, + { SCE_RUST_WORD, "word", FALSE }, + { SCE_RUST_WORD2, "word2", FALSE }, + { SCE_RUST_WORD3, "word3", FALSE }, + { SCE_RUST_WORD4, "word4", FALSE }, + { SCE_RUST_WORD5, "word5", FALSE }, + { SCE_RUST_WORD6, "word6", FALSE }, + { SCE_RUST_WORD7, "word7", FALSE }, + { SCE_RUST_STRING, "string", FALSE }, + { SCE_RUST_STRINGR, "stringraw", FALSE }, + { SCE_RUST_CHARACTER, "character", FALSE }, + { SCE_RUST_OPERATOR, "operator", FALSE }, + { SCE_RUST_IDENTIFIER, "identifier", FALSE }, + { SCE_RUST_LIFETIME, "lifetime", FALSE }, + { SCE_RUST_MACRO, "macro", FALSE }, + { SCE_RUST_LEXERROR, "lexerror", FALSE } +}; +static const HLKeyword highlighting_keywords_RUST[] = +{ + { 0, "primary", FALSE }, + /* SCI_SETKEYWORDS = 1 - secondary + global tags file types */ + { 1, "secondary", TRUE }, + { 2, "tertiary", FALSE }, + /* SCI_SETKEYWORDS = 3 is for current session types - see editor_lexer_get_type_keyword_idx() */ +}; +#define highlighting_properties_RUST EMPTY_PROPERTIES /* SH */ #define highlighting_lexer_SH SCLEX_BASH diff --git a/src/symbols.c b/src/symbols.c index 0d820d11e1..f03626fd1d 100644 --- a/src/symbols.c +++ b/src/symbols.c @@ -300,6 +300,7 @@ const gchar *symbols_get_context_separator(gint ft_id) case GEANY_FILETYPES_GLSL: /* for structs */ /*case GEANY_FILETYPES_RUBY:*/ /* not sure what to use atm*/ case GEANY_FILETYPES_PHP: + case GEANY_FILETYPES_RUST: return "::"; /* avoid confusion with other possible separators in group/section name */ @@ -775,6 +776,22 @@ static void add_top_level_items(GeanyDocument *doc) NULL); break; } + case GEANY_FILETYPES_RUST: + { + tag_list_add_groups(tag_store, + &(tv_iters.tag_namespace), _("Modules"), "classviewer-namespace", + &(tv_iters.tag_struct), _("Structures"), "classviewer-struct", + &(tv_iters.tag_interface), _("Traits"), "classviewer-class", + &(tv_iters.tag_class), _("Implementations"), "classviewer-class", + &(tv_iters.tag_function), _("Functions"), "classviewer-method", + &(tv_iters.tag_type), _("Typedefs / Enums"), "classviewer-struct", + &(tv_iters.tag_variable), _("Variables"), "classviewer-var", + &(tv_iters.tag_macro), _("Macros"), "classviewer-macro", + &(tv_iters.tag_member), _("Methods"), "classviewer-member", + &(tv_iters.tag_other), _("Other"), "classviewer-other", NULL, + NULL); + break; + } case GEANY_FILETYPES_PERL: { tag_list_add_groups(tag_store, diff --git a/tagmanager/ctags/Makefile.am b/tagmanager/ctags/Makefile.am index 25184ef9a1..a110b920c1 100644 --- a/tagmanager/ctags/Makefile.am +++ b/tagmanager/ctags/Makefile.am @@ -43,6 +43,7 @@ parsers = \ r.c \ rest.c \ ruby.c \ + rust.c \ sh.c \ sql.c \ tcl.c \ diff --git a/tagmanager/ctags/makefile.win32 b/tagmanager/ctags/makefile.win32 index f1a11a9496..598620945c 100644 --- a/tagmanager/ctags/makefile.win32 +++ b/tagmanager/ctags/makefile.win32 @@ -48,7 +48,7 @@ $(COMPLIB): abaqus.o abc.o args.o c.o cobol.o fortran.o make.o conf.o pascal.o p actionscript.o nsis.o objc.o \ haskell.o haxe.o html.o python.o lregex.o asciidoc.o rest.o sh.o ctags.o entry.o get.o keyword.o nestlevel.o \ options.o \ -parse.o basic.o read.o sort.o strlist.o latex.o markdown.o matlab.o docbook.o tcl.o ruby.o asm.o sql.o txt2tags.o css.o \ +parse.o basic.o read.o sort.o strlist.o latex.o markdown.o matlab.o docbook.o tcl.o ruby.o rust.o asm.o sql.o txt2tags.o css.o \ vstring.o r.o $(AR) rc $@ $^ $(RANLIB) $@ diff --git a/tagmanager/ctags/parsers.h b/tagmanager/ctags/parsers.h index e572a51c8f..fc3ed1d3a1 100644 --- a/tagmanager/ctags/parsers.h +++ b/tagmanager/ctags/parsers.h @@ -59,7 +59,8 @@ CobolParser, \ ObjcParser, \ AsciidocParser, \ - AbaqusParser + AbaqusParser, \ + RustParser /* langType of each parser 0 CParser @@ -107,6 +108,7 @@ langType of each parser 42 ObjcParser 43 AsciidocParser 44 AbaqusParser +45 Rust */ #endif /* _PARSERS_H */ diff --git a/tagmanager/ctags/rust.c b/tagmanager/ctags/rust.c new file mode 100644 index 0000000000..70de97151c --- /dev/null +++ b/tagmanager/ctags/rust.c @@ -0,0 +1,909 @@ +/* +* +* This source code is released for free distribution under the terms of the +* GNU General Public License. +* +* This module contains functions for generating tags for Rust files. +*/ + +/* +* INCLUDE FILES +*/ +#include "general.h" /* must always come first */ +#include "main.h" + +#include + +#include "keyword.h" +#include "parse.h" +#include "entry.h" +#include "options.h" +#include "read.h" +#include "vstring.h" + +/* +* MACROS +*/ +#define MAX_STRING_LENGTH 64 + +/* +* DATA DECLARATIONS +*/ + +typedef enum { + K_MOD, + K_STRUCT, + K_TRAIT, + K_IMPL, + K_FN, + K_ENUM, + K_TYPE, + K_STATIC, + K_MACRO, + K_FIELD, + K_VARIANT, + K_METHOD, + K_NONE +} RustKind; + +static kindOption rustKinds[] = { + {TRUE, 'n', "namespace", "module"}, + {TRUE, 's', "struct", "structural type"}, + {TRUE, 'i', "interface", "trait interface"}, + {TRUE, 'c', "class", "implementation"}, + {TRUE, 'f', "function", "Function"}, + {TRUE, 'g', "enum", "Enum"}, + {TRUE, 't', "typedef", "Type Alias"}, + {TRUE, 'v', "variable", "Global variable"}, + {TRUE, 'M', "macro", "Macro Definition"}, + {TRUE, 'm', "field", "A struct field"}, + {TRUE, 'e', "enumerator", "An enum variant"}, + {TRUE, 'F', "method", "A method"}, +}; + +typedef enum { + TOKEN_WHITESPACE, + TOKEN_STRING, + TOKEN_IDENT, + TOKEN_LSHIFT, + TOKEN_RSHIFT, + TOKEN_EOF +} tokenType; + +typedef struct { + /* Characters */ + int cur_c; + int next_c; + + /* Tokens */ + int cur_token; + vString* token_str; + unsigned long line; + MIOPos pos; +} lexerState; + +/* +* FUNCTION PROTOTYPES +*/ + +static void parseBlock (lexerState *lexer, boolean delim, int kind, vString *scope); + +/* +* FUNCTION DEFINITIONS +*/ + +/* Resets the scope string to the old length */ +static void resetScope (vString *scope, size_t old_len) +{ + scope->length = old_len; + scope->buffer[old_len] = '\0'; +} + +/* Adds a name to the end of the scope string */ +static void addToScope (vString *scope, vString *name) +{ + if (vStringLength(scope) > 0) + vStringCatS(scope, "::"); + vStringCat(scope, name); +} + +/* Write the lexer's current token to string, taking care of special tokens */ +static void writeCurTokenToStr (lexerState *lexer, vString *out_str) +{ + switch (lexer->cur_token) + { + case TOKEN_IDENT: + vStringCat(out_str, lexer->token_str); + break; + case TOKEN_STRING: + vStringPut(out_str, '"'); + vStringCat(out_str, lexer->token_str); + vStringPut(out_str, '"'); + break; + case TOKEN_WHITESPACE: + vStringPut(out_str, ' '); + break; + case TOKEN_LSHIFT: + vStringCatS(out_str, "<<"); + break; + case TOKEN_RSHIFT: + vStringCatS(out_str, ">>"); + break; + default: + vStringPut(out_str, (char) lexer->cur_token); + } +} + +/* Reads a character from the file */ +static void advanceChar (lexerState *lexer) +{ + lexer->cur_c = lexer->next_c; + lexer->next_c = fileGetc(); +} + +/* Reads N characters from the file */ +static void advanceNChar (lexerState *lexer, int n) +{ + while (n--) + advanceChar(lexer); +} + + +static boolean isWhitespace (int c) +{ + return c == ' ' || c == '\t' || c == '\r' || c == '\n'; +} + +static boolean isAscii (int c) +{ + return (c >= 0) && (c < 0x80); +} + +/* This isn't quite right for Unicode identifiers */ +static boolean isIdentifierStart (int c) +{ + return (isAscii(c) && (isalpha(c) || c == '_')) || !isAscii(c); +} + +/* This isn't quite right for Unicode identifiers */ +static boolean isIdentifierContinue (int c) +{ + return (isAscii(c) && (isalnum(c) || c == '_')) || !isAscii(c); +} + +static void scanWhitespace (lexerState *lexer) +{ + while (isWhitespace(lexer->cur_c)) + advanceChar(lexer); +} + +/* Normal line comments start with two /'s and continue until the next \n + * (NOT any other newline character!). Additionally, a shebang in the beginning + * of the file also counts as a line comment. + * Block comments start with / followed by a * and end with a * followed by a /. + * Unlike in C/C++ they nest. */ +static void scanComments (lexerState *lexer) +{ + /* // or #! */ + if (lexer->next_c == '/' || lexer->next_c == '!') + { + advanceNChar(lexer, 2); + while (lexer->cur_c != EOF && lexer->cur_c != '\n') + advanceChar(lexer); + } + else if (lexer->next_c == '*') + { + int level = 1; + advanceNChar(lexer, 2); + while (lexer->cur_c != EOF && level > 0) + { + if (lexer->cur_c == '*' && lexer->next_c == '/') + { + level--; + advanceNChar(lexer, 2); + } + else if (lexer->cur_c == '/' && lexer->next_c == '*') + { + level++; + advanceNChar(lexer, 2); + } + else + { + advanceChar(lexer); + } + } + } +} + +static void scanIdentifier (lexerState *lexer) +{ + vStringClear(lexer->token_str); + do + { + vStringPut(lexer->token_str, (char) lexer->cur_c); + advanceChar(lexer); + } while(lexer->cur_c != EOF && isIdentifierContinue(lexer->cur_c)); +} + +/* Double-quoted strings, we only care about the \" escape. These + * last past the end of the line, so be careful not too store too much + * of them (see MAX_STRING_LENGTH). The only place we look at their + * contents is in the function definitions, and there the valid strings are + * things like "C" and "Rust" */ +static void scanString (lexerState *lexer) +{ + vStringClear(lexer->token_str); + advanceChar(lexer); + while (lexer->cur_c != EOF && lexer->cur_c != '"') + { + if (lexer->cur_c == '\\' && lexer->next_c == '"') + advanceChar(lexer); + if (vStringLength(lexer->token_str) < MAX_STRING_LENGTH) + vStringPut(lexer->token_str, (char) lexer->cur_c); + advanceChar(lexer); + } + advanceChar(lexer); +} + +/* Raw strings look like this: r"" or r##""## where the number of + * hashes must match */ +static void scanRawString (lexerState *lexer) +{ + size_t num_initial_hashes = 0; + vStringClear(lexer->token_str); + advanceChar(lexer); + /* Count how many leading hashes there are */ + while (lexer->cur_c == '#') + { + num_initial_hashes++; + advanceChar(lexer); + } + if (lexer->cur_c != '"') + return; + advanceChar(lexer); + while (lexer->cur_c != EOF) + { + if (vStringLength(lexer->token_str) < MAX_STRING_LENGTH) + vStringPut(lexer->token_str, (char) lexer->cur_c); + /* Count how many trailing hashes there are. If the number is equal or more + * than the number of leading hashes, break. */ + if (lexer->cur_c == '"') + { + size_t num_trailing_hashes = 0; + advanceChar(lexer); + while (lexer->cur_c == '#' && num_trailing_hashes < num_initial_hashes) + { + num_trailing_hashes++; + + if (vStringLength(lexer->token_str) < MAX_STRING_LENGTH) + vStringPut(lexer->token_str, (char) lexer->cur_c); + advanceChar(lexer); + } + if (num_trailing_hashes == num_initial_hashes) + { + /* Strip the trailing hashes and quotes */ + if (vStringLength(lexer->token_str) < MAX_STRING_LENGTH && vStringLength(lexer->token_str) > num_trailing_hashes + 1) + { + lexer->token_str->length = vStringLength(lexer->token_str) - num_trailing_hashes - 1; + lexer->token_str->buffer[lexer->token_str->length] = '\0'; + } + break; + } + } + else + { + advanceChar(lexer); + } + } +} + +/* Advances the parser one token, optionally skipping whitespace + * (otherwise it is concatenated and returned as a single whitespace token). + * Whitespace is needed to properly render function signatures. Unrecognized + * token starts are stored literally, e.g. token may equal to a character '#'. */ +static int advanceToken (lexerState *lexer, boolean skip_whitspace) +{ + boolean have_whitespace = FALSE; + lexer->line = getSourceLineNumber(); + lexer->pos = getInputFilePosition(); + while (lexer->cur_c != EOF) + { + if (isWhitespace(lexer->cur_c)) + { + scanWhitespace(lexer); + have_whitespace = TRUE; + } + else if (lexer->cur_c == '/' && (lexer->next_c == '/' || lexer->next_c == '*')) + { + scanComments(lexer); + have_whitespace = TRUE; + } + else + { + if (have_whitespace && !skip_whitspace) + return lexer->cur_token = TOKEN_WHITESPACE; + break; + } + } + lexer->line = getSourceLineNumber(); + lexer->pos = getInputFilePosition(); + while (lexer->cur_c != EOF) + { + if (lexer->cur_c == '"') + { + scanString(lexer); + return lexer->cur_token = TOKEN_STRING; + } + else if (lexer->cur_c == 'r' && (lexer->next_c == '#' || lexer->next_c == '"')) + { + scanRawString(lexer); + return lexer->cur_token = TOKEN_STRING; + } + else if (isIdentifierStart(lexer->cur_c)) + { + scanIdentifier(lexer); + return lexer->cur_token = TOKEN_IDENT; + } + /* These shift tokens aren't too important for tag-generation per se, + * but they confuse the skipUntil code which tracks the <> pairs. */ + else if (lexer->cur_c == '>' && lexer->next_c == '>') + { + advanceNChar(lexer, 2); + return lexer->cur_token = TOKEN_RSHIFT; + } + else if (lexer->cur_c == '<' && lexer->next_c == '<') + { + advanceNChar(lexer, 2); + return lexer->cur_token = TOKEN_LSHIFT; + } + else + { + int c = lexer->cur_c; + advanceChar(lexer); + return lexer->cur_token = c; + } + } + return lexer->cur_token = TOKEN_EOF; +} + +static void initLexer (lexerState *lexer) +{ + advanceNChar(lexer, 2); + lexer->token_str = vStringNew(); + + if (lexer->cur_c == '#' && lexer->next_c == '!') + scanComments(lexer); + advanceToken(lexer, TRUE); +} + +static void deInitLexer (lexerState *lexer) +{ + vStringDelete(lexer->token_str); + lexer->token_str = NULL; +} + +static void addTag (vString* ident, const char* type, const char* arg_list, int kind, unsigned long line, MIOPos pos, vString *scope, int parent_kind) +{ + if (kind == K_NONE) + return; + tagEntryInfo tag; + initTagEntry(&tag, ident->buffer); + + tag.lineNumber = line; + tag.filePosition = pos; + tag.sourceFileName = getSourceFileName(); + + tag.kindName = rustKinds[kind].name; + tag.kind = rustKinds[kind].letter; + + tag.extensionFields.arglist = arg_list; + tag.extensionFields.varType = type; + if (parent_kind != K_NONE) + { + tag.extensionFields.scope[0] = rustKinds[parent_kind].name; + tag.extensionFields.scope[1] = scope->buffer; + } + makeTagEntry(&tag); +} + +/* Skip tokens until one of the goal tokens is hit. Escapes when level = 0 if there are no goal tokens. + * Keeps track of balanced <>'s, ()'s, []'s, and {}'s and ignores the goal tokens within those pairings */ +static void skipUntil (lexerState *lexer, int goal_tokens[], int num_goal_tokens) +{ + int angle_level = 0; + int paren_level = 0; + int brace_level = 0; + int bracket_level = 0; + while (lexer->cur_token != TOKEN_EOF) + { + if (angle_level == 0 && paren_level == 0 && brace_level == 0 + && bracket_level == 0) + { + int ii = 0; + for(ii = 0; ii < num_goal_tokens; ii++) + { + if (lexer->cur_token == goal_tokens[ii]) + { + break; + } + } + if (ii < num_goal_tokens) + break; + } + switch (lexer->cur_token) + { + case '<': + angle_level++; + break; + case '(': + paren_level++; + break; + case '{': + brace_level++; + break; + case '[': + bracket_level++; + break; + case '>': + angle_level--; + break; + case ')': + paren_level--; + break; + case '}': + brace_level--; + break; + case ']': + bracket_level--; + break; + case TOKEN_RSHIFT: + if (angle_level >= 2) + angle_level -= 2; + break; + /* TOKEN_LSHIFT is never interpreted as two <'s in valid Rust code */ + default: + break; + } + /* Has to be after the token switch to catch the case when we start with the initial level token */ + if (num_goal_tokens == 0 && angle_level == 0 && paren_level == 0 && brace_level == 0 + && bracket_level == 0) + break; + advanceToken(lexer, TRUE); + } +} + +/* Function format: + * "fn" [] "(" [] ")" ["->" ] "{" [] "}"*/ +static void parseFn (lexerState *lexer, vString *scope, int parent_kind) +{ + int kind = (parent_kind == K_TRAIT || parent_kind == K_IMPL) ? K_METHOD : K_FN; + vString *name; + vString *arg_list; + unsigned long line; + MIOPos pos; + int paren_level = 0; + boolean found_paren = FALSE; + boolean valid_signature = TRUE; + + advanceToken(lexer, TRUE); + if (lexer->cur_token != TOKEN_IDENT) + return; + + name = vStringNewCopy(lexer->token_str); + arg_list = vStringNew(); + + line = lexer->line; + pos = lexer->pos; + + advanceToken(lexer, TRUE); + + /* HACK: This is a bit coarse as far as what tag entry means by + * 'arglist'... */ + while (lexer->cur_token != '{' && lexer->cur_token != ';') + { + if (lexer->cur_token == '}') + { + valid_signature = FALSE; + break; + } + else if (lexer->cur_token == '(') + { + found_paren = TRUE; + paren_level++; + } + else if (lexer->cur_token == ')') + { + paren_level--; + if (paren_level < 0) + { + valid_signature = FALSE; + break; + } + } + else if (lexer->cur_token == TOKEN_EOF) + { + valid_signature = FALSE; + break; + } + writeCurTokenToStr(lexer, arg_list); + advanceToken(lexer, FALSE); + } + if (!found_paren || paren_level != 0) + valid_signature = FALSE; + + if (valid_signature) + { + vStringStripTrailing(arg_list); + addTag(name, NULL, arg_list->buffer, kind, line, pos, scope, parent_kind); + addToScope(scope, name); + parseBlock(lexer, TRUE, kind, scope); + } + + vStringDelete(name); + vStringDelete(arg_list); +} + +/* Mod format: + * "mod" "{" [] "}" + * "mod" ";"*/ +static void parseMod (lexerState *lexer, vString *scope, int parent_kind) +{ + advanceToken(lexer, TRUE); + if (lexer->cur_token != TOKEN_IDENT) + return; + + addTag(lexer->token_str, NULL, NULL, K_MOD, lexer->line, lexer->pos, scope, parent_kind); + addToScope(scope, lexer->token_str); + + advanceToken(lexer, TRUE); + + parseBlock(lexer, TRUE, K_MOD, scope); +} + +/* Trait format: + * "trait" [] "{" [] "}" + */ +static void parseTrait (lexerState *lexer, vString *scope, int parent_kind) +{ + int goal_tokens[] = {'{'}; + + advanceToken(lexer, TRUE); + if (lexer->cur_token != TOKEN_IDENT) + return; + + addTag(lexer->token_str, NULL, NULL, K_TRAIT, lexer->line, lexer->pos, scope, parent_kind); + addToScope(scope, lexer->token_str); + + advanceToken(lexer, TRUE); + + skipUntil(lexer, goal_tokens, 1); + + parseBlock(lexer, TRUE, K_TRAIT, scope); +} + +/* Skips type blocks of the form , ...> */ +static void skipTypeBlock (lexerState *lexer) +{ + if (lexer->cur_token == '<') + { + skipUntil(lexer, NULL, 0); + advanceToken(lexer, TRUE); + } +} + +/* Essentially grabs the last ident before 'for', '<' and '{', which + * tends to correspond to what we want as the impl tag entry name */ +static void parseQualifiedType (lexerState *lexer, vString* name) +{ + while (lexer->cur_token != TOKEN_EOF) + { + if (lexer->cur_token == TOKEN_IDENT) + { + if (strcmp(lexer->token_str->buffer, "for") == 0) + break; + vStringClear(name); + vStringCat(name, lexer->token_str); + } + else if (lexer->cur_token == '<' || lexer->cur_token == '{') + { + break; + } + advanceToken(lexer, TRUE); + } + skipTypeBlock(lexer); +} + +/* Impl format: + * "impl" [] [] ["for" []] "{" [] "}" + */ +static void parseImpl (lexerState *lexer, vString *scope, int parent_kind) +{ + unsigned long line; + MIOPos pos; + vString *name; + + advanceToken(lexer, TRUE); + + line = lexer->line; + pos = lexer->pos; + + skipTypeBlock(lexer); + + name = vStringNew(); + + parseQualifiedType(lexer, name); + + if (lexer->cur_token == TOKEN_IDENT && strcmp(lexer->token_str->buffer, "for") == 0) + { + advanceToken(lexer, TRUE); + parseQualifiedType(lexer, name); + } + + addTag(name, NULL, NULL, K_IMPL, line, pos, scope, parent_kind); + addToScope(scope, name); + + parseBlock(lexer, TRUE, K_IMPL, scope); + + vStringDelete(name); +} + +/* Static format: + * "static" ["mut"] + */ +static void parseStatic (lexerState *lexer, vString *scope, int parent_kind) +{ + advanceToken(lexer, TRUE); + if (lexer->cur_token != TOKEN_IDENT) + return; + if (strcmp(lexer->token_str->buffer, "mut") == 0) + { + advanceToken(lexer, TRUE); + } + if (lexer->cur_token != TOKEN_IDENT) + return; + + addTag(lexer->token_str, NULL, NULL, K_STATIC, lexer->line, lexer->pos, scope, parent_kind); +} + +/* Type format: + * "type" + */ +static void parseType (lexerState *lexer, vString *scope, int parent_kind) +{ + advanceToken(lexer, TRUE); + if (lexer->cur_token != TOKEN_IDENT) + return; + + addTag(lexer->token_str, NULL, NULL, K_TYPE, lexer->line, lexer->pos, scope, parent_kind); +} + +/* Structs and enums are very similar syntax-wise. + * It is possible to parse variants a bit more cleverly (e.g. make tuple variants functions and + * struct variants structs) but it'd be too clever and the signature wouldn't make too much sense without + * the enum's definition (e.g. for the type bounds) + * + * Struct/Enum format: + * "struct/enum" [] "{" [,]+ "}" + * "struct/enum" [] ";" + * */ +static void parseStructOrEnum (lexerState *lexer, vString *scope, int parent_kind, boolean is_struct) +{ + int kind = is_struct ? K_STRUCT : K_ENUM; + int field_kind = is_struct ? K_FIELD : K_VARIANT; + int goal_tokens1[] = {';', '{'}; + + advanceToken(lexer, TRUE); + if (lexer->cur_token != TOKEN_IDENT) + return; + + addTag(lexer->token_str, NULL, NULL, kind, lexer->line, lexer->pos, scope, parent_kind); + addToScope(scope, lexer->token_str); + + skipUntil(lexer, goal_tokens1, 2); + + if (lexer->cur_token == '{') + { + vString *field_name = vStringNew(); + while (lexer->cur_token != TOKEN_EOF) + { + if (lexer->cur_token == TOKEN_IDENT) + { + int goal_tokens2[] = {'}', ','}; + if (strcmp(lexer->token_str->buffer, "priv") == 0) + { + advanceToken(lexer, TRUE); + if (lexer->cur_token != TOKEN_IDENT) + { + /* Something's up with this field, skip to the next one */ + skipUntil(lexer, goal_tokens2, 2); + continue; + } + } + + vStringClear(field_name); + vStringCat(field_name, lexer->token_str); + addTag(field_name, NULL, NULL, field_kind, lexer->line, lexer->pos, scope, kind); + skipUntil(lexer, goal_tokens2, 2); + } + if (lexer->cur_token == '}') + { + advanceToken(lexer, TRUE); + break; + } + advanceToken(lexer, TRUE); + } + vStringDelete(field_name); + } +} + +/* Skip the body of the macro. Can't use skipUntil here as + * the body of the macro may have arbitrary code which confuses it (e.g. + * bitshift operators/function return arrows) */ +static void skipMacro (lexerState *lexer) +{ + int level = 0; + int plus_token = 0; + int minus_token = 0; + + advanceToken(lexer, TRUE); + if (lexer->cur_token == '(') + { + plus_token = '('; + minus_token = ')'; + } + else if (lexer->cur_token == '{') + { + plus_token = '{'; + minus_token = '}'; + } + else + { + return; + } + + while (lexer->cur_token != TOKEN_EOF) + { + if (lexer->cur_token == plus_token) + level++; + else if (lexer->cur_token == minus_token) + level--; + if (level == 0) + break; + advanceToken(lexer, TRUE); + } + advanceToken(lexer, TRUE); +} + +/* + * Macro rules format: + * "macro_rules" "!" + */ +static void parseMacroRules (lexerState *lexer, vString *scope, int parent_kind) +{ + advanceToken(lexer, TRUE); + + if (lexer->cur_token != '!') + return; + + advanceToken(lexer, TRUE); + + if (lexer->cur_token != TOKEN_IDENT) + return; + + addTag(lexer->token_str, NULL, NULL, K_MACRO, lexer->line, lexer->pos, scope, parent_kind); + + skipMacro(lexer); +} + +/* + * Rust is very liberal with nesting, so this function is used pretty much for any block + */ +static void parseBlock (lexerState *lexer, boolean delim, int kind, vString *scope) +{ + int level = 1; + if (delim) + { + if (lexer->cur_token != '{') + return; + advanceToken(lexer, TRUE); + } + while (lexer->cur_token != TOKEN_EOF) + { + if (lexer->cur_token == TOKEN_IDENT) + { + size_t old_scope_len = vStringLength(scope); + if (strcmp(lexer->token_str->buffer, "fn") == 0) + { + parseFn(lexer, scope, kind); + } + else if(strcmp(lexer->token_str->buffer, "mod") == 0) + { + parseMod(lexer, scope, kind); + } + else if(strcmp(lexer->token_str->buffer, "static") == 0) + { + parseStatic(lexer, scope, kind); + } + else if(strcmp(lexer->token_str->buffer, "trait") == 0) + { + parseTrait(lexer, scope, kind); + } + else if(strcmp(lexer->token_str->buffer, "type") == 0) + { + parseType(lexer, scope, kind); + } + else if(strcmp(lexer->token_str->buffer, "impl") == 0) + { + parseImpl(lexer, scope, kind); + } + else if(strcmp(lexer->token_str->buffer, "struct") == 0) + { + parseStructOrEnum(lexer, scope, kind, TRUE); + } + else if(strcmp(lexer->token_str->buffer, "enum") == 0) + { + parseStructOrEnum(lexer, scope, kind, FALSE); + } + else if(strcmp(lexer->token_str->buffer, "macro_rules") == 0) + { + parseMacroRules(lexer, scope, kind); + } + else + { + advanceToken(lexer, TRUE); + if (lexer->cur_token == '!') + { + skipMacro(lexer); + } + } + resetScope(scope, old_scope_len); + } + else if (lexer->cur_token == '{') + { + level++; + advanceToken(lexer, TRUE); + } + else if (lexer->cur_token == '}') + { + level--; + advanceToken(lexer, TRUE); + } + else if (lexer->cur_token == '\'') + { + /* Skip over the 'static lifetime, as it confuses the static parser above */ + advanceToken(lexer, TRUE); + if (lexer->cur_token == TOKEN_IDENT && strcmp(lexer->token_str->buffer, "static") == 0) + advanceToken(lexer, TRUE); + } + else + { + advanceToken(lexer, TRUE); + } + if (delim && level <= 0) + break; + } +} + +static void findRustTags (void) +{ + lexerState lexer; + vString* scope = vStringNew(); + initLexer(&lexer); + + parseBlock(&lexer, FALSE, K_NONE, scope); + vStringDelete(scope); + + deInitLexer(&lexer); +} + +extern parserDefinition *RustParser (void) +{ + static const char *const extensions[] = { "rs", NULL }; + parserDefinition *def = parserNew ("Rust"); + def->kinds = rustKinds; + def->kindCount = KIND_COUNT (rustKinds); + def->extensions = extensions; + def->parser = findRustTags; + + return def; +} diff --git a/tests/ctags/Makefile.am b/tests/ctags/Makefile.am index 826dd8ad0b..35c5151f2d 100644 --- a/tests/ctags/Makefile.am +++ b/tests/ctags/Makefile.am @@ -229,6 +229,8 @@ test_sources = \ tabindent.py \ test.py \ test.vhd \ + test_input.rs \ + test_input2.rs \ traffic_signal.v \ traits.php \ union.f \ diff --git a/tests/ctags/test_input.rs b/tests/ctags/test_input.rs new file mode 100644 index 0000000000..f99d2cfed3 --- /dev/null +++ b/tests/ctags/test_input.rs @@ -0,0 +1,138 @@ +#! fn ignored_in_comment() {} +#[feature(globs)]; +#[feature(macro_rules)]; +use std::*; +use test_input2::*; +mod test_input2; + +/* + * fn ignored_in_comment() {} + */ + +// fn ignored_in_comment() {} + +/* /* + * */ + fn ignored_in_nested_comment() {} + */ + +static size: uint = 1; + +struct S1 { + only_field: [int, ..size] +} + +macro_rules! test_macro +{ + () => {1} +} + +fn yada(a:int,c:Foo,b:test_input2::fruit::SomeStruct) -> ~str { + a.to_str() +} + +fn main() { + use test_input2::fruit::*; + io::println(foo_bar_test_func(SomeStruct{red_value:1,green_value:2,blue_value:3},(4,5)).to_str()); + let a=Foo{foo_field_1:2}; + a.my_method(1); + let c=a_cat(3); + let d=Foo{foo_field_1:a.foo_field_1+2}; a.test(); + println(a.foo_field_1.to_str()); + ignore! + ( + fn ignored_inside_macro() {} + ) + + let _ = "fn ignored_in_string() {} + "; + + let _ = r##"fn ignored_in_raw_string() {}""##; + + fn nested() {} +} + +struct Bar(int); + +struct Baz(int); + +struct Foo{foo_field_1:int} + +struct Foo2 { + x:int, + y:int +} + +impl Foo { + fn my_method(&self,_:int){ print("my_method of foo");} +} + +enum Animal { + a_anteater(int), + a_bear(int), + a_cat(int), + a_dog(int), +} + +trait Testable +{ fn test(&self); + fn test1(&self); + fn test2(&self); +} + +trait DoZ { + fn do_z(&self); +} + +impl Testable for Foo { + fn test(&self) { + println(self.foo_field_1.to_str()); + } + + fn test1(&self) { + println(self.foo_field_1.to_str()); + } + + fn test2(&self) { + println(self.foo_field_1.to_str()); + } +} + +impl DoZ for Foo { + fn do_z(&self) { + println(self.foo_field_1.to_str()); + } +} + +trait SuperTraitTest:Testable+DoZ { +} + +fn gfunc(x:&X) { + let a1=a_anteater(1); + let a2=a_bear(1); + let a3=a_cat(1); + let a4=a_dog(1); + x.test(); + x.do_z(); +} + +struct TraitedStructTest { + x:X +} + +trait ParametrizedTrait { + fn test(&self); +} + +impl ParametrizedTrait for TraitedStructTest { + fn test(&self) { + } +} + +fn some2(a:Animal) { + match a { + a_cat(x)=> println("cat"), + _ => println("not a cat") + } + +} diff --git a/tests/ctags/test_input.rs.tags b/tests/ctags/test_input.rs.tags new file mode 100644 index 0000000000..2c6c0f15c8 --- /dev/null +++ b/tests/ctags/test_input.rs.tags @@ -0,0 +1,42 @@ +# format=tagmanager +AnimalÌ2Ö0 +BarÌ2048Ö0 +BazÌ2048Ö0 +DoZÌ32Ö0 +FooÌ1Ö0 +FooÌ2048Ö0 +Foo2Ì2048Ö0 +ParametrizedTraitÌ32Ö0 +S1Ì2048Ö0 +SuperTraitTestÌ32Ö0 +TestableÌ32Ö0 +TraitedStructTestÌ1Ö0 +TraitedStructTestÌ2048Ö0 +a_anteaterÌ4ÎAnimalÖ0 +a_bearÌ4ÎAnimalÖ0 +a_catÌ4ÎAnimalÖ0 +a_dogÌ4ÎAnimalÖ0 +do_zÌ128Í(&self)ÎDoZÖ0 +do_zÌ128Í(&self)ÎFooÖ0 +foo_field_1Ì8ÎFooÖ0 +gfuncÌ16Í(x:&X)Ö0 +mainÌ16Í()Ö0 +my_methodÌ128Í(&self,_:int)ÎFooÖ0 +nestedÌ16Í()ÎmainÖ0 +only_fieldÌ8ÎS1Ö0 +sizeÌ16384Ö0 +some2Ì16Í(a:Animal)Ö0 +testÌ128Í(&self)ÎFooÖ0 +testÌ128Í(&self)ÎParametrizedTraitÖ0 +testÌ128Í(&self)ÎTestableÖ0 +testÌ128Í(&self)ÎTraitedStructTestÖ0 +test1Ì128Í(&self)ÎFooÖ0 +test1Ì128Í(&self)ÎTestableÖ0 +test2Ì128Í(&self)ÎFooÖ0 +test2Ì128Í(&self)ÎTestableÖ0 +test_input2Ì256Ö0 +test_macroÌ65536Ö0 +xÌ8ÎFoo2Ö0 +xÌ8ÎTraitedStructTestÖ0 +yÌ8ÎFoo2Ö0 +yadaÌ16Í(a:int,c:Foo,b:test_input2::fruit::SomeStruct) -> ~strÖ0 diff --git a/tests/ctags/test_input2.rs b/tests/ctags/test_input2.rs new file mode 100644 index 0000000000..dbd0d344ce --- /dev/null +++ b/tests/ctags/test_input2.rs @@ -0,0 +1,40 @@ +pub fn foo_bar_test_func(apples:fruit::SomeStruct,(oranges,lemon):(int,int))->int{ + let some_var_name=2*oranges; + let a=SomeLongStructName{v:0}; + println("a");println("b"); println("c"); + veg::another_function(apples.red_value,oranges,lemon); + some_var_name-apples.red_value+lemon+a.v +} + +pub mod fruit { + pub struct SomeStruct{ + red_value:int,green_value:int,blue_value:int + } +} + +fn free_func() { +} + +impl SomeLongStructName { + fn fooo() { + } + fn baaz() { + } +} + +pub struct SomeLongStructName {v:int} + +mod veg{ + pub fn another_function(a:int,b:int,c:int)->int { + a+b+c + } +} + +mod mineral { + fn granite() { + } + fn limestone() { + } + fn chalk() { + } +} diff --git a/tests/ctags/test_input2.rs.tags b/tests/ctags/test_input2.rs.tags new file mode 100644 index 0000000000..cbcddf0ebd --- /dev/null +++ b/tests/ctags/test_input2.rs.tags @@ -0,0 +1,19 @@ +# format=tagmanager +SomeLongStructNameÌ1Ö0 +SomeLongStructNameÌ2048Ö0 +SomeStructÌ2048ÎfruitÖ0 +another_functionÌ16Í(a:int,b:int,c:int)->intÎvegÖ0 +baazÌ128Í()ÎSomeLongStructNameÖ0 +blue_valueÌ8Îfruit::SomeStructÖ0 +chalkÌ16Í()ÎmineralÖ0 +foo_bar_test_funcÌ16Í(apples:fruit::SomeStruct,(oranges,lemon):(int,int))->intÖ0 +foooÌ128Í()ÎSomeLongStructNameÖ0 +free_funcÌ16Í()Ö0 +fruitÌ256Ö0 +graniteÌ16Í()ÎmineralÖ0 +green_valueÌ8Îfruit::SomeStructÖ0 +limestoneÌ16Í()ÎmineralÖ0 +mineralÌ256Ö0 +red_valueÌ8Îfruit::SomeStructÖ0 +vÌ8ÎSomeLongStructNameÖ0 +vegÌ256Ö0 diff --git a/wscript b/wscript index 1fac2ea829..5075f2cd51 100644 --- a/wscript +++ b/wscript @@ -105,6 +105,7 @@ ctags_sources = set([ 'tagmanager/ctags/read.c', 'tagmanager/ctags/rest.c', 'tagmanager/ctags/ruby.c', + 'tagmanager/ctags/rust.c', 'tagmanager/ctags/sh.c', 'tagmanager/ctags/sort.c', 'tagmanager/ctags/sql.c',