Browse Source

Implemented the commands in the 'Edit>Format' menu for AvalonEdit.

Allow using 'command' attribute on <MenuItem> with custom routed commands defined in AddIns.
Implemented offset mapping in AvalonEdit. This allows replacing text in the document without removing all text markers from the replaced region.

git-svn-id: svn://svn.sharpdevelop.net/sharpdevelop/trunk@4191 1ccf3a8d-04fe-1044-b7c0-cef0b8235c61
shortcuts
Daniel Grunwald 16 years ago
parent
commit
2db5ccb19e
  1. 61
      AddIns/ICSharpCode.SharpDevelop.addin
  2. 53
      src/AddIns/DisplayBindings/AvalonEdit.AddIn/AvalonEdit.AddIn.addin
  3. 13
      src/AddIns/DisplayBindings/AvalonEdit.AddIn/Src/CodeEditor.cs
  4. 8
      src/AddIns/DisplayBindings/AvalonEdit.AddIn/Src/CustomCommands.cs
  5. 91
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/AvalonEditCommands.cs
  6. 47
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Document/DocumentChangeEventArgs.cs
  7. 8
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Document/DocumentChangeOperation.cs
  8. 177
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Document/OffsetChangeMap.cs
  9. 158
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Document/TextDocument.cs
  10. 6
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Document/TextSegmentCollection.cs
  11. 3
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Document/UndoStack.cs
  12. 273
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Editing/EditingCommandHandler.cs
  13. 2
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Editing/TextArea.cs
  14. 2
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/ICSharpCode.AvalonEdit.csproj
  15. 4
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Indentation/DefaultIndentationStrategy.cs
  16. 73
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Utils/TextUtilities.cs
  17. 1
      src/Main/Base/Project/Src/Commands/NavigationCommands.cs
  18. 2
      src/Main/Base/Project/Src/Editor/DocumentUtilitites.cs
  19. 1
      src/Main/Base/Project/Src/Gui/WorkbenchSingleton.cs
  20. 19
      src/Main/Core/Project/Src/AddInTree/AddIn/AddIn.cs
  21. 4
      src/Main/Core/Project/Src/AddInTree/AddIn/Runtime.cs
  22. 2
      src/Main/ICSharpCode.Core.Presentation/Menu/MenuCommand.cs
  23. 65
      src/Main/ICSharpCode.Core.Presentation/Menu/MenuService.cs

61
AddIns/ICSharpCode.SharpDevelop.addin

@ -48,7 +48,6 @@ @@ -48,7 +48,6 @@
<Doozer name="Directory" class="ICSharpCode.SharpDevelop.DirectoryDoozer"/>
<Doozer name="TaskBoundAdditionalLogger" class="ICSharpCode.SharpDevelop.Project.TaskBoundAdditionalLoggerDoozer"/>
</Import>
<Import assembly=":ICSharpCode.TextEditor"/>
</Runtime>
<Path name = "/SharpDevelop/Workbench/Ambiences">
@ -1645,7 +1644,7 @@ @@ -1645,7 +1644,7 @@
<MenuItem id = "SplitView"
label = "${res:XML.MainMenu.WindowMenu.Split}"
icon = "Icons.16x16.SplitWindow"
command = "SplitView"/>
command = "ICSharpCode.SharpDevelop.SharpDevelopRoutedCommands.SplitView"/>
<MenuItem id = "CloseAll"
label = "${res:XML.MainMenu.FileMenu.CloseAll}"
icon = "Icons.16x16.CloseAllDocuments"
@ -1855,60 +1854,10 @@ @@ -1855,60 +1854,10 @@
</Path>
<Path name = "/SharpDevelop/Workbench/MainMenu/Edit">
<MenuItem insertafter = "Delete" insertbefore = "SelectAll" id = "Separator2" type = "Separator" />
<MenuItem insertafter = "Separator2" insertbefore = "Separator3" id = "Format" label = "${res:XML.MainMenu.EditMenu.FormatMenu}" type="Menu">
<Condition name = "WindowActive" activewindow="ICSharpCode.SharpDevelop.DefaultEditor.Gui.Editor.ITextEditorControlProvider" action="Disable">
<MenuItem id = "RemoveLeadingWs"
label = "${res:XML.MainMenu.EditMenu.FormatMenu.RlWs}"
class ="ICSharpCode.SharpDevelop.DefaultEditor.Commands.RemoveLeadingWS"/>
<MenuItem id = "RemoveTrailingWs"
label = "${res:XML.MainMenu.EditMenu.FormatMenu.RtWs}"
class ="ICSharpCode.SharpDevelop.DefaultEditor.Commands.RemoveTrailingWS"/>
<MenuItem id = "Seperator1" type = "Separator" />
<MenuItem id = "UpperCase"
label = "${res:XML.MainMenu.EditMenu.FormatMenu.UpperCase}"
icon = "Icons.16x16.LowerToUpperCase"
class ="ICSharpCode.SharpDevelop.DefaultEditor.Commands.ToUpperCase"/>
<MenuItem id = "LowerCase"
label = "${res:XML.MainMenu.EditMenu.FormatMenu.LowerCase}"
icon = "Icons.16x16.UpperToLowerCase"
class ="ICSharpCode.SharpDevelop.DefaultEditor.Commands.ToLowerCase"/>
<MenuItem id = "Capitalize"
label = "${res:XML.MainMenu.EditMenu.FormatMenu.Capitalize}"
class ="ICSharpCode.SharpDevelop.DefaultEditor.Commands.CapitalizeAction"/>
<MenuItem id = "InvertCase"
label = "${res:XML.MainMenu.EditMenu.FormatMenu.InvertCase}"
class ="ICSharpCode.SharpDevelop.DefaultEditor.Commands.InvertCaseAction"/>
<MenuItem id = "Separator2" type = "Separator" />
<MenuItem id = "SortSelection"
label = "${res:XML.MainMenu.EditMenu.FormatMenu.SortLines}"
class ="ICSharpCode.SharpDevelop.DefaultEditor.Commands.SortSelection"/>
<MenuItem id = "Separator3" type = "Separator" />
<MenuItem id = "Tabs2Spaces"
label = "${res:XML.MainMenu.EditMenu.FormatMenu.Tab2Space}"
class ="ICSharpCode.SharpDevelop.DefaultEditor.Commands.ConvertTabsToSpaces"/>
<MenuItem id = "Spaces2Tabs"
label = "${res:XML.MainMenu.EditMenu.FormatMenu.Space2Tab}"
class ="ICSharpCode.SharpDevelop.DefaultEditor.Commands.ConvertSpacesToTabs"/>
<MenuItem id = "LeadingTabs2Spaces"
label = "${res:XML.MainMenu.EditMenu.FormatMenu.LdTab2Space}"
class ="ICSharpCode.SharpDevelop.DefaultEditor.Commands.ConvertLeadingTabsToSpaces"/>
<MenuItem id = "LeadingSpaces2Tabs"
label = "${res:XML.MainMenu.EditMenu.FormatMenu.LdSpace2Tab}"
class ="ICSharpCode.SharpDevelop.DefaultEditor.Commands.ConvertLeadingSpacesToTabs"/>
<MenuItem id = "Separator4" type = "Separator" />
<MenuItem id = "Comment"
icon = "Icons.16x16.CommentRegion"
label = "${res:XML.TextAreaContextMenu.CommentUncommentSelection}"
class= "ICSharpCode.SharpDevelop.DefaultEditor.Commands.CommentRegion"/>
<MenuItem id = "Indent"
label = "${res:XML.TextAreaContextMenu.Indent}"
shortcut = "Control|I"
class = "ICSharpCode.SharpDevelop.DefaultEditor.Commands.IndentSelection" />
</Condition>
<MenuItem insertafter = "Delete" id = "Separator2" type = "Separator" />
<MenuItem id = "Format" label = "${res:XML.MainMenu.EditMenu.FormatMenu}" type="Menu">
</MenuItem>
<MenuItem insertafter = "Format" insertbefore = "Separator3" id = "Folding" label = "${res:XML.MainMenu.EditMenu.FoldingMenu}" type="Menu">
<MenuItem id = "Folding" label = "${res:XML.MainMenu.EditMenu.FoldingMenu}" type="Menu">
<Condition name = "WindowActive" activewindow="ICSharpCode.SharpDevelop.DefaultEditor.Gui.Editor.ITextEditorControlProvider" action="Disable">
<MenuItem id = "ToggleFolding"
label = "${res:XML.MainMenu.EditMenu.FoldingMenu.ToggleFolding}"
@ -1924,7 +1873,7 @@ @@ -1924,7 +1873,7 @@
class = "ICSharpCode.SharpDevelop.DefaultEditor.Commands.ShowDefinitionsOnly"/>
</Condition>
</MenuItem>
<MenuItem insertafter = "Separator2" insertbefore = "SelectAll" id = "Separator3" type = "Separator" />
<MenuItem insertbefore = "SelectAll" id = "Separator3" type = "Separator" />
</Path>
<Path name = "/SharpDevelop/ViewContent/DefaultTextEditor/OptionsDialog">

