예제 #1
0
        public static bool GuessSemicolonInsertionOffset(TextEditorData data, ISegment curLine, int caretOffset, out int outOffset)
        {
            int  lastNonWsOffset = caretOffset;
            char lastNonWsChar   = '\0';

            outOffset = caretOffset;

            int max = curLine.EndOffset;

            int end = caretOffset;

            while (end > 1 && char.IsWhiteSpace(data.GetCharAt(end)))
            {
                end--;
            }
            int end2 = end;

            while (end2 > 1 && char.IsLetter(data.GetCharAt(end2 - 1)))
            {
                end2--;
            }
            if (end != end2)
            {
                string token = data.GetTextBetween(end2, end + 1);
                // guess property context
                if (token == "get" || token == "set")
                {
                    return(false);
                }
            }

            var    offset   = curLine.Offset;
            string lineText = data.GetTextAt(caretOffset, max - caretOffset);
            var    lexer    = new CSharpCompletionEngineBase.MiniLexer(lineText);

            lexer.Parse((ch, i) => {
                if (lexer.IsInSingleComment || lexer.IsInMultiLineComment)
                {
                    return(true);
                }
                if (ch == '}' && lexer.IsFistNonWs && !IsSemicolonalreadyPlaced(data, caretOffset))
                {
                    lastNonWsChar = ';';
                    return(true);
                }
                if (!char.IsWhiteSpace(ch))
                {
                    lastNonWsOffset = caretOffset + i;
                    lastNonWsChar   = ch;
                }
                return(false);
            });
            // if the line ends with ';' the line end is not the correct place for a new semicolon.
            if (lastNonWsChar == ';')
            {
                return(false);
            }
            outOffset = lastNonWsOffset;
            return(true);
        }
		public bool FixLineStart (TextEditorData textEditorData, IStateMachineIndentEngine stateTracker, int lineNumber)
		{
			if (lineNumber > DocumentLocation.MinLine) {
				DocumentLine line = textEditorData.Document.GetLine (lineNumber);
				if (line == null)
					return false;

				DocumentLine prevLine = textEditorData.Document.GetLine (lineNumber - 1);
				if (prevLine == null)
					return false;
				string trimmedPreviousLine = textEditorData.Document.GetTextAt (prevLine).TrimStart ();

				//xml doc comments
				//check previous line was a doc comment
				//check there's a following line?
				if (trimmedPreviousLine.StartsWith ("///", StringComparison.Ordinal)) {
					if (textEditorData.GetTextAt (line.Offset, line.Length).TrimStart ().StartsWith ("///", StringComparison.Ordinal))
						return false;
					//check that the newline command actually inserted a newline
					textEditorData.EnsureCaretIsNotVirtual ();
					var nextLineSegment = textEditorData.Document.GetLine (lineNumber + 1);
					string nextLine = nextLineSegment != null ? textEditorData.Document.GetTextAt (nextLineSegment).TrimStart () : "";

					if (trimmedPreviousLine.Length > "///".Length || nextLine.StartsWith ("///", StringComparison.Ordinal)) {
						var insertionPoint = textEditorData.Caret.Offset;
						int inserted = textEditorData.Insert (insertionPoint, "/// ");
						textEditorData.Caret.Offset = insertionPoint + inserted;
						return true;
					}
					//multi-line comments
				} else if (stateTracker.IsInsideMultiLineComment) {
					if (textEditorData.GetTextAt (line.Offset, line.Length).TrimStart ().StartsWith ("*", StringComparison.Ordinal))
						return false;
					textEditorData.EnsureCaretIsNotVirtual ();
					string commentPrefix = string.Empty;
					if (trimmedPreviousLine.StartsWith ("* ", StringComparison.Ordinal)) {
						commentPrefix = "* ";
					} else if (trimmedPreviousLine.StartsWith ("/**", StringComparison.Ordinal) || trimmedPreviousLine.StartsWith ("/*", StringComparison.Ordinal)) {
						commentPrefix = " * ";
					} else if (trimmedPreviousLine.StartsWith ("*", StringComparison.Ordinal)) {
						commentPrefix = "*";
					}

					int indentSize = line.GetIndentation (textEditorData.Document).Length;
					var insertedText = prevLine.GetIndentation (textEditorData.Document) + commentPrefix;
					textEditorData.Replace (line.Offset, indentSize, insertedText);
					textEditorData.Caret.Offset = line.Offset + insertedText.Length;
					return true;
				} else if (wasInStringLiteral) {
					var lexer = new CSharpCompletionEngineBase.MiniLexer (textEditorData.Document.GetTextAt (0, prevLine.EndOffset));
					lexer.Parse ();
					if (!lexer.IsInString)
						return false;
					textEditorData.EnsureCaretIsNotVirtual ();
					textEditorData.Insert (prevLine.Offset + prevLine.Length, "\" +");

					int indentSize = line.GetIndentation (textEditorData.Document).Length;
					var insertedText = prevLine.GetIndentation (textEditorData.Document) + (trimmedPreviousLine.StartsWith ("\"", StringComparison.Ordinal) ? "" : "\t") + "\"";
					textEditorData.Replace (line.Offset, indentSize, insertedText);
					return true;
				}
			}
			return false;
		}
		public override bool KeyPress (Gdk.Key key, char keyChar, Gdk.ModifierType modifier)
		{
			bool skipFormatting = StateTracker.IsInsideOrdinaryCommentOrString ||
			                      StateTracker.IsInsidePreprocessorDirective;

			cursorPositionBeforeKeyPress = textEditorData.Caret.Offset;
			bool isSomethingSelected = textEditorData.IsSomethingSelected;
			if (key == Gdk.Key.BackSpace && textEditorData.Caret.Offset == lastInsertedSemicolon) {
				textEditorData.Document.Undo ();
				lastInsertedSemicolon = -1;
				return false;
			}
			lastInsertedSemicolon = -1;
			if (keyChar == ';' && !(textEditorData.CurrentMode is TextLinkEditMode) && !DoInsertTemplate () && !isSomethingSelected && PropertyService.Get (
				    "SmartSemicolonPlacement",
				    false
			    )) {
				bool retval = base.KeyPress (key, keyChar, modifier);
				DocumentLine curLine = textEditorData.Document.GetLine (textEditorData.Caret.Line);
				string text = textEditorData.Document.GetTextAt (curLine);
				if (!(text.EndsWith (";", StringComparison.Ordinal) || text.Trim ().StartsWith ("for", StringComparison.Ordinal))) {
					int guessedOffset;

					if (GuessSemicolonInsertionOffset (textEditorData, curLine, textEditorData.Caret.Offset, out guessedOffset)) {
						using (var undo = textEditorData.OpenUndoGroup ()) {
							textEditorData.Remove (textEditorData.Caret.Offset - 1, 1);
							textEditorData.Caret.Offset = guessedOffset;
							lastInsertedSemicolon = textEditorData.Caret.Offset + 1;
							retval = base.KeyPress (key, keyChar, modifier);
						}
					}
				}
				using (var undo = textEditorData.OpenUndoGroup ()) {
					if (OnTheFlyFormatting && textEditorData != null && !(textEditorData.CurrentMode is TextLinkEditMode) && !(textEditorData.CurrentMode is InsertionCursorEditMode)) {
						OnTheFlyFormatter.FormatStatmentAt (Document, textEditorData.Caret.Location);
					}
				}
				return retval;
			}
			
			if (key == Gdk.Key.Tab) {
				SafeUpdateIndentEngine (textEditorData.Caret.Offset);
				if (stateTracker.IsInsideStringLiteral && !textEditorData.IsSomethingSelected) {
					var lexer = new CSharpCompletionEngineBase.MiniLexer (textEditorData.Document.GetTextAt (0, textEditorData.Caret.Offset));
					lexer.Parse ();
					if (lexer.IsInString) {
						textEditorData.InsertAtCaret ("\\t");
						return false;
					}
				}
			}


			if (key == Gdk.Key.Tab && DefaultSourceEditorOptions.Instance.TabIsReindent && !CompletionWindowManager.IsVisible && !(textEditorData.CurrentMode is TextLinkEditMode) && !DoInsertTemplate () && !isSomethingSelected) {
				ReindentOnTab ();

				return false;
			}

			SafeUpdateIndentEngine (textEditorData.Caret.Offset);
			if (!stateTracker.IsInsideOrdinaryCommentOrString) {
				if (keyChar == '@') {
					var retval = base.KeyPress (key, keyChar, modifier);
					int cursor = textEditorData.Caret.Offset;
					if (cursor < textEditorData.Length && textEditorData.GetCharAt (cursor) == '"')
						ConvertNormalToVerbatimString (textEditorData, cursor + 1);
					return retval;
				}
			}


			//do the smart indent
			if (textEditorData.Options.IndentStyle == IndentStyle.Smart || textEditorData.Options.IndentStyle == IndentStyle.Virtual) {
				bool retval;
				//capture some of the current state
				int oldBufLen = textEditorData.Length;
				int oldLine = textEditorData.Caret.Line + 1;
				bool reIndent = false;

				//pass through to the base class, which actually inserts the character
				//and calls HandleCodeCompletion etc to handles completion
				using (var undo = textEditorData.OpenUndoGroup ()) {
					DoPreInsertionSmartIndent (key);
				}
				wasInStringLiteral = stateTracker.IsInsideStringLiteral;
				bool automaticReindent;
				// need to be outside of an undo group - otherwise it interferes with other text editor extension
				// esp. the documentation insertion undo steps.
				retval = base.KeyPress (key, keyChar, modifier);
				//handle inserted characters
				if (textEditorData.Caret.Offset <= 0 || textEditorData.IsSomethingSelected)
					return retval;
				
				lastCharInserted = TranslateKeyCharForIndenter (key, keyChar, textEditorData.GetCharAt (textEditorData.Caret.Offset - 1));
				if (lastCharInserted == '\0')
					return retval;
				using (var undo = textEditorData.OpenUndoGroup ()) {
					SafeUpdateIndentEngine (textEditorData.Caret.Offset);

					if (key == Gdk.Key.Return && modifier == Gdk.ModifierType.ControlMask) {
						FixLineStart (textEditorData, stateTracker, textEditorData.Caret.Line + 1);
					} else {
						if (!(oldLine == textEditorData.Caret.Line + 1 && lastCharInserted == '\n') && (oldBufLen != textEditorData.Length || lastCharInserted != '\0')) {
							DoPostInsertionSmartIndent (lastCharInserted, out reIndent);
						} else {
							reIndent = lastCharInserted == '\n';
						}
					}
					//reindent the line after the insertion, if needed
					//N.B. if the engine says we need to reindent, make sure that it's because a char was 
					//inserted rather than just updating the stack due to moving around

					SafeUpdateIndentEngine (textEditorData.Caret.Offset);
					automaticReindent = (stateTracker.NeedsReindent && lastCharInserted != '\0');
					if (key == Gdk.Key.Return && (reIndent || automaticReindent)) {
						if (textEditorData.Options.IndentStyle == IndentStyle.Virtual) {
							if (textEditorData.GetLine (textEditorData.Caret.Line).Length == 0)
								textEditorData.Caret.Column = textEditorData.IndentationTracker.GetVirtualIndentationColumn (textEditorData.Caret.Location);
						} else {
							DoReSmartIndent ();
						}
					}
				}

				if (reIndent || key != Gdk.Key.Return && automaticReindent) {
					using (var undo = textEditorData.OpenUndoGroup ()) {
						DoReSmartIndent ();
					}
				}
				if (!skipFormatting) {
					if (keyChar == ';' || keyChar == '}') {
						using (var undo = textEditorData.OpenUndoGroup ()) {
							if (OnTheFlyFormatting && textEditorData != null && !(textEditorData.CurrentMode is TextLinkEditMode) && !(textEditorData.CurrentMode is InsertionCursorEditMode)) {
								OnTheFlyFormatter.FormatStatmentAt (Document, textEditorData.Caret.Location);
							}
						}
					}
				}

				SafeUpdateIndentEngine (textEditorData.Caret.Offset);
				lastCharInserted = '\0';
				CheckXmlCommentCloseTag (keyChar);
				return retval;
			}

			if (textEditorData.Options.IndentStyle == IndentStyle.Auto && DefaultSourceEditorOptions.Instance.TabIsReindent && key == Gdk.Key.Tab) {
				bool retval = base.KeyPress (key, keyChar, modifier);
				DoReSmartIndent ();
				CheckXmlCommentCloseTag (keyChar);
				return retval;
			}

			//pass through to the base class, which actually inserts the character
			//and calls HandleCodeCompletion etc to handles completion
			var result = base.KeyPress (key, keyChar, modifier);

			if (key == Gdk.Key.Return || key == Gdk.Key.KP_Enter) {
				DoReSmartIndent ();
			}

			CheckXmlCommentCloseTag (keyChar);

			if (!skipFormatting && keyChar == '}')
				RunFormatter (new DocumentLocation (textEditorData.Caret.Location.Line, textEditorData.Caret.Location.Column));
			return result;
		}
        /// <summary>
        /// Builds a compileable stub file out of an entity.
        /// </summary>
        /// <returns>
        /// A string representing the stub
        /// </returns>
        /// <param name='memberStartOffset'>
        /// The offset where the member starts in the returned text.
        /// </param>
        static string BuildStub(MonoDevelop.Ide.Gui.Document data, CSharpCompletionTextEditorExtension.TypeSystemTreeSegment seg, int startOffset, int endOffset, out int memberStartOffset)
        {
            var pf = data.ParsedDocument.ParsedFile as CSharpUnresolvedFile;

            if (pf == null)
            {
                memberStartOffset = 0;
                return(null);
            }

            var sb = new StringBuilder();

            int closingBrackets = 0;
            // use the member start location to determine the using scope, because this information is in sync, the position in
            // the file may have changed since last parse run (we have up 2 date locations from the type segment tree).
            var scope = pf.GetUsingScope(seg.Entity.Region.Begin);

            while (scope != null && !string.IsNullOrEmpty(scope.NamespaceName))
            {
                // Hack: some syntax errors lead to invalid namespace names.
                if (scope.NamespaceName.EndsWith("<invalid>", StringComparison.Ordinal))
                {
                    scope = scope.Parent;
                    continue;
                }
                sb.Append("namespace Stub {");
                sb.Append(data.Editor.EolMarker);
                closingBrackets++;
                while (scope.Parent != null && scope.Parent.Region == scope.Region)
                {
                    scope = scope.Parent;
                }
                scope = scope.Parent;
            }

            var parent = seg.Entity.DeclaringTypeDefinition;

            while (parent != null)
            {
                sb.Append("class " + parent.Name + " {");
                sb.Append(data.Editor.EolMarker);
                closingBrackets++;
                parent = parent.DeclaringTypeDefinition;
            }

            memberStartOffset = sb.Length;
            var text = data.Editor.GetTextBetween(seg.Offset, endOffset);

            sb.Append(text);

            var lex = new CSharpCompletionEngineBase.MiniLexer(text);

            lex.Parse(ch => {
                if (lex.IsInString || lex.IsInChar || lex.IsInVerbatimString || lex.IsInSingleComment || lex.IsInMultiLineComment || lex.IsInPreprocessorDirective)
                {
                    return;
                }
                if (ch == '{')
                {
                    closingBrackets++;
                }
                else if (ch == '}')
                {
                    closingBrackets--;
                }
            });


            // Insert at least caret column eol markers otherwise the reindent of the generated closing bracket
            // could interfere with the current indentation.
            var endLocation = data.Editor.OffsetToLocation(endOffset);

            for (int i = 0; i <= endLocation.Column; i++)
            {
                sb.Append(data.Editor.EolMarker);
            }
            sb.Append(data.Editor.EolMarker);
            sb.Append(new string ('}', closingBrackets));
            return(sb.ToString());
        }
