///////////////////////////////////////////////////////////////////////////////////////////////////// // PUBLIC PROCEDURES ///////////////////////////////////////////////////////////////////////////////////////////////////// /// <summary> /// Attempts to locate structural text, such as bracket pairs, at the specified offset. /// </summary> /// <param name="snapshotOffset">The <see cref="TextSnapshotOffset"/> to examine.</param> /// <param name="options">The <see cref="IStructureMatchOptions"/> to use.</param> /// <returns>An <see cref="IStructureMatchResultSet"/> that contains the results that were found, if any.</returns> /// <remarks> /// When the result set is empty, no structural delimiter was next to the specified offset. /// When the result set contains a single result, a structural delimiter such as a bracket was found next to the specified offset, /// but no matching structural delimiter was found. /// The result set may contain multiple entries in cases such as when a language wishes to flag <c>#if...#else...#endif</c> blocks as a structure. /// </remarks> public override IStructureMatchResultSet Match(TextSnapshotOffset snapshotOffset, IStructureMatchOptions options) { if (snapshotOffset.IsDeleted) { throw new ArgumentNullException("snapshotOffset"); } // Get a snapshot reader and configure it for quick initial lookup ITextSnapshotReader reader = snapshotOffset.Snapshot.GetReader(snapshotOffset.Offset); reader.Options.DefaultTokenLoadBufferLength = 250; reader.Options.InitialTokenLoadBufferLength = 4; IToken token = reader.Token; if (token != null) { // If the token is not a multi-line comment but is at the start of a token, check the previous token if ((token.Id != SimpleTokenId.MultiLineCommentText) && (reader.IsAtTokenStart)) { token = reader.ReadTokenReverse(); } // If the token is a multi-line comment... if (token.Id == SimpleTokenId.MultiLineCommentText) { // The Simple language programmatic lexer variant only has a single token for the entire comment so // ensure the target offset is at a delimiter (and not within the body of the comment)... // For most other languages, you'd want to scan tokens to find a matching delimiter token instead bool isAtStart = (snapshotOffset.Offset <= token.StartOffset + 2); bool isAtEnd = (snapshotOffset.Offset >= token.EndOffset - 2); if (isAtStart || isAtEnd) { // Get the token's text and ensure it ends with a proper delimiter string tokenText = reader.TokenText; if ((token.Length >= 4) && (tokenText.EndsWith("*/", StringComparison.Ordinal))) { // Found a valid match StructureMatchResultCollection results = new StructureMatchResultCollection(); results.Add(new StructureMatchResult(new TextSnapshotRange(reader.Snapshot, token.StartOffset, token.StartOffset + 2)) { IsSource = isAtStart, NavigationSnapshotOffset = new TextSnapshotOffset(reader.Snapshot, token.StartOffset) }); results.Add(new StructureMatchResult(new TextSnapshotRange(reader.Snapshot, token.EndOffset - 2, token.EndOffset)) { IsSource = !isAtStart, NavigationSnapshotOffset = new TextSnapshotOffset(reader.Snapshot, token.EndOffset) }); return(new StructureMatchResultSet(results)); } } } } // Call the base method return(base.Match(snapshotOffset, options)); }
///////////////////////////////////////////////////////////////////////////////////////////////////// // PUBLIC PROCEDURES ///////////////////////////////////////////////////////////////////////////////////////////////////// /// <summary> /// Returns the tag ranges that intersect with the specified normalized snapshot ranges. /// </summary> /// <param name="snapshotRanges">The collection of normalized snapshot ranges.</param> /// <param name="parameter">An optional parameter that provides contextual information about the tag request.</param> /// <returns>The tag ranges that intersect with the specified normalized snapshot ranges.</returns> public override IEnumerable <TagSnapshotRange <IClassificationTag> > GetTags(NormalizedTextSnapshotRangeCollection snapshotRanges, object parameter) { // Loop through the requested snapshot ranges... foreach (TextSnapshotRange snapshotRange in snapshotRanges) { // If the snapshot range is not zero-length... if (!snapshotRange.IsZeroLength) { // Get a snapshot reader ITextSnapshotReader reader = snapshotRange.Snapshot.GetReader(snapshotRange.StartOffset); // If not already at the start of a line, back up to the start if (!reader.IsAtSnapshotLineStart) { reader.GoToCurrentSnapshotLineStart(); } // Read through the snapshot until the end of the target range is reached while ((!reader.IsAtSnapshotEnd) && (reader.Offset < snapshotRange.EndOffset)) { // Save the start of the line offset int lineStartOffset = reader.Offset; // Get the line start text (we need at most 6 chars for this sample) string lineStartText = reader.PeekText(6); // Go to the end of the line reader.GoToCurrentSnapshotLineEnd(); // Add a range for the line if it starts with one of the defined strings... // The StyleRegistryClassificationTag is a special ClassificationTag that allows you to indicate // an alternate IHighlightingStyleRegistry to use for syntax highlighting... if using the // normal AmbientHighlightingStyleRegistry, you'd just use a regular ClassificationTag instead if (lineStartText.StartsWith("---")) { // Apply green to lines that start with "---" yield return(new TagSnapshotRange <IClassificationTag>( new TextSnapshotRange(snapshotRange.Snapshot, new TextRange(lineStartOffset, reader.Offset)), new StyleRegistryClassificationTag(commentCT, styleRegistry) )); } else if (lineStartText.StartsWith("Error:")) { // Apply maroon to lines that start with "Error:" yield return(new TagSnapshotRange <IClassificationTag>( new TextSnapshotRange(snapshotRange.Snapshot, new TextRange(lineStartOffset, reader.Offset)), new StyleRegistryClassificationTag(errorCT, styleRegistry) )); } // Consume the newline reader.GoToNextSnapshotLineStart(); } } } }
/// <summary> /// Requests that an <see cref="IQuickInfoSession"/> be opened for the specified <see cref="IEditorView"/>. /// </summary> /// <param name="view">The <see cref="IEditorView"/> that will host the session.</param> /// <param name="context">A context object returned by <see cref="GetContext"/>.</param> /// <returns> /// <c>true</c> if a session was opened; otherwise, <c>false</c>. /// </returns> protected override bool RequestSession(IEditorView view, object context) { // Create a session and assign a context that can be used to identify it QuickInfoSession session = new QuickInfoSession(); session.Context = context; TextRangeContext textRangeContext = context as TextRangeContext; if (textRangeContext != null) { // Get a reader initialized to the offset ITextSnapshotReader reader = view.CurrentSnapshot.GetReader(textRangeContext.Range.StartOffset); IToken token = reader.Token; if (token != null) { // Create some marked-up content indicating the token at the offset and the line it's on session.Content = new HtmlContentProvider( String.Format("Target word: <b>{0}</b><br/>Token: <b>{1}</b><br/><span style=\"color: Green;\">Found on line {2}.</span>", HtmlContentProvider.Escape(view.CurrentSnapshot.GetSubstring(textRangeContext.Range)), token.Key, view.OffsetToPosition(textRangeContext.Range.StartOffset).Line + 1), view.DefaultBackgroundColor).GetContent(); // Open the session session.Open(view, textRangeContext.Range); return(true); } } else { LineNumberMarginContext marginContext = context as LineNumberMarginContext; if (marginContext != null) { // Create some marked-up content indicating the line number session.Content = new HtmlContentProvider(String.Format("Line number: <b>{0}</b>", marginContext.LineIndex + 1), view.DefaultBackgroundColor).GetContent(); // Get the margin IEditorViewMargin margin = view.Margins[EditorViewMarginKeys.LineNumber]; // Get the view line that contains the line ITextViewLine viewLine = view.GetViewLine(new TextPosition(marginContext.LineIndex, 0)); if ((margin != null) && (viewLine != null)) { // Get line bounds relative to the margin Rect bounds = view.TransformFromTextArea(viewLine.Bounds); bounds.X = 0; bounds.Width = margin.VisualElement.RenderSize.Width; // Open the session session.Open(view, PlacementMode.Bottom, view.VisualElement, bounds); return(true); } } } return(false); }
///////////////////////////////////////////////////////////////////////////////////////////////////// // OBJECT ///////////////////////////////////////////////////////////////////////////////////////////////////// /// <summary> /// Initializes an instance of the <c>MainControl</c> class. /// </summary> public MainControl() { InitializeComponent(); // Load a language from a language definition editor.Document.Language = ActiproSoftware.ProductSamples.SyntaxEditorSamples.Common.SyntaxEditorHelper.LoadLanguageDefinitionFromResourceStream("CSharp.langdef"); // Create a reader reader = editor.Document.CurrentSnapshot.GetReader(0); }
///////////////////////////////////////////////////////////////////////////////////////////////////// // PUBLIC PROCEDURES ///////////////////////////////////////////////////////////////////////////////////////////////////// /// <summary> /// Performs a parsing operation using the parameters specified in the supplied <see cref="IParseRequest"/> /// and returns the resulting parse data. /// </summary> /// <param name="request">The <see cref="IParseRequest"/> that contains data about the requested parsing operation.</param> /// <returns>An <see cref="IParseData"/> that is the result of the parsing operation.</returns> /// <remarks> /// A <see cref="IParseRequestDispatcher"/> typically calls this method when a queued parse request is ready to be processed. /// </remarks> public override IParseData Parse(IParseRequest request) { SimpleParseData parseData = new SimpleParseData(); // // NOTE: Make sure that you've set up an ambient parse request dispatcher for your application // (see documentation on 'Parse Requests and Dispatchers') so that this parser is called in // a worker thread as the editor is updated // // Most parsers will use the request.TextBufferReader property in some fashion to scan through // text and not a snapshot directly... in this basic sample though, we're going to use the // tokenization provided by the snapshot's reader so we can only proceed if there is a // snapshot passed to us if (request.Snapshot != null) { ITextSnapshotReader reader = request.Snapshot.GetReader(0); bool isFunctionStart = false; while (!reader.IsAtSnapshotEnd) { IToken token = reader.ReadToken(); if (token != null) { switch (token.Key) { case "Keyword": // If a function token, mark that this is a function start... the next identifier should be the function name isFunctionStart = (reader.Snapshot.GetSubstring(token.TextRange) == "function"); break; case "Identifier": // If this is the function name... if (isFunctionStart) { parseData.Functions.Add(new TextSnapshotRange(reader.Snapshot, token.TextRange)); isFunctionStart = false; } break; case "Whitespace": // Ignore break; default: // Flag as no longer in a function start isFunctionStart = false; break; } } } } return(parseData); }
private string ReadArrayIndexExpression(ITextSnapshotReader reader) { // we're looking for an expression of the form [123] or [myVariable] // if we don't find one, return false. string indexValue = null; var token = reader.ReadTokenReverse(); while (token != null) { var text = reader.PeekText(token.Length); if (token.Key == "Identifier" && indexValue == null) { // substitute 0 for the name of the variable to give us // the best chance of matching something when we look up the path in a document indexValue = "0"; } else if ((token.Key == "IntegerNumber") && indexValue == null) { indexValue = text; } else if (token.Key == "Whitespace") { // skip it } else if (token.Key == "OpenSquareBrace") { if (indexValue == null) { // we didn't find a properly formed (and simple) index expression // before hitting the square brace return(null); } else { break; } } token = reader.ReadTokenReverse(); } if (indexValue == null) { return(null); } return("[" + indexValue + "]"); }
public void OnEditorDocumentTextChanged(object sender, EditorSnapshotChangedEventArgs e) { string typedText = e.TypedText; if (typedText != null && typedText == ".") { ITextSnapshotReader reader = this.nitriqSyntaxEditor_0.ActiveView.GetReader(); reader.ReadCharacterReverseThrough('.'); IToken token = reader.ReadTokenReverse(); if (token != null) { this.method_2(false, reader.TokenText); } } }
private static string FindPrecedingField(ITextSnapshotReader reader) { while (true) { var token = reader.ReadTokenReverse(); if (token == null) { return(null); } if (token.Key == "Field") { return(GetFieldName(reader.PeekText(token.Length))); } } }
public TextRange Parse(TextSnapshotRange snapshotRange, ActiproLex.ILexerTarget parseTarget) { //System.Diagnostics.Debug.WriteLine("LexParse " + snapshotRange.ToString()); int index = snapshotRange.StartOffset; int ix = index; parseTarget.OnPreParse(ref ix); if (parseTarget.HasInitialContext) { } else { int l = snapshotRange.EndOffset - index; ITextSnapshotReader reader = snapshotRange.Snapshot.GetReader(index); if (reader.Offset != index) { throw new System.Exception("What??!!?"); } StringBuilder text = new StringBuilder(); var read = reader.ReadText(l); text.Append(read); //System.Diagnostics.Debug.WriteLine($"Parse read {read.Length} chars: {text.ToString()}"); var lexer = new SBLexer(new Antlr.AntlrInputStream(text.ToString())); var tokens = new Antlr.CommonTokenStream(lexer); tokens.Fill(); foreach (var token in tokens.GetTokens()) { if (token.Type >= 0) { parseTarget.OnTokenParsed(new SyntaxEditorAntlrToken(token, index, snapshotRange.StartLine.Index), null); } } } parseTarget.OnPostParse(snapshotRange.EndOffset); return(snapshotRange.TextRange); }
private string DetermineFullMemberExpression(string tokenText, ITextSnapshotReader reader) { var sb = new StringBuilder(tokenText); var token = reader.ReadTokenReverse(); while (token != null) { var text = reader.PeekText(token.Length); if (token.Key == "Identifier" || (token.Key == "Punctuation" && text == ".") || (token.Key == "Keyword" && text == "this")) { sb.Insert(0, text); } else if (token.Key == "CloseSquareBrace") { var indexExpression = ReadArrayIndexExpression(reader); if (indexExpression == null) { // we're not going to be able to make sense // of the rest of the expression, so bail out. break; } sb.Insert(0, indexExpression); } else if (token.Key == "Whitespace") { // skip it } else { break; } token = reader.ReadTokenReverse(); } return(sb.ToString()); }
/// <summary> /// Occurs when the <c>SyntaxEditor.ViewSelectionChanged</c> event occurs. /// </summary> /// <param name="sender">The sender of the event.</param> /// <param name="e">An <see cref="EditorViewSelectionEventArgs"/> that contains the event data.</param> private void OnSyntaxEditorViewSelectionChanged(object sender, EditorViewSelectionEventArgs e) { // Quit if this event is not for the active view if (!e.View.IsActive) { return; } // Update line, col, and character display linePanel.Text = String.Format("Ln {0}", e.CaretPosition.DisplayLine); columnPanel.Text = String.Format("Col {0}", e.CaretDisplayCharacterColumn); characterPanel.Text = String.Format("Ch {0}", e.CaretPosition.DisplayCharacter); // If token info should be displayed in the statusbar... if (toggleTokenInfoMenuItem.IsChecked) { // Get a snapshot reader ITextSnapshotReader reader = e.View.CurrentSnapshot.GetReader(e.View.Selection.EndOffset); IToken token = reader.Token; if (token != null) { IMergableToken mergableToken = token as IMergableToken; if (mergableToken != null) { tokenPanel.Content = String.Format("{0} / {1} / {2}{3}", mergableToken.Lexer.Key, mergableToken.LexicalState.Key, token.Key, (e.View.Selection.EndOffset == token.StartOffset ? "*" : String.Empty)); } else { tokenPanel.Content = String.Format("{0} / {1}{2}", e.View.SyntaxEditor.Document.Language.Key, token.Key, (e.View.Selection.EndOffset == token.StartOffset ? "*" : String.Empty)); } return; } } tokenPanel.Content = null; }
///////////////////////////////////////////////////////////////////////////////////////////////////// // PUBLIC PROCEDURES ///////////////////////////////////////////////////////////////////////////////////////////////////// /// <summary> /// Returns the ideal amount of indent, always in columns, for the line containing the snapshot offset. /// </summary> /// <param name="snapshotOffset">The <see cref="TextSnapshotOffset"/> whose line should be examined.</param> /// <param name="defaultAmount">The default indent amount, which is the amount used in <c>Block</c> mode.</param> /// <returns>The ideal amount of indent, always in columns, for the line containing the snapshot offset.</returns> /// <remarks> /// This method is called when the <see cref="IndentMode"/> is <c>Smart</c>. /// The containing <see cref="ITextDocument"/> is accessible via the snapshot range's <see cref="ITextSnapshot"/>. /// </remarks> public override int GetIndentAmount(TextSnapshotOffset snapshotOffset, int defaultAmount) { // If the snapshot offset is deleted, return the default amount if (snapshotOffset.IsDeleted) { return(defaultAmount); } // Get the ICodeDocument from the snapshot ICodeDocument document = snapshotOffset.Snapshot.Document as ICodeDocument; if (document == null) { return(defaultAmount); } // Get the tab size int tabSize = document.TabSize; // Get a reader ITextSnapshotReader reader = snapshotOffset.Snapshot.GetReader(snapshotOffset.Offset); if (reader == null) { return(defaultAmount); } // Get the indentation base line index int indentationBaseLineIndex = Math.Max(0, snapshotOffset.Line.Index - 1); // Ensure we are at the start of the current token if (!reader.IsAtTokenStart) { reader.GoToCurrentTokenStart(); } // If finding indentation for an open curly brace, move back a token bool isForOpenCurlyBrace = (reader.Token.Id == SimpleTokenId.OpenCurlyBrace); if (isForOpenCurlyBrace) { reader.GoToPreviousToken(); } // Loop backwards bool keywordFoundAfterStatement = false; bool statementFound = false; while (true) { switch (reader.Token.Id) { case SimpleTokenId.OpenCurlyBrace: { // Indent from this open curly brace return(reader.SnapshotLine.IndentAmount + tabSize); } case SimpleTokenId.CloseCurlyBrace: // Return the indent level of the matching { reader.GoToPreviousMatchingTokenById(SimpleTokenId.CloseCurlyBrace, SimpleTokenId.OpenCurlyBrace); return(reader.SnapshotLine.IndentAmount); case SimpleTokenId.CloseParenthesis: case SimpleTokenId.SemiColon: if (!statementFound) { // Flag that a statement was found statementFound = true; if (!keywordFoundAfterStatement) { // Use this line as indentation base indentationBaseLineIndex = reader.SnapshotLine.Index; } } break; default: if ((!keywordFoundAfterStatement) && (!statementFound) && (reader.Offset < snapshotOffset.Offset) && (reader.Token.Id >= SimpleTokenId.Function) && (reader.Token.Id <= SimpleTokenId.Var)) { // Flag that a keyword was found keywordFoundAfterStatement = true; // Use this line as indentation base indentationBaseLineIndex = reader.SnapshotLine.Index; } break; } // Go to the previous token if (!reader.GoToPreviousToken()) { break; } } // Indent a level if on the statement after the keyword return(reader.Snapshot.Lines[indentationBaseLineIndex].IndentAmount + (keywordFoundAfterStatement && isForOpenCurlyBrace ? tabSize : 0)); }
public SyntaxEditorAntlrStream(ITextSnapshotReader reader) { m_reader = reader; }
/// <summary> /// Updates the hit test info. /// </summary> /// <param name="result">The hit test result.</param> private void UpdateHitTestInfo(IHitTestResult result) { StringBuilder text = new StringBuilder(); if (result != null) { text.AppendFormat("Snapshot version {0}{1}", result.Snapshot.Version.Number, Environment.NewLine); if (result.View != null) { text.AppendFormat("Over '{0}' view{1}", this.GetPlacementName(result.View), Environment.NewLine); } switch (result.Type) { case HitTestResultType.Splitter: { EditorViewSplitter splitter = result.VisualElement as EditorViewSplitter; if (splitter != null) { text.AppendLine("Over view splitter"); } break; } case HitTestResultType.ViewMargin: text.AppendFormat("Over '{0}' margin{1}", result.ViewMargin.Key, Environment.NewLine); text.AppendFormat("Closest text position is ({0},{1}){2}", result.Position.Line, result.Position.Character, Environment.NewLine); break; case HitTestResultType.ViewScrollBar: { System.Windows.Controls.Primitives.ScrollBar scrollBar = result.VisualElement as System.Windows.Controls.Primitives.ScrollBar; if (scrollBar != null) { text.AppendFormat("Over '{0}' scrollbar{1}", scrollBar.Orientation, Environment.NewLine); } break; } case HitTestResultType.ViewScrollBarBlock: text.AppendLine("Over scroll bar block"); break; case HitTestResultType.ViewScrollBarSplitter: { ScrollBarSplitter splitter = result.VisualElement as ScrollBarSplitter; if (splitter != null) { text.AppendLine("Over scroll bar splitter"); } break; } case HitTestResultType.ViewScrollBarTray: text.AppendLine("Over scroll bar tray (that can contain other controls like buttons)"); break; case HitTestResultType.ViewTextArea: text.AppendFormat("Not directly over any view line or character{0}", Environment.NewLine); text.AppendFormat("Closest text position is ({0},{1}){2}", result.Position.Line, result.Position.Character, Environment.NewLine); break; case HitTestResultType.ViewTextAreaOverCharacter: { ITextSnapshotReader reader = result.GetReader(); text.AppendFormat("Directly over offset {0} and text position ({1},{2}){3}", result.Offset, result.Position.Line, result.Position.Character, Environment.NewLine); text.AppendFormat("Directly over character '{0}'{1}", reader.Character, Environment.NewLine); IToken token = reader.Token; if (token != null) { text.AppendFormat("Directly over token '{0}' with range ({1},{2})-({3},{4}){5}", token.Key, token.StartPosition.Line, token.StartPosition.Character, token.EndPosition.Line, token.EndPosition.Character, Environment.NewLine); text.AppendFormat("Directly over token text '{0}'{1}", reader.TokenText, Environment.NewLine); } break; } case HitTestResultType.ViewTextAreaOverIntraTextSpacer: text.AppendFormat("Over spacer '{0}' on document line {1}{2}", result.IntraTextSpacerTag, result.Position.Line, Environment.NewLine); break; case HitTestResultType.ViewTextAreaOverLine: text.AppendFormat("Over whitespace at the end of document line {0}{1}", result.Position.Line, Environment.NewLine); break; default: if (result.VisualElement != null) { text.AppendFormat("Over a '{0}' element{1}", result.VisualElement.GetType().FullName, Environment.NewLine); } else { text.AppendLine("No other hit test details available"); } break; } } else { text.AppendLine("Not over the SyntaxEditor"); } resultsTextBox.Text = text.ToString(); }
///////////////////////////////////////////////////////////////////////////////////////////////////// // PUBLIC PROCEDURES ///////////////////////////////////////////////////////////////////////////////////////////////////// /// <summary> /// Performs a parsing operation using the parameters specified in the supplied <see cref="IParseRequest"/> /// and returns the resulting parse data. /// </summary> /// <param name="request">The <see cref="IParseRequest"/> that contains data about the requested parsing operation.</param> /// <returns>An <see cref="IParseData"/> that is the result of the parsing operation.</returns> /// <remarks> /// A <see cref="IParseRequestDispatcher"/> typically calls this method when a queued parse request is ready to be processed. /// </remarks> public override IParseData Parse(IParseRequest request) { if (request == null) { throw new ArgumentNullException("request"); } // Create parse data ParentParseData parseData = new ParentParseData(); parseData.Snapshot = request.Snapshot; // Initialize generated text StringBuilder generatedText = new StringBuilder(); generatedText.Append("using System;\n"); generatedText.Append("using System.Collections.Generic;\n\n"); generatedText.Append("using System.Linq;\n\n"); generatedText.Append("[EditorBrowsable(EditorBrowsableState.Never)]\n"); generatedText.Append("class __Generated {\n"); generatedText.Append("\t[EditorBrowsable(EditorBrowsableState.Never)]\n"); generatedText.Append("\tvoid __WriteOutput() {\n"); ITextSnapshotReader sourceReader = request.Snapshot.GetReader(0); int lastDelimiterOffset = 0; bool lastDelimiterWasStart = false; while (!sourceReader.IsAtSnapshotEnd) { IToken token = sourceReader.ReadToken(); if (token != null) { switch (token.Id) { case ParentTokenId.ChildCodeBlockStart: case ParentTokenId.ChildOutputBlockStart: if (token.StartOffset - lastDelimiterOffset > 0) { // Append generated text string text = sourceReader.Snapshot.GetSubstring(new TextRange(lastDelimiterOffset, token.StartOffset), LineTerminator.Newline); generatedText.Append("\t\tResponse.Write(@\""); generatedText.Append(text.Replace("\"", "\"\"")); generatedText.Append("\");\n"); } // Store the last delimiter offset lastDelimiterOffset = token.EndOffset; lastDelimiterWasStart = true; break; case ParentTokenId.ChildCodeBlockEnd: if ((lastDelimiterWasStart) && (token.StartOffset - lastDelimiterOffset > 0)) { // Get the text between the delimiters string text = sourceReader.Snapshot.GetSubstring(new TextRange(lastDelimiterOffset, token.StartOffset), LineTerminator.Newline); generatedText.Append("\t\t"); // Add a mapping parseData.TextRangeMappings.Add(Tuple.Create(new TextRange(lastDelimiterOffset, token.StartOffset), TextRange.FromSpan(generatedText.Length, text.Length))); // Append the text directly generatedText.Append(text); generatedText.Append("\n"); } // Store the last delimiter offset lastDelimiterOffset = token.EndOffset; lastDelimiterWasStart = false; break; case ParentTokenId.ChildOutputBlockEnd: if ((lastDelimiterWasStart) && (token.StartOffset - lastDelimiterOffset > 0)) { // Get the text between the delimiters and append a Response.Write string text = sourceReader.Snapshot.GetSubstring(new TextRange(lastDelimiterOffset, token.StartOffset), LineTerminator.Newline); generatedText.Append("\t\tResponse.Write("); // Add a mapping parseData.TextRangeMappings.Add(Tuple.Create(new TextRange(lastDelimiterOffset, token.StartOffset), TextRange.FromSpan(generatedText.Length, text.Length))); // Append the text directly generatedText.Append(text); generatedText.Append(");\n"); } // Store the last delimiter offset lastDelimiterOffset = token.EndOffset; lastDelimiterWasStart = false; break; } } } if (lastDelimiterOffset < sourceReader.Snapshot.Length) { // Append generated text string text = sourceReader.Snapshot.GetSubstring(new TextRange(lastDelimiterOffset, sourceReader.Snapshot.Length), LineTerminator.Newline); generatedText.Append("\t\tResponse.Write(@\""); generatedText.Append(text.Replace("\"", "\"\"")); generatedText.Append("\");\n"); } // Store the generated text generatedText.Append("\t}\n"); generatedText.Append("}\n"); // Get parse data for the translated code CodeDocument generatedDocument = new CodeDocument(); generatedDocument.Language = childLanguage; generatedDocument.SetText(generatedText.ToString()); // Get a reader ITextBufferReader generatedReader = generatedDocument.CurrentSnapshot.GetReader(0).BufferReader; // Create a request ParseRequest generatedRequest = new ParseRequest(Guid.NewGuid().ToString(), generatedReader, childParser, generatedDocument); generatedRequest.Snapshot = generatedDocument.CurrentSnapshot; // Parse generatedDocument.ParseData = childParser.Parse(generatedRequest); parseData.GeneratedParseData = generatedDocument.ParseData as ILLParseData; return(parseData); }
/// <summary> /// Examines the snapshot text to determine more detail about the context. /// </summary> /// <param name="context">The <see cref="SimpleContext"/> to update.</param> /// <param name="includeArgumentInfo">Whether to populate the argument-related context properties, for use with parameter info.</param> private static void UpdateFromSnapshotText(SimpleContext context, bool includeArgumentInfo) { // Get the snapshot offset TextSnapshotOffset snapshotOffset = context.SnapshotOffset; // Create a default initialization range context.InitializationSnapshotRange = new TextSnapshotRange(snapshotOffset); // Get a snapshot reader ITextSnapshotReader reader = snapshotOffset.Snapshot.GetReader(snapshotOffset.Offset); if (reader == null) { return; } IToken token = reader.ReadToken(); if (token != null) { // If the current token is a comment, flag no context switch (token.Id) { case SimpleTokenId.MultiLineCommentEndDelimiter: case SimpleTokenId.MultiLineCommentLineTerminator: case SimpleTokenId.MultiLineCommentStartDelimiter: case SimpleTokenId.MultiLineCommentText: case SimpleTokenId.SingleLineCommentEndDelimiter: case SimpleTokenId.SingleLineCommentStartDelimiter: case SimpleTokenId.SingleLineCommentText: context.Type = SimpleContextType.None; return; } } // If we are in a function declaration block... if (context.Type == SimpleContextType.FunctionDeclarationBlock) { // Get the AST ICodeDocument document = snapshotOffset.Snapshot.Document as ICodeDocument; if (document == null) { return; } ILLParseData parseData = document.ParseData as ILLParseData; if (parseData == null) { return; } CompilationUnit compilationUnit = parseData.Ast as CompilationUnit; if ((compilationUnit == null) || (!compilationUnit.HasMembers)) { return; } // If argument data should be scanned... if (includeArgumentInfo) { // Scan backward to look for argument data reader.Offset = snapshotOffset.Offset; reader.GoToCurrentTokenStart(); var startOffset = reader.Offset; token = reader.ReadTokenReverse(); while (token != null) { // Quit if we look behind too far (for performance reasons) - 500 is arbitrary number and could be tweaked if ((!context.ArgumentIndex.HasValue) && (startOffset - reader.Offset > 500)) { return; } switch (token.Id) { case SimpleTokenId.CloseParenthesis: // Skip over ( ... ) pair if (!reader.GoToPreviousMatchingTokenById(SimpleTokenId.CloseParenthesis, SimpleTokenId.OpenParenthesis)) { return; } break; case SimpleTokenId.Comma: // Update the context data if (context.ArgumentIndex.HasValue) { context.ArgumentIndex++; } else { context.ArgumentIndex = 1; context.ArgumentSnapshotOffset = new TextSnapshotOffset(reader.Snapshot, token.EndOffset); } break; case SimpleTokenId.OpenParenthesis: // Update the context data context.ArgumentListSnapshotOffset = new TextSnapshotOffset(reader.Snapshot, token.EndOffset); if (!context.ArgumentIndex.HasValue) { context.ArgumentIndex = 0; context.ArgumentSnapshotOffset = context.ArgumentListSnapshotOffset; } // Go to the previous token reader.GoToPreviousToken(); token = reader.Token; if ((token != null) && (token.Id == SimpleTokenId.Identifier)) { // Loop through the AST nodes to ensure that the identifier text matches a declared function name string functionName = reader.TokenText; foreach (FunctionDeclaration functionAstNode in compilationUnit.Members) { // If the name matches the function's name... if (functionAstNode.Name == functionName) { // Update the target function context.TargetFunction = functionAstNode; break; } } } return; default: // Quit if any tokens are found that aren't allowed in invocations if (!IsTokenAllowedInInvocation(token.Id)) { return; } else { break; } } // Move back a token token = reader.ReadTokenReverse(); } } else { // If the current token is an identifier... if ((token != null) && (token.Id == SimpleTokenId.Identifier)) { // Store the identifier snapshot range in case this is a function reference token TextSnapshotRange identifierSnapshotRange = new TextSnapshotRange(reader.Snapshot, token.TextRange); // Next, check to ensure the next non-whitespace token is a '(' token = reader.ReadToken(); while (!reader.IsAtSnapshotEnd) { if (token != null) { switch (token.Id) { case SimpleTokenId.Whitespace: // Continue break; case SimpleTokenId.OpenParenthesis: { // Loop through the AST nodes to ensure that the identifier text matches a declared function name foreach (FunctionDeclaration functionAstNode in compilationUnit.Members) { // If the name matches the function's name... if (functionAstNode.Name == identifierSnapshotRange.Text) { // Update the context type context.Type = SimpleContextType.FunctionReference; context.InitializationSnapshotRange = identifierSnapshotRange; context.TargetFunction = functionAstNode; break; } } return; } default: // Quit return; } } // Advance token = reader.ReadToken(); } } } } }
///////////////////////////////////////////////////////////////////////////////////////////////////// // NON-PUBLIC PROCEDURES ///////////////////////////////////////////////////////////////////////////////////////////////////// /// <summary> /// Formats the specified snapshot range. /// </summary> /// <param name="change">The <see cref="ITextChange"/> to use.</param> /// <param name="snapshotRange">The snapshot range.</param> private void FormatCore(ITextChange change, TextSnapshotRange snapshotRange) { // Get the snapshot ITextSnapshot snapshot = snapshotRange.Snapshot; if (snapshot == null) { return; } // Get the snapshot reader ITextSnapshotReader reader = snapshot.GetReader(snapshotRange.StartOffset); // Optimize reader options reader.Options.PrimaryScanDirection = TextScanDirection.Forward; reader.Options.InitialTokenLoadBufferLength = Math.Min(100000, snapshotRange.Length); reader.Options.DefaultTokenLoadBufferLength = reader.Options.InitialTokenLoadBufferLength; // Get the code document ICodeDocument document = snapshot.Document as ICodeDocument; if (document == null) { return; } // Get the tab size int tabSize = document.TabSize; // Keep track of the last non whitespace token Id int lastNonWhitespaceTokenId = -1; // Keep track of the indent level int indentLevel = 0; // Loop through the document while ((reader.Token != null) && (reader.Offset < snapshotRange.EndOffset)) { // If the token is whitespace, delete the text if (reader.Token.Id == SimpleTokenId.Whitespace) { change.DeleteText(reader.Token.TextRange); } // The token is not whitespace else { // Create a variable that will contain the text to be inserted string insertText = ""; // Determine the insertText value based on the previous non-whitespace token and the current token switch (lastNonWhitespaceTokenId) { case SimpleTokenId.CloseCurlyBrace: // If the token is a close curly brace, decrement the indent level if (reader.Token.Id == SimpleTokenId.CloseCurlyBrace) { indentLevel = Math.Max(0, indentLevel - 1); insertText = "\n" + StringHelper.GetIndentText(document.AutoConvertTabsToSpaces, tabSize, indentLevel * tabSize); } else { // If the indent level is zero, a function declaration just finished, which means we want an extra newline if (indentLevel == 0) { insertText = "\n\n"; } else { insertText = "\n" + StringHelper.GetIndentText(document.AutoConvertTabsToSpaces, tabSize, indentLevel * tabSize); } } break; case SimpleTokenId.CloseParenthesis: // If the current token is an OpenCurlyBrace, determine whether the brace goes on a new line or not if (reader.Token.Id == SimpleTokenId.OpenCurlyBrace) { if (openingBraceOnNewLine) { insertText = "\n" + StringHelper.GetIndentText(document.AutoConvertTabsToSpaces, tabSize, indentLevel * tabSize); } else { insertText = " "; } } break; case SimpleTokenId.Identifier: case SimpleTokenId.Number: // Sometimes a space should be added after an identifier or number, sometimes not if (reader.Token.Id != SimpleTokenId.SemiColon && reader.Token.Id != SimpleTokenId.CloseParenthesis && reader.Token.Id != SimpleTokenId.OpenParenthesis && reader.Token.Id != SimpleTokenId.Comma) { insertText = " "; } break; case SimpleTokenId.Comma: case SimpleTokenId.Function: case SimpleTokenId.Return: case SimpleTokenId.Var: case SimpleTokenId.Multiplication: case SimpleTokenId.Equality: case SimpleTokenId.Inequality: case SimpleTokenId.Assignment: case SimpleTokenId.Subtraction: case SimpleTokenId.Addition: case SimpleTokenId.Division: // Keywords and operators get a space insertText = " "; break; case SimpleTokenId.MultiLineCommentText: // Multiline comments get an extra newline insertText = "\n\n" + StringHelper.GetIndentText(document.AutoConvertTabsToSpaces, tabSize, indentLevel * tabSize); break; case SimpleTokenId.OpenCurlyBrace: // If the token is not a close curly brace, increment the indent level if (reader.Token.Id != SimpleTokenId.CloseCurlyBrace) { indentLevel++; } insertText = "\n" + StringHelper.GetIndentText(document.AutoConvertTabsToSpaces, tabSize, indentLevel * tabSize); break; case SimpleTokenId.SemiColon: case SimpleTokenId.SingleLineCommentText: // If the token is a close curly brace, decrement the indent level if (reader.Token.Id == SimpleTokenId.CloseCurlyBrace) { indentLevel = Math.Max(0, indentLevel - 1); } insertText = "\n" + StringHelper.GetIndentText(document.AutoConvertTabsToSpaces, tabSize, indentLevel * tabSize); break; } // Insert the replacement text change.InsertText(reader.Token.StartOffset, insertText); // Update the last non-whitespace token Id lastNonWhitespaceTokenId = reader.Token.Id; } // Go to the next token reader.GoToNextToken(); } // If the entire document was formatted, add a newline to the end if ((snapshot.SnapshotRange.StartOffset == snapshotRange.StartOffset) && (snapshot.SnapshotRange.EndOffset == snapshotRange.EndOffset)) { change.InsertText(snapshotRange.EndOffset, "\n"); } }
private string DetermineFullMemberExpression(string tokenText, ITextSnapshotReader reader) { var sb = new StringBuilder(tokenText); var token = reader.ReadTokenReverse(); while (token != null) { var text = reader.PeekText(token.Length); if (token.Key == "Identifier" || (token.Key == "Punctuation" && text == ".") || (token.Key == "Keyword" && text == "this")) { sb.Insert(0, text); } else if (token.Key == "CloseSquareBrace") { var indexExpression = ReadArrayIndexExpression(reader); if (indexExpression == null) { // we're not going to be able to make sense // of the rest of the expression, so bail out. break; } sb.Insert(0, indexExpression); } else if (token.Key == "Whitespace") { // skip it } else { break; } token = reader.ReadTokenReverse(); } return sb.ToString(); }
/// <summary> /// Initializes a new instance of the <c>JavascriptOutliningSource</c> class. /// </summary> /// <param name="snapshot">The <see cref="ITextSnapshot"/> to use for this outlining source.</param> public JavascriptOutliningSource(ITextSnapshot snapshot) : base(snapshot) { int curlyBraceStartOffset = -1; int curlyBraceLevel = 0; int commentStartOffset = -1; IToken token; // Get a text snapshot reader so that we can read tokens ITextSnapshotReader reader = snapshot.GetReader(0); // Read through the entire snapshot while (!reader.IsAtSnapshotEnd) { // Get the next token token = reader.ReadToken(); if (token != null) { switch (token.Key) { case "MultiLineCommentStartDelimiter": // A multi-line comment is starting... save its start offset if (commentStartOffset == -1) { commentStartOffset = token.StartOffset; } break; case "MultiLineCommentEndDelimiter": // A multi-line comment is ending... add its range to the outlining source if (commentStartOffset != -1) { this.AddNode(new TextRange(commentStartOffset, token.EndOffset), multiLineCommentDefinition); commentStartOffset = -1; } break; case "OpenCurlyBrace": // An open curly brace... save its start offset if it's a top-level brace if (curlyBraceLevel++ == 0) { if (curlyBraceStartOffset == -1) { curlyBraceStartOffset = token.StartOffset; } } break; case "CloseCurlyBrace": // A close curly brace... add its range to the outlining source if it's a top-level brace if (curlyBraceLevel > 0) { curlyBraceLevel--; if (curlyBraceLevel == 0) { this.AddNode(new TextRange(curlyBraceStartOffset, token.EndOffset), curlyBraceDefinition); curlyBraceStartOffset = -1; } } break; } } else { break; } } // If there are any "open" nodes (never found a matching end), add them too if (commentStartOffset != -1) { this.AddOpenNode(commentStartOffset, multiLineCommentDefinition); } if (curlyBraceStartOffset != -1) { this.AddOpenNode(curlyBraceStartOffset, curlyBraceDefinition); } }
private string ReadArrayIndexExpression(ITextSnapshotReader reader) { // we're looking for an expression of the form [123] or [myVariable] // if we don't find one, return false. string indexValue = null; var token = reader.ReadTokenReverse(); while (token != null) { var text = reader.PeekText(token.Length); if (token.Key == "Identifier" && indexValue == null) { // substitute 0 for the name of the variable to give us // the best chance of matching something when we look up the path in a document indexValue = "0"; } else if ((token.Key == "IntegerNumber") && indexValue == null) { indexValue = text; } else if (token.Key == "Whitespace") { // skip it } else if (token.Key == "OpenSquareBrace") { if (indexValue == null) { // we didn't find a properly formed (and simple) index expression // before hitting the square brace return null; } break; } token = reader.ReadTokenReverse(); } if (indexValue == null) { return null; } return "[" + indexValue + "]"; }
private static string FindPrecedingField(ITextSnapshotReader reader) { while (true) { var token = reader.ReadTokenReverse(); if (token == null) return null; if (token.Key == "Field") return GetFieldName(reader.PeekText(token.Length)); } }