53
src/AddIns/DisplayBindings/AvalonEdit.AddIn/AvalonEdit.AddIn.addin

@ -8,6 +8,7 @@ @@ -8,6 +8,7 @@
</Manifest>
<Runtime>
<Import assembly = ":ICSharpCode.AvalonEdit"/>
<Import assembly = "ICSharpCode.AvalonEdit.AddIn.dll"/>
</Runtime>
@ -20,4 +21,56 @@ @@ -20,4 +21,56 @@
<Path name = "/SharpDevelop/ViewContent/AvalonEdit/ContextMenu">
<Include id="DefaultEditor" path="/SharpDevelop/ViewContent/TextEditor/ContextMenu"/>
</Path>
<Path name = "/SharpDevelop/Workbench/MainMenu/Edit/Format">
<Condition name = "WindowActive" activewindow="ICSharpCode.SharpDevelop.Editor.ITextEditorProvider" action="Disable">
<MenuItem id = "RemoveLeadingWs"
label = "${res:XML.MainMenu.EditMenu.FormatMenu.RlWs}"
command = "ICSharpCode.AvalonEdit.AvalonEditCommands.RemoveLeadingWhitespace"/>
<MenuItem id = "RemoveTrailingWs"
label = "${res:XML.MainMenu.EditMenu.FormatMenu.RtWs}"
command = "ICSharpCode.AvalonEdit.AvalonEditCommands.RemoveTrailingWhitespace"/>
<MenuItem id = "Seperator1" type = "Separator" />
<MenuItem id = "UpperCase"
label = "${res:XML.MainMenu.EditMenu.FormatMenu.UpperCase}"
icon = "Icons.16x16.LowerToUpperCase"
command = "ICSharpCode.AvalonEdit.AvalonEditCommands.ConvertToUppercase"/>
<MenuItem id = "LowerCase"
label = "${res:XML.MainMenu.EditMenu.FormatMenu.LowerCase}"
icon = "Icons.16x16.UpperToLowerCase"
command = "ICSharpCode.AvalonEdit.AvalonEditCommands.ConvertToLowercase"/>
<MenuItem id = "Capitalize"
label = "${res:XML.MainMenu.EditMenu.FormatMenu.Capitalize}"
command = "ICSharpCode.AvalonEdit.AvalonEditCommands.ConvertToTitleCase"/>
<MenuItem id = "InvertCase"
label = "${res:XML.MainMenu.EditMenu.FormatMenu.InvertCase}"
command = "ICSharpCode.AvalonEdit.AvalonEditCommands.InvertCase"/>
<MenuItem id = "Separator2" type = "Separator" />
<MenuItem id = "SortSelection"
label = "${res:XML.MainMenu.EditMenu.FormatMenu.SortLines}"
class ="ICSharpCode.SharpDevelop.Editor.Commands.SortSelection"/>
<MenuItem id = "Separator3" type = "Separator" />
<MenuItem id = "Tabs2Spaces"
label = "${res:XML.MainMenu.EditMenu.FormatMenu.Tab2Space}"
command = "ICSharpCode.AvalonEdit.AvalonEditCommands.ConvertTabsToSpaces"/>
<MenuItem id = "Spaces2Tabs"
label = "${res:XML.MainMenu.EditMenu.FormatMenu.Space2Tab}"
command = "ICSharpCode.AvalonEdit.AvalonEditCommands.ConvertSpacesToTabs"/>
<MenuItem id = "LeadingTabs2Spaces"
label = "${res:XML.MainMenu.EditMenu.FormatMenu.LdTab2Space}"
command = "ICSharpCode.AvalonEdit.AvalonEditCommands.ConvertLeadingTabsToSpaces"/>
<MenuItem id = "LeadingSpaces2Tabs"
label = "${res:XML.MainMenu.EditMenu.FormatMenu.LdSpace2Tab}"
command = "ICSharpCode.AvalonEdit.AvalonEditCommands.ConvertLeadingSpacesToTabs"/>
<MenuItem id = "Separator4" type = "Separator" />
<MenuItem id = "Comment"
icon = "Icons.16x16.CommentRegion"
label = "${res:XML.TextAreaContextMenu.CommentUncommentSelection}"
class = "ICSharpCode.SharpDevelop.Editor.Commands.CommentRegion"/>
<MenuItem id = "Indent"
label = "${res:XML.TextAreaContextMenu.Indent}"
shortcut = "Control|I"
command = "ICSharpCode.AvalonEdit.AvalonEditCommands.IndentSelection"/>
</Condition>
</Path>
</AddIn>

13
src/AddIns/DisplayBindings/AvalonEdit.AddIn/Src/CodeEditor.cs

@ -154,8 +154,6 @@ namespace ICSharpCode.AvalonEdit.AddIn @@ -154,8 +154,6 @@ namespace ICSharpCode.AvalonEdit.AddIn
textEditor.TextArea.Caret.PositionChanged += caret_PositionChanged;
textEditor.TextArea.DefaultInputHandler.CommandBindings.Add(
new CommandBinding(CustomCommands.CtrlSpaceCompletion, OnCodeCompletion));
textEditor.TextArea.DefaultInputHandler.CommandBindings.Add(
new CommandBinding(CustomCommands.DeleteLine, OnDeleteLine));
textView.BackgroundRenderers.Add(textMarkerService);
textView.LineTransformers.Add(textMarkerService);
@ -383,17 +381,6 @@ namespace ICSharpCode.AvalonEdit.AddIn @@ -383,17 +381,6 @@ namespace ICSharpCode.AvalonEdit.AddIn
}
}
void OnDeleteLine(object sender, ExecutedRoutedEventArgs e)
{
TextEditor textEditor = GetTextEditorFromSender(sender);
e.Handled = true;
using (textEditor.Document.RunUpdate()) {
DocumentLine currentLine = textEditor.Document.GetLineByNumber(textEditor.TextArea.Caret.Line);
textEditor.Select(currentLine.Offset, currentLine.TotalLength);
textEditor.SelectedText = string.Empty;
}
}
CompletionWindow completionWindow;
SharpDevelopInsightWindow insightWindow;

8
src/AddIns/DisplayBindings/AvalonEdit.AddIn/Src/CustomCommands.cs

@ -11,7 +11,7 @@ using System.Windows.Input; @@ -11,7 +11,7 @@ using System.Windows.Input;
namespace ICSharpCode.AvalonEdit.AddIn
{
/// <summary>
/// Custom commands for AvalonEdit.
/// Custom commands for CodeEditor.
/// </summary>
public static class CustomCommands
{
@ -20,11 +20,5 @@ namespace ICSharpCode.AvalonEdit.AddIn @@ -20,11 +20,5 @@ namespace ICSharpCode.AvalonEdit.AddIn
new InputGestureCollection {
new KeyGesture(Key.Space, ModifierKeys.Control)
});
public static readonly RoutedCommand DeleteLine = new RoutedCommand(
"DeleteLine", typeof(CodeEditor),
new InputGestureCollection {
new KeyGesture(Key.D, ModifierKeys.Control)
});
}
}

