public override string GetDataTipText(int line, int col, out TextSpan span) { CMakeParsing.TokenData tokenData; if (_lines != null && CMakeParsing.ParseForToken(_lines, line, col, out tokenData) && !tokenData.InParens) { if (tokenData.TokenInfo.Token == (int)CMakeToken.Keyword) { // Get a Quick Info tip for the command at the cursor. span = tokenData.TokenInfo.ToTextSpan(line); string lineText = _lines.ToList()[line]; string tokenText = lineText.ExtractToken(tokenData.TokenInfo); CMakeCommandId id = CMakeKeywords.GetCommandId(tokenText); if (CMakeSubcommandMethods.HasSubcommands(id)) { // Parse to get the subcommand, if there is one. CMakeParsing.ParameterInfoResult result = CMakeParsing.ParseForParameterInfo(_lines, line, -1, true); return(CMakeSubcommandMethods.GetSubcommandQuickInfoTip(id, result.SubcommandName)); } return(CMakeMethods.GetCommandQuickInfoTip(id)); } else if (tokenData.TokenInfo.Token == (int)CMakeToken.Identifier) { // Get a Quick Info tip for the function called at the cursor, if // there is one. string lineText = _lines.ToList()[line]; string tokenText = lineText.ExtractToken(tokenData.TokenInfo); List <string> parameters = CMakeParsing.ParseForParameterNames(_lines, tokenText); if (parameters != null) { span = tokenData.TokenInfo.ToTextSpan(line); return(string.Format("{0}({1})", tokenText, string.Join(" ", parameters))); } } } span = new TextSpan(); return(null); }
public void TestQuickInfoTips() { // Test ordinary commands. Assert.AreEqual("if(expression)", CMakeMethods.GetCommandQuickInfoTip(CMakeCommandId.If)); Assert.AreEqual("set(variable value)", CMakeMethods.GetCommandQuickInfoTip(CMakeCommandId.Set)); Assert.IsNull(CMakeMethods.GetCommandQuickInfoTip( CMakeCommandId.AddCustomCommand)); // Test subcommands. Assert.AreEqual("cmake_policy(PUSH)", CMakeSubcommandMethods.GetSubcommandQuickInfoTip( CMakeCommandId.CMakePolicy, "PUSH")); Assert.AreEqual("export(PACKAGE name)", CMakeSubcommandMethods.GetSubcommandQuickInfoTip( CMakeCommandId.Export, "PACKAGE")); Assert.AreEqual("file(READ filename variable)", CMakeSubcommandMethods.GetSubcommandQuickInfoTip( CMakeCommandId.File, "READ")); Assert.IsNull(CMakeSubcommandMethods.GetSubcommandQuickInfoTip( CMakeCommandId.Install, "CODE")); }
public override AuthoringScope ParseSource(ParseRequest req) { CMakeAuthoringScope scope = new CMakeAuthoringScope(); if (!CMakeSource.IsCMakeFile(req.FileName)) { // Don't do IntelliSense parsing for ordinary text files. return(scope); } CMakeSource source = (CMakeSource)GetSource(req.FileName); if (req.Sink.HiddenRegions) { req.Sink.ProcessHiddenRegions = true; List <TextSpan> regions = CMakeParsing.ParseForFunctionBodies( source.GetLines()); foreach (TextSpan textSpan in regions) { req.Sink.AddHiddenRegion(textSpan); } } if (req.Sink.BraceMatching) { List <CMakeParsing.SpanPair> pairs = null; switch ((CMakeToken)req.TokenInfo.Token) { case CMakeToken.OpenParen: case CMakeToken.CloseParen: pairs = CMakeParsing.ParseForParens(source.GetLines()); break; case CMakeToken.VariableStart: case CMakeToken.VariableStartEnv: case CMakeToken.VariableStartCache: case CMakeToken.VariableStartSetEnv: case CMakeToken.VariableEnd: pairs = CMakeParsing.ParseForVariableBraces(source.GetLines(), req.Line); break; case CMakeToken.GeneratorStart: case CMakeToken.GeneratorEnd: pairs = CMakeParsing.ParseForGeneratorBraces(source.GetLines(), req.Line); break; } if (pairs != null) { foreach (CMakeParsing.SpanPair pair in pairs) { req.Sink.MatchPair(pair.First, pair.Second, 0); } } } if (req.Reason == ParseReason.MemberSelect || req.Reason == ParseReason.MemberSelectAndHighlightBraces || req.Reason == ParseReason.CompleteWord) { // Set an appropriate declarations object depending on the token that // triggered member selection. CMakeToken token = (CMakeToken)req.TokenInfo.Token; if (token == CMakeToken.String) { // If the token is a string and the user has began to reference a // variable inside the string, treat the string as if it was the // appropriate type of variable start token and display member // selection for variables. string line = source.GetLine(req.Line); string tokenText = line.ExtractToken(req.TokenInfo); if (tokenText.EndsWith("${")) { token = CMakeToken.VariableStart; } else if (tokenText.EndsWith("$ENV{")) { token = CMakeToken.VariableStartEnv; } else if (tokenText.EndsWith("$CACHE{")) { token = CMakeToken.VariableStartCache; } } if (token == CMakeToken.VariableStart) { List <string> vars = CMakeParsing.ParseForVariables( source.GetLines(), req.Line); CMakeVariableDeclarations decls = new CMakeVariableDeclarations(vars, CMakeVariableType.Variable); decls.AddItems(source.GetIncludeCacheVariables(), CMakeItemDeclarations.ItemType.Variable); string functionName = CMakeParsing.ParseForCurrentFunction( source.GetLines(), req.Line); if (functionName != null) { List <string> paramNames = CMakeParsing.ParseForParameterNames( source.GetLines(), functionName); paramNames.Add("ARGN"); decls.AddItems(paramNames, CMakeItemDeclarations.ItemType.Variable); } scope.SetDeclarations(decls); } else if (token == CMakeToken.VariableStartEnv) { List <string> vars = CMakeParsing.ParseForEnvVariables( source.GetLines()); CMakeVariableDeclarations decls = new CMakeVariableDeclarations(vars, CMakeVariableType.EnvVariable); decls.AddItems(source.GetIncludeCacheEnvVariables(), CMakeItemDeclarations.ItemType.Variable); scope.SetDeclarations(decls); } else if (token == CMakeToken.VariableStartCache) { List <string> vars = CMakeParsing.ParseForCacheVariables( source.GetLines()); CMakeVariableDeclarations decls = new CMakeVariableDeclarations(vars, CMakeVariableType.CacheVariable); decls.AddItems(source.GetIncludeCacheCacheVariables(), CMakeItemDeclarations.ItemType.Variable); scope.SetDeclarations(decls); } else if (token == CMakeToken.Identifier) { CMakeParsing.TokenData tokenData; CMakeParsing.ParseForToken(source.GetLines(), req.Line, req.TokenInfo.StartIndex, out tokenData); if (!tokenData.InParens) { CMakeItemDeclarations decls = new CMakeItemDeclarations(); IEnumerable <string> commands = CMakeKeywords.GetAllCommands( CMakePackage.Instance.CMakeOptionPage.ShowDeprecated); if (!CMakePackage.Instance.CMakeOptionPage.CommandsLower) { commands = commands.Select(x => x.ToUpper()); } decls.AddItems(commands, CMakeItemDeclarations.ItemType.Command); decls.AddItems( CMakeParsing.ParseForFunctionNames(source.GetLines(), false), CMakeItemDeclarations.ItemType.Function); decls.AddItems( CMakeParsing.ParseForFunctionNames(source.GetLines(), true), CMakeItemDeclarations.ItemType.Macro); decls.AddItems(source.GetIncludeCacheFunctions(), CMakeItemDeclarations.ItemType.Function); decls.AddItems(source.GetIncludeCacheMacros(), CMakeItemDeclarations.ItemType.Macro); scope.SetDeclarations(decls); } else { Declarations decls = CMakeDeclarationsFactory.CreateDeclarations( tokenData.Command, req, source, tokenData.ParameterIndex > 0 ? tokenData.PriorParameters : null); scope.SetDeclarations(decls); } } else if (token == CMakeToken.OpenParen) { CMakeCommandId id = CMakeParsing.ParseForTriggerCommandId( source.GetLines(), req.Line, req.TokenInfo.StartIndex); Declarations decls = CMakeDeclarationsFactory.CreateDeclarations( id, req, source); scope.SetDeclarations(decls); } else if (token == CMakeToken.WhiteSpace) { CMakeParsing.TokenData tokenData; CMakeParsing.ParseForToken(source.GetLines(), req.Line, req.TokenInfo.StartIndex, out tokenData); Declarations decls = CMakeDeclarationsFactory.CreateDeclarations( tokenData.Command, req, source, tokenData.ParameterIndex > 0 ? tokenData.PriorParameters : null); scope.SetDeclarations(decls); } else if (token == CMakeToken.GeneratorStart) { scope.SetDeclarations(new CMakeGeneratorDeclarations()); } } else if (req.Reason == ParseReason.MethodTip) { CMakeParsing.ParameterInfoResult result = CMakeParsing.ParseForParameterInfo(source.GetLines(), req.Line, req.TokenInfo.EndIndex); if (result.CommandName != null && result.CommandSpan.HasValue) { if (result.SubcommandName == null) { req.Sink.StartName(result.CommandSpan.Value, result.CommandName); } else { req.Sink.StartName(result.CommandSpan.Value, result.CommandSpan + "(" + result.SubcommandName); } if (result.BeginSpan.HasValue) { req.Sink.StartParameters(result.BeginSpan.Value); } foreach (TextSpan span in result.SeparatorSpans) { req.Sink.NextParameter(span); } if (result.EndSpan.HasValue) { req.Sink.EndParameters(result.EndSpan.Value); } CMakeCommandId id = CMakeKeywords.GetCommandId(result.CommandName); if (id == CMakeCommandId.Unspecified) { // If it's a user-defined function or macro, parse to try to find // its parameters. List <string> parameters = CMakeParsing.ParseForParameterNames( source.GetLines(), result.CommandName); if (parameters == null) { parameters = source.GetParametersFromIncludeCache( result.CommandName); } if (parameters != null) { scope.SetMethods(new CMakeUserMethods(result.CommandName, parameters)); } } else { scope.SetMethods(CMakeMethods.GetCommandParameters(id, result.SubcommandName)); } } } else if (req.Reason == ParseReason.Goto) { scope.SetLines(source.GetLines()); scope.SetFileName(req.FileName); } else if (req.Reason == ParseReason.QuickInfo) { scope.SetLines(source.GetLines()); } else if (req.Reason == ParseReason.Check) { foreach (ParseForErrorMethod method in _parseForErrorMethods) { List <CMakeErrorInfo> info = method(source.GetLines()); foreach (CMakeErrorInfo item in info) { CMakeError err = item.ErrorCode; if (_errorStrings.ContainsKey(err) && (!_enabledMethods.ContainsKey(err) || _enabledMethods[err]())) { req.Sink.AddError(req.FileName, _errorStrings[err], item.Span, item.Warning ? Severity.Warning : Severity.Error); } } } if (CMakePackage.Instance.CMakeOptionPage.ParseIncludedFiles) { source.BuildIncludeCache(source.GetLines()); source.UpdateIncludeCache(); source.PruneIncludeCache(); } else { source.ClearIncludeCache(); } } return(scope); }
public bool ScanTokenAndProvideInfoAboutIt(TokenInfo tokenInfo, ref int state) { if (_textFile) { // Don't perform syntax highlighting if the file is an ordinary text // file. return(false); } if (GetStringFlag(state) && _offset < _source.Length) { // If the line begins inside a string token, begin by scanning the rest // of the string. ScanString(tokenInfo, ref state, false); _scannedNonWhitespace = true; _lastWhitespace = false; return(true); } bool bracketComment = GetBracketCommentFlag(state); bool bracketArgument = GetBracketArgumentFlag(state); if ((bracketComment || bracketArgument) && _offset < _source.Length) { // If the line begins inside a bracket comment token, begin by scanning // the rest of the bracket comment. ScanBracketCommentOrArgument(tokenInfo, bracketComment, ref state); _lastWhitespace = true; return(true); } bool originalScannedNonWhitespace = _scannedNonWhitespace; bool originalLastWhitespace = _lastWhitespace; int originalVariableDepth = GetVariableDepth(state); SetVariableDepth(ref state, 0); bool noSeparator = GetNoSeparatorFlag(state); SetNoSeparatorFlag(ref state, false); tokenInfo.Trigger = TokenTriggers.None; _lastWhitespace = false; while (_offset < _source.Length) { if (char.IsWhiteSpace(_source[_offset])) { // Scan a whitespace token. tokenInfo.StartIndex = _offset; while (_offset < _source.Length && char.IsWhiteSpace(_source[_offset])) { _offset++; } tokenInfo.EndIndex = _offset - 1; tokenInfo.Color = TokenColor.Text; tokenInfo.Token = (int)CMakeToken.WhiteSpace; CMakeCommandId id = GetLastCommand(state); if (InsideParens(state)) { if (!noSeparator) { if (CMakeSubcommandMethods.HasSubcommands(id)) { // The first whitespace token after a subcommand marks // the beginning of the parameters. The remaining // whitespace parameters separate consecutive parameters. if (GetSubcommandParmsFlag(state)) { if (GetNeedSubcommandFlag(state)) { SetNeedSubcommandFlag(ref state, false); tokenInfo.Trigger = TokenTriggers.ParameterStart; } else if (!originalLastWhitespace) { tokenInfo.Trigger = TokenTriggers.ParameterNext; } } } else if (id == CMakeCommandId.Unspecified || GetSeparatorCount(state) > 0) { if (!originalLastWhitespace) { SetSeparatorCount(ref state, GetSeparatorCount(state) - 1); tokenInfo.Trigger = TokenTriggers.ParameterNext; } } } if (CMakeKeywords.TriggersMemberSelection(id) || CMakeKeywords.TriggersMemberSelectionOnWhiteSpace(id)) { tokenInfo.Trigger |= TokenTriggers.MemberSelect; } } SetNoSeparatorFlag(ref state, true); _lastWhitespace = true; return(true); } else if (_source[_offset] == '#') { // Check if it's a bracket comment. If so, handle it specially. if (_offset + 1 < _source.Length && _source[_offset + 1] == '[') { int i = _offset + 2; while (i < _source.Length && _source[i] == '=') { i++; } if (i < _source.Length && _source[i] == '[') { SetBracketCommentFlag(ref state, true); ScanBracketCommentOrArgument(tokenInfo, true, ref state); _lastWhitespace = true; return(true); } } // Scan a comment token. tokenInfo.StartIndex = _offset; int endPos = _source.IndexOf('\n', _offset); if (endPos > _offset) { while (endPos < _source.Length - 1 && (_source[endPos + 1] == '\r' || _source[endPos + 1] == '\n')) { endPos++; } } else { endPos = _source.Length - 1; } tokenInfo.EndIndex = endPos; tokenInfo.Color = TokenColor.Comment; tokenInfo.Token = (int)CMakeToken.Comment; _offset = endPos + 1; SetNoSeparatorFlag(ref state, noSeparator); return(true); } // If we haven't returned by this point, the token is something other // than whitespace. Therefore, set the flag indicating that a // non-whitespace token has been scanned on the current line so that // any additional identifier characters at the end won't trigger member // selection. If you add any additional token, ensure that you handle // them after this point. _scannedNonWhitespace = true; if (_source[_offset] == '"') { // Scan a string token. ScanString(tokenInfo, ref state, true); return(true); } else if (_source[_offset] == '(') { // Scan an opening parenthesis. if (!InsideParens(state)) { CMakeCommandId id = GetLastCommand(state); if (CMakeKeywords.TriggersMemberSelection(id)) { tokenInfo.Trigger |= TokenTriggers.MemberSelect; SetNeedSubcommandFlag(ref state, true); SetNoSeparatorFlag(ref state, true); } else { tokenInfo.Trigger |= TokenTriggers.ParameterStart; SetNoSeparatorFlag(ref state, true); } } IncParenDepth(ref state); tokenInfo.StartIndex = _offset; tokenInfo.EndIndex = _offset; tokenInfo.Color = TokenColor.Text; tokenInfo.Token = (int)CMakeToken.OpenParen; tokenInfo.Trigger |= TokenTriggers.MatchBraces; _offset++; return(true); } else if (_source[_offset] == ')') { // Scan a closing parenthesis. DecParenDepth(ref state); if (GetParenDepth(state) == 0) { SetLastCommand(ref state, CMakeCommandId.Unspecified); SetSeparatorCount(ref state, 0); tokenInfo.Trigger = TokenTriggers.ParameterEnd; } tokenInfo.StartIndex = _offset; tokenInfo.EndIndex = _offset; tokenInfo.Color = TokenColor.Text; tokenInfo.Token = (int)CMakeToken.CloseParen; tokenInfo.Trigger |= TokenTriggers.MatchBraces; _offset++; return(true); } else if (char.IsLetter(_source[_offset]) || _source[_offset] == '_' || (originalVariableDepth == 0 && _source[_offset] == '\\')) { // Scan a keyword, identifier, or file name token. bool isFileName = false; bool isNumeric = false; tokenInfo.StartIndex = _offset; if (_source[_offset] == '\\' && _offset != _source.Length - 1) { // If the identifier starts with an escape sequence, skip over it. _offset++; isFileName = true; } while (_offset < _source.Length - 1) { char ch = _source[_offset + 1]; if (ch == '-') { // Variable names may contain hyphens but function names // can't. There classify an identifier with a hyphen in it // as a numeric identifier. isNumeric = true; } else if (originalVariableDepth == 0 && ScanFileNameChar()) { isFileName = true; } else if (!char.IsLetterOrDigit(ch) && ch != '_') { break; } _offset++; } tokenInfo.EndIndex = _offset; _offset++; CMakeCommandId id = GetLastCommand(state); string substr = _source.ExtractToken(tokenInfo); if (originalVariableDepth != 0) { // If we're inside curly braces following a dollar sign, treat // the identifier as a variable. tokenInfo.Color = TokenColor.Identifier; tokenInfo.Token = (int)CMakeToken.Variable; SetVariableDepth(ref state, originalVariableDepth); } else if ((id == CMakeCommandId.Set || id == CMakeCommandId.Unset) && substr.StartsWith("ENV{")) { // Inside a SET or UNSET command, ENV{ indicates an environment // variable. This token is case-sensitive. SetVariableDepth(ref state, originalVariableDepth + 1); tokenInfo.EndIndex = tokenInfo.StartIndex + 3; tokenInfo.Color = TokenColor.Identifier; tokenInfo.Token = (int)CMakeToken.VariableStartSetEnv; _offset = tokenInfo.EndIndex + 1; } else if (isNumeric) { tokenInfo.Color = TokenColor.Identifier; tokenInfo.Token = (int)CMakeToken.NumericIdentifier; } else if (isFileName) { // If we found characters that aren't valid in an identifier, // treat the token as a file name. tokenInfo.Color = TokenColor.Identifier; tokenInfo.Token = (int)CMakeToken.FileName; } else { // Check whether the string is a keyword or not. string tokenText = _source.ExtractToken(tokenInfo); bool isKeyword = false; if (!InsideParens(state)) { isKeyword = CMakeKeywords.IsCommand(tokenText); id = CMakeKeywords.GetCommandId(tokenText); SetLastCommand(ref state, id); SetSeparatorCount(ref state, CMakeMethods.GetParameterCount(id)); int count = GetSeparatorCount(state); if (count > 0) { SetSeparatorCount(ref state, count - 1); } } else { isKeyword = CMakeKeywords.IsKeyword(id, tokenText); if (isKeyword) { SetSubcommandParmsFlag(ref state, CMakeSubcommandMethods.HasSubcommandParameters( id, tokenText)); } } tokenInfo.Color = isKeyword ? TokenColor.Keyword : TokenColor.Identifier; tokenInfo.Token = isKeyword ? (int)CMakeToken.Keyword : (int)CMakeToken.Identifier; } if (tokenInfo.StartIndex == tokenInfo.EndIndex) { if (!InsideParens(state) && !originalScannedNonWhitespace) { // Trigger member selection if we're not inside parentheses. tokenInfo.Trigger |= TokenTriggers.MemberSelect; } else if (!originalScannedNonWhitespace || originalLastWhitespace) { // Always trigger member selection in response to certain // commands. if (CMakeKeywords.TriggersMemberSelection(id) || CMakeKeywords.TriggersMemberSelectionOnWhiteSpace(id)) { tokenInfo.Trigger |= TokenTriggers.MemberSelect; } } } return(true); } else if (char.IsDigit(_source[_offset]) || _source[_offset] == '-') { // Variable names can start with numbers or hyphens in CMake, but // function names can't. We'll call these tokens "numeric // identifiers" here and treat them accordingly when parsing. tokenInfo.StartIndex = _offset; bool isFileName = false; while (_offset < _source.Length - 1) { char ch = _source[_offset + 1]; if (originalVariableDepth == 0 && ScanFileNameChar()) { isFileName = true; } else if (!char.IsLetterOrDigit(ch) && ch != '_') { break; } _offset++; } tokenInfo.EndIndex = _offset; _offset++; tokenInfo.Color = TokenColor.Identifier; if (originalVariableDepth != 0) { tokenInfo.Token = (int)CMakeToken.Variable; SetVariableDepth(ref state, originalVariableDepth); } else if (isFileName) { tokenInfo.Token = (int)CMakeToken.FileName; } else { tokenInfo.Token = (int)CMakeToken.NumericIdentifier; } return(true); } else if (_source[_offset] == '$') { // Scan a variable start token. CMakeToken varToken = _varTokenMap.FirstOrDefault( x => _source.Substring(_offset).StartsWith(x.Value)).Key; tokenInfo.StartIndex = _offset; _offset++; if (varToken != CMakeToken.Unspecified) { if (varToken != CMakeToken.GeneratorStart) { SetVariableDepth(ref state, originalVariableDepth + 1); } tokenInfo.Token = (int)varToken; tokenInfo.Trigger = TokenTriggers.MemberSelect | TokenTriggers.MatchBraces; _offset += _varTokenMap[varToken].Length - 1; } tokenInfo.EndIndex = _offset - 1; tokenInfo.Color = TokenColor.Identifier; return(true); } else if (_source[_offset] == '}') { // Scan a variable end token. tokenInfo.StartIndex = _offset; tokenInfo.EndIndex = _offset; tokenInfo.Color = TokenColor.Identifier; tokenInfo.Token = (int)CMakeToken.VariableEnd; tokenInfo.Trigger = TokenTriggers.MatchBraces; SetVariableDepth(ref state, originalVariableDepth > 0 ? originalVariableDepth - 1 : 0); _offset++; return(true); } else if (_source[_offset] == '>') { // Scan a generator expression end token. tokenInfo.StartIndex = _offset; tokenInfo.EndIndex = _offset; tokenInfo.Color = TokenColor.Identifier; tokenInfo.Token = (int)CMakeToken.GeneratorEnd; tokenInfo.Trigger = TokenTriggers.MatchBraces; SetVariableDepth(ref state, originalVariableDepth > 0 ? originalVariableDepth - 1 : 0); _offset++; return(true); } else if (_source[_offset] == ':') { // Scan a colon. tokenInfo.StartIndex = _offset; tokenInfo.EndIndex = _offset; tokenInfo.Color = TokenColor.Identifier; tokenInfo.Token = (int)CMakeToken.Colon; _offset++; return(true); } else if (_source[_offset] == '[') { // Scan a bracket argument, if it is one. int i = _offset + 1; while (i < _source.Length && _source[i] == '=') { i++; } if (i < _source.Length && _source[i] == '[') { SetBracketArgumentFlag(ref state, true); ScanBracketCommentOrArgument(tokenInfo, false, ref state); _lastWhitespace = false; return(true); } } _offset++; } return(false); }