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; }
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; }
static string BuildStub (MonoDevelop.Ide.Gui.Document data, CSharpCompletionTextEditorExtension.TypeSystemTreeSegment seg, 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 (Math.Max (0, 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 (); }
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; }