91
src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/AvalonEditCommands.cs

@ -0,0 +1,91 @@ @@ -0,0 +1,91 @@
// <file>
// <copyright see="prj:///doc/copyright.txt"/>
// <license see="prj:///doc/license.txt"/>
// <owner name="Daniel Grunwald"/>
// <version>$Revision$</version>
// </file>
using System;
using System.Windows.Input;
namespace ICSharpCode.AvalonEdit
{
/// <summary>
/// Custom commands for AvalonEdit.
/// </summary>
public static class AvalonEditCommands
{
/// <summary>
/// Deletes the current line.
/// The default shortcut is Ctrl+D.
/// </summary>
public static readonly RoutedCommand DeleteLine = new RoutedCommand(
"DeleteLine", typeof(TextEditor),
new InputGestureCollection {
new KeyGesture(Key.D, ModifierKeys.Control)
});
/// <summary>
/// Removes leading whitespace from the selected lines (or the whole document if the selection is empty).
/// </summary>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly", MessageId = "Whitespace",
Justification = "WPF uses 'Whitespace'")]
public static readonly RoutedCommand RemoveLeadingWhitespace = new RoutedCommand("RemoveLeadingWhitespace", typeof(TextEditor));
/// <summary>
/// Removes trailing whitespace from the selected lines (or the whole document if the selection is empty).
/// </summary>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly", MessageId = "Whitespace",
Justification = "WPF uses 'Whitespace'")]
public static readonly RoutedCommand RemoveTrailingWhitespace = new RoutedCommand("RemoveTrailingWhitespace", typeof(TextEditor));
/// <summary>
/// Converts the selected text to upper case.
/// </summary>
public static readonly RoutedCommand ConvertToUppercase = new RoutedCommand("ConvertToUppercase", typeof(TextEditor));
/// <summary>
/// Converts the selected text to lower case.
/// </summary>
public static readonly RoutedCommand ConvertToLowercase = new RoutedCommand("ConvertToLowercase", typeof(TextEditor));
/// <summary>
/// Converts the selected text to title case.
/// </summary>
public static readonly RoutedCommand ConvertToTitleCase = new RoutedCommand("ConvertToTitleCase", typeof(TextEditor));
/// <summary>
/// Inverts the case of the selected text.
/// </summary>
public static readonly RoutedCommand InvertCase = new RoutedCommand("InvertCase", typeof(TextEditor));
/// <summary>
/// Converts tabs to spaces in the selected text.
/// </summary>
public static readonly RoutedCommand ConvertTabsToSpaces = new RoutedCommand("ConvertTabsToSpaces", typeof(TextEditor));
/// <summary>
/// Converts spaces to tabs in the selected text.
/// </summary>
public static readonly RoutedCommand ConvertSpacesToTabs = new RoutedCommand("ConvertSpacesToTabs", typeof(TextEditor));
/// <summary>
/// Converts leading tabs to spaces in the selected lines (or the whole document if the selection is empty).
/// </summary>
public static readonly RoutedCommand ConvertLeadingTabsToSpaces = new RoutedCommand("ConvertLeadingTabsToSpaces", typeof(TextEditor));
/// <summary>
/// Converts leading spaces to tabs in the selected lines (or the whole document if the selection is empty).
/// </summary>
public static readonly RoutedCommand ConvertLeadingSpacesToTabs = new RoutedCommand("ConvertLeadingSpacesToTabs", typeof(TextEditor));
/// <summary>
/// Runs the IIndentationStrategy on the selected lines (or the whole document if the selection is empty).
/// </summary>
public static readonly RoutedCommand IndentSelection = new RoutedCommand(
"IndentSelection", typeof(TextEditor),
new InputGestureCollection {
new KeyGesture(Key.I, ModifierKeys.Control)
});
}
}

47
src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Document/DocumentChangeEventArgs.cs

@ -37,11 +37,43 @@ namespace ICSharpCode.AvalonEdit.Document @@ -37,11 +37,43 @@ namespace ICSharpCode.AvalonEdit.Document
get { return InsertedText.Length; }
}
volatile OffsetChangeMap offsetChangeMap;
/// <summary>
/// Gets the OffsetChangeMap associated with this document change.
/// </summary>
public OffsetChangeMap OffsetChangeMap {
get {
OffsetChangeMap map = offsetChangeMap;
if (map == null) {
// create OffsetChangeMap on demand
map = new OffsetChangeMap();
if (this.RemovalLength > 0)
map.Add(new OffsetChangeMapEntry(this.Offset, -this.RemovalLength));
if (this.InsertionLength > 0)
map.Add(new OffsetChangeMapEntry(this.Offset, this.InsertionLength));
offsetChangeMap = map;
}
return map;
}
}
/// <summary>
/// Gets the OffsetChangeMap, or null if the default offset map (=removal followed by insertion) is being used.
/// </summary>
internal OffsetChangeMap OffsetChangeMapOrNull {
get {
return offsetChangeMap;
}
}
/// <summary>
/// Gets the new offset where the specified offset moves after this document change.
/// </summary>
public int GetNewOffset(int offset, AnchorMovementType movementType)
{
if (offsetChangeMap != null)
return offsetChangeMap.GetNewOffset(offset, movementType);
if (offset >= this.Offset) {
if (offset <= this.Offset + this.RemovalLength) {
offset = this.Offset;
@ -58,12 +90,27 @@ namespace ICSharpCode.AvalonEdit.Document @@ -58,12 +90,27 @@ namespace ICSharpCode.AvalonEdit.Document
/// Creates a new DocumentChangeEventArgs object.
/// </summary>
public DocumentChangeEventArgs(int offset, int removalLength, string insertedText)
: this(offset, removalLength, insertedText, null)
{
}
/// <summary>
/// Creates a new DocumentChangeEventArgs object.
/// </summary>
public DocumentChangeEventArgs(int offset, int removalLength, string insertedText, OffsetChangeMap offsetChangeMap)
{
if (insertedText == null)
throw new ArgumentNullException("insertedText");
this.Offset = offset;
this.RemovalLength = removalLength;
this.InsertedText = insertedText;
if (offsetChangeMap != null) {
if (!offsetChangeMap.IsValidForDocumentChange(offset, removalLength, insertedText.Length))
throw new ArgumentException("OffsetChangeMap is not valid for this document change", "offsetChangeMap");
this.offsetChangeMap = offsetChangeMap;
}
}
}
}

8
src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Document/DocumentChangeOperation.cs

@ -18,23 +18,25 @@ namespace ICSharpCode.AvalonEdit.Document @@ -18,23 +18,25 @@ namespace ICSharpCode.AvalonEdit.Document
int offset;
string removedText;
string insertedText;
OffsetChangeMap offsetChangeMap;
public DocumentChangeOperation(TextDocument document, int offset, string removedText, string insertedText)
public DocumentChangeOperation(TextDocument document, int offset, string removedText, string insertedText, OffsetChangeMap offsetChangeMap)
{
this.document = document;
this.offset = offset;
this.removedText = removedText;
this.insertedText = insertedText;
this.offsetChangeMap = offsetChangeMap;
}
public void Undo()
{
document.Replace(offset, insertedText.Length, removedText);
document.Replace(offset, insertedText.Length, removedText, offsetChangeMap != null ? offsetChangeMap.Invert() : null);
}
public void Redo()
{
document.Replace(offset, removedText.Length, insertedText);
document.Replace(offset, removedText.Length, insertedText, offsetChangeMap);
}
}
}

177
src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Document/OffsetChangeMap.cs

