Пример #1
0
        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);
        }
Пример #2
0
        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);
        }
Пример #3
0
 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);
     }
 }
Пример #4
0
        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);
            }
        }
Пример #5
0
        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);
        }
Пример #6
0
        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);
            }
            }
        }
Пример #7
0
        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;
 }
Пример #9
0
 public PythonTextBufferInfo GetBufferInfo(ITextBuffer textBuffer)
 {
     return(PythonTextBufferInfo.ForBuffer(this, textBuffer));
 }
Пример #10
0
        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);
        }
Пример #11
0
        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);
        }
Пример #12
0
        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);
        }