private PythonTextBufferInfo ReplaceBufferInfo() { TraceWithStack("ReplaceBufferInfo"); var newInfo = new PythonTextBufferInfo(Services, Buffer); foreach (var sink in _eventSinks) { newInfo._eventSinks[sink.Key] = sink.Value; } Buffer.Properties[PythonTextBufferInfoKey] = newInfo; Buffer.ContentTypeChanged -= Buffer_ContentTypeChanged; Buffer.Changed -= Buffer_TextContentChanged; Buffer.ChangedLowPriority -= Buffer_TextContentChangedLowPriority; if (Buffer is ITextBuffer2 buffer2) { buffer2.ChangedOnBackground -= Buffer_TextContentChangedOnBackground; } Interlocked.Exchange(ref _waitingForEntry, null)?.TrySetResult(null); InvokeSinks(new PythonNewTextBufferInfoEventArgs(PythonTextBufferInfoEvents.NewTextBufferInfo, newInfo)); _traceLog?.Dispose(); return(newInfo); }
private bool FindMatchingPair(BraceKind brace, SnapshotPoint position, int direction, out SnapshotSpan leftSpan, out SnapshotSpan rightSpan) { leftSpan = new SnapshotSpan(position, position); rightSpan = leftSpan; var buffer = PythonTextBufferInfo.ForBuffer(_site, position.Snapshot.TextBuffer); if (buffer == null) { return(false); } if (!(buffer.GetTokenAtPoint(position)?.Trigger ?? TokenTriggers.None).HasFlag(TokenTriggers.MatchBraces)) { return(false); } var snapshot = position.Snapshot; int depth = 0; foreach (var token in (direction > 0 ? buffer.GetTokensForwardFromPoint(position) : buffer.GetTokensInReverseFromPoint(position - 1))) { if (!token.Trigger.HasFlag(TokenTriggers.MatchBraces)) { continue; } var tokenSpan = token.ToSnapshotSpan(snapshot); var txt = tokenSpan.GetText(); var kind = GetBraceKind(txt); if (kind == BraceKind.None) { return(false); } if (kind == brace) { if (txt.IsCloseGrouping()) { depth -= direction; } else { depth += direction; } } if (depth == 0) { leftSpan = tokenSpan; rightSpan = new SnapshotSpan(snapshot, position - 1, 1); return(true); } } return(false); }
public int?GetDesiredIndentation(ITextSnapshotLine line) { if (_pyService.LangPrefs.IndentMode == vsIndentStyle.vsIndentStyleSmart) { return(AutoIndent.GetLineIndentation(PythonTextBufferInfo.ForBuffer(_pyService.Site, line.Snapshot.TextBuffer), line, _textView)); } else { return(null); } }
public bool TryCreateContext(ITextView textView, SnapshotPoint openingPoint, char openingBrace, char closingBrace, out IBraceCompletionContext context) { var bi = PythonTextBufferInfo.ForBuffer(Site, openingPoint.Snapshot.TextBuffer); if (IsValidBraceCompletionContext(bi, openingPoint, openingBrace)) { context = new BraceCompletionContext(); return(true); } else { context = null; return(false); } }
private PythonTextBufferInfo ReplaceBufferInfo() { var newInfo = new PythonTextBufferInfo(Services, Buffer); foreach (var sink in _eventSinks) { newInfo._eventSinks[sink.Key] = sink.Value; } Buffer.Properties[typeof(PythonTextBufferInfo)] = newInfo; Buffer.ContentTypeChanged -= Buffer_ContentTypeChanged; Buffer.Changed -= Buffer_TextContentChanged; Buffer.ChangedLowPriority -= Buffer_TextContentChangedLowPriority; InvokeSinks(new PythonNewTextBufferInfoEventArgs(PythonTextBufferInfoEvents.NewTextBufferInfo, newInfo)); return(newInfo); }
private static bool IsValidBraceCompletionContext(PythonTextBufferInfo buffer, SnapshotPoint openingPoint, char openingBrace) { if (buffer == null) { return(false); } Debug.Assert(openingPoint.Position >= 0, "SnapshotPoint.Position should always be zero or positive."); if (openingPoint.Position < 0) { return(false); } switch (openingBrace) { case '(': case '[': case '{': { // Valid anywhere, including comments / strings return(true); } case '"': case '\'': { // Not valid in comment / strings, so user can easily type triple-quotes var category = buffer.GetTokenAtPoint(openingPoint)?.Category ?? TokenCategory.None; return(!( category == TokenCategory.Comment || category == TokenCategory.LineComment || category == TokenCategory.DocComment || category == TokenCategory.StringLiteral || category == TokenCategory.IncompleteMultiLineStringLiteral )); } default: { Debug.Fail("Unexpected opening brace character."); return(false); } } }
private PythonTextBufferInfo ReplaceBufferInfo() { var newInfo = new PythonTextBufferInfo(Site, Buffer); foreach (var sink in _eventSinks) { newInfo._eventSinks[sink.Key] = sink.Value; } Buffer.Properties[PythonTextBufferInfoKey] = newInfo; Buffer.ContentTypeChanged -= Buffer_ContentTypeChanged; Buffer.Changed -= Buffer_TextContentChanged; Buffer.ChangedLowPriority -= Buffer_TextContentChangedLowPriority; if (Buffer is ITextBuffer2 buffer2) { buffer2.ChangedOnBackground -= Buffer_TextContentChangedOnBackground; } InvokeSinks(new PythonNewTextBufferInfoEventArgs(PythonTextBufferInfoEvents.NewTextBufferInfo, newInfo)); return(newInfo); }
public PythonNewTextBufferInfoEventArgs(PythonTextBufferInfoEvents eventType, PythonTextBufferInfo newInfo) : base(eventType) { NewTextBufferInfo = newInfo; }
public PythonTextBufferInfo GetBufferInfo(ITextBuffer textBuffer) { return(PythonTextBufferInfo.ForBuffer(this, textBuffer)); }
private static int CalculateIndentation( string baseline, ITextSnapshotLine line, IEditorOptions options, PythonTextBufferInfo buffer ) { var snapshot = line.Snapshot; if (snapshot.TextBuffer != buffer.Buffer) { throw new ArgumentException("buffer mismatch"); } int indentation = GetIndentation(baseline, options.GetTabSize()); int tabSize = options.GetIndentSize(); var tokens = buffer.GetTokens(line).ToList(); while (tokens.Count > 0 && IsWhitespace(tokens[tokens.Count - 1].Category)) { tokens.RemoveAt(tokens.Count - 1); } if (tokens.Count == 0 || IsUnterminatedStringToken(tokens[tokens.Count - 1], snapshot)) { return(indentation); } if (HasExplicitLineJoin(tokens, snapshot)) { // explicit line continuation, we indent 1 level for the continued line unless // we're already indented because of multiple line continuation characters. indentation = GetIndentation(line.GetText(), options.GetTabSize()); var joinedLine = line.LineNumber - 1; if (joinedLine >= 0) { var prevLineTokens = buffer.GetTokens(snapshot.GetLineFromLineNumber(joinedLine)).ToList(); if (prevLineTokens.Count == 0 || !HasExplicitLineJoin(prevLineTokens, snapshot)) { indentation += tabSize; } } else { indentation += tabSize; } return(indentation); } var tokenStack = new Stack <TrackingTokenInfo?>(); tokenStack.Push(null); // end with an implicit newline int endAtLine = -1, currentLine = tokens.Last().LineNumber; foreach (var t in buffer.GetTokensInReverseFromPoint(tokens.Last().ToSnapshotSpan(snapshot).Start)) { if (t.LineNumber == currentLine) { tokenStack.Push(t); } else { tokenStack.Push(null); } if (t.LineNumber == endAtLine) { break; } else if (t.Category == TokenCategory.Keyword && PythonKeywords.IsOnlyStatementKeyword(t.GetText(snapshot), buffer.LanguageVersion)) { endAtLine = t.LineNumber - 1; } if (t.LineNumber != currentLine) { currentLine = t.LineNumber; if (t.Category != TokenCategory.WhiteSpace && t.Category != TokenCategory.Comment && t.Category != TokenCategory.LineComment) { tokenStack.Push(t); } } } var indentStack = new Stack <LineInfo>(); var current = LineInfo.Empty; while (tokenStack.Count > 0) { var t = tokenStack.Pop(); if (t == null) { current.NeedsUpdate = true; continue; } var tline = new Lazy <string>(() => snapshot.GetLineFromLineNumber(t.Value.LineNumber).GetText()); if (IsOpenGrouping(t.Value, snapshot)) { indentStack.Push(current); var next = tokenStack.Count > 0 ? tokenStack.Peek() : null; if (next != null && next.Value.LineNumber == t.Value.LineNumber) { // Put indent at same depth as grouping current = new LineInfo { Indentation = t.Value.ToSourceSpan().End.Column - 1 }; } else { // Put indent at one indent deeper than this line current = new LineInfo { Indentation = GetIndentation(tline.Value, tabSize) + tabSize }; } } else if (IsCloseGrouping(t.Value, snapshot)) { if (indentStack.Count > 0) { current = indentStack.Pop(); } else { current.NeedsUpdate = true; } } else if (IsExplicitLineJoin(t.Value, snapshot)) { while (t != null && tokenStack.Count > 0) { t = tokenStack.Pop(); } if (!t.HasValue) { continue; } } else if (current.NeedsUpdate == true) { current = new LineInfo { Indentation = GetIndentation(tline.Value, tabSize) }; } if (ShouldDedentAfterKeyword(t.Value, snapshot)) // dedent after some statements { current.ShouldDedentAfter = true; } if (IsColon(t.Value, snapshot) && // indent after a colon indentStack.Count == 0) // except in a grouping { current.ShouldIndentAfter = true; // If the colon isn't at the end of the line, cancel it out. // If the following is a ShouldDedentAfterKeyword, only one dedent will occur. current.ShouldDedentAfter = (tokenStack.Count != 0 && tokenStack.Peek() != null); } } indentation = current.Indentation + (current.ShouldIndentAfter ? tabSize : 0) - (current.ShouldDedentAfter ? tabSize : 0); return(indentation); }
internal static int?GetLineIndentation(PythonTextBufferInfo buffer, ITextSnapshotLine line, ITextView textView) { if (buffer == null) { return(0); } var options = textView.Options; ITextSnapshotLine baseline; string baselineText; SkipPreceedingBlankLines(line, out baselineText, out baseline); ITextBuffer targetBuffer = line.Snapshot.TextBuffer; if (!targetBuffer.ContentType.IsOfType(PythonCoreConstants.ContentType)) { var match = textView.MapDownToPythonBuffer(line.Start); if (match == null) { return(0); } targetBuffer = match.Value.Snapshot.TextBuffer; } var desiredIndentation = CalculateIndentation(baselineText, baseline, options, buffer); if (desiredIndentation < 0) { desiredIndentation = 0; } // Map indentation back to the view's text buffer. if (textView.TextBuffer != targetBuffer) { var viewLineStart = textView.BufferGraph.MapUpToSnapshot( baseline.Start, PointTrackingMode.Positive, PositionAffinity.Successor, textView.TextSnapshot ); if (viewLineStart.HasValue) { desiredIndentation += viewLineStart.Value - viewLineStart.Value.GetContainingLine().Start; } } var caretLine = textView.Caret.Position.BufferPosition.GetContainingLine(); // VS will get the white space when the user is moving the cursor or when the user is doing an edit which // introduces a new line. When the user is moving the cursor the caret line differs from the line // we're querying. When editing the lines are the same and so we want to account for the white space of // non-blank lines. An alternate strategy here would be to watch for the edit and fix things up after // the fact which is what would happen pre-Dev10 when the language would not get queried for non-blank lines // (and is therefore what C# and other languages are doing). if (caretLine.LineNumber == line.LineNumber) { var lineText = caretLine.GetText(); int indentationUpdate = 0; for (int i = textView.Caret.Position.BufferPosition.Position - caretLine.Start; i < lineText.Length; i++) { if (lineText[i] == ' ') { indentationUpdate++; } else if (lineText[i] == '\t') { indentationUpdate += textView.Options.GetIndentSize(); } else { if (indentationUpdate > desiredIndentation) { // we would dedent this line (e.g. there's a return on the previous line) but the user is // hitting enter with a statement to the right of the caret and they're in the middle of white space. // So we need to instead just maintain the existing indentation level. desiredIndentation = Math.Max(GetIndentation(baselineText, options.GetTabSize()) - indentationUpdate, 0); } else { desiredIndentation -= indentationUpdate; } break; } } } return(desiredIndentation); }
public bool TryTriggerExpansion(ITextView textView) { if (textView == null) { throw new ArgumentNullException(nameof(textView)); } if (_vsExpansionMgr == null) { return(false); } if (!textView.Selection.IsEmpty || textView.Caret.Position.BufferPosition <= 0) { return(false); } var snapshot = textView.TextBuffer.CurrentSnapshot; var caretSpan = new SnapshotSpan(snapshot, new Span(textView.Caret.Position.BufferPosition.Position - 1, 1)); var bufferInfo = PythonTextBufferInfo.ForBuffer(_site, textView.TextBuffer); var tokens = bufferInfo.GetTrackingTokens(caretSpan); if (!tokens.Any()) { return(false); } var token = tokens.First(); var tokenSpan = token.ToSnapshotSpan(snapshot); if (tokenSpan.End.Position != caretSpan.End.Position) { // Match C# behavior and only trigger snippet // if caret is at the end of an identifier. Otherwise, // a TAB should be inserted even if the token matches // a snippet shortcut. return(false); } var text = tokenSpan.GetText(); var textSpan = new TextSpan[1]; textSpan[0].iStartLine = tokenSpan.Start.GetContainingLine().LineNumber; textSpan[0].iStartIndex = tokenSpan.Start.Position - tokenSpan.Start.GetContainingLine().Start; textSpan[0].iEndLine = tokenSpan.End.GetContainingLine().LineNumber; textSpan[0].iEndIndex = tokenSpan.End.Position - tokenSpan.End.GetContainingLine().Start; var client = GetOrCreateExpansionClient(textView); int hr = _vsExpansionMgr.GetExpansionByShortcut( client, CommonGuidList.guidPythonLanguageServiceGuid, text, _editorAdaptersFactoryService.GetViewAdapter(textView), textSpan, 1, out string expansionPath, out string title ); if (ErrorHandler.Succeeded(hr)) { // hr may be S_FALSE if there are multiple expansions, // so we don't want to InsertNamedExpansion yet. VS will // pop up a selection dialog in this case. if (hr == VSConstants.S_OK) { return(ErrorHandler.Succeeded(client.InsertNamedExpansion(title, expansionPath, textSpan[0]))); } return(true); } return(false); }