@ -0,0 +1,177 @@ @@ -0,0 +1,177 @@
// <file>
// <copyright see="prj:///doc/copyright.txt"/>
// <license see="prj:///doc/license.txt"/>
// <owner name="Daniel Grunwald"/>
// <version>$Revision$</version>
// </file>
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
namespace ICSharpCode.AvalonEdit.Document
{
/// <summary>
/// Contains predefined offset change mapping types.
/// </summary>
public enum OffsetChangeMappingType
{
/// <summary>
/// First the old text is removed, then the new text is inserted.
/// </summary>
RemoveAndInsert,
/// <summary>
/// The text is replaced character-by-character.
/// If the new text is longer than the old text, a single insertion at the end is used to account for the difference.
/// If the new text is shorter than the old text, a single deletion at the end is used to account for the difference.
/// </summary>
CharacterReplace
}
/// <summary>
/// Describes a series of offset changes.
/// </summary>
[Serializable]
public sealed class OffsetChangeMap : Collection<OffsetChangeMapEntry>
{
/// <summary>
/// Immutable OffsetChangeMap that is empty.
/// </summary>
public static readonly OffsetChangeMap Empty = new OffsetChangeMap(Utils.Empty<OffsetChangeMapEntry>.ReadOnlyCollection);
/// <summary>
/// Creates a new OffsetChangeMap instance.
/// </summary>
public OffsetChangeMap()
{
}
/// <summary>
/// Creates a new OffsetChangeMap instance.
/// </summary>
public OffsetChangeMap(int capacity)
: base(new List<OffsetChangeMapEntry>(capacity))
{
}
/// <summary>
/// Private constructor for immutable 'Empty' instance.
/// </summary>
private OffsetChangeMap(IList<OffsetChangeMapEntry> entries)
: base(entries)
{
}
/// <summary>
/// Gets the new offset where the specified offset moves after this document change.
/// </summary>
public int GetNewOffset(int offset, AnchorMovementType movementType)
{
foreach (OffsetChangeMapEntry entry in this) {
offset = entry.GetNewOffset(offset, movementType);
}
return offset;
}
/// <summary>
/// Gets whether this OffsetChangeMap is a valid explanation for the specified document change.
/// </summary>
public bool IsValidForDocumentChange(int offset, int removalLength, int insertionLength)
{
int endOffset = offset + removalLength;
foreach (OffsetChangeMapEntry entry in this) {
// check that ChangeMapEntry is in valid range for this document change
if (entry.Offset < offset || entry.Offset + entry.RemovalLength > endOffset)
return false;
endOffset += entry.Delta;
}
// check that the total delta matches
return endOffset == offset + insertionLength;
}
/// <summary>
/// Calculates the inverted OffsetChangeMap (used for the undo operation).
/// </summary>
public OffsetChangeMap Invert()
{
if (this == Empty)
return this;
OffsetChangeMap newMap = new OffsetChangeMap(this.Count);
for (int i = this.Count - 1; i >= 0; i--) {
OffsetChangeMapEntry entry = this[i];
newMap.Add(new OffsetChangeMapEntry(entry.Offset, -entry.Delta));
}
return newMap;
}
}
/// <summary>
/// An entry in the OffsetChangeMap.
/// This represents the offset of a document change (either insertion or removal, not both at once).
/// </summary>
[Serializable]
public struct OffsetChangeMapEntry
{
readonly int offset;
readonly int delta;
/// <summary>
/// The offset at which the change occurs.
/// </summary>
public int Offset {
get { return offset; }
}
/// <summary>
/// The change delta. If positive, it is equal to InsertionLength; if negative, it is equal to RemovalLength.
/// </summary>
public int Delta {
get { return delta; }
}
/// <summary>
/// The number of characters removed.
/// Returns 0 if this entry represents an insertion.
/// </summary>
public int RemovalLength {
get {
return delta < 0 ? -delta : 0;
}
}
/// <summary>
/// The number of characters inserted.
/// Returns 0 if this entry represents a removal.
/// </summary>
public int InsertionLength {
get {
return delta > 0 ? delta : 0;
}
}
/// <summary>
/// Gets the new offset where the specified offset moves after this document change.
/// </summary>
public int GetNewOffset(int oldOffset, AnchorMovementType movementType)
{
if (oldOffset < this.Offset)
return oldOffset;
if (oldOffset > this.Offset + this.RemovalLength)
return oldOffset + this.Delta;
// offset is inside removed region
if (movementType == AnchorMovementType.AfterInsertion)
return this.Offset + this.InsertionLength;
else
return this.Offset;
}
/// <summary>
/// Creates a new OffsetChangeMapEntry instance.
/// </summary>
public OffsetChangeMapEntry(int offset, int delta)
{
this.offset = offset;
this.delta = delta;
}
}
}

158
src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Document/TextDocument.cs

