diff --git a/src/AddIns/BackendBindings/CSharpBinding/Project/Src/FormattingStrategy/CSharpFormattingStrategy.cs b/src/AddIns/BackendBindings/CSharpBinding/Project/Src/FormattingStrategy/CSharpFormattingStrategy.cs index 16f8afe554..ee60a00e71 100644 --- a/src/AddIns/BackendBindings/CSharpBinding/Project/Src/FormattingStrategy/CSharpFormattingStrategy.cs +++ b/src/AddIns/BackendBindings/CSharpBinding/Project/Src/FormattingStrategy/CSharpFormattingStrategy.cs @@ -53,7 +53,6 @@ namespace CSharpBinding.FormattingStrategy } #endregion - /* #region Private functions bool NeedCurlyBracket(string text) { @@ -130,7 +129,7 @@ namespace CSharpBinding.FormattingStrategy } - bool IsInsideStringOrComment(TextArea textArea, LineSegment curLine, int cursorOffset) + bool IsInsideStringOrComment(ITextEditor textArea, IDocumentLine curLine, int cursorOffset) { // scan cur line if it is inside a string or single line comment (//) bool insideString = false; @@ -164,7 +163,7 @@ namespace CSharpBinding.FormattingStrategy return insideString; } - bool IsInsideDocumentationComment(TextArea textArea, LineSegment curLine, int cursorOffset) + bool IsInsideDocumentationComment(ITextEditor textArea, IDocumentLine curLine, int cursorOffset) { for (int i = curLine.Offset; i < cursorOffset; ++i) { char ch = textArea.Document.GetCharAt(i); @@ -246,8 +245,8 @@ namespace CSharpBinding.FormattingStrategy { int regions = 0; int endregions = 0; - foreach (LineSegment line in document.LineSegmentCollection) { - string text = document.GetText(line).Trim(); + for (int i = 1; i <= document.TotalNumberOfLines; i++) { + string text = document.GetLine(i).Text.Trim(); if (text.StartsWith("#region")) { ++regions; } else if (text.StartsWith("#endregion")) { @@ -256,26 +255,27 @@ namespace CSharpBinding.FormattingStrategy } return regions > endregions; } - public override void FormatLine(TextArea textArea, int lineNr, int cursorOffset, char ch) // used for comment tag formater/inserter + + public override void FormatLine(ITextEditor textArea, char ch) // used for comment tag formater/inserter { - textArea.Document.UndoStack.StartUndoGroup(); - FormatLineInternal(textArea, lineNr, cursorOffset, ch); - textArea.Document.UndoStack.EndUndoGroup(); + using (textArea.Document.OpenUndoGroup()) { + FormatLineInternal(textArea, textArea.Caret.Line, textArea.Caret.Offset, ch); + } } - void FormatLineInternal(TextArea textArea, int lineNr, int cursorOffset, char ch) + void FormatLineInternal(ITextEditor textArea, int lineNr, int cursorOffset, char ch) { - LineSegment curLine = textArea.Document.GetLineSegment(lineNr); - LineSegment lineAbove = lineNr > 0 ? textArea.Document.GetLineSegment(lineNr - 1) : null; - string terminator = textArea.TextEditorProperties.LineTerminator; + IDocumentLine curLine = textArea.Document.GetLine(lineNr); + IDocumentLine lineAbove = lineNr > 1 ? textArea.Document.GetLine(lineNr - 1) : null; + string terminator = DocumentUtilitites.GetLineTerminator(textArea.Document, lineNr); + string curLineText; //// local string for curLine segment - string curLineText=""; if (ch == '/') { - curLineText = textArea.Document.GetText(curLine); - string lineAboveText = lineAbove == null ? "" : textArea.Document.GetText(lineAbove); + curLineText = curLine.Text; + string lineAboveText = lineAbove == null ? "" : lineAbove.Text; if (curLineText != null && curLineText.EndsWith("///") && (lineAboveText == null || !lineAboveText.Trim().StartsWith("///"))) { - string indentation = base.GetIndentation(textArea, lineNr); + string indentation = DocumentUtilitites.GetIndentation(textArea.Document, curLine.Offset); object member = GetMemberAfter(textArea, lineNr); if (member != null) { StringBuilder sb = new StringBuilder(); @@ -306,8 +306,7 @@ namespace CSharpBinding.FormattingStrategy } textArea.Document.Insert(cursorOffset, sb.ToString()); - textArea.Refresh(); - textArea.Caret.Position = textArea.Document.OffsetToPosition(cursorOffset + indentation.Length + "/// ".Length + " ".Length + terminator.Length); + textArea.Caret.Offset = cursorOffset + indentation.Length + "/// ".Length + " ".Length + terminator.Length; } } return; @@ -321,7 +320,7 @@ namespace CSharpBinding.FormattingStrategy switch (ch) { case '>': if (IsInsideDocumentationComment(textArea, curLine, cursorOffset)) { - curLineText = textArea.Document.GetText(curLine); + curLineText = curLine.Text; int column = textArea.Caret.Offset - curLine.Offset; int index = Math.Min(column - 1, curLineText.Length - 1); @@ -351,29 +350,24 @@ namespace CSharpBinding.FormattingStrategy case ']': case '}': case '{': - if (textArea.Document.TextEditorProperties.IndentStyle == IndentStyle.Smart) { - textArea.Document.FormattingStrategy.IndentLine(textArea, lineNr); - } + //if (textArea.Document.TextEditorProperties.IndentStyle == IndentStyle.Smart) { + IndentLine(textArea, curLine); + //} break; case '\n': - string lineAboveText = lineAbove == null ? "" : textArea.Document.GetText(lineAbove); + string lineAboveText = lineAbove == null ? "" : lineAbove.Text; //// curLine might have some text which should be added to indentation - curLineText = ""; - if (curLine.Length > 0) { - curLineText = textArea.Document.GetText(curLine); - } - - LineSegment nextLine = lineNr + 1 < textArea.Document.TotalNumberOfLines ? textArea.Document.GetLineSegment(lineNr + 1) : null; - string nextLineText = lineNr + 1 < textArea.Document.TotalNumberOfLines ? textArea.Document.GetText(nextLine) : ""; + curLineText = curLine.Text; - int addCursorOffset = 0; - - if (lineAboveText.Trim().StartsWith("#region") && NeedEndregion(textArea.Document)) { - textArea.Document.Insert(curLine.Offset, "#endregion"); - textArea.Caret.Column = IndentLine(textArea, lineNr); + if (lineAbove != null && lineAbove.Text.Trim().StartsWith("#region") + && NeedEndregion(textArea.Document)) + { + textArea.Document.Insert(cursorOffset, "#endregion"); return; } + /* + int addCursorOffset = 0; if (lineAbove.HighlightSpanStack != null && !lineAbove.HighlightSpanStack.IsEmpty) { if (!lineAbove.HighlightSpanStack.Peek().StopEOL) { // case for /* style comments int index = lineAboveText.IndexOf("/*"); @@ -421,18 +415,18 @@ namespace CSharpBinding.FormattingStrategy addCursorOffset = 1; } } - } - int result = IndentLine(textArea, lineNr) + addCursorOffset; - if (textArea.TextEditorProperties.AutoInsertCurlyBracket) { - string oldLineText = TextUtilities.GetLineAsString(textArea.Document, lineNr - 1); + }*/ + if (textArea.Options.AutoInsertBlockEnd && lineAbove != null) { + string oldLineText = lineAbove.Text; if (oldLineText.EndsWith("{")) { - if (NeedCurlyBracket(textArea.Document.TextContent)) { - textArea.Document.Insert(curLine.Offset + curLine.Length, terminator + "}"); - IndentLine(textArea, lineNr + 1); + if (NeedCurlyBracket(textArea.Document.Text)) { + int insertionPoint = curLine.Offset + curLine.Length; + textArea.Document.Insert(insertionPoint, terminator + "}"); + IndentLine(textArea, textArea.Document.GetLine(lineNr + 1)); + textArea.Caret.Offset = insertionPoint; } } } - textArea.Caret.Column = result; return; } } @@ -549,6 +543,7 @@ namespace CSharpBinding.FormattingStrategy } #endregion + /* #region SearchBracketBackward public override int SearchBracketBackward(IDocument document, int offset, char openBracket, char closingBracket) { diff --git a/src/AddIns/DisplayBindings/AvalonEdit.AddIn/Src/CodeEditor.cs b/src/AddIns/DisplayBindings/AvalonEdit.AddIn/Src/CodeEditor.cs index 3ea22bcd88..d64657bcc1 100644 --- a/src/AddIns/DisplayBindings/AvalonEdit.AddIn/Src/CodeEditor.cs +++ b/src/AddIns/DisplayBindings/AvalonEdit.AddIn/Src/CodeEditor.cs @@ -146,7 +146,8 @@ namespace ICSharpCode.AvalonEdit.AddIn textEditor.Background = Brushes.White; textEditor.FontFamily = new FontFamily("Consolas"); textEditor.FontSize = 13; - textEditor.TextArea.TextEntered += TextArea_TextInput; + textEditor.TextArea.TextEntering += TextArea_TextEntering; + textEditor.TextArea.TextEntered += TextArea_TextEntered; textEditor.MouseHover += textEditor_MouseHover; textEditor.MouseHoverStopped += textEditor_MouseHoverStopped; textEditor.TextArea.Caret.PositionChanged += caret_PositionChanged; @@ -171,15 +172,13 @@ namespace ICSharpCode.AvalonEdit.AddIn void textEditor_TextArea_TextView_ContextMenuOpening(object sender, ContextMenuEventArgs e) { - ITextEditorComponent component = (ITextEditorComponent)sender; - ITextEditor adapter = (ITextEditor)component.GetService(typeof(ITextEditor)); + ITextEditor adapter = GetAdapterFromSender(sender); MenuService.CreateContextMenu(adapter, contextMenuPath).IsOpen = true; } void textEditor_TextArea_TextView_MouseRightButtonDown(object sender, MouseButtonEventArgs e) { - ITextEditorComponent component = (ITextEditorComponent)sender; - TextEditor textEditor = (TextEditor)component.GetService(typeof(TextEditor)); + TextEditor textEditor = GetTextEditorFromSender(sender); var position = textEditor.GetPositionFromPoint(e.GetPosition(textEditor)); if (position.HasValue) { textEditor.TextArea.Caret.Position = position.Value; @@ -305,15 +304,16 @@ namespace ICSharpCode.AvalonEdit.AddIn } } - void TextArea_TextInput(object sender, TextCompositionEventArgs e) + void TextArea_TextEntering(object sender, TextCompositionEventArgs e) { // don't start new code completion if there is still a completion window open if (completionWindow != null) return; - TextArea textArea = (TextArea)sender; - ITextEditor adapter = (ITextEditor)textArea.GetService(typeof(ITextEditor)); - Debug.Assert(adapter != null); + if (e.Handled) + return; + + ITextEditor adapter = GetAdapterFromSender(sender); foreach (char c in e.Text) { foreach (ICodeCompletionBinding cc in CodeCompletionBindings) { @@ -343,18 +343,35 @@ namespace ICSharpCode.AvalonEdit.AddIn } } - TextEditor GetTextEditorFromRoutedCommand(object sender) + void TextArea_TextEntered(object sender, TextCompositionEventArgs e) + { + if (e.Text.Length > 0 && !e.Handled) { + formattingStrategy.FormatLine(GetAdapterFromSender(sender), e.Text[0]); + } + } + + ITextEditor GetAdapterFromSender(object sender) + { + ITextEditorComponent textArea = (ITextEditorComponent)sender; + ITextEditor textEditor = (ITextEditor)textArea.GetService(typeof(ITextEditor)); + if (textEditor == null) + throw new InvalidOperationException("could not find TextEditor service"); + return textEditor; + } + + TextEditor GetTextEditorFromSender(object sender) { - TextArea textArea = (TextArea)sender; + ITextEditorComponent textArea = (ITextEditorComponent)sender; TextEditor textEditor = (TextEditor)textArea.GetService(typeof(TextEditor)); - Debug.Assert(textEditor != null); + if (textEditor == null) + throw new InvalidOperationException("could not find TextEditor service"); return textEditor; } void OnCodeCompletion(object sender, ExecutedRoutedEventArgs e) { CloseExistingCompletionWindows(); - TextEditor textEditor = GetTextEditorFromRoutedCommand(sender); + TextEditor textEditor = GetTextEditorFromSender(sender); foreach (ICodeCompletionBinding cc in CodeCompletionBindings) { if (cc.CtrlSpace(GetAdapter(textEditor))) { e.Handled = true; @@ -365,7 +382,7 @@ namespace ICSharpCode.AvalonEdit.AddIn void OnDeleteLine(object sender, ExecutedRoutedEventArgs e) { - TextEditor textEditor = GetTextEditorFromRoutedCommand(sender); + TextEditor textEditor = GetTextEditorFromSender(sender); e.Handled = true; using (textEditor.Document.RunUpdate()) { DocumentLine currentLine = textEditor.Document.GetLineByNumber(textEditor.TextArea.Caret.Line); diff --git a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Gui/EditingCommandHandler.cs b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Gui/EditingCommandHandler.cs index 915eec2f88..0a306d6398 100644 --- a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Gui/EditingCommandHandler.cs +++ b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Gui/EditingCommandHandler.cs @@ -73,17 +73,9 @@ namespace ICSharpCode.AvalonEdit.Gui static void OnEnter(object target, ExecutedRoutedEventArgs args) { TextArea textArea = GetTextArea(target); - if (textArea != null && textArea.Document != null) { - string newLine = NewLineFinder.GetNewLineFromDocument(textArea.Document, textArea.Caret.Line); - using (textArea.Document.RunUpdate()) { - textArea.ReplaceSelectionWithText(newLine); - if (textArea.IndentationStrategy != null) { - DocumentLine line = textArea.Document.GetLineByNumber(textArea.Caret.Line); - textArea.IndentationStrategy.IndentLine(line); - } - } - textArea.Caret.BringCaretToView(); - args.Handled = true; + if (textArea != null) { + TextComposition textComposition = new TextComposition(InputManager.Current, textArea, "\n"); + textArea.PerformTextInput(new TextCompositionEventArgs(Keyboard.PrimaryDevice, textComposition)); } } #endregion diff --git a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Gui/TextArea.cs b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Gui/TextArea.cs index c46fc10e04..cff3e201b8 100644 --- a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Gui/TextArea.cs +++ b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Gui/TextArea.cs @@ -597,8 +597,25 @@ namespace ICSharpCode.AvalonEdit /// This is like the event, /// but occurs immediately before the TextArea handles the TextInput event. /// + public event TextCompositionEventHandler TextEntering; + + /// + /// Occurs when the TextArea receives text input. + /// This is like the event, + /// but occurs immediately after the TextArea handles the TextInput event. + /// public event TextCompositionEventHandler TextEntered; + /// + /// Raises the TextEntering event. + /// + protected virtual void OnTextEntering(TextCompositionEventArgs e) + { + if (TextEntering != null) { + TextEntering(this, e); + } + } + /// /// Raises the TextEntered event. /// @@ -613,21 +630,48 @@ namespace ICSharpCode.AvalonEdit protected override void OnTextInput(TextCompositionEventArgs e) { base.OnTextInput(e); - if (!e.Handled) { + if (!e.Handled && this.Document != null) { if (e.Text == "\x1b") { // ASCII 0x1b = ESC. // WPF produces a TextInput event with that old ASCII control char // when Escape is pressed. We'll just ignore it. return; } - TextDocument document = this.Document; - if (document != null) { - OnTextEntered(e); - if (!e.Handled) { - ReplaceSelectionWithText(e.Text); - caret.BringCaretToView(); - e.Handled = true; - } + PerformTextInput(e); + e.Handled = true; + } + } + + /// + /// Runs text input. + /// This raises the event, replaces the selection with the text, + /// and then raises the event. + /// + public void PerformTextInput(TextCompositionEventArgs e) + { + if (e == null) + throw new ArgumentNullException("e"); + if (this.Document == null) + throw ThrowUtil.NoDocumentAssigned(); + OnTextEntering(e); + if (!e.Handled) { + if (e.Text == "\n" || e.Text == "\r\n") + ReplaceSelectionWithNewLine(); + else + ReplaceSelectionWithText(e.Text); + OnTextEntered(e); + caret.BringCaretToView(); + } + } + + void ReplaceSelectionWithNewLine() + { + string newLine = NewLineFinder.GetNewLineFromDocument(this.Document, this.Caret.Line); + using (this.Document.RunUpdate()) { + ReplaceSelectionWithText(newLine); + if (this.IndentationStrategy != null) { + DocumentLine line = this.Document.GetLineByNumber(this.Caret.Line); + this.IndentationStrategy.IndentLine(line); } } } diff --git a/src/Main/Base/Project/Src/Editor/AvalonEdit/AvalonEditDocumentAdapter.cs b/src/Main/Base/Project/Src/Editor/AvalonEdit/AvalonEditDocumentAdapter.cs index 49424b947e..9fe935be2a 100644 --- a/src/Main/Base/Project/Src/Editor/AvalonEdit/AvalonEditDocumentAdapter.cs +++ b/src/Main/Base/Project/Src/Editor/AvalonEdit/AvalonEditDocumentAdapter.cs @@ -55,6 +55,10 @@ namespace ICSharpCode.SharpDevelop.Editor get { return line.TotalLength; } } + public int DelimiterLength { + get { return line.DelimiterLength; } + } + public int LineNumber { get { return line.LineNumber; } } diff --git a/src/Main/Base/Project/Src/Editor/AvalonEdit/AvalonEditTextEditorAdapter.cs b/src/Main/Base/Project/Src/Editor/AvalonEdit/AvalonEditTextEditorAdapter.cs index 3a79341b49..882950dc7f 100644 --- a/src/Main/Base/Project/Src/Editor/AvalonEdit/AvalonEditTextEditorAdapter.cs +++ b/src/Main/Base/Project/Src/Editor/AvalonEdit/AvalonEditTextEditorAdapter.cs @@ -113,6 +113,12 @@ namespace ICSharpCode.SharpDevelop.Editor return avalonEditOptions.IndentationString; } } + + public bool AutoInsertBlockEnd { + get { + return true; + } + } } public virtual string FileName { diff --git a/src/Main/Base/Project/Src/Editor/DocumentUtilitites.cs b/src/Main/Base/Project/Src/Editor/DocumentUtilitites.cs index 590efe75fa..4d2333dcb6 100644 --- a/src/Main/Base/Project/Src/Editor/DocumentUtilitites.cs +++ b/src/Main/Base/Project/Src/Editor/DocumentUtilitites.cs @@ -109,6 +109,22 @@ namespace ICSharpCode.SharpDevelop.Editor return document.GetText(segment.Offset, segment.Length); } + /// + /// Gets the line terminator for the document around the specified line number. + /// + public static string GetLineTerminator(IDocument document, int lineNumber) + { + IDocumentLine line = document.GetLine(lineNumber); + if (line.DelimiterLength == 0) { + // at the end of the document, there's no line delimiter, so use the delimiter + // from the previous line + if (lineNumber == 1) + return Environment.NewLine; + line = document.GetLine(lineNumber - 1); + } + return document.GetText(line.Offset + line.Length, line.DelimiterLength); + } + #region ITextSource implementation public static ICSharpCode.AvalonEdit.Document.ITextSource GetTextSource(IDocument document) { diff --git a/src/Main/Base/Project/Src/Editor/IFormattingStrategy.cs b/src/Main/Base/Project/Src/Editor/IFormattingStrategy.cs index 3552bcf075..0d11f448f3 100644 --- a/src/Main/Base/Project/Src/Editor/IFormattingStrategy.cs +++ b/src/Main/Base/Project/Src/Editor/IFormattingStrategy.cs @@ -10,7 +10,7 @@ using System; namespace ICSharpCode.SharpDevelop.Editor { /// - /// Indentation strategy. + /// Indentation and formatting strategy. /// public interface IFormattingStrategy { diff --git a/src/Main/Base/Project/Src/Editor/ITextEditor.cs b/src/Main/Base/Project/Src/Editor/ITextEditor.cs index ccee44dd78..8d079db8fb 100644 --- a/src/Main/Base/Project/Src/Editor/ITextEditor.cs +++ b/src/Main/Base/Project/Src/Editor/ITextEditor.cs @@ -88,6 +88,11 @@ namespace ICSharpCode.SharpDevelop.Editor /// Gets the text used for one indentation level. /// string IndentationString { get; } + + /// + /// Gets whether a '}' should automatically be inserted when a block is opened. + /// + bool AutoInsertBlockEnd { get; } } public interface ITextEditorCaret diff --git a/src/Main/Base/Project/Src/Services/RefactoringService/TextEditorDocument.cs b/src/Main/Base/Project/Src/Services/RefactoringService/TextEditorDocument.cs index 5c4d5dff63..99abd62341 100644 --- a/src/Main/Base/Project/Src/Services/RefactoringService/TextEditorDocument.cs +++ b/src/Main/Base/Project/Src/Services/RefactoringService/TextEditorDocument.cs @@ -54,6 +54,10 @@ namespace ICSharpCode.SharpDevelop.Refactoring get { return line.TotalLength; } } + public int DelimiterLength { + get { return line.DelimiterLength; } + } + public int LineNumber { get { return line.LineNumber + 1; } } diff --git a/src/Main/Base/Project/Src/TextEditor/Gui/TextEditorAdapter.cs b/src/Main/Base/Project/Src/TextEditor/Gui/TextEditorAdapter.cs index a901937fe4..7044150653 100644 --- a/src/Main/Base/Project/Src/TextEditor/Gui/TextEditorAdapter.cs +++ b/src/Main/Base/Project/Src/TextEditor/Gui/TextEditorAdapter.cs @@ -90,6 +90,12 @@ namespace ICSharpCode.SharpDevelop return properties.ConvertTabsToSpaces ? new string(' ', properties.IndentationSize) : "\t"; } } + + public bool AutoInsertBlockEnd { + get { + return true; + } + } } static ICSharpCode.NRefactory.Location ToLocation(TextLocation position) diff --git a/src/Main/ICSharpCode.SharpDevelop.Dom/Project/Src/Refactoring/IDocument.cs b/src/Main/ICSharpCode.SharpDevelop.Dom/Project/Src/Refactoring/IDocument.cs index dc5ac923d7..0fc2adeea1 100644 --- a/src/Main/ICSharpCode.SharpDevelop.Dom/Project/Src/Refactoring/IDocument.cs +++ b/src/Main/ICSharpCode.SharpDevelop.Dom/Project/Src/Refactoring/IDocument.cs @@ -119,6 +119,12 @@ namespace ICSharpCode.SharpDevelop.Editor /// int TotalLength { get; } + /// + /// Gets the length of the line terminator. + /// Returns 1 or 2; or 0 at the end of the document. + /// + int DelimiterLength { get; } + /// /// Gets the number of this line. /// The first line has the number 1.