예제 #5
0
        public override bool KeyPress(Gdk.Key key, char keyChar, Gdk.ModifierType modifier)
        {
            bool skipFormatting = StateTracker.Engine.IsInsideOrdinaryCommentOrString ||
                                  StateTracker.Engine.IsInsidePreprocessorDirective;

            cursorPositionBeforeKeyPress = textEditorData.Caret.Offset;
            bool isSomethingSelected = textEditorData.IsSomethingSelected;

            if (key == Gdk.Key.BackSpace && textEditorData.Caret.Offset == lastInsertedSemicolon)
            {
                textEditorData.Document.Undo();
                lastInsertedSemicolon = -1;
                return(false);
            }
            lastInsertedSemicolon = -1;
            if (keyChar == ';' && !(textEditorData.CurrentMode is TextLinkEditMode) && !DoInsertTemplate() && !isSomethingSelected && PropertyService.Get(
                    "SmartSemicolonPlacement",
                    false
                    ))
            {
                bool         retval  = base.KeyPress(key, keyChar, modifier);
                DocumentLine curLine = textEditorData.Document.GetLine(textEditorData.Caret.Line);
                string       text    = textEditorData.Document.GetTextAt(curLine);
                if (text.EndsWith(";") || text.Trim().StartsWith("for"))
                {
                    return(retval);
                }

                int guessedOffset = GuessSemicolonInsertionOffset(textEditorData, curLine);
                if (guessedOffset != textEditorData.Caret.Offset)
                {
                    using (var undo = textEditorData.OpenUndoGroup()) {
                        textEditorData.Remove(textEditorData.Caret.Offset - 1, 1);
                        textEditorData.Caret.Offset = guessedOffset;
                        lastInsertedSemicolon       = textEditorData.Caret.Offset + 1;
                        retval = base.KeyPress(key, keyChar, modifier);
                    }
                }
                return(retval);
            }

            if (key == Gdk.Key.Tab)
            {
                stateTracker.UpdateEngine();
                if (stateTracker.Engine.IsInsideStringLiteral)
                {
                    var lexer = new CSharpCompletionEngineBase.MiniLexer(textEditorData.Document.GetTextAt(0, textEditorData.Caret.Offset));
                    lexer.Parse();
                    if (lexer.IsInString)
                    {
                        textEditorData.InsertAtCaret("\\t");
                        return(false);
                    }
                }
            }


            if (key == Gdk.Key.Tab && DefaultSourceEditorOptions.Instance.TabIsReindent && !CompletionWindowManager.IsVisible && !(textEditorData.CurrentMode is TextLinkEditMode) && !DoInsertTemplate() && !isSomethingSelected)
            {
                int cursor = textEditorData.Caret.Offset;
                if (stateTracker.Engine.IsInsideVerbatimString)
                {
                    // insert normal tab inside @" ... "
                    if (textEditorData.IsSomethingSelected)
                    {
                        textEditorData.SelectedText = "\t";
                    }
                    else
                    {
                        textEditorData.Insert(cursor, "\t");
                        textEditorData.Caret.Offset++;
                    }
                    textEditorData.Document.CommitLineUpdate(textEditorData.Caret.Line);
                }
                else if (cursor >= 1)
                {
                    if (textEditorData.Caret.Column > 1)
                    {
                        int delta = cursor - this.cursorPositionBeforeKeyPress;
                        if (delta < 2 && delta > 0)
                        {
                            textEditorData.Remove(cursor - delta, delta);
                            textEditorData.Caret.Offset = cursor - delta;
                            textEditorData.Document.CommitLineUpdate(textEditorData.Caret.Line);
                        }
                    }
                    stateTracker.UpdateEngine();
                    DoReSmartIndent();
                }
                return(false);
            }

            //do the smart indent
            if (textEditorData.Options.IndentStyle == IndentStyle.Smart || textEditorData.Options.IndentStyle == IndentStyle.Virtual)
            {
                bool retval;
                //capture some of the current state
                int  oldBufLen    = textEditorData.Length;
                int  oldLine      = textEditorData.Caret.Line + 1;
                bool hadSelection = textEditorData.IsSomethingSelected;
                bool reIndent     = false;

                //pass through to the base class, which actually inserts the character
                //and calls HandleCodeCompletion etc to handles completion
                using (var undo = textEditorData.OpenUndoGroup()) {
                    DoPreInsertionSmartIndent(key);
                }
                retval = base.KeyPress(key, keyChar, modifier);

                using (var undo = textEditorData.OpenUndoGroup()) {
                    //handle inserted characters
                    if (textEditorData.Caret.Offset <= 0 || textEditorData.IsSomethingSelected)
                    {
                        return(retval);
                    }

                    lastCharInserted = TranslateKeyCharForIndenter(key, keyChar, textEditorData.GetCharAt(textEditorData.Caret.Offset - 1));
                    if (lastCharInserted == '\0')
                    {
                        return(retval);
                    }

                    stateTracker.UpdateEngine();

                    if (key == Gdk.Key.Return && modifier == Gdk.ModifierType.ControlMask)
                    {
                        FixLineStart(textEditorData, stateTracker, textEditorData.Caret.Line + 1);
                    }
                    else
                    {
                        if (!(oldLine == textEditorData.Caret.Line + 1 && lastCharInserted == '\n') && (oldBufLen != textEditorData.Length || lastCharInserted != '\0'))
                        {
                            DoPostInsertionSmartIndent(lastCharInserted, hadSelection, out reIndent);
                        }
                    }
                    //reindent the line after the insertion, if needed
                    //N.B. if the engine says we need to reindent, make sure that it's because a char was
                    //inserted rather than just updating the stack due to moving around

                    stateTracker.UpdateEngine();
                    bool automaticReindent = (stateTracker.Engine.NeedsReindent && lastCharInserted != '\0');
                    if (reIndent || automaticReindent)
                    {
                        DoReSmartIndent();
                    }
                    if (!skipFormatting && keyChar == '}')
                    {
                        RunFormatter(new DocumentLocation(textEditorData.Caret.Location.Line, textEditorData.Caret.Location.Column - 1));
                    }
                }

                stateTracker.UpdateEngine();
                lastCharInserted = '\0';
                return(retval);
            }

            if (textEditorData.Options.IndentStyle == IndentStyle.Auto && DefaultSourceEditorOptions.Instance.TabIsReindent && key == Gdk.Key.Tab)
            {
                bool retval = base.KeyPress(key, keyChar, modifier);
                DoReSmartIndent();
                return(retval);
            }

            //pass through to the base class, which actually inserts the character
            //and calls HandleCodeCompletion etc to handles completion
            var result = base.KeyPress(key, keyChar, modifier);

            if (!skipFormatting && keyChar == '}')
            {
                RunFormatter(new DocumentLocation(textEditorData.Caret.Location.Line, textEditorData.Caret.Location.Column - 1));
            }
            return(result);
        }