@ -283,23 +283,31 @@ namespace ICSharpCode.AvalonEdit.Document @@ -283,23 +283,31 @@ namespace ICSharpCode.AvalonEdit.Document
/// <summary>
/// Inserts text.
/// Runtime:
/// for updating the text buffer: m=size of new text, d=distance to last change
/// for updating the text buffer: m=size of new text, d=distance to last change, n=total text length
/// usual: O(m+d)
/// rare: O(m+n)
/// for updating the document lines: O(m*log n), m=number of changed lines
/// for updating the document lines: O(m*log n), m=number of changed lines, n=total number of lines in document
/// </summary>
public void Insert(int offset, string text)
{
Replace(offset, 0, text);
}
/// <summary>
/// Removes text.
/// </summary>
public void Remove(ISegment segment)
{
Replace(segment, string.Empty);
}
/// <summary>
/// Removes text.
/// Runtime:
/// for updating the text buffer: d=distance to last change
/// for updating the text buffer: d=distance to last change, n=total text length
/// usual: O(d)
/// rare: O(n)
/// for updating the document lines: O(m*log n), m=number of changed lines
/// for updating the document lines: O(m*log n), m=number of changed lines, n=total number of lines in document
/// </summary>
public void Remove(int offset, int length)
{
@ -315,60 +323,112 @@ namespace ICSharpCode.AvalonEdit.Document @@ -315,60 +323,112 @@ namespace ICSharpCode.AvalonEdit.Document
{
if (segment == null)
throw new ArgumentNullException("segment");
Replace(segment.Offset, segment.Length, text);
Replace(segment.Offset, segment.Length, text, null);
}
/// <summary>
/// Replaces text.
/// </summary>
public void Replace(int offset, int length, string text)
{
Replace(offset, length, text, null);
}
/// <summary>
/// Replaces text.
/// Runtime:
/// for updating the text buffer: m=size of new text, d=distance to last change, n=total text length
/// usual: O(m+d)
/// rare: O(m+n)
/// for updating the document lines: O(m*log n), m=number of changed lines, n=total number of lines in document
/// </summary>
/// <param name="offset">The starting offset of the text to be replaced.</param>
/// <param name="length">The length of the text to be replaced.</param>
/// <param name="text">The new text.</param>
/// <param name="offsetChangeMappingType">The offsetChangeMappingType determines how offsets inside the old text are mapped to the new text.
/// This affects how the anchors and segments inside the replaced region behave.</param>
public void Replace(int offset, int length, string text, OffsetChangeMappingType offsetChangeMappingType)
{
if (text == null)
throw new ArgumentNullException("text");
switch (offsetChangeMappingType) {
case OffsetChangeMappingType.RemoveAndInsert:
Replace(offset, length, text, null);
break;
case OffsetChangeMappingType.CharacterReplace:
if (text.Length > length) {
OffsetChangeMap map = new OffsetChangeMap(1);
map.Add(new OffsetChangeMapEntry(offset + length, text.Length - length));
Replace(offset, length, text, map);
} else if (text.Length < length) {
OffsetChangeMap map = new OffsetChangeMap(1);
map.Add(new OffsetChangeMapEntry(offset + length - text.Length, text.Length - length));
Replace(offset, length, text, map);
} else {
Replace(offset, length, text, OffsetChangeMap.Empty);
}
break;
}
}
/// <summary>
/// Replaces text.
/// Runtime:
/// for updating the text buffer: m=size of new text, d=distance to last change
/// for updating the text buffer: m=size of new text, d=distance to last change, n=total text length
/// usual: O(m+d)
/// rare: O(m+n)
/// for updating the document lines: O(m*log n), m=number of changed lines
/// for updating the document lines: O(m*log n), m=number of changed lines, n=total number of lines in document
/// </summary>
public void Replace(int offset, int length, string text)
/// <param name="offset">The starting offset of the text to be replaced.</param>
/// <param name="length">The length of the text to be replaced.</param>
/// <param name="text">The new text.</param>
/// <param name="offsetChangeMap">The offsetChangeMap determines how offsets inside the old text are mapped to the new text.
/// This affects how the anchors and segments inside the replaced region behave.
/// If you pass null (the default when using one of the other overloads), the offsets are changed as if
/// the old text is removed first and the new text is inserted later (OffsetChangeMappingType.RemoveAndInsert).
/// If you pass OffsetChangeMap.Empty, then everything will stay in its old place.
/// The offsetChangeMap must be a valid 'explanation' for the document change</param>
public void Replace(int offset, int length, string text, OffsetChangeMap offsetChangeMap)
{
if (inDocumentChanging)
throw new InvalidOperationException("Cannot change document within another document change.");
if (text == null)
throw new ArgumentNullException("text");
BeginUpdate();
// protect document change against corruption by other changes inside the event handlers
inDocumentChanging = true;
try {
VerifyRange(offset, length);
if (text == null)
throw new ArgumentNullException("text");
if (length == 0 && text.Length == 0)
return;
fireTextChanged = true;
DocumentChangeEventArgs args = new DocumentChangeEventArgs(offset, length, text);
// fire DocumentChanging event
if (Changing != null)
Changing(this, args);
DelayedEvents delayedEvents = new DelayedEvents();
// now do the real work
anchorTree.RemoveText(offset, length, delayedEvents);
ReplaceInternal(offset, length, text);
anchorTree.InsertText(offset, text.Length);
delayedEvents.RaiseEvents();
// fire DocumentChanged event
if (Changed != null)
Changed(this, args);
if (inDocumentChanging)
throw new InvalidOperationException("Cannot change document within another document change.");
// protect document change against corruption by other changes inside the event handlers
inDocumentChanging = true;
try {
// The range verification must wait until after the BeginUpdate() call because the document
// might be modified inside the UpdateStarted event.
VerifyRange(offset, length);
DoReplace(offset, length, text, offsetChangeMap);
} finally {
inDocumentChanging = false;
}
} finally {
inDocumentChanging = false;
EndUpdate();
}
}
void ReplaceInternal(int offset, int length, string text)
void DoReplace(int offset, int length, string text, OffsetChangeMap offsetChangeMap)
{
if (length == 0 && text.Length == 0)
return;
DocumentChangeEventArgs args = new DocumentChangeEventArgs(offset, length, text, offsetChangeMap);
// fire DocumentChanging event
if (Changing != null)
Changing(this, args);
fireTextChanged = true;
DelayedEvents delayedEvents = new DelayedEvents();
// now update the textBuffer and lineTree
if (offset == 0 && length == textBuffer.Length) {
// optimize replacing the whole document
textBuffer.Text = text;
lineManager.Rebuild(text);
} else {
@ -383,6 +443,24 @@ namespace ICSharpCode.AvalonEdit.Document @@ -383,6 +443,24 @@ namespace ICSharpCode.AvalonEdit.Document
lineTree.CheckProperties();
#endif
}
// update text anchors
if (offsetChangeMap == null) {
anchorTree.RemoveText(offset, length, delayedEvents);
anchorTree.InsertText(offset, text.Length);
} else {
foreach (OffsetChangeMapEntry entry in offsetChangeMap) {
anchorTree.RemoveText(entry.Offset, entry.RemovalLength, delayedEvents);
anchorTree.InsertText(entry.Offset, entry.InsertionLength);
}
}
// raise delayed events after our data structures are consistent again
delayedEvents.RaiseEvents();
// fire DocumentChanged event
if (Changed != null)
Changed(this, args);
}
#endregion

6
src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Document/TextSegmentCollection.cs

@ -80,8 +80,10 @@ namespace ICSharpCode.AvalonEdit.Document @@ -80,8 +80,10 @@ namespace ICSharpCode.AvalonEdit.Document
void OnDocumentChanged(DocumentChangeEventArgs e)
{
RemoveText(e.Offset, e.RemovalLength);
InsertText(e.Offset, e.InsertionLength);
foreach (OffsetChangeMapEntry entry in e.OffsetChangeMap) {
RemoveText(entry.Offset, entry.RemovalLength);
InsertText(entry.Offset, entry.InsertionLength);
}
}
#endregion

3
src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Document/UndoStack.cs

@ -275,7 +275,8 @@ namespace ICSharpCode.AvalonEdit.Document @@ -275,7 +275,8 @@ namespace ICSharpCode.AvalonEdit.Document
document,
e.Offset,
document.GetText(e.Offset, e.RemovalLength),
e.InsertedText));
e.InsertedText,
e.OffsetChangeMapOrNull));
}
/// <summary>

273
src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Editing/EditingCommandHandler.cs

