public static bool GuessSemicolonInsertionOffset(IReadonlyTextDocument data, MonoDevelop.Core.Text.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 ICSharpCode.NRefactory.CSharp.Completion.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(TextEditor textEditorData, ICSharpCode.NRefactory6.CSharp.IStateMachineIndentEngine stateTracker, int lineNumber) { if (lineNumber > 1) { var line = textEditorData.GetLine(lineNumber); if (line == null) { return(false); } var prevLine = textEditorData.GetLine(lineNumber - 1); if (prevLine == null) { return(false); } string trimmedPreviousLine = textEditorData.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.GetLine(lineNumber + 1); string nextLine = nextLineSegment != null?textEditorData.GetTextAt(nextLineSegment).TrimStart() : ""; if (trimmedPreviousLine.Length > "///".Length || nextLine.StartsWith("///", StringComparison.Ordinal)) { var insertionPoint = textEditorData.CaretOffset; textEditorData.InsertText(insertionPoint, "/// "); textEditorData.CaretOffset = insertionPoint + "/// ".Length; 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).Length; var insertedText = prevLine.GetIndentation(textEditorData) + commentPrefix; textEditorData.ReplaceText(line.Offset, indentSize, insertedText); textEditorData.CaretOffset = line.Offset + insertedText.Length; return(true); } else if (wasInStringLiteral) { var lexer = new ICSharpCode.NRefactory.CSharp.Completion.CSharpCompletionEngineBase.MiniLexer(textEditorData.GetTextAt(0, prevLine.EndOffset).TrimEnd()); lexer.Parse(); if (!lexer.IsInString) { return(false); } textEditorData.EnsureCaretIsNotVirtual(); var insertedText = "\" +"; textEditorData.InsertText(prevLine.Offset + prevLine.Length, insertedText); var lineOffset = line.Offset + insertedText.Length; int indentSize = textEditorData.CaretOffset - lineOffset; insertedText = prevLine.GetIndentation(textEditorData) + (trimmedPreviousLine.StartsWith("\"", StringComparison.Ordinal) ? "" : "\t") + "\""; textEditorData.ReplaceText(lineOffset, indentSize, insertedText); return(true); } } return(false); }
public override bool KeyPress(KeyDescriptor descriptor) { completionWindowWasVisible = CompletionWindowManager.IsVisible; cursorPositionBeforeKeyPress = Editor.CaretOffset; bool isSomethingSelected = Editor.IsSomethingSelected; if (descriptor.SpecialKey == SpecialKey.BackSpace && Editor.CaretOffset == lastInsertedSemicolon) { EditActions.Undo(Editor); lastInsertedSemicolon = -1; return(false); } lastInsertedSemicolon = -1; if (descriptor.KeyChar == ';' && Editor.EditMode == EditMode.Edit && !DoInsertTemplate() && !isSomethingSelected && PropertyService.Get( "SmartSemicolonPlacement", false ) && !(stateTracker.IsInsideComment || stateTracker.IsInsideString)) { bool retval = base.KeyPress(descriptor); var curLine = Editor.GetLine(Editor.CaretLine); string text = Editor.GetTextAt(curLine); if (!(text.EndsWith(";", StringComparison.Ordinal) || text.Trim().StartsWith("for", StringComparison.Ordinal))) { int guessedOffset; if (GuessSemicolonInsertionOffset(Editor, curLine, Editor.CaretOffset, out guessedOffset)) { using (var undo = Editor.OpenUndoGroup()) { Editor.RemoveText(Editor.CaretOffset - 1, 1); Editor.CaretOffset = guessedOffset; lastInsertedSemicolon = Editor.CaretOffset + 1; retval = base.KeyPress(descriptor); } } } return(retval); } if (descriptor.SpecialKey == SpecialKey.Tab && descriptor.ModifierKeys == ModifierKeys.None && !CompletionWindowManager.IsVisible) { SafeUpdateIndentEngine(Editor.CaretOffset); if (stateTracker.IsInsideStringLiteral && !Editor.IsSomethingSelected) { var lexer = new ICSharpCode.NRefactory.CSharp.Completion.CSharpCompletionEngineBase.MiniLexer(Editor.GetTextAt(0, Editor.CaretOffset)); lexer.Parse(); if (lexer.IsInString) { Editor.InsertAtCaret("\\t"); return(false); } } } if (descriptor.SpecialKey == SpecialKey.Tab && DefaultSourceEditorOptions.Instance.TabIsReindent && !CompletionWindowManager.IsVisible && Editor.EditMode == EditMode.Edit && !DoInsertTemplate() && !isSomethingSelected) { ReindentOnTab(); return(false); } SafeUpdateIndentEngine(Editor.CaretOffset); if (!stateTracker.IsInsideOrdinaryCommentOrString) { if (descriptor.KeyChar == '@') { var retval = base.KeyPress(descriptor); int cursor = Editor.CaretOffset; if (cursor < Editor.Length && Editor.GetCharAt(cursor) == '"') { ConvertNormalToVerbatimString(Editor, cursor + 1); } return(retval); } } //do the smart indent if (!indentationDisabled) { bool retval; //capture some of the current state int oldBufLen = Editor.Length; int oldLine = Editor.CaretLine + 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 = Editor.OpenUndoGroup()) { DoPreInsertionSmartIndent(descriptor.SpecialKey); } wasInStringLiteral = stateTracker.IsInsideStringLiteral; bool returnBetweenBraces = descriptor.SpecialKey == SpecialKey.Return && descriptor.ModifierKeys == ModifierKeys.None && Editor.CaretOffset > 0 && Editor.CaretOffset < Editor.Length && Editor.GetCharAt(Editor.CaretOffset - 1) == '{' && Editor.GetCharAt(Editor.CaretOffset) == '}' && !stateTracker.IsInsideOrdinaryCommentOrString; 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(descriptor); if (descriptor.KeyChar == '/' && stateTracker.IsInsideMultiLineComment) { if (Editor.CaretOffset - 3 >= 0 && Editor.GetCharAt(Editor.CaretOffset - 3) == '*' && Editor.GetCharAt(Editor.CaretOffset - 2) == ' ') { using (var undo = Editor.OpenUndoGroup()) { Editor.RemoveText(Editor.CaretOffset - 2, 1); } } } //handle inserted characters if (Editor.CaretOffset <= 0 || Editor.IsSomethingSelected) { return(retval); } lastCharInserted = TranslateKeyCharForIndenter(descriptor.SpecialKey, descriptor.KeyChar, Editor.GetCharAt(Editor.CaretOffset - 1)); if (lastCharInserted == '\0') { return(retval); } using (var undo = Editor.OpenUndoGroup()) { if (returnBetweenBraces) { var oldOffset = Editor.CaretOffset; Editor.InsertAtCaret(Editor.EolMarker); DoReSmartIndent(); Editor.CaretOffset = oldOffset; } SafeUpdateIndentEngine(Editor.CaretOffset); if (descriptor.SpecialKey == SpecialKey.Return && descriptor.ModifierKeys == ModifierKeys.Control) { FixLineStart(Editor, stateTracker, Editor.CaretLine + 1); } else { if (!(oldLine == Editor.CaretLine + 1 && lastCharInserted == '\n') && (oldBufLen != Editor.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(Editor.CaretOffset); // Automatically reindent in text link mode will cause the mode exiting, therefore we need to prevent that. automaticReindent = (stateTracker.NeedsReindent && lastCharInserted != '\0') && Editor.EditMode == EditMode.Edit; if (descriptor.SpecialKey == SpecialKey.Return && (reIndent || automaticReindent)) { if (Editor.Options.IndentStyle == IndentStyle.Virtual) { if (Editor.GetLine(Editor.CaretLine).Length == 0) { Editor.CaretColumn = Editor.GetVirtualIndentationColumn(Editor.CaretLine); } } else { DoReSmartIndent(); } } } SafeUpdateIndentEngine(Editor.CaretOffset); lastCharInserted = '\0'; CheckXmlCommentCloseTag(descriptor.KeyChar); return(retval); } if (Editor.Options.IndentStyle == IndentStyle.Auto && DefaultSourceEditorOptions.Instance.TabIsReindent && descriptor.SpecialKey == SpecialKey.Tab) { bool retval = base.KeyPress(descriptor); DoReSmartIndent(); CheckXmlCommentCloseTag(descriptor.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(descriptor); CheckXmlCommentCloseTag(descriptor.KeyChar); return(result); }
public bool FixLineStart (TextEditor textEditorData, ICSharpCode.NRefactory6.CSharp.IStateMachineIndentEngine stateTracker, int lineNumber) { if (lineNumber > 1) { var line = textEditorData.GetLine (lineNumber); if (line == null) return false; var prevLine = textEditorData.GetLine (lineNumber - 1); if (prevLine == null) return false; string trimmedPreviousLine = textEditorData.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.GetLine (lineNumber + 1); string nextLine = nextLineSegment != null ? textEditorData.GetTextAt (nextLineSegment).TrimStart () : ""; if (trimmedPreviousLine.Length > "///".Length || nextLine.StartsWith ("///", StringComparison.Ordinal)) { var insertionPoint = textEditorData.CaretOffset; textEditorData.InsertText (insertionPoint, "/// "); textEditorData.CaretOffset = insertionPoint + "/// ".Length; 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).Length; var insertedText = prevLine.GetIndentation (textEditorData) + commentPrefix; textEditorData.ReplaceText (line.Offset, indentSize, insertedText); textEditorData.CaretOffset = line.Offset + insertedText.Length; return true; } else if (wasInStringLiteral) { var lexer = new ICSharpCode.NRefactory.CSharp.Completion.CSharpCompletionEngineBase.MiniLexer (textEditorData.GetTextAt (0, prevLine.EndOffset).TrimEnd ()); lexer.Parse (); if (!lexer.IsInString) return false; textEditorData.EnsureCaretIsNotVirtual (); textEditorData.InsertText (prevLine.Offset + prevLine.Length, "\" +"); int indentSize = textEditorData.CaretOffset - line.Offset; var insertedText = prevLine.GetIndentation (textEditorData) + (trimmedPreviousLine.StartsWith ("\"", StringComparison.Ordinal) ? "" : "\t") + "\""; textEditorData.ReplaceText (line.Offset, indentSize, insertedText); return true; } } return false; }
public static bool GuessSemicolonInsertionOffset (IReadonlyTextDocument data, MonoDevelop.Core.Text.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 ICSharpCode.NRefactory.CSharp.Completion.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 override bool KeyPress (KeyDescriptor descriptor) { completionWindowWasVisible = CompletionWindowManager.IsVisible; cursorPositionBeforeKeyPress = Editor.CaretOffset; bool isSomethingSelected = Editor.IsSomethingSelected; if (descriptor.SpecialKey == SpecialKey.BackSpace && Editor.CaretOffset == lastInsertedSemicolon) { EditActions.Undo (Editor); lastInsertedSemicolon = -1; return false; } lastInsertedSemicolon = -1; if (descriptor.KeyChar == ';' && Editor.EditMode == EditMode.Edit && !DoInsertTemplate () && !isSomethingSelected && PropertyService.Get ( "SmartSemicolonPlacement", false ) && !(stateTracker.IsInsideComment || stateTracker.IsInsideString)) { bool retval = base.KeyPress (descriptor); var curLine = Editor.GetLine (Editor.CaretLine); string text = Editor.GetTextAt (curLine); if (!(text.EndsWith (";", StringComparison.Ordinal) || text.Trim ().StartsWith ("for", StringComparison.Ordinal))) { int guessedOffset; if (GuessSemicolonInsertionOffset (Editor, curLine, Editor.CaretOffset, out guessedOffset)) { using (var undo = Editor.OpenUndoGroup ()) { Editor.RemoveText (Editor.CaretOffset - 1, 1); Editor.CaretOffset = guessedOffset; lastInsertedSemicolon = Editor.CaretOffset + 1; retval = base.KeyPress (descriptor); } } } using (var undo = Editor.OpenUndoGroup ()) { if (OnTheFlyFormatting && Editor != null && Editor.EditMode == EditMode.Edit) { OnTheFlyFormatter.FormatStatmentAt (Editor, DocumentContext, Editor.CaretLocation, optionSet: optionSet); } } return retval; } if (descriptor.SpecialKey == SpecialKey.Tab && descriptor.ModifierKeys == ModifierKeys.None && !CompletionWindowManager.IsVisible) { SafeUpdateIndentEngine (Editor.CaretOffset); if (stateTracker.IsInsideStringLiteral && !Editor.IsSomethingSelected) { var lexer = new ICSharpCode.NRefactory.CSharp.Completion.CSharpCompletionEngineBase.MiniLexer (Editor.GetTextAt (0, Editor.CaretOffset)); lexer.Parse (); if (lexer.IsInString) { Editor.InsertAtCaret ("\\t"); return false; } } } if (descriptor.SpecialKey == SpecialKey.Tab && DefaultSourceEditorOptions.Instance.TabIsReindent && !CompletionWindowManager.IsVisible && Editor.EditMode == EditMode.Edit && !DoInsertTemplate () && !isSomethingSelected) { ReindentOnTab (); return false; } SafeUpdateIndentEngine (Editor.CaretOffset); if (!stateTracker.IsInsideOrdinaryCommentOrString) { if (descriptor.KeyChar == '@') { var retval = base.KeyPress (descriptor); int cursor = Editor.CaretOffset; if (cursor < Editor.Length && Editor.GetCharAt (cursor) == '"') ConvertNormalToVerbatimString (Editor, cursor + 1); return retval; } } //do the smart indent if (!indentationDisabled) { bool retval; //capture some of the current state int oldBufLen = Editor.Length; int oldLine = Editor.CaretLine + 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 = Editor.OpenUndoGroup ()) { DoPreInsertionSmartIndent (descriptor.SpecialKey); } 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 (descriptor); //handle inserted characters if (Editor.CaretOffset <= 0 || Editor.IsSomethingSelected) return retval; lastCharInserted = TranslateKeyCharForIndenter (descriptor.SpecialKey, descriptor.KeyChar, Editor.GetCharAt (Editor.CaretOffset - 1)); if (lastCharInserted == '\0') return retval; using (var undo = Editor.OpenUndoGroup ()) { SafeUpdateIndentEngine (Editor.CaretOffset); if (descriptor.SpecialKey == SpecialKey.Return && descriptor.ModifierKeys == ModifierKeys.Control) { FixLineStart (Editor, stateTracker, Editor.CaretLine + 1); } else { if (!(oldLine == Editor.CaretLine + 1 && lastCharInserted == '\n') && (oldBufLen != Editor.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 (Editor.CaretOffset); // Automatically reindent in text link mode will cause the mode exiting, therefore we need to prevent that. automaticReindent = (stateTracker.NeedsReindent && lastCharInserted != '\0') && Editor.EditMode == EditMode.Edit; if (descriptor.SpecialKey == SpecialKey.Return && (reIndent || automaticReindent)) { if (Editor.Options.IndentStyle == IndentStyle.Virtual) { if (Editor.GetLine (Editor.CaretLine).Length == 0) Editor.CaretColumn = Editor.GetVirtualIndentationColumn (Editor.CaretLine); } else { DoReSmartIndent (); } } } const string reindentChars = ";){}"; if (reIndent || descriptor.SpecialKey != SpecialKey.Return && descriptor.SpecialKey != SpecialKey.Tab && automaticReindent && reindentChars.Contains (descriptor.KeyChar)) { using (var undo = Editor.OpenUndoGroup ()) { DoReSmartIndent (); } } HandleOnTheFlyFormatting (descriptor); SafeUpdateIndentEngine (Editor.CaretOffset); lastCharInserted = '\0'; CheckXmlCommentCloseTag (descriptor.KeyChar); return retval; } if (Editor.Options.IndentStyle == IndentStyle.Auto && DefaultSourceEditorOptions.Instance.TabIsReindent && descriptor.SpecialKey == SpecialKey.Tab) { bool retval = base.KeyPress (descriptor); DoReSmartIndent (); CheckXmlCommentCloseTag (descriptor.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 (descriptor); if (!indentationDisabled && (descriptor.SpecialKey == SpecialKey.Return)) { DoReSmartIndent (); } CheckXmlCommentCloseTag (descriptor.KeyChar); HandleOnTheFlyFormatting (descriptor); return result; }