@ -8,8 +8,10 @@ @@ -8,8 +8,10 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Documents;
using System.Windows.Input;
@ -62,6 +64,20 @@ namespace ICSharpCode.AvalonEdit.Editing @@ -62,6 +64,20 @@ namespace ICSharpCode.AvalonEdit.Editing
CommandBindings.Add(new CommandBinding(ApplicationCommands.Copy, OnCopy, CanCutOrCopy));
CommandBindings.Add(new CommandBinding(ApplicationCommands.Cut, OnCut, CanCutOrCopy));
CommandBindings.Add(new CommandBinding(ApplicationCommands.Paste, OnPaste, CanPaste));
CommandBindings.Add(new CommandBinding(AvalonEditCommands.DeleteLine, OnDeleteLine));
CommandBindings.Add(new CommandBinding(AvalonEditCommands.RemoveLeadingWhitespace, OnRemoveLeadingWhitespace));
CommandBindings.Add(new CommandBinding(AvalonEditCommands.RemoveTrailingWhitespace, OnRemoveTrailingWhitespace));
CommandBindings.Add(new CommandBinding(AvalonEditCommands.ConvertToUppercase, OnConvertToUpperCase));
CommandBindings.Add(new CommandBinding(AvalonEditCommands.ConvertToLowercase, OnConvertToLowerCase));
CommandBindings.Add(new CommandBinding(AvalonEditCommands.ConvertToTitleCase, OnConvertToTitleCase));
CommandBindings.Add(new CommandBinding(AvalonEditCommands.InvertCase, OnInvertCase));
CommandBindings.Add(new CommandBinding(AvalonEditCommands.ConvertTabsToSpaces, OnConvertTabsToSpaces));
CommandBindings.Add(new CommandBinding(AvalonEditCommands.ConvertSpacesToTabs, OnConvertSpacesToTabs));
CommandBindings.Add(new CommandBinding(AvalonEditCommands.ConvertLeadingTabsToSpaces, OnConvertLeadingTabsToSpaces));
CommandBindings.Add(new CommandBinding(AvalonEditCommands.ConvertLeadingSpacesToTabs, OnConvertLeadingSpacesToTabs));
CommandBindings.Add(new CommandBinding(AvalonEditCommands.IndentSelection, OnIndentSelection));
}
static TextArea GetTextArea(object target)
@ -69,6 +85,84 @@ namespace ICSharpCode.AvalonEdit.Editing @@ -69,6 +85,84 @@ namespace ICSharpCode.AvalonEdit.Editing
return target as TextArea;
}
#region Text Transformation Helpers
enum DefaultSegmentType
{
None,
WholeDocument,
CurrentLine
}
/// <summary>
/// Calls transformLine on all lines in the selected range.
/// transformLine needs to handle read-only segments!
/// </summary>
static void TransformSelectedLines(Action<TextArea, DocumentLine> transformLine, object target, ExecutedRoutedEventArgs args, DefaultSegmentType defaultSegmentType)
{
TextArea textArea = GetTextArea(target);
if (textArea != null && textArea.Document != null) {
using (textArea.Document.RunUpdate()) {
DocumentLine start, end;
if (textArea.Selection.IsEmpty) {
if (defaultSegmentType == DefaultSegmentType.CurrentLine) {
start = end = textArea.Document.GetLineByNumber(textArea.Caret.Line);
} else if (defaultSegmentType == DefaultSegmentType.WholeDocument) {
start = textArea.Document.Lines.First();
end = textArea.Document.Lines.Last();
} else {
start = end = null;
}
} else {
start = textArea.Document.GetLineByOffset(textArea.Selection.SurroundingSegment.Offset);
end = textArea.Document.GetLineByOffset(textArea.Selection.SurroundingSegment.EndOffset);
}
if (start != null) {
transformLine(textArea, start);
while (start != end) {
start = start.NextLine;
transformLine(textArea, start);
}
}
}
textArea.Caret.BringCaretToView();
args.Handled = true;
}
}
/// <summary>
/// Calls transformLine on all writable segment in the selected range.
/// </summary>
static void TransformSelectedSegments(Action<TextArea, ISegment> transformSegment, object target, ExecutedRoutedEventArgs args, DefaultSegmentType defaultSegmentType)
{
TextArea textArea = GetTextArea(target);
if (textArea != null && textArea.Document != null) {
using (textArea.Document.RunUpdate()) {
IEnumerable<ISegment> segments;
if (textArea.Selection.IsEmpty) {
if (defaultSegmentType == DefaultSegmentType.CurrentLine) {
segments = new ISegment[] { textArea.Document.GetLineByNumber(textArea.Caret.Line) };
} else if (defaultSegmentType == DefaultSegmentType.WholeDocument) {
segments = textArea.Document.Lines.Cast<ISegment>();
} else {
segments = null;
}
} else {
segments = textArea.Selection.Segments;
}
if (segments != null) {
foreach (ISegment segment in segments.Reverse()) {
foreach (ISegment writableSegment in textArea.ReadOnlySectionProvider.GetDeletableSegments(segment).Reverse()) {
transformSegment(textArea, writableSegment);
}
}
}
}
textArea.Caret.BringCaretToView();
args.Handled = true;
}
}
#endregion
#region EnterLineBreak
static void OnEnter(object target, ExecutedRoutedEventArgs args)
{
@ -112,33 +206,17 @@ namespace ICSharpCode.AvalonEdit.Editing @@ -112,33 +206,17 @@ namespace ICSharpCode.AvalonEdit.Editing
static void OnShiftTab(object target, ExecutedRoutedEventArgs args)
{
TextArea textArea = GetTextArea(target);
if (textArea != null && textArea.Document != null) {
using (textArea.Document.RunUpdate()) {
DocumentLine start, end;
if (textArea.Selection.IsEmpty) {
start = end = textArea.Document.GetLineByNumber(textArea.Caret.Line);
} else {
start = textArea.Document.GetLineByOffset(textArea.Selection.SurroundingSegment.Offset);
end = textArea.Document.GetLineByOffset(textArea.Selection.SurroundingSegment.EndOffset);
}
while (true) {
int offset = start.Offset;
ISegment s = TextUtilities.GetSingleIndentationSegment(textArea.Document, offset, textArea.Options.IndentationSize);
TransformSelectedLines(
delegate (TextArea textArea, DocumentLine line) {
int offset = line.Offset;
ISegment s = TextUtilities.GetSingleIndentationSegment(line.Document, offset, textArea.Options.IndentationSize);
if (s.Length > 0) {
s = textArea.ReadOnlySectionProvider.GetDeletableSegments(s).FirstOrDefault();
if (s != null && s.Length > 0) {
textArea.Document.Remove(s.Offset, s.Length);
}
}
if (start == end)
break;
start = start.NextLine;
}
}
textArea.Caret.BringCaretToView();
args.Handled = true;
}
}, target, args, DefaultSegmentType.CurrentLine);
}
#endregion
@ -288,5 +366,158 @@ namespace ICSharpCode.AvalonEdit.Editing @@ -288,5 +366,158 @@ namespace ICSharpCode.AvalonEdit.Editing
}
}
#endregion
#region DeleteLine
static void OnDeleteLine(object target, ExecutedRoutedEventArgs args)
{
TextArea textArea = GetTextArea(target);
if (textArea != null && textArea.Document != null) {
DocumentLine currentLine = textArea.Document.GetLineByNumber(textArea.Caret.Line);
textArea.Document.Remove(currentLine.Offset, currentLine.TotalLength);
args.Handled = true;
}
}
#endregion
#region Remove..Whitespace / Convert Tabs-Spaces
static void OnRemoveLeadingWhitespace(object target, ExecutedRoutedEventArgs args)
{
TransformSelectedLines(
delegate (TextArea textArea, DocumentLine line) {
line.Document.Remove(TextUtilities.GetLeadingWhitespace(line));
}, target, args, DefaultSegmentType.WholeDocument);
}
static void OnRemoveTrailingWhitespace(object target, ExecutedRoutedEventArgs args)
{
TransformSelectedLines(
delegate (TextArea textArea, DocumentLine line) {
line.Document.Remove(TextUtilities.GetTrailingWhitespace(line));
}, target, args, DefaultSegmentType.WholeDocument);
}
static void OnConvertTabsToSpaces(object target, ExecutedRoutedEventArgs args)
{
TransformSelectedSegments(ConvertTabsToSpaces, target, args, DefaultSegmentType.WholeDocument);
}
static void OnConvertLeadingTabsToSpaces(object target, ExecutedRoutedEventArgs args)
{
TransformSelectedLines(
delegate (TextArea textArea, DocumentLine line) {
ConvertTabsToSpaces(textArea, TextUtilities.GetLeadingWhitespace(line));
}, target, args, DefaultSegmentType.WholeDocument);
}
static void ConvertTabsToSpaces(TextArea textArea, ISegment segment)
{
TextDocument document = textArea.Document;
int endOffset = segment.EndOffset;
string indentationString = new string(' ', textArea.Options.IndentationSize);
for (int offset = segment.Offset; offset < endOffset; offset++) {
if (document.GetCharAt(offset) == '\t') {
document.Replace(offset, 1, indentationString, OffsetChangeMappingType.CharacterReplace);
endOffset += indentationString.Length - 1;
}
}
}
static void OnConvertSpacesToTabs(object target, ExecutedRoutedEventArgs args)
{
TransformSelectedSegments(ConvertSpacesToTabs, target, args, DefaultSegmentType.WholeDocument);
}
static void OnConvertLeadingSpacesToTabs(object target, ExecutedRoutedEventArgs args)
{
TransformSelectedLines(
delegate (TextArea textArea, DocumentLine line) {
ConvertSpacesToTabs(textArea, TextUtilities.GetLeadingWhitespace(line));
}, target, args, DefaultSegmentType.WholeDocument);
}
static void ConvertSpacesToTabs(TextArea textArea, ISegment segment)
{
TextDocument document = textArea.Document;
int endOffset = segment.EndOffset;
int indentationSize = textArea.Options.IndentationSize;
int spacesCount = 0;
for (int offset = segment.Offset; offset < endOffset; offset++) {
if (document.GetCharAt(offset) == ' ') {
spacesCount++;
if (spacesCount == indentationSize) {
document.Replace(offset, indentationSize, "\t", OffsetChangeMappingType.CharacterReplace);
spacesCount = 0;
offset -= indentationSize - 1;
endOffset -= indentationSize - 1;
}
} else {
spacesCount = 0;
}
}
}
#endregion
#region Convert...Case
static void ConvertCase(Func<string, string> transformText, object target, ExecutedRoutedEventArgs args)
{
TransformSelectedSegments(
delegate (TextArea textArea, ISegment segment) {
string oldText = textArea.Document.GetText(segment);
string newText = transformText(oldText);
textArea.Document.Replace(segment.Offset, segment.Length, newText, OffsetChangeMappingType.CharacterReplace);
}, target, args, DefaultSegmentType.WholeDocument);
}
static void OnConvertToUpperCase(object target, ExecutedRoutedEventArgs args)
{
ConvertCase(CultureInfo.CurrentCulture.TextInfo.ToUpper, target, args);
}
static void OnConvertToLowerCase(object target, ExecutedRoutedEventArgs args)
{
ConvertCase(CultureInfo.CurrentCulture.TextInfo.ToLower, target, args);
}
static void OnConvertToTitleCase(object target, ExecutedRoutedEventArgs args)
{
ConvertCase(CultureInfo.CurrentCulture.TextInfo.ToTitleCase, target, args);
}
static void OnInvertCase(object target, ExecutedRoutedEventArgs args)
{
ConvertCase(InvertCase, target, args);
}
static string InvertCase(string text)
{
CultureInfo culture = CultureInfo.CurrentCulture;
char[] buffer = text.ToCharArray();
for (int i = 0; i < buffer.Length; ++i) {
char c = buffer[i];
buffer[i] = char.IsUpper(c) ? char.ToLower(c, culture) : char.ToUpper(c, culture);
}
return new string(buffer);
}
#endregion
static void OnIndentSelection(object target, ExecutedRoutedEventArgs args)
{
TextArea textArea = GetTextArea(target);
if (textArea != null && textArea.Document != null) {
using (textArea.Document.RunUpdate()) {
int start, end;
if (textArea.Selection.IsEmpty) {
start = 1;
end = textArea.Document.LineCount;
} else {
start = textArea.Document.GetLineByOffset(textArea.Selection.SurroundingSegment.Offset).LineNumber;
end = textArea.Document.GetLineByOffset(textArea.Selection.SurroundingSegment.EndOffset).LineNumber;
}
textArea.IndentationStrategy.IndentLines(start, end);
}
textArea.Caret.BringCaretToView();
args.Handled = true;
}
}
}
}

2
src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Editing/TextArea.cs

@ -346,7 +346,7 @@ namespace ICSharpCode.AvalonEdit.Editing @@ -346,7 +346,7 @@ namespace ICSharpCode.AvalonEdit.Editing
/// <summary>
/// Code that updates only the caret but not the selection can cause confusion when
/// keys like 'Delete' delete the (possibly invisible) selected text and not the
/// text around the caret (where the will jump to).
/// text around the caret.
///
/// So we'll ensure that the caret is inside the selection.
/// (when the caret is not in the selection, we'll clear the selection)

2
src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/ICSharpCode.AvalonEdit.csproj

@ -73,6 +73,7 @@ @@ -73,6 +73,7 @@
<Compile Include="..\..\..\Main\GlobalAssemblyInfo.cs">
<Link>Properties\GlobalAssemblyInfo.cs</Link>
</Compile>
<Compile Include="AvalonEditCommands.cs" />
<Compile Include="CaretPositioningMode.cs" />
<Compile Include="CodeCompletion\CompletionListBox.cs" />
<Compile Include="CodeCompletion\CompletionWindowBase.cs" />
@ -98,6 +99,7 @@ @@ -98,6 +99,7 @@
<DependentUpon>DocumentLine.cs</DependentUpon>
</Compile>
<Compile Include="Document\NewLineFinder.cs" />
<Compile Include="Document\OffsetChangeMap.cs" />
<Compile Include="Document\TextDocumentWeakEventManager.cs">
<DependentUpon>TextDocument.cs</DependentUpon>
</Compile>

4
src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Indentation/DefaultIndentationStrategy.cs

@ -25,10 +25,10 @@ namespace ICSharpCode.AvalonEdit.Indentation @@ -25,10 +25,10 @@ namespace ICSharpCode.AvalonEdit.Indentation
TextDocument document = line.Document;
DocumentLine previousLine = line.PreviousLine;
if (previousLine != null) {
ISegment indentationSegment = TextUtilities.GetIndentation(document, previousLine.Offset);
ISegment indentationSegment = TextUtilities.GetWhitespaceAfter(document, previousLine.Offset);
string indentation = document.GetText(indentationSegment);
// copy indentation to line
indentationSegment = TextUtilities.GetIndentation(document, line.Offset);
indentationSegment = TextUtilities.GetWhitespaceAfter(document, line.Offset);
document.Replace(indentationSegment, indentation);
}
}

73
src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Utils/TextUtilities.cs

@ -27,8 +27,10 @@ namespace ICSharpCode.AvalonEdit.Utils @@ -27,8 +27,10 @@ namespace ICSharpCode.AvalonEdit.Utils
"RS", "US"
};
// DEL (ASCII 127) and
// the names of the control characters in the C1 block (Unicode 128 to 159)
static readonly string[] c1Table = {
static readonly string[] delAndC1Table = {
"DEL",
"PAD", "HOP", "BPH", "NBH", "IND", "NEL", "SSA", "ESA", "HTS", "HTJ",
"VTS", "PLD", "PLU", "RI", "SS2", "SS3", "DCS", "PU1", "PU2", "STS",
"CCH", "MW", "SPA", "EPA", "SOS", "SGCI", "SCI", "CSI", "ST", "OSC",
@ -44,23 +46,23 @@ namespace ICSharpCode.AvalonEdit.Utils @@ -44,23 +46,23 @@ namespace ICSharpCode.AvalonEdit.Utils
int num = (int)controlCharacter;
if (num < c0Table.Length)
return c0Table[num];
else if (num == 127)
return "DEL";
else if (num >= 128 && num < 128 + c1Table.Length)
return c1Table[num - 128];
else if (num >= 127 && num <= 159)
return delAndC1Table[num - 127];
else
return num.ToString("x4", CultureInfo.InvariantCulture);
}
#endregion
#region GetIndentation
#region GetWhitespace
/// <summary>
/// Gets all indentation starting at offset.
/// Gets all whitespace (' ' and '\t', but no newlines) after offset.
/// </summary>
/// <param name="textSource">The text source.</param>
/// <param name="offset">The offset where the indentation starts.</param>
/// <returns>The segment containing the indentation.</returns>
public static ISegment GetIndentation(ITextSource textSource, int offset)
/// <param name="offset">The offset where the whitespace starts.</param>
/// <returns>The segment containing the whitespace.</returns>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly", MessageId = "Whitespace",
Justification = "WPF uses 'Whitespace'")]
public static ISegment GetWhitespaceAfter(ITextSource textSource, int offset)
{
if (textSource == null)
throw new ArgumentNullException("textSource");
@ -72,6 +74,57 @@ namespace ICSharpCode.AvalonEdit.Utils @@ -72,6 +74,57 @@ namespace ICSharpCode.AvalonEdit.Utils
}
return new SimpleSegment(offset, pos - offset);
}
/// <summary>
/// Gets all whitespace (' ' and '\t', but no newlines) before offset.
/// </summary>
/// <param name="textSource">The text source.</param>
/// <param name="offset">The offset where the whitespace ends.</param>
/// <returns>The segment containing the whitespace.</returns>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly", MessageId = "Whitespace",
Justification = "WPF uses 'Whitespace'")]
public static ISegment GetWhitespaceBefore(ITextSource textSource, int offset)
{
if (textSource == null)
throw new ArgumentNullException("textSource");
int pos;
for (pos = offset - 1; pos >= 0; pos--) {
char c = textSource.GetCharAt(pos);
if (c != ' ' && c != '\t')
break;
}
return new SimpleSegment(pos, offset - pos);
}
/// <summary>
/// Gets the leading whitespace segment on the document line.
/// </summary>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly", MessageId = "Whitespace",
Justification = "WPF uses 'Whitespace'")]
public static ISegment GetLeadingWhitespace(DocumentLine documentLine)
{
if (documentLine == null)
throw new ArgumentNullException("documentLine");
return GetWhitespaceAfter(documentLine.Document, documentLine.Offset);
}
/// <summary>
/// Gets the trailing whitespace segment on the document line.
/// </summary>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly", MessageId = "Whitespace",
Justification = "WPF uses 'Whitespace'")]
public static ISegment GetTrailingWhitespace(DocumentLine documentLine)
{
if (documentLine == null)
throw new ArgumentNullException("documentLine");
ISegment segment = GetWhitespaceBefore(documentLine.Document, documentLine.EndOffset);
// If the whole line consists of whitespace, we consider all of it as leading whitespace,
// so return an empty segment as trailing whitespace.
if (segment.Offset == documentLine.Offset)
return new SimpleSegment(documentLine.EndOffset, 0);
else
return segment;
}
#endregion
#region GetSingleIndentationSegment

1
src/Main/Base/Project/Src/Commands/NavigationCommands.cs

@ -55,7 +55,6 @@ namespace ICSharpCode.SharpDevelop.Commands @@ -55,7 +55,6 @@ namespace ICSharpCode.SharpDevelop.Commands
public void UpdateEnabledState()
{
CommandManager.InvalidateRequerySuggested();
//splitButton.IsEnabled = NavigationService.CanNavigateBack;
//splitButton.IsDropDownEnabled = NavigationService.Count>1;
}

2
src/Main/Base/Project/Src/Editor/DocumentUtilitites.cs

@ -103,7 +103,7 @@ namespace ICSharpCode.SharpDevelop.Editor @@ -103,7 +103,7 @@ namespace ICSharpCode.SharpDevelop.Editor
/// <returns>The indentation text.</returns>
public static string GetIndentation(IDocument document, int offset)
{
ISegment segment = TextUtilities.GetIndentation(GetTextSource(document), offset);
ISegment segment = TextUtilities.GetWhitespaceAfter(GetTextSource(document), offset);
return document.GetText(segment.Offset, segment.Length);
}

1
src/Main/Base/Project/Src/Gui/WorkbenchSingleton.cs

@ -93,7 +93,6 @@ namespace ICSharpCode.SharpDevelop.Gui @@ -93,7 +93,6 @@ namespace ICSharpCode.SharpDevelop.Gui
{
WorkbenchSingleton.workbench = workbench;
Core.Presentation.MenuService.RegisterCommandClass(typeof(SharpDevelopRoutedCommands));
DisplayBindingService.InitializeService();
LayoutConfiguration.LoadLayoutConfiguration();
FileService.InitializeService();

19
src/Main/Core/Project/Src/AddInTree/AddIn/AddIn.cs

@ -28,19 +28,28 @@ namespace ICSharpCode.Core @@ -28,19 +28,28 @@ namespace ICSharpCode.Core
static bool hasShownErrorMessage = false;
public object CreateObject(string className)
{
Type t = FindType(className);
if (t != null)
return Activator.CreateInstance(t);
else
return null;
}
public Type FindType(string className)
{
LoadDependencies();
foreach (Runtime runtime in runtimes) {
object o = runtime.CreateInstance(className);
if (o != null) {
return o;
Type t = runtime.FindType(className);
if (t != null) {
return t;
}
}
if (hasShownErrorMessage) {
LoggingService.Error("Cannot create object: " + className);
LoggingService.Error("Cannot find class: " + className);
} else {
hasShownErrorMessage = true;
MessageService.ShowError("Cannot create object: " + className + "\nFuture missing objects will not cause an error message.");
MessageService.ShowError("Cannot find class: " + className + "\nFuture missing objects will not cause an error message.");
}
return null;
}

4
src/Main/Core/Project/Src/AddInTree/AddIn/Runtime.cs

@ -111,13 +111,13 @@ namespace ICSharpCode.Core @@ -111,13 +111,13 @@ namespace ICSharpCode.Core
}
}
public object CreateInstance(string instance)
public Type FindType(string className)
{
if (IsActive) {
Assembly asm = LoadedAssembly;
if (asm == null)
return null;
return asm.CreateInstance(instance);
return asm.GetType(className);
} else {
return null;
}

2
src/Main/ICSharpCode.Core.Presentation/Menu/MenuCommand.cs

@ -22,7 +22,7 @@ namespace ICSharpCode.Core.Presentation @@ -22,7 +22,7 @@ namespace ICSharpCode.Core.Presentation
{
string commandName = codon.Properties["command"];
if (!string.IsNullOrEmpty(commandName)) {
var wpfCommand = MenuService.GetRegisteredCommand(commandName);
var wpfCommand = MenuService.GetRegisteredCommand(codon.AddIn, commandName);
if (wpfCommand != null) {
return wpfCommand;
} else {

65
src/Main/ICSharpCode.Core.Presentation/Menu/MenuService.cs

@ -20,45 +20,64 @@ namespace ICSharpCode.Core.Presentation @@ -20,45 +20,64 @@ namespace ICSharpCode.Core.Presentation
/// </summary>
public static class MenuService
{
static List<Type> commandClasses = new List<Type> {
typeof(ApplicationCommands),
typeof(NavigationCommands)
};
static Dictionary<string, System.Windows.Input.ICommand> knownCommands = LoadDefaultKnownCommands();
static Dictionary<string, System.Windows.Input.ICommand> LoadDefaultKnownCommands()
{
var knownCommands = new Dictionary<string, System.Windows.Input.ICommand>();
foreach (Type t in new Type[] { typeof(ApplicationCommands), typeof(NavigationCommands) }) {
foreach (PropertyInfo p in t.GetProperties()) {
knownCommands.Add(p.Name, (System.Windows.Input.ICommand)p.GetValue(null, null));
}
}
return knownCommands;
}
/// <summary>
/// Gets a known WPF command.
/// </summary>
/// <param name="addIn">The addIn definition that defines the command class.</param>
/// <param name="commandName">The name of the command, e.g. "Copy".</param>
/// <returns>The WPF ICommand with the given name, or null if thecommand was not found.</returns>
public static System.Windows.Input.ICommand GetRegisteredCommand(string commandName)
public static System.Windows.Input.ICommand GetRegisteredCommand(AddIn addIn, string commandName)
{
if (addIn == null)
throw new ArgumentNullException("addIn");
if (commandName == null)
throw new ArgumentNullException("commandName");
lock (commandClasses) {
foreach (Type t in commandClasses) {
PropertyInfo p = t.GetProperty(commandName, BindingFlags.Public | BindingFlags.Static);
if (p != null) {
return (System.Windows.Input.ICommand)(p.GetValue(null, null));
}
FieldInfo f = t.GetField(commandName, BindingFlags.Public | BindingFlags.Static);
if (f != null) {
return (System.Windows.Input.ICommand)(f.GetValue(null));
}
System.Windows.Input.ICommand command;
lock (knownCommands) {
if (knownCommands.TryGetValue(commandName, out command))
return command;
}
int pos = commandName.LastIndexOf('.');
if (pos > 0) {
string className = commandName.Substring(0, pos);
string propertyName = commandName.Substring(pos + 1);
Type classType = addIn.FindType(className);
if (classType != null) {
PropertyInfo p = classType.GetProperty(propertyName, BindingFlags.Public | BindingFlags.Static);
if (p != null)
return (System.Windows.Input.ICommand)p.GetValue(null, null);
FieldInfo f = classType.GetField(propertyName, BindingFlags.Public | BindingFlags.Static);
if (f != null)
return (System.Windows.Input.ICommand)f.GetValue(null);
}
return null;
}
return null;
}
/// <summary>
///
/// Registers a WPF command for use with the &lt;MenuItem command="name"&gt; syntax.
/// </summary>
public static void RegisterCommandClass(Type commandClass)
public static void RegisterKnownCommand(string name, System.Windows.Input.ICommand command)
{
if (commandClass == null)
throw new ArgumentNullException("commandClass");
lock (commandClasses) {
if (!commandClasses.Contains(commandClass))
commandClasses.Add(commandClass);
if (name == null)
throw new ArgumentNullException("name");
if (command == null)
throw new ArgumentNullException("command");
lock (knownCommands) {
knownCommands.Add(name, command);
}
}

Loading…
Cancel
Save