From fc54ffd43b5edae6d1cb8a0b06c19d1eab23934c Mon Sep 17 00:00:00 2001 From: Doghole Date: Wed, 7 Jan 2026 11:33:05 +0800 Subject: [PATCH] first commit --- .gitattributes | 63 + .gitignore | 363 ++++++ EmoneyFiddlerPlugins.sln | 44 + .../Controls/EmoneyTabControl.Designer.cs | 237 ++++ .../Emoney/Controls/EmoneyTabControl.cs | 343 ++++++ .../Emoney/Controls/EmoneyTabControl.resx | 120 ++ EmoneyInteceptor/Emoney/EmoneyHelper.cs | 13 + EmoneyInteceptor/EmoneyInteceptor.csproj | 122 ++ .../Fiddler/Interfaces/IFiddlerLayout.cs | 16 + .../Interfaces/IFiddlerViewProvider.cs | 9 + .../Models/FiddlerPluginApplication.cs | 62 + .../Fiddler/Models/FiddlerTabPage.cs | 35 + .../Fiddler/Models/MahjongPlugin.cs | 63 + .../Fiddler/Models/PluginViewController.cs | 62 + EmoneyInteceptor/Properties/AssemblyInfo.cs | 35 + EmoneyInteceptor/packages.config | 15 + .../Controls/RequestDecodeControl.Designer.cs | 233 ++++ .../Controls/RequestDecodeControl.cs | 68 + .../Controls/RequestDecodeControl.resx | 120 ++ FiddlerDecryption/FiddlerDecryption.csproj | 149 +++ FiddlerDecryption/FodyWeavers.xml | 3 + FiddlerDecryption/Properties/AssemblyInfo.cs | 37 + FiddlerDecryption/README.md | 17 + FiddlerDecryption/RequestDecode.cs | 105 ++ FiddlerDecryption/ResponseDecode.cs | 93 ++ FiddlerDecryption/Utils/DecodeUtil.cs | 212 ++++ FiddlerDecryption/Utils/Setting.cs | 39 + FiddlerDecryption/app.config | 15 + FiddlerDecryption/move-package.bat | 22 + FiddlerDecryption/packages.config | 18 + .../Project/Extensions/ControlExtensions.cs | 27 + .../Project/ICSharpCode.TextEditorEx.csproj | 43 + .../Project/Properties/AssemblyInfo.cs | 13 + .../Project/Properties/Resources.Designer.cs | 193 +++ .../Project/Properties/Resources.resx | 160 +++ ICSharpCode.TextEditor/Project/Readme.txt | 3 + .../Project/Resources/ASPX.xshd | 17 + .../Project/Resources/BAT-Mode.xshd | 32 + .../Project/Resources/Boo.xshd | 303 +++++ .../Project/Resources/CPP-Mode.xshd | 206 ++++ .../Project/Resources/CSharp-Mode.xshd | 339 +++++ .../Project/Resources/Coco-Mode.xshd | 97 ++ .../Project/Resources/HTML-Mode.xshd | 385 ++++++ .../Resources/ICSharpCode.TextEditor.snk | Bin 0 -> 596 bytes .../Project/Resources/JSON.xshd | 66 + .../Project/Resources/Java-Mode.xshd | 180 +++ .../Project/Resources/JavaScript-Mode.xshd | 136 ++ .../Project/Resources/Lua-Mode.xshd | 101 ++ .../Project/Resources/Mode.xsd | 296 +++++ .../Project/Resources/PHP-Mode.xshd | 198 +++ .../Project/Resources/Patch-Mode.xshd | 37 + .../Project/Resources/RightArrow.cur | Bin 0 -> 326 bytes .../Project/Resources/SQL-Mode.xshd | 200 +++ .../Project/Resources/SyntaxModes.xml | 61 + .../Resources/SyntaxModesEx.Readme.txt | 20 + .../Project/Resources/SyntaxModesEx.xml | 8 + .../Project/Resources/Tex-Mode.xshd | 108 ++ .../Project/Resources/TextEditorControl.bmp | Bin 0 -> 824 bytes .../Project/Resources/VBNET-Mode.xshd | 265 ++++ .../Project/Resources/XML-Mode.xshd | 60 + .../Project/Resources/cut.png | Bin 0 -> 648 bytes .../Project/Resources/htmlpage.png | Bin 0 -> 614 bytes .../Project/Resources/sc_cancel.png | Bin 0 -> 510 bytes .../Project/Resources/sc_copy.png | Bin 0 -> 600 bytes .../Project/Resources/sc_cut.png | Bin 0 -> 802 bytes .../Project/Resources/sc_linearrowend.png | Bin 0 -> 539 bytes .../Project/Resources/sc_paste.png | Bin 0 -> 793 bytes .../Project/Resources/sc_redo.png | Bin 0 -> 894 bytes .../Project/Resources/sc_searchdialog.png | Bin 0 -> 680 bytes .../Project/Resources/sc_selectall.png | Bin 0 -> 595 bytes .../Project/Resources/sc_undo.png | Bin 0 -> 888 bytes .../Project/Resources/splitdocument.png | Bin 0 -> 614 bytes .../Project/Src/Actions/BookmarkActions.cs | 80 ++ .../Project/Src/Actions/CaretActions.cs | 203 +++ .../Project/Src/Actions/ClipBoardActions.cs | 42 + .../Src/Actions/FindAndReplaceFormActions.cs | 69 ++ .../Project/Src/Actions/FoldActions.cs | 68 + .../Project/Src/Actions/FormatActions.cs | 219 ++++ .../Src/Actions/GoToLineNumberAction.cs | 28 + .../Project/Src/Actions/HomeEndActions.cs | 114 ++ .../Project/Src/Actions/IEditAction.cs | 58 + .../Project/Src/Actions/MiscActions.cs | 902 ++++++++++++++ .../Project/Src/Actions/SelectionActions.cs | 176 +++ .../Project/Src/Document/AbstractSegment.cs | 52 + .../Src/Document/BookmarkManager/Bookmark.cs | 164 +++ .../BookmarkManager/BookmarkEventHandler.cs | 32 + .../BookmarkManager/BookmarkManager.cs | 246 ++++ .../BookmarkManager/BookmarkManagerMemento.cs | 101 ++ .../Project/Src/Document/DefaultDocument.cs | 457 +++++++ .../Document/DefaultTextEditorProperties.cs | 321 +++++ .../Project/Src/Document/DocumentEventArgs.cs | 103 ++ .../Project/Src/Document/DocumentFactory.cs | 57 + .../FoldingStrategy/CSharpFoldingStrategy.cs | 107 ++ .../Document/FoldingStrategy/FoldMarker.cs | 174 +++ .../FoldingStrategy/FoldingManager.cs | 362 ++++++ .../FoldingStrategy/IFoldingStrategy.cs | 24 + .../FoldingStrategy/IFoldingStrategyEx.cs | 10 + .../FoldingStrategy/IndentFoldingStrategy.cs | 47 + .../FoldingStrategy/JSONFoldingStrategy.cs | 129 ++ .../FoldingStrategy/XmlFoldingStrategy.cs | 310 +++++ .../DefaultFormattingStrategy.cs | 218 ++++ .../FormattingStrategy/IFormattingStrategy.cs | 59 + .../DefaultHighlightingStrategy.cs | 917 ++++++++++++++ .../HighlightingStrategy/FontContainer.cs | 103 ++ .../HighlightBackground.cs | 51 + .../HighlightingStrategy/HighlightColor.cs | 274 +++++ .../HighlightingStrategy/HighlightInfo.cs | 25 + .../HighlightingStrategy/HighlightRuleSet.cs | 182 +++ .../HighlightingColorNotFoundException.cs | 32 + .../HighlightingDefinitionInvalidException.cs | 37 + .../HighlightingDefinitionParser.cs | 110 ++ .../HighlightingManager.cs | 166 +++ .../HighlightingStrategyFactory.cs | 40 + .../IHighlightingStrategy.cs | 67 + .../HighlightingStrategy/NextMarker.cs | 63 + .../HighlightingStrategy/PrevMarker.cs | 63 + .../Src/Document/HighlightingStrategy/Span.cs | 157 +++ .../HighlightingStrategy/SpanStack.cs | 118 ++ .../SyntaxModes/FileSyntaxModeProvider.cs | 84 ++ .../SyntaxModes/ISyntaxModeFileProvider.cs | 23 + .../SyntaxModes/ResourceSyntaxModeProvider.cs | 48 + .../ResourceSyntaxModeProviderEx.cs | 48 + .../SyntaxModes/SyntaxMode.cs | 96 ++ .../Document/HighlightingStrategy/TextWord.cs | 239 ++++ .../Project/Src/Document/IDocument.cs | 315 +++++ .../Project/Src/Document/ISegment.cs | 32 + .../Src/Document/ITextEditorProperties.cs | 177 +++ .../Document/LineManager/DeferredEventList.cs | 44 + .../Src/Document/LineManager/LineManager.cs | 369 ++++++ .../LineManager/LineManagerEventArgs.cs | 97 ++ .../Src/Document/LineManager/LineSegment.cs | 259 ++++ .../Document/LineManager/LineSegmentTree.cs | 477 +++++++ .../Document/MarkerStrategy/MarkerStrategy.cs | 119 ++ .../Src/Document/MarkerStrategy/TextMarker.cs | 103 ++ .../Src/Document/Selection/ColumnRange.cs | 66 + .../Document/Selection/DefaultSelection.cs | 133 ++ .../Src/Document/Selection/ISelection.cs | 64 + .../Document/Selection/SelectionManager.cs | 466 +++++++ .../Project/Src/Document/TextAnchor.cs | 118 ++ .../GapTextBufferStrategy.cs | 194 +++ .../TextBufferStrategy/ITextBufferStrategy.cs | 85 ++ .../StringTextBufferStrategy.cs | 85 ++ .../Project/Src/Document/TextLocation.cs | 128 ++ .../Project/Src/Document/TextUtilities.cs | 313 +++++ .../Project/Src/Gui/AbstractMargin.cs | 115 ++ .../Project/Src/Gui/BracketHighlighter.cs | 86 ++ .../Project/Src/Gui/BrushRegistry.cs | 65 + .../Project/Src/Gui/Caret.cs | 510 ++++++++ .../AbstractCompletionWindow.cs | 214 ++++ .../CodeCompletionListView.cs | 293 +++++ .../CompletionWindow/CodeCompletionWindow.cs | 364 ++++++ .../CompletionWindow/DeclarationViewWindow.cs | 125 ++ .../Gui/CompletionWindow/ICompletionData.cs | 114 ++ .../ICompletionDataProvider.cs | 62 + .../Project/Src/Gui/DrawableLine.cs | 190 +++ .../Project/Src/Gui/FoldMargin.cs | 276 +++++ .../Project/Src/Gui/GutterMargin.cs | 159 +++ .../Project/Src/Gui/HRuler.cs | 54 + .../Project/Src/Gui/IconBarMargin.cs | 254 ++++ ICSharpCode.TextEditor/Project/Src/Gui/Ime.cs | 179 +++ .../Gui/InsightWindow/IInsightDataProvider.cs | 53 + .../Src/Gui/InsightWindow/InsightWindow.cs | 199 +++ .../Project/Src/Gui/TextArea.cs | 947 ++++++++++++++ .../Src/Gui/TextAreaClipboardHandler.cs | 268 ++++ .../Project/Src/Gui/TextAreaControl.cs | 463 +++++++ .../Src/Gui/TextAreaDragDropHandler.cs | 144 +++ .../Project/Src/Gui/TextAreaMouseHandler.cs | 492 ++++++++ .../Project/Src/Gui/TextAreaUpdate.cs | 85 ++ .../Project/Src/Gui/TextEditorControl.cs | 396 ++++++ .../Project/Src/Gui/TextEditorControlBase.cs | 759 ++++++++++++ .../Project/Src/Gui/TextEditorControlEx.cs | 541 ++++++++ .../Project/Src/Gui/TextView.cs | 1096 +++++++++++++++++ .../Src/Gui/ToolTipRequestEventArgs.cs | 62 + .../Project/Src/Undo/IUndoableOperation.cs | 26 + .../Project/Src/Undo/UndoQueue.cs | 53 + .../Project/Src/Undo/UndoStack.cs | 242 ++++ .../Project/Src/Undo/UndoableDelete.cs | 72 ++ .../Project/Src/Undo/UndoableInsert.cs | 73 ++ .../Project/Src/Undo/UndoableReplace.cs | 75 ++ .../Src/Util/AugmentableRedBlackTree.cs | 590 +++++++++ .../Project/Src/Util/CheckedList.cs | 148 +++ .../Project/Src/Util/FileReader.cs | 152 +++ .../Project/Src/Util/LoggingService.cs | 24 + .../Project/Src/Util/LookupTable.cs | 156 +++ .../Project/Src/Util/MouseWheelHandler.cs | 36 + .../Project/Src/Util/RedBlackTreeIterator.cs | 85 ++ .../Project/Src/Util/RtfWriter.cs | 190 +++ .../Project/Src/Util/TextUtility.cs | 81 ++ .../Project/Src/Util/TipPainter.cs | 193 +++ .../Project/Src/Util/TipPainterTools.cs | 301 +++++ .../Project/Src/Util/TipSection.cs | 84 ++ .../Project/Src/Util/TipSpacer.cs | 36 + .../Project/Src/Util/TipSplitter.cs | 99 ++ .../Project/Src/Util/TipText.cs | 183 +++ .../Project/Src/Util/WeakCollection.cs | 136 ++ ICSharpCode.TextEditor/Project/SyntaxModes.cs | 24 + .../ContextMenuStripEx.Designer.cs | 16 + .../UserControls/ContextMenuStripEx.cs | 47 + .../UserControls/FindAndReplaceForm.cs | 556 +++++++++ .../FindAndReplaceForm.designer.cs | 216 ++++ .../UserControls/FindAndReplaceForm.resx | 145 +++ .../Project/UserControls/FormatCodeHtml.cs | 115 ++ .../UserControls/FormatCodeHtml.designer.cs | 261 ++++ .../Project/UserControls/FormatCodeHtml.resx | 120 ++ .../Project/UserControls/GotoForm.Designer.cs | 106 ++ .../Project/UserControls/GotoForm.cs | 67 + .../Project/UserControls/GotoForm.resx | 145 +++ .../Project/UserControls/Int32TextBox.cs | 58 + .../Project/Utils/EventHelperUtils.cs | 121 ++ .../Project/Utils/WeakEventHandler.cs | 37 + QuickTools/Form1.Designer.cs | 185 +++ QuickTools/Form1.cs | 139 +++ QuickTools/Form1.resx | 60 + QuickTools/Program.cs | 17 + QuickTools/QuickTools.csproj | 15 + 215 files changed, 31856 insertions(+) create mode 100644 .gitattributes create mode 100644 .gitignore create mode 100644 EmoneyFiddlerPlugins.sln create mode 100644 EmoneyInteceptor/Emoney/Controls/EmoneyTabControl.Designer.cs create mode 100644 EmoneyInteceptor/Emoney/Controls/EmoneyTabControl.cs create mode 100644 EmoneyInteceptor/Emoney/Controls/EmoneyTabControl.resx create mode 100644 EmoneyInteceptor/Emoney/EmoneyHelper.cs create mode 100644 EmoneyInteceptor/EmoneyInteceptor.csproj create mode 100644 EmoneyInteceptor/Fiddler/Interfaces/IFiddlerLayout.cs create mode 100644 EmoneyInteceptor/Fiddler/Interfaces/IFiddlerViewProvider.cs create mode 100644 EmoneyInteceptor/Fiddler/Models/FiddlerPluginApplication.cs create mode 100644 EmoneyInteceptor/Fiddler/Models/FiddlerTabPage.cs create mode 100644 EmoneyInteceptor/Fiddler/Models/MahjongPlugin.cs create mode 100644 EmoneyInteceptor/Fiddler/Models/PluginViewController.cs create mode 100644 EmoneyInteceptor/Properties/AssemblyInfo.cs create mode 100644 EmoneyInteceptor/packages.config create mode 100644 FiddlerDecryption/Controls/RequestDecodeControl.Designer.cs create mode 100644 FiddlerDecryption/Controls/RequestDecodeControl.cs create mode 100644 FiddlerDecryption/Controls/RequestDecodeControl.resx create mode 100644 FiddlerDecryption/FiddlerDecryption.csproj create mode 100644 FiddlerDecryption/FodyWeavers.xml create mode 100644 FiddlerDecryption/Properties/AssemblyInfo.cs create mode 100644 FiddlerDecryption/README.md create mode 100644 FiddlerDecryption/RequestDecode.cs create mode 100644 FiddlerDecryption/ResponseDecode.cs create mode 100644 FiddlerDecryption/Utils/DecodeUtil.cs create mode 100644 FiddlerDecryption/Utils/Setting.cs create mode 100644 FiddlerDecryption/app.config create mode 100644 FiddlerDecryption/move-package.bat create mode 100644 FiddlerDecryption/packages.config create mode 100644 ICSharpCode.TextEditor/Project/Extensions/ControlExtensions.cs create mode 100644 ICSharpCode.TextEditor/Project/ICSharpCode.TextEditorEx.csproj create mode 100644 ICSharpCode.TextEditor/Project/Properties/AssemblyInfo.cs create mode 100644 ICSharpCode.TextEditor/Project/Properties/Resources.Designer.cs create mode 100644 ICSharpCode.TextEditor/Project/Properties/Resources.resx create mode 100644 ICSharpCode.TextEditor/Project/Readme.txt create mode 100644 ICSharpCode.TextEditor/Project/Resources/ASPX.xshd create mode 100644 ICSharpCode.TextEditor/Project/Resources/BAT-Mode.xshd create mode 100644 ICSharpCode.TextEditor/Project/Resources/Boo.xshd create mode 100644 ICSharpCode.TextEditor/Project/Resources/CPP-Mode.xshd create mode 100644 ICSharpCode.TextEditor/Project/Resources/CSharp-Mode.xshd create mode 100644 ICSharpCode.TextEditor/Project/Resources/Coco-Mode.xshd create mode 100644 ICSharpCode.TextEditor/Project/Resources/HTML-Mode.xshd create mode 100644 ICSharpCode.TextEditor/Project/Resources/ICSharpCode.TextEditor.snk create mode 100644 ICSharpCode.TextEditor/Project/Resources/JSON.xshd create mode 100644 ICSharpCode.TextEditor/Project/Resources/Java-Mode.xshd create mode 100644 ICSharpCode.TextEditor/Project/Resources/JavaScript-Mode.xshd create mode 100644 ICSharpCode.TextEditor/Project/Resources/Lua-Mode.xshd create mode 100644 ICSharpCode.TextEditor/Project/Resources/Mode.xsd create mode 100644 ICSharpCode.TextEditor/Project/Resources/PHP-Mode.xshd create mode 100644 ICSharpCode.TextEditor/Project/Resources/Patch-Mode.xshd create mode 100644 ICSharpCode.TextEditor/Project/Resources/RightArrow.cur create mode 100644 ICSharpCode.TextEditor/Project/Resources/SQL-Mode.xshd create mode 100644 ICSharpCode.TextEditor/Project/Resources/SyntaxModes.xml create mode 100644 ICSharpCode.TextEditor/Project/Resources/SyntaxModesEx.Readme.txt create mode 100644 ICSharpCode.TextEditor/Project/Resources/SyntaxModesEx.xml create mode 100644 ICSharpCode.TextEditor/Project/Resources/Tex-Mode.xshd create mode 100644 ICSharpCode.TextEditor/Project/Resources/TextEditorControl.bmp create mode 100644 ICSharpCode.TextEditor/Project/Resources/VBNET-Mode.xshd create mode 100644 ICSharpCode.TextEditor/Project/Resources/XML-Mode.xshd create mode 100644 ICSharpCode.TextEditor/Project/Resources/cut.png create mode 100644 ICSharpCode.TextEditor/Project/Resources/htmlpage.png create mode 100644 ICSharpCode.TextEditor/Project/Resources/sc_cancel.png create mode 100644 ICSharpCode.TextEditor/Project/Resources/sc_copy.png create mode 100644 ICSharpCode.TextEditor/Project/Resources/sc_cut.png create mode 100644 ICSharpCode.TextEditor/Project/Resources/sc_linearrowend.png create mode 100644 ICSharpCode.TextEditor/Project/Resources/sc_paste.png create mode 100644 ICSharpCode.TextEditor/Project/Resources/sc_redo.png create mode 100644 ICSharpCode.TextEditor/Project/Resources/sc_searchdialog.png create mode 100644 ICSharpCode.TextEditor/Project/Resources/sc_selectall.png create mode 100644 ICSharpCode.TextEditor/Project/Resources/sc_undo.png create mode 100644 ICSharpCode.TextEditor/Project/Resources/splitdocument.png create mode 100644 ICSharpCode.TextEditor/Project/Src/Actions/BookmarkActions.cs create mode 100644 ICSharpCode.TextEditor/Project/Src/Actions/CaretActions.cs create mode 100644 ICSharpCode.TextEditor/Project/Src/Actions/ClipBoardActions.cs create mode 100644 ICSharpCode.TextEditor/Project/Src/Actions/FindAndReplaceFormActions.cs create mode 100644 ICSharpCode.TextEditor/Project/Src/Actions/FoldActions.cs create mode 100644 ICSharpCode.TextEditor/Project/Src/Actions/FormatActions.cs create mode 100644 ICSharpCode.TextEditor/Project/Src/Actions/GoToLineNumberAction.cs create mode 100644 ICSharpCode.TextEditor/Project/Src/Actions/HomeEndActions.cs create mode 100644 ICSharpCode.TextEditor/Project/Src/Actions/IEditAction.cs create mode 100644 ICSharpCode.TextEditor/Project/Src/Actions/MiscActions.cs create mode 100644 ICSharpCode.TextEditor/Project/Src/Actions/SelectionActions.cs create mode 100644 ICSharpCode.TextEditor/Project/Src/Document/AbstractSegment.cs create mode 100644 ICSharpCode.TextEditor/Project/Src/Document/BookmarkManager/Bookmark.cs create mode 100644 ICSharpCode.TextEditor/Project/Src/Document/BookmarkManager/BookmarkEventHandler.cs create mode 100644 ICSharpCode.TextEditor/Project/Src/Document/BookmarkManager/BookmarkManager.cs create mode 100644 ICSharpCode.TextEditor/Project/Src/Document/BookmarkManager/BookmarkManagerMemento.cs create mode 100644 ICSharpCode.TextEditor/Project/Src/Document/DefaultDocument.cs create mode 100644 ICSharpCode.TextEditor/Project/Src/Document/DefaultTextEditorProperties.cs create mode 100644 ICSharpCode.TextEditor/Project/Src/Document/DocumentEventArgs.cs create mode 100644 ICSharpCode.TextEditor/Project/Src/Document/DocumentFactory.cs create mode 100644 ICSharpCode.TextEditor/Project/Src/Document/FoldingStrategy/CSharpFoldingStrategy.cs create mode 100644 ICSharpCode.TextEditor/Project/Src/Document/FoldingStrategy/FoldMarker.cs create mode 100644 ICSharpCode.TextEditor/Project/Src/Document/FoldingStrategy/FoldingManager.cs create mode 100644 ICSharpCode.TextEditor/Project/Src/Document/FoldingStrategy/IFoldingStrategy.cs create mode 100644 ICSharpCode.TextEditor/Project/Src/Document/FoldingStrategy/IFoldingStrategyEx.cs create mode 100644 ICSharpCode.TextEditor/Project/Src/Document/FoldingStrategy/IndentFoldingStrategy.cs create mode 100644 ICSharpCode.TextEditor/Project/Src/Document/FoldingStrategy/JSONFoldingStrategy.cs create mode 100644 ICSharpCode.TextEditor/Project/Src/Document/FoldingStrategy/XmlFoldingStrategy.cs create mode 100644 ICSharpCode.TextEditor/Project/Src/Document/FormattingStrategy/DefaultFormattingStrategy.cs create mode 100644 ICSharpCode.TextEditor/Project/Src/Document/FormattingStrategy/IFormattingStrategy.cs create mode 100644 ICSharpCode.TextEditor/Project/Src/Document/HighlightingStrategy/DefaultHighlightingStrategy.cs create mode 100644 ICSharpCode.TextEditor/Project/Src/Document/HighlightingStrategy/FontContainer.cs create mode 100644 ICSharpCode.TextEditor/Project/Src/Document/HighlightingStrategy/HighlightBackground.cs create mode 100644 ICSharpCode.TextEditor/Project/Src/Document/HighlightingStrategy/HighlightColor.cs create mode 100644 ICSharpCode.TextEditor/Project/Src/Document/HighlightingStrategy/HighlightInfo.cs create mode 100644 ICSharpCode.TextEditor/Project/Src/Document/HighlightingStrategy/HighlightRuleSet.cs create mode 100644 ICSharpCode.TextEditor/Project/Src/Document/HighlightingStrategy/HighlightingColorNotFoundException.cs create mode 100644 ICSharpCode.TextEditor/Project/Src/Document/HighlightingStrategy/HighlightingDefinitionInvalidException.cs create mode 100644 ICSharpCode.TextEditor/Project/Src/Document/HighlightingStrategy/HighlightingDefinitionParser.cs create mode 100644 ICSharpCode.TextEditor/Project/Src/Document/HighlightingStrategy/HighlightingManager.cs create mode 100644 ICSharpCode.TextEditor/Project/Src/Document/HighlightingStrategy/HighlightingStrategyFactory.cs create mode 100644 ICSharpCode.TextEditor/Project/Src/Document/HighlightingStrategy/IHighlightingStrategy.cs create mode 100644 ICSharpCode.TextEditor/Project/Src/Document/HighlightingStrategy/NextMarker.cs create mode 100644 ICSharpCode.TextEditor/Project/Src/Document/HighlightingStrategy/PrevMarker.cs create mode 100644 ICSharpCode.TextEditor/Project/Src/Document/HighlightingStrategy/Span.cs create mode 100644 ICSharpCode.TextEditor/Project/Src/Document/HighlightingStrategy/SpanStack.cs create mode 100644 ICSharpCode.TextEditor/Project/Src/Document/HighlightingStrategy/SyntaxModes/FileSyntaxModeProvider.cs create mode 100644 ICSharpCode.TextEditor/Project/Src/Document/HighlightingStrategy/SyntaxModes/ISyntaxModeFileProvider.cs create mode 100644 ICSharpCode.TextEditor/Project/Src/Document/HighlightingStrategy/SyntaxModes/ResourceSyntaxModeProvider.cs create mode 100644 ICSharpCode.TextEditor/Project/Src/Document/HighlightingStrategy/SyntaxModes/ResourceSyntaxModeProviderEx.cs create mode 100644 ICSharpCode.TextEditor/Project/Src/Document/HighlightingStrategy/SyntaxModes/SyntaxMode.cs create mode 100644 ICSharpCode.TextEditor/Project/Src/Document/HighlightingStrategy/TextWord.cs create mode 100644 ICSharpCode.TextEditor/Project/Src/Document/IDocument.cs create mode 100644 ICSharpCode.TextEditor/Project/Src/Document/ISegment.cs create mode 100644 ICSharpCode.TextEditor/Project/Src/Document/ITextEditorProperties.cs create mode 100644 ICSharpCode.TextEditor/Project/Src/Document/LineManager/DeferredEventList.cs create mode 100644 ICSharpCode.TextEditor/Project/Src/Document/LineManager/LineManager.cs create mode 100644 ICSharpCode.TextEditor/Project/Src/Document/LineManager/LineManagerEventArgs.cs create mode 100644 ICSharpCode.TextEditor/Project/Src/Document/LineManager/LineSegment.cs create mode 100644 ICSharpCode.TextEditor/Project/Src/Document/LineManager/LineSegmentTree.cs create mode 100644 ICSharpCode.TextEditor/Project/Src/Document/MarkerStrategy/MarkerStrategy.cs create mode 100644 ICSharpCode.TextEditor/Project/Src/Document/MarkerStrategy/TextMarker.cs create mode 100644 ICSharpCode.TextEditor/Project/Src/Document/Selection/ColumnRange.cs create mode 100644 ICSharpCode.TextEditor/Project/Src/Document/Selection/DefaultSelection.cs create mode 100644 ICSharpCode.TextEditor/Project/Src/Document/Selection/ISelection.cs create mode 100644 ICSharpCode.TextEditor/Project/Src/Document/Selection/SelectionManager.cs create mode 100644 ICSharpCode.TextEditor/Project/Src/Document/TextAnchor.cs create mode 100644 ICSharpCode.TextEditor/Project/Src/Document/TextBufferStrategy/GapTextBufferStrategy.cs create mode 100644 ICSharpCode.TextEditor/Project/Src/Document/TextBufferStrategy/ITextBufferStrategy.cs create mode 100644 ICSharpCode.TextEditor/Project/Src/Document/TextBufferStrategy/StringTextBufferStrategy.cs create mode 100644 ICSharpCode.TextEditor/Project/Src/Document/TextLocation.cs create mode 100644 ICSharpCode.TextEditor/Project/Src/Document/TextUtilities.cs create mode 100644 ICSharpCode.TextEditor/Project/Src/Gui/AbstractMargin.cs create mode 100644 ICSharpCode.TextEditor/Project/Src/Gui/BracketHighlighter.cs create mode 100644 ICSharpCode.TextEditor/Project/Src/Gui/BrushRegistry.cs create mode 100644 ICSharpCode.TextEditor/Project/Src/Gui/Caret.cs create mode 100644 ICSharpCode.TextEditor/Project/Src/Gui/CompletionWindow/AbstractCompletionWindow.cs create mode 100644 ICSharpCode.TextEditor/Project/Src/Gui/CompletionWindow/CodeCompletionListView.cs create mode 100644 ICSharpCode.TextEditor/Project/Src/Gui/CompletionWindow/CodeCompletionWindow.cs create mode 100644 ICSharpCode.TextEditor/Project/Src/Gui/CompletionWindow/DeclarationViewWindow.cs create mode 100644 ICSharpCode.TextEditor/Project/Src/Gui/CompletionWindow/ICompletionData.cs create mode 100644 ICSharpCode.TextEditor/Project/Src/Gui/CompletionWindow/ICompletionDataProvider.cs create mode 100644 ICSharpCode.TextEditor/Project/Src/Gui/DrawableLine.cs create mode 100644 ICSharpCode.TextEditor/Project/Src/Gui/FoldMargin.cs create mode 100644 ICSharpCode.TextEditor/Project/Src/Gui/GutterMargin.cs create mode 100644 ICSharpCode.TextEditor/Project/Src/Gui/HRuler.cs create mode 100644 ICSharpCode.TextEditor/Project/Src/Gui/IconBarMargin.cs create mode 100644 ICSharpCode.TextEditor/Project/Src/Gui/Ime.cs create mode 100644 ICSharpCode.TextEditor/Project/Src/Gui/InsightWindow/IInsightDataProvider.cs create mode 100644 ICSharpCode.TextEditor/Project/Src/Gui/InsightWindow/InsightWindow.cs create mode 100644 ICSharpCode.TextEditor/Project/Src/Gui/TextArea.cs create mode 100644 ICSharpCode.TextEditor/Project/Src/Gui/TextAreaClipboardHandler.cs create mode 100644 ICSharpCode.TextEditor/Project/Src/Gui/TextAreaControl.cs create mode 100644 ICSharpCode.TextEditor/Project/Src/Gui/TextAreaDragDropHandler.cs create mode 100644 ICSharpCode.TextEditor/Project/Src/Gui/TextAreaMouseHandler.cs create mode 100644 ICSharpCode.TextEditor/Project/Src/Gui/TextAreaUpdate.cs create mode 100644 ICSharpCode.TextEditor/Project/Src/Gui/TextEditorControl.cs create mode 100644 ICSharpCode.TextEditor/Project/Src/Gui/TextEditorControlBase.cs create mode 100644 ICSharpCode.TextEditor/Project/Src/Gui/TextEditorControlEx.cs create mode 100644 ICSharpCode.TextEditor/Project/Src/Gui/TextView.cs create mode 100644 ICSharpCode.TextEditor/Project/Src/Gui/ToolTipRequestEventArgs.cs create mode 100644 ICSharpCode.TextEditor/Project/Src/Undo/IUndoableOperation.cs create mode 100644 ICSharpCode.TextEditor/Project/Src/Undo/UndoQueue.cs create mode 100644 ICSharpCode.TextEditor/Project/Src/Undo/UndoStack.cs create mode 100644 ICSharpCode.TextEditor/Project/Src/Undo/UndoableDelete.cs create mode 100644 ICSharpCode.TextEditor/Project/Src/Undo/UndoableInsert.cs create mode 100644 ICSharpCode.TextEditor/Project/Src/Undo/UndoableReplace.cs create mode 100644 ICSharpCode.TextEditor/Project/Src/Util/AugmentableRedBlackTree.cs create mode 100644 ICSharpCode.TextEditor/Project/Src/Util/CheckedList.cs create mode 100644 ICSharpCode.TextEditor/Project/Src/Util/FileReader.cs create mode 100644 ICSharpCode.TextEditor/Project/Src/Util/LoggingService.cs create mode 100644 ICSharpCode.TextEditor/Project/Src/Util/LookupTable.cs create mode 100644 ICSharpCode.TextEditor/Project/Src/Util/MouseWheelHandler.cs create mode 100644 ICSharpCode.TextEditor/Project/Src/Util/RedBlackTreeIterator.cs create mode 100644 ICSharpCode.TextEditor/Project/Src/Util/RtfWriter.cs create mode 100644 ICSharpCode.TextEditor/Project/Src/Util/TextUtility.cs create mode 100644 ICSharpCode.TextEditor/Project/Src/Util/TipPainter.cs create mode 100644 ICSharpCode.TextEditor/Project/Src/Util/TipPainterTools.cs create mode 100644 ICSharpCode.TextEditor/Project/Src/Util/TipSection.cs create mode 100644 ICSharpCode.TextEditor/Project/Src/Util/TipSpacer.cs create mode 100644 ICSharpCode.TextEditor/Project/Src/Util/TipSplitter.cs create mode 100644 ICSharpCode.TextEditor/Project/Src/Util/TipText.cs create mode 100644 ICSharpCode.TextEditor/Project/Src/Util/WeakCollection.cs create mode 100644 ICSharpCode.TextEditor/Project/SyntaxModes.cs create mode 100644 ICSharpCode.TextEditor/Project/UserControls/ContextMenuStripEx.Designer.cs create mode 100644 ICSharpCode.TextEditor/Project/UserControls/ContextMenuStripEx.cs create mode 100644 ICSharpCode.TextEditor/Project/UserControls/FindAndReplaceForm.cs create mode 100644 ICSharpCode.TextEditor/Project/UserControls/FindAndReplaceForm.designer.cs create mode 100644 ICSharpCode.TextEditor/Project/UserControls/FindAndReplaceForm.resx create mode 100644 ICSharpCode.TextEditor/Project/UserControls/FormatCodeHtml.cs create mode 100644 ICSharpCode.TextEditor/Project/UserControls/FormatCodeHtml.designer.cs create mode 100644 ICSharpCode.TextEditor/Project/UserControls/FormatCodeHtml.resx create mode 100644 ICSharpCode.TextEditor/Project/UserControls/GotoForm.Designer.cs create mode 100644 ICSharpCode.TextEditor/Project/UserControls/GotoForm.cs create mode 100644 ICSharpCode.TextEditor/Project/UserControls/GotoForm.resx create mode 100644 ICSharpCode.TextEditor/Project/UserControls/Int32TextBox.cs create mode 100644 ICSharpCode.TextEditor/Project/Utils/EventHelperUtils.cs create mode 100644 ICSharpCode.TextEditor/Project/Utils/WeakEventHandler.cs create mode 100644 QuickTools/Form1.Designer.cs create mode 100644 QuickTools/Form1.cs create mode 100644 QuickTools/Form1.resx create mode 100644 QuickTools/Program.cs create mode 100644 QuickTools/QuickTools.csproj diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..1ff0c42 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,63 @@ +############################################################################### +# Set default behavior to automatically normalize line endings. +############################################################################### +* text=auto + +############################################################################### +# Set default behavior for command prompt diff. +# +# This is need for earlier builds of msysgit that does not have it on by +# default for csharp files. +# Note: This is only used by command line +############################################################################### +#*.cs diff=csharp + +############################################################################### +# Set the merge driver for project and solution files +# +# Merging from the command prompt will add diff markers to the files if there +# are conflicts (Merging from VS is not affected by the settings below, in VS +# the diff markers are never inserted). Diff markers may cause the following +# file extensions to fail to load in VS. An alternative would be to treat +# these files as binary and thus will always conflict and require user +# intervention with every merge. To do so, just uncomment the entries below +############################################################################### +#*.sln merge=binary +#*.csproj merge=binary +#*.vbproj merge=binary +#*.vcxproj merge=binary +#*.vcproj merge=binary +#*.dbproj merge=binary +#*.fsproj merge=binary +#*.lsproj merge=binary +#*.wixproj merge=binary +#*.modelproj merge=binary +#*.sqlproj merge=binary +#*.wwaproj merge=binary + +############################################################################### +# behavior for image files +# +# image files are treated as binary by default. +############################################################################### +#*.jpg binary +#*.png binary +#*.gif binary + +############################################################################### +# diff behavior for common document formats +# +# Convert binary document formats to text before diffing them. This feature +# is only available from the command line. Turn it on by uncommenting the +# entries below. +############################################################################### +#*.doc diff=astextplain +#*.DOC diff=astextplain +#*.docx diff=astextplain +#*.DOCX diff=astextplain +#*.dot diff=astextplain +#*.DOT diff=astextplain +#*.pdf diff=astextplain +#*.PDF diff=astextplain +#*.rtf diff=astextplain +#*.RTF diff=astextplain diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9491a2f --- /dev/null +++ b/.gitignore @@ -0,0 +1,363 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. +## +## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore + +# User-specific files +*.rsuser +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Mono auto generated files +mono_crash.* + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +[Ww][Ii][Nn]32/ +[Aa][Rr][Mm]/ +[Aa][Rr][Mm]64/ +bld/ +[Bb]in/ +[Oo]bj/ +[Oo]ut/ +[Ll]og/ +[Ll]ogs/ + +# Visual Studio 2015/2017 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# Visual Studio 2017 auto generated files +Generated\ Files/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUnit +*.VisualState.xml +TestResult.xml +nunit-*.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# Benchmark Results +BenchmarkDotNet.Artifacts/ + +# .NET Core +project.lock.json +project.fragment.lock.json +artifacts/ + +# ASP.NET Scaffolding +ScaffoldingReadMe.txt + +# StyleCop +StyleCopReport.xml + +# Files built by Visual Studio +*_i.c +*_p.c +*_h.h +*.ilk +*.meta +*.obj +*.iobj +*.pch +*.pdb +*.ipdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*_wpftmp.csproj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# Visual Studio Trace Files +*.e2e + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# AxoCover is a Code Coverage Tool +.axoCover/* +!.axoCover/settings.json + +# Coverlet is a free, cross platform Code Coverage Tool +coverage*.json +coverage*.xml +coverage*.info + +# Visual Studio code coverage results +*.coverage +*.coveragexml + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# Note: Comment the next line if you want to checkin your web deploy settings, +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# NuGet Symbol Packages +*.snupkg +# The packages folder can be ignored because of Package Restore +**/[Pp]ackages/* +# except build/, which is used as an MSBuild target. +!**/[Pp]ackages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/[Pp]ackages/repositories.config +# NuGet v3's project.json files produces more ignorable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt +*.appx +*.appxbundle +*.appxupload + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!?*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +orleans.codegen.cs + +# Including strong name files can present a security risk +# (https://github.com/github/gitignore/pull/2483#issue-259490424) +#*.snk + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm +ServiceFabricBackup/ +*.rptproj.bak + +# SQL Server files +*.mdf +*.ldf +*.ndf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings +*.rptproj.rsuser +*- [Bb]ackup.rdl +*- [Bb]ackup ([0-9]).rdl +*- [Bb]ackup ([0-9][0-9]).rdl + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat +node_modules/ + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) +*.vbw + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# CodeRush personal settings +.cr/personal + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc + +# Cake - Uncomment if you are using it +# tools/** +# !tools/packages.config + +# Tabs Studio +*.tss + +# Telerik's JustMock configuration file +*.jmconfig + +# BizTalk build output +*.btp.cs +*.btm.cs +*.odx.cs +*.xsd.cs + +# OpenCover UI analysis results +OpenCover/ + +# Azure Stream Analytics local run output +ASALocalRun/ + +# MSBuild Binary and Structured Log +*.binlog + +# NVidia Nsight GPU debugger configuration file +*.nvuser + +# MFractors (Xamarin productivity tool) working folder +.mfractor/ + +# Local History for Visual Studio +.localhistory/ + +# BeatPulse healthcheck temp database +healthchecksdb + +# Backup folder for Package Reference Convert tool in Visual Studio 2017 +MigrationBackup/ + +# Ionide (cross platform F# VS Code tools) working folder +.ionide/ + +# Fody - auto-generated XML schema +FodyWeavers.xsd \ No newline at end of file diff --git a/EmoneyFiddlerPlugins.sln b/EmoneyFiddlerPlugins.sln new file mode 100644 index 0000000..f0dc608 --- /dev/null +++ b/EmoneyFiddlerPlugins.sln @@ -0,0 +1,44 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.5.33516.290 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FiddlerDecryption", "FiddlerDecryption\FiddlerDecryption.csproj", "{1210036A-18F1-4786-BC4F-C3B2AECDE45B}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EmoneyInteceptor", "EmoneyInteceptor\EmoneyInteceptor.csproj", "{77AF554C-514F-4513-811A-AB2CA1BCCCAF}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "QuickTools", "QuickTools\QuickTools.csproj", "{0A5E365F-A87F-40EE-8EE5-5131BBC5856C}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ICSharpCode.TextEditorEx", "ICSharpCode.TextEditor\Project\ICSharpCode.TextEditorEx.csproj", "{2D18BE89-D210-49EB-A9DD-2246FBB3DF6D}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {1210036A-18F1-4786-BC4F-C3B2AECDE45B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1210036A-18F1-4786-BC4F-C3B2AECDE45B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1210036A-18F1-4786-BC4F-C3B2AECDE45B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1210036A-18F1-4786-BC4F-C3B2AECDE45B}.Release|Any CPU.Build.0 = Release|Any CPU + {77AF554C-514F-4513-811A-AB2CA1BCCCAF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {77AF554C-514F-4513-811A-AB2CA1BCCCAF}.Debug|Any CPU.Build.0 = Debug|Any CPU + {77AF554C-514F-4513-811A-AB2CA1BCCCAF}.Release|Any CPU.ActiveCfg = Release|Any CPU + {77AF554C-514F-4513-811A-AB2CA1BCCCAF}.Release|Any CPU.Build.0 = Release|Any CPU + {0A5E365F-A87F-40EE-8EE5-5131BBC5856C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0A5E365F-A87F-40EE-8EE5-5131BBC5856C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0A5E365F-A87F-40EE-8EE5-5131BBC5856C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0A5E365F-A87F-40EE-8EE5-5131BBC5856C}.Release|Any CPU.Build.0 = Release|Any CPU + {2D18BE89-D210-49EB-A9DD-2246FBB3DF6D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2D18BE89-D210-49EB-A9DD-2246FBB3DF6D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2D18BE89-D210-49EB-A9DD-2246FBB3DF6D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2D18BE89-D210-49EB-A9DD-2246FBB3DF6D}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + VisualSVNWorkingCopyRoot = . + SolutionGuid = {6282D12B-C0D1-4523-8160-A6BC12E74513} + EndGlobalSection +EndGlobal diff --git a/EmoneyInteceptor/Emoney/Controls/EmoneyTabControl.Designer.cs b/EmoneyInteceptor/Emoney/Controls/EmoneyTabControl.Designer.cs new file mode 100644 index 0000000..c5f970f --- /dev/null +++ b/EmoneyInteceptor/Emoney/Controls/EmoneyTabControl.Designer.cs @@ -0,0 +1,237 @@ +namespace EmoneyInteceptor.Emoney.Controls +{ + partial class EmoneyTabControl + { + /// + /// 必需的设计器变量。 + /// + private System.ComponentModel.IContainer components = null; + + /// + /// 清理所有正在使用的资源。 + /// + /// 如果应释放托管资源,为 true;否则为 false。 + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region 组件设计器生成的代码 + + /// + /// 设计器支持所需的方法 - 不要修改 + /// 使用代码编辑器修改此方法的内容。 + /// + private void InitializeComponent() + { + this.tableLayoutPanel1 = new System.Windows.Forms.TableLayoutPanel(); + this.groupBox1 = new System.Windows.Forms.GroupBox(); + this.txtRequestJson = new System.Windows.Forms.TextBox(); + this.flowLayoutPanel2 = new System.Windows.Forms.FlowLayoutPanel(); + this.btnEncode = new System.Windows.Forms.Button(); + this.btnDecode = new System.Windows.Forms.Button(); + this.txtProtobufBody = new System.Windows.Forms.TextBox(); + this.groupBox2 = new System.Windows.Forms.GroupBox(); + this.label1 = new System.Windows.Forms.Label(); + this.txtProtocolID = new System.Windows.Forms.TextBox(); + this.btnRrquestEncode = new System.Windows.Forms.Button(); + this.label2 = new System.Windows.Forms.Label(); + this.txtAuth = new System.Windows.Forms.TextBox(); + this.panel1 = new System.Windows.Forms.Panel(); + this.tableLayoutPanel1.SuspendLayout(); + this.groupBox1.SuspendLayout(); + this.flowLayoutPanel2.SuspendLayout(); + this.groupBox2.SuspendLayout(); + this.panel1.SuspendLayout(); + this.SuspendLayout(); + // + // tableLayoutPanel1 + // + this.tableLayoutPanel1.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) + | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.tableLayoutPanel1.ColumnCount = 1; + this.tableLayoutPanel1.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 100F)); + this.tableLayoutPanel1.Controls.Add(this.groupBox1, 0, 0); + this.tableLayoutPanel1.Controls.Add(this.flowLayoutPanel2, 0, 1); + this.tableLayoutPanel1.Controls.Add(this.groupBox2, 0, 2); + this.tableLayoutPanel1.Location = new System.Drawing.Point(6, 69); + this.tableLayoutPanel1.Name = "tableLayoutPanel1"; + this.tableLayoutPanel1.RowCount = 3; + this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 75.54112F)); + this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 35F)); + this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 24.45888F)); + this.tableLayoutPanel1.Size = new System.Drawing.Size(569, 415); + this.tableLayoutPanel1.TabIndex = 1; + // + // groupBox1 + // + this.groupBox1.Controls.Add(this.txtRequestJson); + this.groupBox1.Dock = System.Windows.Forms.DockStyle.Fill; + this.groupBox1.Location = new System.Drawing.Point(3, 3); + this.groupBox1.Name = "groupBox1"; + this.groupBox1.Size = new System.Drawing.Size(563, 281); + this.groupBox1.TabIndex = 0; + this.groupBox1.TabStop = false; + this.groupBox1.Text = "JSON"; + // + // txtRequestJson + // + this.txtRequestJson.Dock = System.Windows.Forms.DockStyle.Fill; + this.txtRequestJson.Location = new System.Drawing.Point(3, 19); + this.txtRequestJson.MaxLength = 2147483647; + this.txtRequestJson.Multiline = true; + this.txtRequestJson.Name = "txtRequestJson"; + this.txtRequestJson.ScrollBars = System.Windows.Forms.ScrollBars.Both; + this.txtRequestJson.Size = new System.Drawing.Size(557, 259); + this.txtRequestJson.TabIndex = 0; + // + // flowLayoutPanel2 + // + this.flowLayoutPanel2.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom))); + this.flowLayoutPanel2.Controls.Add(this.btnEncode); + this.flowLayoutPanel2.Controls.Add(this.btnDecode); + this.flowLayoutPanel2.Location = new System.Drawing.Point(203, 290); + this.flowLayoutPanel2.Name = "flowLayoutPanel2"; + this.flowLayoutPanel2.Size = new System.Drawing.Size(162, 29); + this.flowLayoutPanel2.TabIndex = 3; + // + // btnEncode + // + this.btnEncode.Location = new System.Drawing.Point(3, 3); + this.btnEncode.Name = "btnEncode"; + this.btnEncode.Size = new System.Drawing.Size(75, 23); + this.btnEncode.TabIndex = 0; + this.btnEncode.Text = "编码↓"; + this.btnEncode.UseVisualStyleBackColor = true; + this.btnEncode.Click += new System.EventHandler(this.btnEncode_Click); + // + // btnDecode + // + this.btnDecode.Location = new System.Drawing.Point(84, 3); + this.btnDecode.Name = "btnDecode"; + this.btnDecode.Size = new System.Drawing.Size(75, 23); + this.btnDecode.TabIndex = 1; + this.btnDecode.Text = "解码↑"; + this.btnDecode.UseVisualStyleBackColor = true; + // + // txtProtobufBody + // + this.txtProtobufBody.Dock = System.Windows.Forms.DockStyle.Fill; + this.txtProtobufBody.Location = new System.Drawing.Point(3, 19); + this.txtProtobufBody.MaxLength = 2147483647; + this.txtProtobufBody.Multiline = true; + this.txtProtobufBody.Name = "txtProtobufBody"; + this.txtProtobufBody.ScrollBars = System.Windows.Forms.ScrollBars.Both; + this.txtProtobufBody.Size = new System.Drawing.Size(557, 65); + this.txtProtobufBody.TabIndex = 0; + // + // groupBox2 + // + this.groupBox2.Controls.Add(this.txtProtobufBody); + this.groupBox2.Location = new System.Drawing.Point(3, 325); + this.groupBox2.Name = "groupBox2"; + this.groupBox2.Size = new System.Drawing.Size(563, 87); + this.groupBox2.TabIndex = 2; + this.groupBox2.TabStop = false; + this.groupBox2.Text = "Protobuf(Base64)"; + // + // label1 + // + this.label1.AutoSize = true; + this.label1.Location = new System.Drawing.Point(3, 10); + this.label1.Name = "label1"; + this.label1.Size = new System.Drawing.Size(74, 17); + this.label1.TabIndex = 0; + this.label1.Text = "Protocol ID"; + // + // txtProtocolID + // + this.txtProtocolID.Location = new System.Drawing.Point(93, 7); + this.txtProtocolID.Name = "txtProtocolID"; + this.txtProtocolID.Size = new System.Drawing.Size(100, 23); + this.txtProtocolID.TabIndex = 1; + // + // btnRrquestEncode + // + this.btnRrquestEncode.Location = new System.Drawing.Point(199, 7); + this.btnRrquestEncode.Name = "btnRrquestEncode"; + this.btnRrquestEncode.Size = new System.Drawing.Size(75, 23); + this.btnRrquestEncode.TabIndex = 2; + this.btnRrquestEncode.Text = "请求"; + this.btnRrquestEncode.UseVisualStyleBackColor = true; + // + // label2 + // + this.label2.AutoSize = true; + this.label2.Location = new System.Drawing.Point(3, 39); + this.label2.Name = "label2"; + this.label2.Size = new System.Drawing.Size(85, 17); + this.label2.TabIndex = 0; + this.label2.Text = "Authorization"; + // + // txtAuth + // + this.txtAuth.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.txtAuth.Location = new System.Drawing.Point(93, 36); + this.txtAuth.Name = "txtAuth"; + this.txtAuth.Size = new System.Drawing.Size(471, 23); + this.txtAuth.TabIndex = 1; + // + // panel1 + // + this.panel1.Controls.Add(this.btnRrquestEncode); + this.panel1.Controls.Add(this.tableLayoutPanel1); + this.panel1.Controls.Add(this.txtAuth); + this.panel1.Controls.Add(this.label2); + this.panel1.Controls.Add(this.txtProtocolID); + this.panel1.Controls.Add(this.label1); + this.panel1.Dock = System.Windows.Forms.DockStyle.Fill; + this.panel1.Location = new System.Drawing.Point(0, 0); + this.panel1.Name = "panel1"; + this.panel1.Size = new System.Drawing.Size(578, 484); + this.panel1.TabIndex = 2; + // + // EmoneyTabControl + // + this.AutoScaleDimensions = new System.Drawing.SizeF(7F, 17F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.Controls.Add(this.panel1); + this.Font = new System.Drawing.Font("微软雅黑", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134))); + this.Margin = new System.Windows.Forms.Padding(4); + this.Name = "EmoneyTabControl"; + this.Size = new System.Drawing.Size(578, 484); + this.tableLayoutPanel1.ResumeLayout(false); + this.groupBox1.ResumeLayout(false); + this.groupBox1.PerformLayout(); + this.flowLayoutPanel2.ResumeLayout(false); + this.groupBox2.ResumeLayout(false); + this.groupBox2.PerformLayout(); + this.panel1.ResumeLayout(false); + this.panel1.PerformLayout(); + this.ResumeLayout(false); + + } + + #endregion + private System.Windows.Forms.TableLayoutPanel tableLayoutPanel1; + private System.Windows.Forms.GroupBox groupBox1; + private System.Windows.Forms.TextBox txtRequestJson; + private System.Windows.Forms.FlowLayoutPanel flowLayoutPanel2; + private System.Windows.Forms.Button btnEncode; + private System.Windows.Forms.Button btnDecode; + private System.Windows.Forms.GroupBox groupBox2; + private System.Windows.Forms.TextBox txtProtobufBody; + private System.Windows.Forms.Label label1; + private System.Windows.Forms.TextBox txtProtocolID; + private System.Windows.Forms.Button btnRrquestEncode; + private System.Windows.Forms.Label label2; + private System.Windows.Forms.TextBox txtAuth; + private System.Windows.Forms.Panel panel1; + } +} diff --git a/EmoneyInteceptor/Emoney/Controls/EmoneyTabControl.cs b/EmoneyInteceptor/Emoney/Controls/EmoneyTabControl.cs new file mode 100644 index 0000000..cad4332 --- /dev/null +++ b/EmoneyInteceptor/Emoney/Controls/EmoneyTabControl.cs @@ -0,0 +1,343 @@ +using Fiddler; +using Flurl.Http; +using Newtonsoft.Json.Linq; +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Data; +using System.Diagnostics; +using System.Drawing; +using System.IO; +using System.Linq; +using System.Text; +using System.Text.Json.Nodes; +using System.Threading.Tasks; +using System.Windows.Forms; + +namespace EmoneyInteceptor.Emoney.Controls +{ + public partial class EmoneyTabControl : UserControl + { + public EmoneyTabControl() + { + InitializeComponent(); + } + + public void OnWebSocketMessage(object sender, WebSocketMessageEventArgs args) + { + if (sender is Session session) + { + FiddlerApplication.Log.LogString($"Host name:{session.hostname}, host:{session.host}"); + } + } + + public void BeforeRequest(Session oSession) + { + // host 带端口,hostname 不带 + if (oSession == null) return; + if (!oSession.hostname.ToLower().EndsWith("emoney.cn")) return; + //FiddlerApplication.Log.LogString($"Host name:{oSession.hostname}, host:{oSession.host}, url: {oSession.url}"); + if (oSession.host.ToLower().Contains("mstat.emoney.cn")) + { + FiddlerApplication.Log.LogString($"mstat request uri: {oSession.url}"); + if (oSession.uriContains("/UserAnalysis/upload")) + { + // 清除信息,不追踪 + JObject jo = JObject.Parse(oSession.GetRequestBodyAsString()); + jo["events"] = new JArray(); + jo["pages"] = new JArray(); + FiddlerApplication.Log.LogString("Clean UserAnalysis from Emony"); + oSession.RequestBody = Encoding.UTF8.GetBytes(jo.ToString(Newtonsoft.Json.Formatting.None)); + } + } + } + + public void BeforeResponse(Session oSession) + { + int pid = 888020000; + if (oSession == null) return; + if (!oSession.hostname.ToLower().EndsWith("emoney.cn") + && !oSession.hostname.ToLower().EndsWith("aliyuncs.com")) return; + + if (oSession.RequestHeaders.Exists("Authorization")) + { + if (txtAuth.Text != oSession.RequestHeaders["Authorization"]) + txtAuth.Text = oSession.RequestHeaders["Authorization"]; + } + + if (oSession.uriContains("/user/auth/login")) + { + return; + // 用户信息 inject + if (!oSession.ResponseHeaders.ExistsAndContains("Content-Type", "application/json")) return; + + Debug.Write(oSession.GetResponseBodyAsString()); + + JObject jo = JObject.Parse(oSession.GetResponseBodyAsString()); + jo["detail"]["pc"]["isMaster"] = true; + jo["detail"]["pc"]["isZy"] = true; + jo["detail"]["pc"]["pid"] = pid; + oSession.ResponseBody = Encoding.UTF8.GetBytes(jo.ToString(Newtonsoft.Json.Formatting.None)); + } + if (oSession.uriContains("/user/info")) + { + // 用户信息 inject + if (!oSession.ResponseHeaders.ExistsAndContains("Content-Type", "application/json")) return; + + Debug.Write(oSession.GetResponseBodyAsString()); + + JObject jo = JObject.Parse(oSession.GetResponseBodyAsString()); + jo["detail"]["pc"]["isMaster"] = true; + jo["detail"]["pc"]["isZy"] = true; + jo["detail"]["pc"]["pid"] = pid; + jo["detail"]["login"]["loginType"] = 1; + jo["detail"]["isShareSucc"] = true; + jo["detail"]["enableLiveShare"] = true; + oSession.ResponseBody = Encoding.UTF8.GetBytes(jo.ToString(Newtonsoft.Json.Formatting.None)); + } + if (oSession.uriContains("/kankan/kankan/LoginKanKan")) + { + // 用户信息 inject + if (!oSession.ResponseHeaders.ExistsAndContains("Content-Type", "application/json")) return; + + Debug.Write(oSession.GetResponseBodyAsString()); + + JObject jo = JObject.Parse(oSession.GetResponseBodyAsString()); + jo["detail"]["pid"] = pid; + oSession.ResponseBody = Encoding.UTF8.GetBytes(jo.ToString(Newtonsoft.Json.Formatting.None)); + } + if (oSession.uriContains("/Config/UserExtendInfo/Index")) + { + return; + // 额外信息 inject + if (!oSession.ResponseHeaders.ExistsAndContains("Content-Type", "application/json")) return; + Debug.Write(oSession.GetResponseBodyAsString()); + + JObject jo = JObject.Parse(oSession.GetResponseBodyAsString()); + jo["detail"]["product"]["id"] = pid; + oSession.ResponseBody = Encoding.UTF8.GetBytes(jo.ToString(Newtonsoft.Json.Formatting.None)); + } + if (oSession.uriContains("/user/authority/isshareenable")) + { + return; + if (!oSession.ResponseHeaders.ExistsAndContains("Content-Type", "application/json")) return; + Debug.Write(oSession.GetResponseBodyAsString()); + + JObject jo = JObject.Parse(oSession.GetResponseBodyAsString()); + jo["detail"] = true; + oSession.ResponseBody = Encoding.UTF8.GetBytes(jo.ToString(Newtonsoft.Json.Formatting.None)); + } + if (oSession.uriContains("v1000003/config_ind_online.json") && oSession.HTTPMethodIs("GET")) + { + if (File.Exists("config_ind_online.json")) + { + string configFile = File.ReadAllText("config_ind_online.json"); + oSession.ResponseBody = Encoding.UTF8.GetBytes(configFile); + } + } + if (oSession.uriContains("/Strategy/PickStock/Filter")) + { + // 选股过滤器默认规则拦截,将 isLocked 由 true 改为 false + if (!oSession.ResponseHeaders.ExistsAndContains("Content-Type", "application/json")) return; + Debug.WriteLine(oSession.GetResponseBodyAsString()); + + JObject jo = JObject.Parse(oSession.GetResponseBodyAsString()); + + // 三个层级 + // 顶层 detail.isLocked + // 子层 detail.subFilters[].isLocked + // 孙层 detail.subFilters[].....fields[].isLocked + // 如果 subFilters 还有 subFilters 就继续遍历 + void UnlockFilter(JToken subFilter) + { + if (subFilter == null) return; + + // 当前层级有 isLocked 则清除 + // 适用于顶级 detail、每层 subFilter、孙子节点 field + subFilter.SelectToken("isLocked")?.Replace(false); + + var subFilters = subFilter.SelectToken("subFilters"); + if (subFilters != null) + { + // 当前层 subFilters 不为 null,则对其中每个 subFilter 都处理 + foreach(var _subFilter in subFilters) + { + UnlockFilter(_subFilter); + } + } + else + { + // 当前层已不含 subFilters,则应该存在 fields + var fields = subFilter?.SelectToken("fields"); + if (fields != null) + { + foreach (var field in fields) + { + field?.SelectToken("isLocked")?.Replace(false); + } + } + } + } + string[] strategyCodes = { + "strategy_intersection_more", // 策略叠加内的策略叠加 + "super_selection_cldj" // 超级选股内的策略叠加 + }; + void AddStrategyIntersection(JToken subFilter) + { + if (subFilter == null) return; // 当前 token 是空直接返回 + JArray subFilters = subFilter.SelectToken("subFilters")?.Value(); + if (strategyCodes.Contains(subFilter["code"].Value())) + { + // 到相应层级,如果原本没有的就覆盖添加相应的内容 + if (subFilters == null) + { + subFilters = new JArray(); + subFilter["subFilters"] = subFilters; + } + // 载入我们自己准备好的策略权限 + if (!File.Exists("prepared_strategies.json")) return; + string str = File.ReadAllText("prepared_strategies.json"); + JArray jArray = JArray.Parse(str); + foreach (JToken strategy in jArray) + { + if (!subFilters.Any(f => f["code"].Value() == strategy["code"].Value())) + { + subFilters.Add(strategy); + } + } + return; + } + if (subFilters == null) return; + foreach (var _subFilter in subFilters) + { + AddStrategyIntersection(_subFilter); + } + } + + // detail 本身就是一个 filter + UnlockFilter(jo.SelectToken("detail")); + AddStrategyIntersection(jo.SelectToken("detail")); + + oSession.ResponseBody = Encoding.UTF8.GetBytes(jo.ToString(Newtonsoft.Json.Formatting.None)); + } + if (oSession.uriContains("/user/Authority/GetMyAuthority")) + { + if (!oSession.ResponseHeaders.ExistsAndContains("Content-Type", "application/json")) return; + Debug.WriteLine(oSession.GetResponseBodyAsString()); + + JObject jo = JObject.Parse(oSession.GetResponseBodyAsString()); + + // 添加快速选股权限,该权限未在 config_ind_online.json 中体现 + // 是在分析网页选股的时候发现的 + + var stockPickingAuthorities = new Dictionary + { + { "kuaisuxuangu", "快速选股" }, + { "zonghexuangu", "综合选股" }, + { "celvediejia", "策略叠加" } + }; + + foreach (var stockPickingAuthority in stockPickingAuthorities) + { + jo["detail"].Value().Add(new JObject + { + ["code"] = stockPickingAuthority.Key, + ["name"] = stockPickingAuthority.Value, + ["isValid"] = true, + ["isFee"] = true, + ["startTime"] = 1688486400000, + ["endTime"] = 7258089599000, + ["needPermission"] = true, + ["groupId"] = 9, + ["groupName"] = "选股" + }); + } + + if (File.Exists("config_ind_online.json")) + { + string configFile = File.ReadAllText("config_ind_online.json"); + JObject indConfig = JObject.Parse(configFile); + JObject indIdNameConfig = indConfig["indIdNameConfig"].Value(); + JObject indMap = indConfig["indMap"].Value(); + JArray indGruopList = indConfig["indGroupList"].Value(); + foreach (var pair in indMap) + { + string indId = pair.Key; + JObject ind = pair.Value as JObject; + if (ind["isCalc"].Value() == 1) continue; + if (ind["noPermShow"] == null || ind["noPermShow"].Value()) continue; + string permCode = ind["permCode"].Value(); + string groupId = "10"; + string groupName = "决策"; + // 只专注于在线隐藏指标 + if (jo["detail"].Any(o => o["code"].Value() == permCode)) + { + Debug.WriteLine($"已有 {permCode} 不覆盖"); + continue; + } + jo["detail"].Value().Add(new JObject + { + ["code"] = permCode, + ["name"] = indIdNameConfig[indId].Value(), + ["isValid"] = true, + ["isFee"] = true, + ["startTime"] = 1688486400000, + ["endTime"] = 7258089599000, + ["needPermission"] = true, + ["groupId"] = int.Parse(groupId), + ["groupName"] = groupName + }); + } + } + + oSession.ResponseBody = Encoding.UTF8.GetBytes(jo.ToString(Newtonsoft.Json.Formatting.None)); + } + } + + private async void btnEncode_Click(object sender, EventArgs e) + { + if (txtProtocolID.Text.Trim() == string.Empty) + { + MessageBox.Show("ProtocolID 不能为空!", "警告", MessageBoxButtons.OK, MessageBoxIcon.Warning); + return; + } + int protocolId; + try + { + protocolId = Convert.ToInt32(txtProtocolID.Text.Trim()); + } + catch + { + MessageBox.Show("ProtocolID 只能为数字!", "警告", MessageBoxButtons.OK, MessageBoxIcon.Warning); + return; + } + var url = "http://localhost:9999/api/v1/emoney/converter/request/encode"; + try + { + string r = await url.PostJsonAsync(new + { + protocolId, + protocolBody = txtRequestJson.Text, + }).ReceiveString(); + if (r != null) + { + JObject jo = JObject.Parse(r); + if (jo["code"].Value() == 0) + { + txtProtobufBody.Text = jo["result"].Value(); + } + else + { + MessageBox.Show(jo["msg"].Value(), "警告", MessageBoxButtons.OK, MessageBoxIcon.Warning); + } + } + } + catch (Exception ex) + { + FiddlerApplication.Log.LogString($"转换 JSON 为 Protobuf 请求出现错误:{ex.Message}"); + FiddlerApplication.Log.LogString(ex.StackTrace); + MessageBox.Show($"转换 JSON 为 Protobuf 请求出现错误:{ex.Message}", "警告", MessageBoxButtons.OK, MessageBoxIcon.Warning); + } + } + } +} diff --git a/EmoneyInteceptor/Emoney/Controls/EmoneyTabControl.resx b/EmoneyInteceptor/Emoney/Controls/EmoneyTabControl.resx new file mode 100644 index 0000000..1af7de1 --- /dev/null +++ b/EmoneyInteceptor/Emoney/Controls/EmoneyTabControl.resx @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/EmoneyInteceptor/Emoney/EmoneyHelper.cs b/EmoneyInteceptor/Emoney/EmoneyHelper.cs new file mode 100644 index 0000000..721b91d --- /dev/null +++ b/EmoneyInteceptor/Emoney/EmoneyHelper.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace EmoneyInteceptor.Emoney +{ + public class EmoneyHelper + { + + } +} diff --git a/EmoneyInteceptor/EmoneyInteceptor.csproj b/EmoneyInteceptor/EmoneyInteceptor.csproj new file mode 100644 index 0000000..9b3cd65 --- /dev/null +++ b/EmoneyInteceptor/EmoneyInteceptor.csproj @@ -0,0 +1,122 @@ + + + + + Debug + AnyCPU + {77AF554C-514F-4513-811A-AB2CA1BCCCAF} + Library + Properties + EmoneyInteceptor + EmoneyInteceptor + v4.6.2 + 512 + true + + + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + D:\Program Files\Fiddler\Fiddler.exe + + + ..\packages\Flurl.4.0.0\lib\net461\Flurl.dll + + + ..\packages\Flurl.Http.4.0.2\lib\net461\Flurl.Http.dll + + + ..\packages\Microsoft.Bcl.AsyncInterfaces.6.0.0\lib\net461\Microsoft.Bcl.AsyncInterfaces.dll + + + ..\packages\Newtonsoft.Json.13.0.3\lib\net45\Newtonsoft.Json.dll + + + + ..\packages\System.Buffers.4.5.1\lib\net461\System.Buffers.dll + + + + + ..\packages\System.Memory.4.5.4\lib\net461\System.Memory.dll + + + + ..\packages\System.Numerics.Vectors.4.5.0\lib\net46\System.Numerics.Vectors.dll + + + ..\packages\System.Runtime.CompilerServices.Unsafe.6.0.0\lib\net461\System.Runtime.CompilerServices.Unsafe.dll + + + ..\packages\System.Text.Encodings.Web.6.0.0\lib\net461\System.Text.Encodings.Web.dll + + + ..\packages\System.Text.Json.6.0.4\lib\net461\System.Text.Json.dll + + + ..\packages\System.Threading.Tasks.Extensions.4.5.4\lib\net461\System.Threading.Tasks.Extensions.dll + + + ..\packages\System.ValueTuple.4.5.0\lib\net461\System.ValueTuple.dll + + + + + + + + + + + + UserControl + + + EmoneyTabControl.cs + + + + + + + + + + + + + EmoneyTabControl.cs + + + + + + + + copy "$(TargetPath)" "D:\Program Files\Fiddler\Scripts\$(TargetFilename)" + + + + + 这台计算机上缺少此项目引用的 NuGet 程序包。使用“NuGet 程序包还原”可下载这些程序包。有关更多信息,请参见 http://go.microsoft.com/fwlink/?LinkID=322105。缺少的文件是 {0}。 + + + + \ No newline at end of file diff --git a/EmoneyInteceptor/Fiddler/Interfaces/IFiddlerLayout.cs b/EmoneyInteceptor/Fiddler/Interfaces/IFiddlerLayout.cs new file mode 100644 index 0000000..be81773 --- /dev/null +++ b/EmoneyInteceptor/Fiddler/Interfaces/IFiddlerLayout.cs @@ -0,0 +1,16 @@ +using System.Drawing; + +namespace EmoneyInteceptor.Fiddler.Interfaces +{ + /// + /// 为插件提供 fiddler 布局相关的支持 + /// + internal interface IFiddlerLayout + { + /// + /// fiddler tab view 尺寸变化时调用。 + /// + /// + void OnTabViewSizeChanged(Size size); + } +} \ No newline at end of file diff --git a/EmoneyInteceptor/Fiddler/Interfaces/IFiddlerViewProvider.cs b/EmoneyInteceptor/Fiddler/Interfaces/IFiddlerViewProvider.cs new file mode 100644 index 0000000..a5d9488 --- /dev/null +++ b/EmoneyInteceptor/Fiddler/Interfaces/IFiddlerViewProvider.cs @@ -0,0 +1,9 @@ +using EmoneyInteceptor.Fiddler.Models; +using System.Collections.Generic; +namespace EmoneyInteceptor.Fiddler.Interfaces +{ + public interface IFiddlerViewProvider + { + IList BuildFiddlerTabPages(); + } +} diff --git a/EmoneyInteceptor/Fiddler/Models/FiddlerPluginApplication.cs b/EmoneyInteceptor/Fiddler/Models/FiddlerPluginApplication.cs new file mode 100644 index 0000000..52326c7 --- /dev/null +++ b/EmoneyInteceptor/Fiddler/Models/FiddlerPluginApplication.cs @@ -0,0 +1,62 @@ +using Fiddler; +using EmoneyInteceptor.Fiddler.Interfaces; + +namespace EmoneyInteceptor.Fiddler.Models +{ + /// + /// Fiddler 插件应用的入口 + /// + public abstract class FiddlerPluginApplication : IAutoTamper3 + { + public virtual void OnLoad() + { + IFiddlerViewProvider viewProvider = GetFiddlerViewProvider(); + if (viewProvider != null) + { + PluginViewController.InsertFiddlerTabPage(viewProvider); + } + } + + public abstract IFiddlerViewProvider GetFiddlerViewProvider(); + + public virtual void OnBeforeUnload() + { + + } + + public virtual void AutoTamperRequestBefore(Session oSession) + { + + } + + public virtual void AutoTamperRequestAfter(Session oSession) + { + + } + + public virtual void AutoTamperResponseBefore(Session oSession) + { + + } + + public virtual void AutoTamperResponseAfter(Session oSession) + { + + } + + public virtual void OnBeforeReturningError(Session oSession) + { + + } + + public virtual void OnPeekAtResponseHeaders(Session oSession) + { + + } + + public virtual void OnPeekAtRequestHeaders(Session oSession) + { + + } + } +} diff --git a/EmoneyInteceptor/Fiddler/Models/FiddlerTabPage.cs b/EmoneyInteceptor/Fiddler/Models/FiddlerTabPage.cs new file mode 100644 index 0000000..351dc4c --- /dev/null +++ b/EmoneyInteceptor/Fiddler/Models/FiddlerTabPage.cs @@ -0,0 +1,35 @@ +using System; +using Fiddler; + +namespace EmoneyInteceptor.Fiddler.Models +{ + public class FiddlerTabPage + { + /// + /// 获取 Fiddler Tab 标签页的 Title。 + /// + public string TabTitle { get; } + + /// + /// 获取或设置 Fiddler Tab 标签页的 Icon。 + /// + public SessionIcons TabIcon { get; set; } = SessionIcons.Silverlight; + + /// + /// 获取 Fiddler Tab 标签页内的 UserControl。 + /// + public System.Windows.Forms.UserControl WinFormUserControl { get; } + + public FiddlerTabPage(string tabTitle, System.Windows.Forms.UserControl winFormUserControl) + { + if (string.IsNullOrWhiteSpace(tabTitle)) + { + throw new ArgumentNullException(nameof(tabTitle)); + } + + TabTitle = tabTitle; + WinFormUserControl = winFormUserControl ?? throw new ArgumentNullException(nameof(winFormUserControl)); + } + + } +} diff --git a/EmoneyInteceptor/Fiddler/Models/MahjongPlugin.cs b/EmoneyInteceptor/Fiddler/Models/MahjongPlugin.cs new file mode 100644 index 0000000..2b269cd --- /dev/null +++ b/EmoneyInteceptor/Fiddler/Models/MahjongPlugin.cs @@ -0,0 +1,63 @@ +using System; +using System.Collections.Generic; +using Fiddler; +using EmoneyInteceptor.Fiddler.Interfaces; +using EmoneyInteceptor.Fiddler.Models; +using EmoneyInteceptor.Emoney.Controls; + +// is required +[assembly: RequiredVersion("4.5.1.2")] + +namespace FiddlerPlugin.Fiddler.Models +{ + public class EmoneyPlugin : FiddlerPluginApplication, IFiddlerViewProvider, IResponseInspector2, IWSMInspector + { + public HTTPResponseHeaders headers { get => throw new NotImplementedException(); set => throw new NotImplementedException(); } + public byte[] body { get => throw new NotImplementedException(); set => throw new NotImplementedException(); } + + public bool bDirty => throw new NotImplementedException(); + + public bool bReadOnly { get => throw new NotImplementedException(); set => throw new NotImplementedException(); } + + public override void OnLoad() + { + FiddlerApplication.Log.LogString("Load MyFiddlerFiddlerPlugin"); + // base.OnLoad() is required + base.OnLoad(); + } + + public override IFiddlerViewProvider GetFiddlerViewProvider() + { + return this; + } + + public override void AutoTamperRequestAfter(Session oSession) + { + // do your work + } + + public IList BuildFiddlerTabPages() + { + string title = "益盟辅助"; + EmoneyTabControl control = new EmoneyTabControl(); + FiddlerApplication.OnWebSocketMessage += control.OnWebSocketMessage; + FiddlerApplication.BeforeResponse += control.BeforeResponse; + FiddlerApplication.BeforeRequest += control.BeforeRequest; + FiddlerTabPage fiddlerTabPage = new FiddlerTabPage(title, control); + return new List() + { + fiddlerTabPage + }; + } + + public void Clear() + { + throw new NotImplementedException(); + } + + public void AssignMessage(WebSocketMessage oWSM) + { + throw new NotImplementedException(); + } + } +} \ No newline at end of file diff --git a/EmoneyInteceptor/Fiddler/Models/PluginViewController.cs b/EmoneyInteceptor/Fiddler/Models/PluginViewController.cs new file mode 100644 index 0000000..23792fb --- /dev/null +++ b/EmoneyInteceptor/Fiddler/Models/PluginViewController.cs @@ -0,0 +1,62 @@ +using System; +using System.Collections.Generic; +using Fiddler; +using EmoneyInteceptor.Fiddler.Interfaces; + +namespace EmoneyInteceptor.Fiddler.Models +{ + internal class PluginViewController + { + private static readonly IList FiddlerLayoutViews = new List(); + + public static void InsertFiddlerTabPage(IFiddlerViewProvider fiddlerViewProvider) + { + // 提供 fiddler 窗口变化的支持 + FiddlerApplication.UI.tabsViews.SizeChanged += TabsViewsOnSizeChanged; + foreach (FiddlerTabPage fiddlerTabPage in fiddlerViewProvider.BuildFiddlerTabPages()) + { + InsertFiddlerTabPage(fiddlerTabPage); + } + } + + private static void InsertFiddlerTabPage(FiddlerTabPage fiddlerTabPage) + { + // new tab + System.Windows.Forms.TabPage tabPage = new System.Windows.Forms.TabPage(fiddlerTabPage.TabTitle) { ImageIndex = (int)fiddlerTabPage.TabIcon }; + + if (fiddlerTabPage.WinFormUserControl != null) + { + InsertWinFormTabPage(tabPage, fiddlerTabPage.WinFormUserControl); + } + + UpdateViewSize(); + } + + private static void InsertWinFormTabPage(System.Windows.Forms.TabPage tabPage, System.Windows.Forms.UserControl userControl) + { + // add view to tab + tabPage.Controls.Add(userControl); + // add tab to fiddler + FiddlerApplication.UI.tabsViews.TabPages.Add(tabPage); + + if (userControl is IFiddlerLayout layout) + { + FiddlerLayoutViews.Add(layout); + } + } + + private static void TabsViewsOnSizeChanged(object sender, EventArgs e) + { + UpdateViewSize(); + } + + private static void UpdateViewSize() + { + System.Drawing.Size size = FiddlerApplication.UI.tabsViews.Size; + foreach (IFiddlerLayout view in FiddlerLayoutViews) + { + view.OnTabViewSizeChanged(size); + } + } + } +} diff --git a/EmoneyInteceptor/Properties/AssemblyInfo.cs b/EmoneyInteceptor/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..55ac982 --- /dev/null +++ b/EmoneyInteceptor/Properties/AssemblyInfo.cs @@ -0,0 +1,35 @@ +using System.Reflection; +using System.Runtime.InteropServices; + +// 有关程序集的一般信息由以下 +// 控制。更改这些特性值可修改 +// 与程序集关联的信息。 +[assembly: AssemblyTitle("EmoneyInteceptor")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("EmoneyInteceptor")] +[assembly: AssemblyCopyright("Copyright © 2023")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// 将 ComVisible 设置为 false 会使此程序集中的类型 +//对 COM 组件不可见。如果需要从 COM 访问此程序集中的类型 +//请将此类型的 ComVisible 特性设置为 true。 +[assembly: ComVisible(false)] + +// 如果此项目向 COM 公开,则下列 GUID 用于类型库的 ID +[assembly: Guid("77af554c-514f-4513-811a-ab2ca1bcccaf")] + +// 程序集的版本信息由下列四个值组成: +// +// 主版本 +// 次版本 +// 生成号 +// 修订号 +// +//可以指定所有这些值,也可以使用“生成号”和“修订号”的默认值 +//通过使用 "*",如下所示: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/EmoneyInteceptor/packages.config b/EmoneyInteceptor/packages.config new file mode 100644 index 0000000..45a386e --- /dev/null +++ b/EmoneyInteceptor/packages.config @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/FiddlerDecryption/Controls/RequestDecodeControl.Designer.cs b/FiddlerDecryption/Controls/RequestDecodeControl.Designer.cs new file mode 100644 index 0000000..6beeed7 --- /dev/null +++ b/FiddlerDecryption/Controls/RequestDecodeControl.Designer.cs @@ -0,0 +1,233 @@ +namespace FiddlerDecryption.Controls +{ + partial class RequestDecodeControl + { + /// + /// 必需的设计器变量。 + /// + private System.ComponentModel.IContainer components = null; + + /// + /// 清理所有正在使用的资源。 + /// + /// 如果应释放托管资源,为 true;否则为 false。 + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region 组件设计器生成的代码 + + /// + /// 设计器支持所需的方法 - 不要修改 + /// 使用代码编辑器修改此方法的内容。 + /// + private void InitializeComponent() + { + this.tabControl1 = new System.Windows.Forms.TabControl(); + this.tabPage1 = new System.Windows.Forms.TabPage(); + this.txtSupposedClass = new System.Windows.Forms.TextBox(); + this.txtAuth = new System.Windows.Forms.TextBox(); + this.txtProtoName = new System.Windows.Forms.TextBox(); + this.label4 = new System.Windows.Forms.Label(); + this.txtProtoId = new System.Windows.Forms.TextBox(); + this.label2 = new System.Windows.Forms.Label(); + this.label3 = new System.Windows.Forms.Label(); + this.label1 = new System.Windows.Forms.Label(); + this.tecJson = new ICSharpCode.TextEditor.TextEditorControlEx(); + this.tabPage2 = new System.Windows.Forms.TabPage(); + this.textBox1 = new System.Windows.Forms.TextBox(); + this.label5 = new System.Windows.Forms.Label(); + this.tabControl1.SuspendLayout(); + this.tabPage1.SuspendLayout(); + this.tabPage2.SuspendLayout(); + this.SuspendLayout(); + // + // tabControl1 + // + this.tabControl1.Controls.Add(this.tabPage1); + this.tabControl1.Controls.Add(this.tabPage2); + this.tabControl1.Dock = System.Windows.Forms.DockStyle.Fill; + this.tabControl1.Location = new System.Drawing.Point(0, 0); + this.tabControl1.Name = "tabControl1"; + this.tabControl1.SelectedIndex = 0; + this.tabControl1.Size = new System.Drawing.Size(552, 434); + this.tabControl1.TabIndex = 0; + // + // tabPage1 + // + this.tabPage1.Controls.Add(this.txtSupposedClass); + this.tabPage1.Controls.Add(this.txtAuth); + this.tabPage1.Controls.Add(this.txtProtoName); + this.tabPage1.Controls.Add(this.label4); + this.tabPage1.Controls.Add(this.txtProtoId); + this.tabPage1.Controls.Add(this.label2); + this.tabPage1.Controls.Add(this.label3); + this.tabPage1.Controls.Add(this.label1); + this.tabPage1.Controls.Add(this.tecJson); + this.tabPage1.Location = new System.Drawing.Point(4, 22); + this.tabPage1.Name = "tabPage1"; + this.tabPage1.Padding = new System.Windows.Forms.Padding(3); + this.tabPage1.Size = new System.Drawing.Size(544, 408); + this.tabPage1.TabIndex = 0; + this.tabPage1.Text = "基本解析"; + this.tabPage1.UseVisualStyleBackColor = true; + // + // txtSupposedClass + // + this.txtSupposedClass.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.txtSupposedClass.Location = new System.Drawing.Point(107, 32); + this.txtSupposedClass.Name = "txtSupposedClass"; + this.txtSupposedClass.ReadOnly = true; + this.txtSupposedClass.Size = new System.Drawing.Size(431, 21); + this.txtSupposedClass.TabIndex = 3; + // + // txtAuth + // + this.txtAuth.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.txtAuth.Location = new System.Drawing.Point(107, 56); + this.txtAuth.Name = "txtAuth"; + this.txtAuth.ReadOnly = true; + this.txtAuth.Size = new System.Drawing.Size(431, 21); + this.txtAuth.TabIndex = 4; + // + // txtProtoName + // + this.txtProtoName.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.txtProtoName.Location = new System.Drawing.Point(279, 8); + this.txtProtoName.Name = "txtProtoName"; + this.txtProtoName.ReadOnly = true; + this.txtProtoName.Size = new System.Drawing.Size(259, 21); + this.txtProtoName.TabIndex = 2; + // + // label4 + // + this.label4.AutoSize = true; + this.label4.Location = new System.Drawing.Point(7, 36); + this.label4.Name = "label4"; + this.label4.Size = new System.Drawing.Size(89, 12); + this.label4.TabIndex = 6; + this.label4.Text = "Supposed Class"; + // + // txtProtoId + // + this.txtProtoId.Location = new System.Drawing.Point(107, 8); + this.txtProtoId.Name = "txtProtoId"; + this.txtProtoId.ReadOnly = true; + this.txtProtoId.Size = new System.Drawing.Size(77, 21); + this.txtProtoId.TabIndex = 1; + // + // label2 + // + this.label2.AutoSize = true; + this.label2.Location = new System.Drawing.Point(6, 60); + this.label2.Name = "label2"; + this.label2.Size = new System.Drawing.Size(83, 12); + this.label2.TabIndex = 6; + this.label2.Text = "Authorization"; + // + // label3 + // + this.label3.AutoSize = true; + this.label3.Location = new System.Drawing.Point(190, 11); + this.label3.Name = "label3"; + this.label3.Size = new System.Drawing.Size(83, 12); + this.label3.TabIndex = 5; + this.label3.Text = "Protocol Name"; + // + // label1 + // + this.label1.AutoSize = true; + this.label1.Location = new System.Drawing.Point(6, 12); + this.label1.Name = "label1"; + this.label1.Size = new System.Drawing.Size(71, 12); + this.label1.TabIndex = 5; + this.label1.Text = "Protocol ID"; + // + // tecJson + // + this.tecJson.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) + | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.tecJson.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle; + this.tecJson.FoldingStrategy = "JSON"; + this.tecJson.Font = new System.Drawing.Font("Courier New", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.tecJson.IsReadOnly = true; + this.tecJson.Location = new System.Drawing.Point(6, 81); + this.tecJson.Name = "tecJson"; + this.tecJson.Size = new System.Drawing.Size(532, 321); + this.tecJson.SyntaxHighlighting = "JSON"; + this.tecJson.TabIndex = 5; + this.tecJson.TextChanged += new System.EventHandler(this.tecJson_TextChanged); + // + // tabPage2 + // + this.tabPage2.Controls.Add(this.label5); + this.tabPage2.Controls.Add(this.textBox1); + this.tabPage2.Location = new System.Drawing.Point(4, 22); + this.tabPage2.Name = "tabPage2"; + this.tabPage2.Padding = new System.Windows.Forms.Padding(3); + this.tabPage2.Size = new System.Drawing.Size(544, 408); + this.tabPage2.TabIndex = 1; + this.tabPage2.Text = "设置"; + this.tabPage2.UseVisualStyleBackColor = true; + // + // textBox1 + // + this.textBox1.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.textBox1.Location = new System.Drawing.Point(89, 15); + this.textBox1.Name = "textBox1"; + this.textBox1.Size = new System.Drawing.Size(449, 21); + this.textBox1.TabIndex = 0; + // + // label5 + // + this.label5.AutoSize = true; + this.label5.Location = new System.Drawing.Point(6, 18); + this.label5.Name = "label5"; + this.label5.Size = new System.Drawing.Size(77, 12); + this.label5.TabIndex = 1; + this.label5.Text = "API Host地址"; + // + // RequestDecodeControl + // + this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 12F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.Controls.Add(this.tabControl1); + this.Name = "RequestDecodeControl"; + this.Size = new System.Drawing.Size(552, 434); + this.tabControl1.ResumeLayout(false); + this.tabPage1.ResumeLayout(false); + this.tabPage1.PerformLayout(); + this.tabPage2.ResumeLayout(false); + this.tabPage2.PerformLayout(); + this.ResumeLayout(false); + + } + + #endregion + + private System.Windows.Forms.TabControl tabControl1; + private System.Windows.Forms.TabPage tabPage1; + private System.Windows.Forms.TextBox txtSupposedClass; + private System.Windows.Forms.TextBox txtAuth; + private System.Windows.Forms.TextBox txtProtoName; + private System.Windows.Forms.Label label4; + private System.Windows.Forms.TextBox txtProtoId; + private System.Windows.Forms.Label label2; + private System.Windows.Forms.Label label3; + private System.Windows.Forms.Label label1; + private ICSharpCode.TextEditor.TextEditorControlEx tecJson; + private System.Windows.Forms.TabPage tabPage2; + private System.Windows.Forms.Label label5; + private System.Windows.Forms.TextBox textBox1; + } +} diff --git a/FiddlerDecryption/Controls/RequestDecodeControl.cs b/FiddlerDecryption/Controls/RequestDecodeControl.cs new file mode 100644 index 0000000..8a6614f --- /dev/null +++ b/FiddlerDecryption/Controls/RequestDecodeControl.cs @@ -0,0 +1,68 @@ +using Newtonsoft.Json.Linq; +using System; +using System.Windows.Forms; + +namespace FiddlerDecryption.Controls +{ + public partial class RequestDecodeControl : UserControl + { + + public RequestDecodeControl() + { + InitializeComponent(); + } + + private void tecJson_TextChanged(object sender, EventArgs e) + { + UpdateAndCheckFoldings(); + } + + private void UpdateAndCheckFoldings() + { + tecJson.Document.FoldingManager.UpdateFoldings(null, null); + } + + public void SetData(JObject jo, string auth) + { + txtAuth.Text = auth; + if (jo == null) + { + Clear(); + return; + } + txtProtoId.Text = jo["protocolId"]?.Value().ToString(); + txtSupposedClass.Text = jo["supposedClassName"]?.Value().ToString(); + + + + if (!jo["ok"].Value()) + { + tecJson.Text = string.Empty; + tecJson.Text = jo["message"].Value().ToString(); + UpdateAndCheckFoldings(); + } + else + { + tecJson.Text = string.Empty; + tecJson.Text = jo.ToString(Newtonsoft.Json.Formatting.Indented); + UpdateAndCheckFoldings(); + } + } + + public void Clear() + { + tecJson.Text = string.Empty; + txtAuth.Clear(); + txtProtoId.Clear(); + txtProtoName.Clear(); + txtSupposedClass.Clear(); + } + + public void AddToTab(TabPage o) + { + o.Text = "EProtobuf"; + o.Controls.Add(this); + o.Controls[0].Dock = DockStyle.Fill; + } + } +} diff --git a/FiddlerDecryption/Controls/RequestDecodeControl.resx b/FiddlerDecryption/Controls/RequestDecodeControl.resx new file mode 100644 index 0000000..1af7de1 --- /dev/null +++ b/FiddlerDecryption/Controls/RequestDecodeControl.resx @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/FiddlerDecryption/FiddlerDecryption.csproj b/FiddlerDecryption/FiddlerDecryption.csproj new file mode 100644 index 0000000..079d238 --- /dev/null +++ b/FiddlerDecryption/FiddlerDecryption.csproj @@ -0,0 +1,149 @@ + + + + + + Debug + AnyCPU + {1210036A-18F1-4786-BC4F-C3B2AECDE45B} + Library + Properties + FiddlerDecryption + FiddlerDecryption + v4.6.2 + 512 + true + + + + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + true + + + + ..\packages\Costura.Fody.6.0.0-beta0000\lib\netstandard2.0\Costura.dll + + + D:\Program Files\Fiddler\Fiddler.exe + + + ..\packages\Flurl.4.0.0\lib\net461\Flurl.dll + + + ..\packages\Flurl.Http.4.0.2\lib\net461\Flurl.Http.dll + + + ..\packages\Microsoft.Bcl.AsyncInterfaces.6.0.0\lib\net461\Microsoft.Bcl.AsyncInterfaces.dll + + + ..\packages\Newtonsoft.Json.13.0.3\lib\net45\Newtonsoft.Json.dll + + + D:\Program Files\Fiddler\Inspectors\Standard.dll + + + + ..\packages\System.Buffers.4.5.1\lib\net461\System.Buffers.dll + + + + + ..\packages\System.Memory.4.5.4\lib\net461\System.Memory.dll + + + + ..\packages\System.Numerics.Vectors.4.5.0\lib\net46\System.Numerics.Vectors.dll + + + ..\packages\System.Runtime.CompilerServices.Unsafe.6.0.0\lib\net461\System.Runtime.CompilerServices.Unsafe.dll + + + ..\packages\System.Text.Encodings.Web.6.0.0\lib\net461\System.Text.Encodings.Web.dll + + + ..\packages\System.Text.Json.6.0.4\lib\net461\System.Text.Json.dll + + + ..\packages\System.Threading.Tasks.Extensions.4.5.4\lib\net461\System.Threading.Tasks.Extensions.dll + + + packages\System.ValueTuple.4.5.0\lib\net461\System.ValueTuple.dll + + + + + + + + + + ..\packages\Tomlyn.0.17.0\lib\netstandard2.0\Tomlyn.dll + + + + + UserControl + + + RequestDecodeControl.cs + + + + + + + + + + + + + + RequestDecodeControl.cs + + + + + {2d18be89-d210-49eb-a9dd-2246fbb3df6d} + ICSharpCode.TextEditorEx + + + + + copy "$(TargetPath)" "D:\Program Files\Fiddler\Inspectors\$(TargetFilename)" +"$(ProjectDir)move-package.bat" "D:\Program Files\Fiddler\" + + + + + 这台计算机上缺少此项目引用的 NuGet 程序包。使用“NuGet 程序包还原”可下载这些程序包。有关更多信息,请参见 http://go.microsoft.com/fwlink/?LinkID=322105。缺少的文件是 {0}。 + + + + + + + + + \ No newline at end of file diff --git a/FiddlerDecryption/FodyWeavers.xml b/FiddlerDecryption/FodyWeavers.xml new file mode 100644 index 0000000..5029e70 --- /dev/null +++ b/FiddlerDecryption/FodyWeavers.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/FiddlerDecryption/Properties/AssemblyInfo.cs b/FiddlerDecryption/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..3b446d5 --- /dev/null +++ b/FiddlerDecryption/Properties/AssemblyInfo.cs @@ -0,0 +1,37 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// 有关程序集的一般信息由以下 +// 控制。更改这些特性值可修改 +// 与程序集关联的信息。 +[assembly: AssemblyTitle("FiddlerDecryption")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("FiddlerDecryption")] +[assembly: AssemblyCopyright("Copyright © 2019")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// 将 ComVisible 设置为 false 会使此程序集中的类型 +//对 COM 组件不可见。如果需要从 COM 访问此程序集中的类型 +//请将此类型的 ComVisible 特性设置为 true。 +[assembly: ComVisible(false)] + +// 如果此项目向 COM 公开,则下列 GUID 用于类型库的 ID +[assembly: Guid("1210036a-18f1-4786-bc4f-c3b2aecde45b")] + +// 程序集的版本信息由下列四个值组成: +// +// 主版本 +// 次版本 +// 生成号 +// 修订号 +// +//可以指定所有这些值,也可以使用“生成号”和“修订号”的默认值 +//通过使用 "*",如下所示: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] +[assembly: Fiddler.RequiredVersion("4.6.20171.9220")] diff --git a/FiddlerDecryption/README.md b/FiddlerDecryption/README.md new file mode 100644 index 0000000..682a87e --- /dev/null +++ b/FiddlerDecryption/README.md @@ -0,0 +1,17 @@ +# FiddlerDecryption + +功能:实时解密接口数据并格式化展示。 + +![](https://img-blog.csdnimg.cn/20191026140952602.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0dkZWVy,size_16,color_FFFFFF,t_70) + +代码内容: + +* DecryptionUtil.cs:解密方法 +* RequestDecryption.cs:请求数据的解密插件 +* RequestDecryptionFormat.cs:请求数据的解密&格式化插件 +* ResponseDecryption.cs:返回数据的解密插件 +* ResponseDecryptionFormat.cs:返回数据的解密&格式化插件 + +如需应用到自己的项目,修改 DecryptionUtil 中的解密算法即可。 + +更多介绍请见[Fiddler 插件开发:数据解密](https://blog.csdn.net/Gdeer/article/details/102756017)。 diff --git a/FiddlerDecryption/RequestDecode.cs b/FiddlerDecryption/RequestDecode.cs new file mode 100644 index 0000000..aa150a8 --- /dev/null +++ b/FiddlerDecryption/RequestDecode.cs @@ -0,0 +1,105 @@ +using Fiddler; +using System.Windows.Forms; +using Util; +using FiddlerDecryption.Controls; + +namespace Request +{ + /// + /// 益盟请求解码为格式化 JSON 字符串 + /// + public sealed class RequestDecode : Inspector2, IRequestInspector2 + { + private bool mBDirty; + private bool mBReadOnly; + private byte[] mBody; + private HTTPRequestHeaders mRequestHeaders; + private RequestDecodeControl requestDecodeControl; + + public RequestDecode() + { + requestDecodeControl = new RequestDecodeControl(); + } + + /// + /// 实现 字段 + /// + public bool bDirty + { + get + { + return mBDirty; + } + } + /// + /// 实现 字段 + /// + public byte[] body + { + get + { + return mBody; + } + + set + { + mBody = value; + requestDecodeControl.SetData( + DecodeUtil.DoDecryptionNew(headers, mBody).Result, + DecodeUtil.NeedToDecrypt(headers) && headers.Exists("Authorization") ? + headers["Authorization"] : string.Empty); + } + } + + /// + /// 实现 字段 + /// + public bool bReadOnly + { + get + { + return mBReadOnly; + } + + set + { + mBReadOnly = value; + } + } + + /// + /// 实现 字段 + /// + public HTTPRequestHeaders headers + { + get + { + return mRequestHeaders; + } + set + { + mRequestHeaders = value; + + } + + } + + /// + /// 实现 方法 + /// + public override void AddToTab(TabPage o) + { + requestDecodeControl.AddToTab(o); + o.Text = "EProtobuf"; + } + + public void Clear() + { + mBody = null; + requestDecodeControl.Clear(); + } + + // 在 Tab 上的摆放位置 + public override int GetOrder() => 200; + } +} diff --git a/FiddlerDecryption/ResponseDecode.cs b/FiddlerDecryption/ResponseDecode.cs new file mode 100644 index 0000000..968ba75 --- /dev/null +++ b/FiddlerDecryption/ResponseDecode.cs @@ -0,0 +1,93 @@ +using Fiddler; +using System; +using Standard; +using System.Windows.Forms; +using Util; + +namespace Response +{ + public sealed class ResponseDecode : Inspector2, IResponseInspector2, IBaseInspector2 + { + private bool mBDirty; + private bool mBReadOnly; + private byte[] mBody; + private HTTPResponseHeaders mResponseHeaders; + private ResponseTextViewer mResponseTextViewer; + + public ResponseDecode() + { + mResponseTextViewer = new ResponseTextViewer(); + } + + public bool bDirty + { + get + { + return mBDirty; + } + } + + public byte[] body + { + get + { + return mBody; + } + + set + { + mBody = value; + byte[] decodedBody = DecodeUtil.DoDecryption(headers, mBody).Result; + if (decodedBody != null) + { + mResponseTextViewer.body = decodedBody; + } + else + { + Clear(); + mResponseTextViewer.body = value; + } + } + } + + public bool bReadOnly + { + get + { + return mBReadOnly; + } + + set + { + mBReadOnly = value; + } + } + + public HTTPResponseHeaders headers + { + get + { + return mResponseHeaders; + } + set + { + mResponseHeaders = value; + } + } + + public override void AddToTab(TabPage o) + { + mResponseTextViewer.AddToTab(o); + o.Text = "EProtobuf"; + } + + public void Clear() + { + mBody = null; + mResponseTextViewer.Clear(); + } + + // 在 Tab 上的摆放位置 + public override int GetOrder() => 200; + } +} diff --git a/FiddlerDecryption/Utils/DecodeUtil.cs b/FiddlerDecryption/Utils/DecodeUtil.cs new file mode 100644 index 0000000..dba5bb4 --- /dev/null +++ b/FiddlerDecryption/Utils/DecodeUtil.cs @@ -0,0 +1,212 @@ +using Fiddler; +using FiddlerDecryption.Utils; +using Flurl.Http; +using Newtonsoft.Json.Linq; +using System; +using System.Text; +using System.Text.RegularExpressions; +using System.Threading.Tasks; + +namespace Util +{ + class DecodeUtil + { + + static Uri GetAPIUri(string path) + { + Setting setting = Setting.Instance; + Uri uri = new Uri(setting.ApiHost); + string scheme = uri.Scheme.ToLower(); + if (scheme != "http" && scheme != "https") + { + setting.ApiHost = Setting.DEFAULT_API_HOST; + uri = new Uri(setting.ApiHost); + } + UriBuilder builder = new UriBuilder(uri); + builder.Path += path; + return builder.Uri; + } + + /// + /// 清除返回 JObject 中 cachedSize 和 serializedSize 字段 + /// + /// + /// + public static JToken ClearSizeInfo(JToken j) + { + if (j == null) return null; + if (j is JArray ja) { + for (int i = 0; i < ja.Count; i++) + { + ja[i] = ClearSizeInfo(ja[i]); + } + return ja; + } + if (j is JObject jo) + { + jo.Remove("cachedSize"); + jo.Remove("serializedSize"); + foreach (var p in jo) + { + string key = p.Key; + JToken val = p.Value; + jo[key] = ClearSizeInfo(val); + } + return jo; + } + return j; + } + + public static async Task DoDecryptionNew(HTTPRequestHeaders headers, byte[] mBody) + { + if (NeedToDecrypt(headers)) + { + var url = "http://localhost:7790/api/v1/proto/request/decode"; + try + { + string r = await url.PostJsonAsync(new + { + protocolId = Convert.ToInt32(headers["X-Protocol-Id"]), + protocolBody = Convert.ToBase64String(mBody) + }).Result.GetStringAsync(); + if (r != null) + { + JObject jo = JObject.Parse(r); + return ClearSizeInfo(jo) as JObject; + } + else + { + return null; + } + } + catch (Exception ex) + { + return new JObject() + { + new JProperty("ok", false), + new JProperty("resultCode", 502), + new JProperty("message", ex.InnerException?.Message ?? ex.Message) + }; + } + } + return null; + } + + public static async Task DoDecryption(HTTPRequestHeaders headers, byte[] mBody) + { + // 判断是否是对应内容 + string showBody = ""; + if (NeedToDecrypt(headers)) + { + var url = "http://localhost:7790/api/v1/proto/request/decode"; + try + { + string r = await url.PostJsonAsync(new + { + protocolId = Convert.ToInt32(headers["X-Protocol-Id"]), + protocolBody = Convert.ToBase64String(mBody) + }).Result.GetStringAsync(); + if (r != null) + { + JObject jo = JObject.Parse(r); + if (jo["ok"].Value()) + { + JObject result = jo["data"].Value(); + result = ClearSizeInfo(result) as JObject; + jo["data"] = result; + } + showBody = jo.ToString(); + } + else + { + return null; + } + } + catch (Exception ex) + { + showBody = ex.Message; + } + } + + return Encoding.UTF8.GetBytes(showBody); + } + + public static async Task DoDecryption(HTTPResponseHeaders headers, byte[] mBody) + { + // 判断是否是对应内容 + string showBody = ""; + if (NeedToDecrypt(headers)) + { + var url = "http://localhost:7790/api/v1/proto/response/decode"; + try + { + int? protocolId = headers["X-Protocol-Id"] == null ? (int?)null : Convert.ToInt32(headers["X-Protocol-Id"]); + string r = await url.PostJsonAsync(new + { + protocolId, + protocolBody = Convert.ToBase64String(mBody) + }).Result.GetStringAsync(); + if (r != null) + { + JObject jo = JObject.Parse(r); + if (jo["ok"].Value()) + { + JObject result = jo["data"].Value(); + result = ClearSizeInfo(result) as JObject; + jo["data"] = result; + } + showBody = jo.ToString(); + } + else + { + return null; + } + } + catch (Exception ex) + { + showBody = ex.Message; + } + } + + return Encoding.UTF8.GetBytes(showBody); + } + + /// + /// 请求是否需要做益盟解密 + /// + /// 请求的 Headers + /// + public static bool NeedToDecrypt(HTTPRequestHeaders headers) + { + if (headers.Exists("Host") && headers.Exists("Content-Type") && headers.Exists("X-Protocol-Id")) + { + var host = headers["Host"]; + var contentType = headers["Content-Type"]; + var protocolId = headers["X-Protocol-Id"]; + + return Regex.IsMatch(protocolId, "^\\d+$") + && host.ToLower().EndsWith(".emoney.cn") + && contentType.ToLower().Contains("application/x-protobuf-v3"); + } + return false; + } + + /// + /// 响应是否需要做益盟解密 + /// + /// 返回的 Headers + /// + public static bool NeedToDecrypt(HTTPResponseHeaders headers) + { + if (headers.Exists("Content-Type") && headers.Exists("X-Protocol-Id")) + { + var contentType = headers["Content-Type"]; + var protocolId = headers["X-Protocol-Id"]; + + return Regex.IsMatch(protocolId, "^\\d+$") + && contentType.ToLower().Contains("application/x-protobuf-v3"); + } + return false; + } + } +} diff --git a/FiddlerDecryption/Utils/Setting.cs b/FiddlerDecryption/Utils/Setting.cs new file mode 100644 index 0000000..ab1864d --- /dev/null +++ b/FiddlerDecryption/Utils/Setting.cs @@ -0,0 +1,39 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Tomlyn; + +namespace FiddlerDecryption.Utils +{ + internal class Setting + { + public static string DEFAULT_API_HOST = "http://localhost:7788"; + public string ApiHost { get; set; } = DEFAULT_API_HOST; + public static Setting Instance + { + get + { + Setting setting; + try + { + setting = Toml.ToModel(File.ReadAllText("emoney.toml")); + } + catch (Exception ex) + { + setting = new Setting(); + setting.Save(); + } + return setting; + } + } + + public void Save() + { + string toml = Toml.FromModel(this); + File.WriteAllText("emoney.toml", toml); + } + } +} diff --git a/FiddlerDecryption/app.config b/FiddlerDecryption/app.config new file mode 100644 index 0000000..117ebe2 --- /dev/null +++ b/FiddlerDecryption/app.config @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/FiddlerDecryption/move-package.bat b/FiddlerDecryption/move-package.bat new file mode 100644 index 0000000..70bcacf --- /dev/null +++ b/FiddlerDecryption/move-package.bat @@ -0,0 +1,22 @@ +@echo off +setlocal + +rem 检查是否提供了目标路径参数 +if "%~1"=="" ( + echo Usage: %0 targetPath + exit /b 1 +) + +rem 设置目标路径 +set "targetPath=%~1" + +rem 查找并复制 DLL 文件 +for /r ..\packages\ %%D in (.) do ( + if exist "%%D\net461\*.dll" ( + echo Copying DLLs from %%D\net461 to %targetPath% + xcopy "%%D\net461\*.dll" "%targetPath%" /Y /I + ) +) + +echo Done. +endlocal \ No newline at end of file diff --git a/FiddlerDecryption/packages.config b/FiddlerDecryption/packages.config new file mode 100644 index 0000000..b19cdf0 --- /dev/null +++ b/FiddlerDecryption/packages.config @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/ICSharpCode.TextEditor/Project/Extensions/ControlExtensions.cs b/ICSharpCode.TextEditor/Project/Extensions/ControlExtensions.cs new file mode 100644 index 0000000..ddab7c4 --- /dev/null +++ b/ICSharpCode.TextEditor/Project/Extensions/ControlExtensions.cs @@ -0,0 +1,27 @@ +using System; +using System.ComponentModel; +using System.Reflection; +using System.Windows.Forms; + +namespace ICSharpCode.TextEditor.Extensions +{ + public static class ControlExtensions + { + public static EventHandlerList GetEventHandlerList(this Control c) + { + return GetEventHandlerListInternal(c); + } + + public static EventHandlerList GetEventHandlerList(this Component c) + { + return GetEventHandlerListInternal(c); + } + + private static EventHandlerList GetEventHandlerListInternal(object obj) + { + Type type = obj.GetType(); + PropertyInfo pi = type.GetProperty("Events", BindingFlags.NonPublic | BindingFlags.Instance); + return (EventHandlerList)pi.GetValue(obj, null); + } + } +} \ No newline at end of file diff --git a/ICSharpCode.TextEditor/Project/ICSharpCode.TextEditorEx.csproj b/ICSharpCode.TextEditor/Project/ICSharpCode.TextEditorEx.csproj new file mode 100644 index 0000000..6a4cb7e --- /dev/null +++ b/ICSharpCode.TextEditor/Project/ICSharpCode.TextEditorEx.csproj @@ -0,0 +1,43 @@ + + + + 1.2.1 + Library + net4.6.2;net5.0-windows;net6.0-windows + true + 10 + enable + ICSharpCode.TextEditorEx + Copyright © Stef Heyenrath, SharpDevelop, Qwertie and MysticBoy 2014-2022 + https://github.com/StefH/ICSharpCode.TextEditorEx + https://github.com/StefH/ICSharpCode.TextEditorEx + git + TextEditor;ICSharpCode + Stef Heyenrath, SharpDevelop, Qwertie and MysticBoy + LGPL-2.1-or-later + ICSharpCode.TextEditor + + + + true + + + + + + + + + + + True + True + Resources.resx + + + ResXFileCodeGenerator + Resources.Designer.cs + + + + \ No newline at end of file diff --git a/ICSharpCode.TextEditor/Project/Properties/AssemblyInfo.cs b/ICSharpCode.TextEditor/Project/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..39e6802 --- /dev/null +++ b/ICSharpCode.TextEditor/Project/Properties/AssemblyInfo.cs @@ -0,0 +1,13 @@ +using System; +using System.Reflection; +using System.Runtime.InteropServices; + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("64fb04c7-a744-45a0-9050-13a1825a6980")] + +[assembly: CLSCompliant(true)] \ No newline at end of file diff --git a/ICSharpCode.TextEditor/Project/Properties/Resources.Designer.cs b/ICSharpCode.TextEditor/Project/Properties/Resources.Designer.cs new file mode 100644 index 0000000..19d5132 --- /dev/null +++ b/ICSharpCode.TextEditor/Project/Properties/Resources.Designer.cs @@ -0,0 +1,193 @@ +//------------------------------------------------------------------------------ +// +// 此代码由工具生成。 +// 运行时版本:4.0.30319.42000 +// +// 对此文件的更改可能会导致不正确的行为,并且如果 +// 重新生成代码,这些更改将会丢失。 +// +//------------------------------------------------------------------------------ + +namespace ICSharpCode.TextEditor.Properties { + using System; + + + /// + /// 一个强类型的资源类,用于查找本地化的字符串等。 + /// + // 此类是由 StronglyTypedResourceBuilder + // 类通过类似于 ResGen 或 Visual Studio 的工具自动生成的。 + // 若要添加或移除成员,请编辑 .ResX 文件,然后重新运行 ResGen + // (以 /str 作为命令选项),或重新生成 VS 项目。 + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() { + } + + /// + /// 返回此类使用的缓存的 ResourceManager 实例。 + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("ICSharpCode.TextEditor.Properties.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// 重写当前线程的 CurrentUICulture 属性,对 + /// 使用此强类型资源类的所有资源查找执行重写。 + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// 查找 System.Drawing.Bitmap 类型的本地化资源。 + /// + internal static System.Drawing.Bitmap cut { + get { + object obj = ResourceManager.GetObject("cut", resourceCulture); + return ((System.Drawing.Bitmap)(obj)); + } + } + + /// + /// 查找 System.Drawing.Bitmap 类型的本地化资源。 + /// + internal static System.Drawing.Bitmap htmlpage { + get { + object obj = ResourceManager.GetObject("htmlpage", resourceCulture); + return ((System.Drawing.Bitmap)(obj)); + } + } + + /// + /// 查找 System.Byte[] 类型的本地化资源。 + /// + internal static byte[] RightArrow { + get { + object obj = ResourceManager.GetObject("RightArrow", resourceCulture); + return ((byte[])(obj)); + } + } + + /// + /// 查找 System.Drawing.Bitmap 类型的本地化资源。 + /// + internal static System.Drawing.Bitmap sc_cancel { + get { + object obj = ResourceManager.GetObject("sc_cancel", resourceCulture); + return ((System.Drawing.Bitmap)(obj)); + } + } + + /// + /// 查找 System.Drawing.Bitmap 类型的本地化资源。 + /// + internal static System.Drawing.Bitmap sc_copy { + get { + object obj = ResourceManager.GetObject("sc_copy", resourceCulture); + return ((System.Drawing.Bitmap)(obj)); + } + } + + /// + /// 查找 System.Drawing.Bitmap 类型的本地化资源。 + /// + internal static System.Drawing.Bitmap sc_cut { + get { + object obj = ResourceManager.GetObject("sc_cut", resourceCulture); + return ((System.Drawing.Bitmap)(obj)); + } + } + + /// + /// 查找 System.Drawing.Bitmap 类型的本地化资源。 + /// + internal static System.Drawing.Bitmap sc_linearrowend { + get { + object obj = ResourceManager.GetObject("sc_linearrowend", resourceCulture); + return ((System.Drawing.Bitmap)(obj)); + } + } + + /// + /// 查找 System.Drawing.Bitmap 类型的本地化资源。 + /// + internal static System.Drawing.Bitmap sc_paste { + get { + object obj = ResourceManager.GetObject("sc_paste", resourceCulture); + return ((System.Drawing.Bitmap)(obj)); + } + } + + /// + /// 查找 System.Drawing.Bitmap 类型的本地化资源。 + /// + internal static System.Drawing.Bitmap sc_redo { + get { + object obj = ResourceManager.GetObject("sc_redo", resourceCulture); + return ((System.Drawing.Bitmap)(obj)); + } + } + + /// + /// 查找 System.Drawing.Bitmap 类型的本地化资源。 + /// + internal static System.Drawing.Bitmap sc_searchdialog { + get { + object obj = ResourceManager.GetObject("sc_searchdialog", resourceCulture); + return ((System.Drawing.Bitmap)(obj)); + } + } + + /// + /// 查找 System.Drawing.Bitmap 类型的本地化资源。 + /// + internal static System.Drawing.Bitmap sc_selectall { + get { + object obj = ResourceManager.GetObject("sc_selectall", resourceCulture); + return ((System.Drawing.Bitmap)(obj)); + } + } + + /// + /// 查找 System.Drawing.Bitmap 类型的本地化资源。 + /// + internal static System.Drawing.Bitmap sc_undo { + get { + object obj = ResourceManager.GetObject("sc_undo", resourceCulture); + return ((System.Drawing.Bitmap)(obj)); + } + } + + /// + /// 查找 System.Drawing.Bitmap 类型的本地化资源。 + /// + internal static System.Drawing.Bitmap splitdocument { + get { + object obj = ResourceManager.GetObject("splitdocument", resourceCulture); + return ((System.Drawing.Bitmap)(obj)); + } + } + } +} diff --git a/ICSharpCode.TextEditor/Project/Properties/Resources.resx b/ICSharpCode.TextEditor/Project/Properties/Resources.resx new file mode 100644 index 0000000..a1bf0c9 --- /dev/null +++ b/ICSharpCode.TextEditor/Project/Properties/Resources.resx @@ -0,0 +1,160 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=5.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=5.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + + ..\Resources\cut.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + + + ..\Resources\htmlpage.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + + + ..\resources\rightarrow.cur;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + ..\Resources\sc_cancel.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + + + ..\Resources\sc_copy.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + + + ..\Resources\sc_cut.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + + + ..\Resources\sc_linearrowend.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + + + ..\Resources\sc_paste.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + + + ..\Resources\sc_redo.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + + + ..\Resources\sc_searchdialog.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + + + ..\Resources\sc_selectall.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + + + ..\Resources\sc_undo.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + + + ..\Resources\splitdocument.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + + \ No newline at end of file diff --git a/ICSharpCode.TextEditor/Project/Readme.txt b/ICSharpCode.TextEditor/Project/Readme.txt new file mode 100644 index 0000000..7b0acff --- /dev/null +++ b/ICSharpCode.TextEditor/Project/Readme.txt @@ -0,0 +1,3 @@ +Based on http://www.codeproject.com/Articles/30936/Using-ICSharpCode-TextEditor + +ContextMenu : MysticBoy at https://github.com/maikebing/ICSharpCode.TextEditorEx \ No newline at end of file diff --git a/ICSharpCode.TextEditor/Project/Resources/ASPX.xshd b/ICSharpCode.TextEditor/Project/Resources/ASPX.xshd new file mode 100644 index 0000000..c691f4b --- /dev/null +++ b/ICSharpCode.TextEditor/Project/Resources/ASPX.xshd @@ -0,0 +1,17 @@ + + + + + + <% + %> + + + + + + // + + + + diff --git a/ICSharpCode.TextEditor/Project/Resources/BAT-Mode.xshd b/ICSharpCode.TextEditor/Project/Resources/BAT-Mode.xshd new file mode 100644 index 0000000..16b97f5 --- /dev/null +++ b/ICSharpCode.TextEditor/Project/Resources/BAT-Mode.xshd @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ICSharpCode.TextEditor/Project/Resources/Boo.xshd b/ICSharpCode.TextEditor/Project/Resources/Boo.xshd new file mode 100644 index 0000000..1a4ec03 --- /dev/null +++ b/ICSharpCode.TextEditor/Project/Resources/Boo.xshd @@ -0,0 +1,303 @@ + + + + + + + + + + + + + + + + + &<>~!@$%^*()-+=|\#/{}[]:;"' , .? + + + """ + """ + + + + # + + + + // + + + + /* + */ + + + + " + " + + + + """ + """ + + + + ' + ' + + + + @@/ + / + + + + /@! @@!/@ + / + + + ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + &<>~!@%^*()-+=|\#/{}[]:;"' , .? + + + /* + */ + + + + + + &<>~!@%^*()-+=|\#/{}[]:;"' , .? + + + /* + */ + + + + + + &<>~!@%^*()-+=|\#/{}[]:;"' , .? + + + ${ + } + + + + + + + + diff --git a/ICSharpCode.TextEditor/Project/Resources/CPP-Mode.xshd b/ICSharpCode.TextEditor/Project/Resources/CPP-Mode.xshd new file mode 100644 index 0000000..1504df1 --- /dev/null +++ b/ICSharpCode.TextEditor/Project/Resources/CPP-Mode.xshd @@ -0,0 +1,206 @@ + + + + + + + + + + + + + + ~!%^*()-+=|\#/{}[]:;"'<> , .? + + + # + + + + // + + + + /* + */ + + + + " + " + + + + ' + ' + + + ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ICSharpCode.TextEditor/Project/Resources/CSharp-Mode.xshd b/ICSharpCode.TextEditor/Project/Resources/CSharp-Mode.xshd new file mode 100644 index 0000000..e42943a --- /dev/null +++ b/ICSharpCode.TextEditor/Project/Resources/CSharp-Mode.xshd @@ -0,0 +1,339 @@ + + + + + + + + + + + + + + + + + + + &<>~!%^*()-+=|\#/{}[]:;"' , .? + + + # + + + + ///@!/@ + + + + //@!/@ + + + //// + + + + /* + */ + + + + " + " + + + + @@" + " + + + + ' + ' + + + ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + <>~!@%^*()-+=|\#/{}[]:;"' , .? + + + + + + + + + + + + <>~!@%^*()-+=|\#/{}[]:;"' , .? + + + < + > + + + + + + + + + + + + + + + &<>~!%^*()-+=|\#/{}[]:;"' , .? + + + + + + + + + + + + + + + + + + + <>~!@%^*()-+=|\#/{}[]:;"' , .? + + + " + " + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ICSharpCode.TextEditor/Project/Resources/Coco-Mode.xshd b/ICSharpCode.TextEditor/Project/Resources/Coco-Mode.xshd new file mode 100644 index 0000000..0eb3b93 --- /dev/null +++ b/ICSharpCode.TextEditor/Project/Resources/Coco-Mode.xshd @@ -0,0 +1,97 @@ + + + + + + + + + &<>~!@%^*()-+=|\#/{}[]:;"' , .? + + + // + + + + /* + */ + + + + + COMPILER + TOKENNAMES + + + + " + " + + + + ' + ' + + + + < + > + + + + (. + .) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ICSharpCode.TextEditor/Project/Resources/HTML-Mode.xshd b/ICSharpCode.TextEditor/Project/Resources/HTML-Mode.xshd new file mode 100644 index 0000000..5ca4ec4 --- /dev/null +++ b/ICSharpCode.TextEditor/Project/Resources/HTML-Mode.xshd @@ -0,0 +1,385 @@ + + + + + + + + + + + + + + + <!-- + --> + + + <script> + </script> + + + <script lang="JavaScript"> + </script> + + + <script lang="JScript"> + </script> + + + <script lang="VBScript"> + </script> + + + <script@C + </script> + + + < + > + + + + & + ; + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + /= + + + " + " + + + + ' + ' + + + = + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ICSharpCode.TextEditor/Project/Resources/ICSharpCode.TextEditor.snk b/ICSharpCode.TextEditor/Project/Resources/ICSharpCode.TextEditor.snk new file mode 100644 index 0000000000000000000000000000000000000000..a49f400e767a63cff1b0f7febf0c10343d230b6d GIT binary patch literal 596 zcmV-a0;~N80ssI2Bme+XQ$aES1ONa50096WpVmUMQ4OSI%Q$c1gpaNbGB%y_t?hRK zTGzB+bsR&MM+0P7$%9j8`-o^O=-7rH*@Sf@Ur6#3A!6mi81mAfK`>yrOQLx5@eOkz$?z+Ch_8NMWO3D1 zSuXQo_?#bjgAv|SMMz4``&37VBkE;Zp?M52q_V#YF5wa`67>U@GG$ z%k#&`vXbY0L-Yt|KKahW-5ufQvw_uBR%86?f9&;$eM`b7O|dLHx%fn~JRAkwGkVj8 zQdxCM3*G~?xsx3-)zPtgQXTElzZ9Q`N#EJfe&ahCx}>)!%<@{VET6E+Jc<>@JCh=< z2T{TZN0tlA;&fCIfWW15zsu8$GH$`HhzJAmgf~Ta#qbxt#v!~x(%8F|NeW@+<}kys zq^2`^kHDWlrqFV3pTH{Di(d*7)ao+U6;7ub|g_AhHeUz=u(TZ=T&YeH4F$DCo!&DnT6wqW(l zFvsBrH-lYCG3TE`w+SMB;?IBxe6|LUu0bjTJ@0`0xa502dM1UEv7q)+6NP3UJ(~t= izMc*^c*r(Tg^PXu&cFM>4=-sg+K7J$BNGRc(Q*?32P9np literal 0 HcmV?d00001 diff --git a/ICSharpCode.TextEditor/Project/Resources/JSON.xshd b/ICSharpCode.TextEditor/Project/Resources/JSON.xshd new file mode 100644 index 0000000..c6932e6 --- /dev/null +++ b/ICSharpCode.TextEditor/Project/Resources/JSON.xshd @@ -0,0 +1,66 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + &<>~%^*()-+=!|\/{}[]:;"' , ? + + + // + + + + /* + */ + + + + " + " + + + + + + + + + + diff --git a/ICSharpCode.TextEditor/Project/Resources/Java-Mode.xshd b/ICSharpCode.TextEditor/Project/Resources/Java-Mode.xshd new file mode 100644 index 0000000..913d16e --- /dev/null +++ b/ICSharpCode.TextEditor/Project/Resources/Java-Mode.xshd @@ -0,0 +1,180 @@ + + + + + + + + + + + + + + + ~!%^*()-+=|\#/{}[]:;"'<> , .? + + + // + + + + /* + */ + + + + " + " + + + + ' + ' + + + ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ~!%^*()-+=|\#/{}[]:;"'<> , .? + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ICSharpCode.TextEditor/Project/Resources/JavaScript-Mode.xshd b/ICSharpCode.TextEditor/Project/Resources/JavaScript-Mode.xshd new file mode 100644 index 0000000..23a4708 --- /dev/null +++ b/ICSharpCode.TextEditor/Project/Resources/JavaScript-Mode.xshd @@ -0,0 +1,136 @@ + + + + + + + + + + + + + + =!><+-/*%&|^~.}{,;][?: + + + // + + + + /* + */ + + + + " + " + + + + ' + ' + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ICSharpCode.TextEditor/Project/Resources/Lua-Mode.xshd b/ICSharpCode.TextEditor/Project/Resources/Lua-Mode.xshd new file mode 100644 index 0000000..cdd490a --- /dev/null +++ b/ICSharpCode.TextEditor/Project/Resources/Lua-Mode.xshd @@ -0,0 +1,101 @@ + + + + + + + + + + + ~@$%^&*()+=|\[]{};"'<> ,#: + + + --[[ + ]] + + + + -- + + + + " + " + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/ICSharpCode.TextEditor/Project/Resources/Mode.xsd b/ICSharpCode.TextEditor/Project/Resources/Mode.xsd new file mode 100644 index 0000000..82bce45 --- /dev/null +++ b/ICSharpCode.TextEditor/Project/Resources/Mode.xsd @@ -0,0 +1,296 @@ + + + + + This schema defines the syntax for mode definitions in SharpDevelop. + The schema can be simplified quite a bit but it does the job as is. + + + If you are using this file as a reference it is probably easiest to scroll to + the botton to find the definition of the root element called SyntaxDefinition and + then unwind the different type definitions and refernces. + + Note on coloring: + Many tags define how some symbol should be colored. If a specific symbol + can not be matched onto either a Span definition, Keyword, or a Digit/Number it + will be rendered in the current default color. Which is the default color of the + current span or the default color of the mode as a whole if no span has been entered. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ICSharpCode.TextEditor/Project/Resources/PHP-Mode.xshd b/ICSharpCode.TextEditor/Project/Resources/PHP-Mode.xshd new file mode 100644 index 0000000..39d5fa3 --- /dev/null +++ b/ICSharpCode.TextEditor/Project/Resources/PHP-Mode.xshd @@ -0,0 +1,198 @@ + + + + + + + + + + ~!%^*()-+=|\#/{}[]:;"'<> , .? + + + # + + + + /// + + + + //@!/@ + + + + /* + */ + + + + " + " + + + + @@" + " + + + + ' + ' + + + ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ~!@%^*()-+=|\#/{}[]:;"'<> , .? + + + + + + + ~!@%^*()-+=|\#/{}[]:;"'<> , .? + + + < + > + + + + + + + + + ~!@%^*()-+=|\#/{}[]:;"'<> , .? + + + " + " + + + + + + + + + + + + diff --git a/ICSharpCode.TextEditor/Project/Resources/Patch-Mode.xshd b/ICSharpCode.TextEditor/Project/Resources/Patch-Mode.xshd new file mode 100644 index 0000000..1f7100e --- /dev/null +++ b/ICSharpCode.TextEditor/Project/Resources/Patch-Mode.xshd @@ -0,0 +1,37 @@ + + + + + + + + ~!%^*()-+=|\#/{}[]:;"'<> , .? + + + Index: + + + == + + + --- + + + +++ + + + @@ + + + - + + + + + + + + + + + + diff --git a/ICSharpCode.TextEditor/Project/Resources/RightArrow.cur b/ICSharpCode.TextEditor/Project/Resources/RightArrow.cur new file mode 100644 index 0000000000000000000000000000000000000000..5691efbaf8997b9957eff92ce6b842edf232689a GIT binary patch literal 326 zcmb8pF%H5o5Jb^2NTfj`6`~_0rH_z~>*NS@TnmYUYmh3fJ17vtUlJ*G)_NZA%91Uu zl+-lRR#HyVo;-n(S|rxLrWl)fw>>utP6zIWcsFc9Ev+*;6zPZ^Dny^4&>^D+#UB>c h!0r2_C;OLO7?!jBu0|K~h9}B2`;cEaT=BqQ{{a3le&zrG literal 0 HcmV?d00001 diff --git a/ICSharpCode.TextEditor/Project/Resources/SQL-Mode.xshd b/ICSharpCode.TextEditor/Project/Resources/SQL-Mode.xshd new file mode 100644 index 0000000..4bac191 --- /dev/null +++ b/ICSharpCode.TextEditor/Project/Resources/SQL-Mode.xshd @@ -0,0 +1,200 @@ + + + + + + + + + + &<>~!%^*()-+=|\#/{}[]:;"' , .? + + ' + ' + + + -- + + + /* + */ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/ICSharpCode.TextEditor/Project/Resources/SyntaxModes.xml b/ICSharpCode.TextEditor/Project/Resources/SyntaxModes.xml new file mode 100644 index 0000000..3cfcddb --- /dev/null +++ b/ICSharpCode.TextEditor/Project/Resources/SyntaxModes.xml @@ -0,0 +1,61 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ICSharpCode.TextEditor/Project/Resources/SyntaxModesEx.Readme.txt b/ICSharpCode.TextEditor/Project/Resources/SyntaxModesEx.Readme.txt new file mode 100644 index 0000000..b32582a --- /dev/null +++ b/ICSharpCode.TextEditor/Project/Resources/SyntaxModesEx.Readme.txt @@ -0,0 +1,20 @@ +Lua syntax highlighting based on http://github.com/nano-byte/ICSharpCode.TextEditor +SQL syntax highlighting based on http://www.codeproject.com/Articles/111969/Extending-ICSharpCode-TextEditor-Syntax-Highlighti + +All supported: +ASP/XHTML +BAT +Boo +Coco +C++.NET +C# +HTML +Java +JavaScript +Patch +PHP +TeX +VBNET +XML +Lua +SQL \ No newline at end of file diff --git a/ICSharpCode.TextEditor/Project/Resources/SyntaxModesEx.xml b/ICSharpCode.TextEditor/Project/Resources/SyntaxModesEx.xml new file mode 100644 index 0000000..ff73f2a --- /dev/null +++ b/ICSharpCode.TextEditor/Project/Resources/SyntaxModesEx.xml @@ -0,0 +1,8 @@ + + + + \ No newline at end of file diff --git a/ICSharpCode.TextEditor/Project/Resources/Tex-Mode.xshd b/ICSharpCode.TextEditor/Project/Resources/Tex-Mode.xshd new file mode 100644 index 0000000..91083b0 --- /dev/null +++ b/ICSharpCode.TextEditor/Project/Resources/Tex-Mode.xshd @@ -0,0 +1,108 @@ + + + + + + + + + + &~!@%^*()-+=|\#/{}[]:;"'<> , .? + + + % + + + + $$ + $$ + + + \[ + \] + + + + \ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + &~!@%^*()-+=|\#/{}[]:;"'<> , .? + + + + % + + + + \ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ICSharpCode.TextEditor/Project/Resources/TextEditorControl.bmp b/ICSharpCode.TextEditor/Project/Resources/TextEditorControl.bmp new file mode 100644 index 0000000000000000000000000000000000000000..4bd828012d3d1d6ae8eb3b8e620b0e019a362950 GIT binary patch literal 824 zcmZ?rwP0od12Z700mK4O%*Y@C76%bR+z<>C_|Nd4rfB`D^~58fOMr_169+&lVd@ze z8i1N0egZO{w1hwqNU#AY3G^h`WSDv&mmokfA8ZK399YPK%tW>bSv^6+feMh-!_9#^ z8_oc^9_%}0^}l}oB4`D&dWf5WS|Fi;<`JMQntKQuj;tPN5s?TOUuZtX&m}`WPzy + + + + + + + + + + + + + + + + ~!@%^*()-+=|\#/{}[]:;"'<>,.? + + + " + " + + + + # + + + + # + # + + + + ' + + + + REM@C + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ~!@%^*()-+=|\#/{}[]:;"'<>,.? + + + + + + + + + + + + + + diff --git a/ICSharpCode.TextEditor/Project/Resources/XML-Mode.xshd b/ICSharpCode.TextEditor/Project/Resources/XML-Mode.xshd new file mode 100644 index 0000000..5678048 --- /dev/null +++ b/ICSharpCode.TextEditor/Project/Resources/XML-Mode.xshd @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + + + <!-- + --> + + + <![CDATA[ + ]]> + + + <!DOCTYPE + > + + + <? + ?> + + + < + > + + + & + ; + + + + + /= + + " + " + + + ' + ' + + = + + + + + + + + + diff --git a/ICSharpCode.TextEditor/Project/Resources/cut.png b/ICSharpCode.TextEditor/Project/Resources/cut.png new file mode 100644 index 0000000000000000000000000000000000000000..f215d6f6b7c81ab344a3e53e0e5e756c58c82d90 GIT binary patch literal 648 zcmV;30(bq1P)Sxb0Y6MkDSd{nPwTp^L>b`TxVmKBiF^NLQ>My+_!0?|)hBPe_#}P$?rlUF;M20U z`oNWE|K(DrsR+gN%g)?+`OfqmmmiA8O_U(YfPnV(E$8mN{jZQVJ-L7LxzmTQJ^!PS zHqDyoKn&^H)Oq>Q$Nzns&wsn~^6P)|vPC&W#R9Kw95|FC`?q!91!~-K_R;^uDLWSs zj7A}sG%2IZxvQP(HeS-nn71T-`ku2F9(?#8KXKbp!Qe&~yaot4r%3=c-cDF`=YPn& z3!hUrzxqFO*Ny)VU;p?Y-nniMuVuI+UIUD>=B$ZZdhM^2Z-JeJcbDl-H)3?9>AG_?ve_7A86r2Wlb?y7)ShoTkzeah}rl)Oq{=a0=sekDkpZ?d4n{v>% zcgJPxg7sgx4Lmup8sO5j^?B5iYcCv|)*VgT_U6A?-trsfIcr`APCfD&B(EM;R_0K# z{lAb!kTg~UrQ ilDgypkCC?sx*7nz@;j(q!3lK=n!AY({UO#lFTB>(_`g8%^e{{R4h=>PzA zFaQARU;qF*m;eA5Z<1fdMgRZ-+(|@1RCwB?(@#iLQ547V@0A)GM5#nbB@&`VNKzpN z;i5l_!X}7{h^$Sth!)|(RxN9%i)hor7KH{ut+X~JH8d*)DTKnNAwne(`@=MU-n;+a zX(3O97h~Xs19$P`a}Vcmm58vBvnjqzI*JNyBYADXfRTo3v`y>uT5g22*dwYQU4@L%`yqF$dn^Ex$o=?uonPZ&* zyw1JieQJTSUZS_*2-{Q(gLm%X+{z` zsC70)wY}Tm-GDk zR^Uf*oR+T{T75f#v9;IetVN;d?rP;wev;IDFHyOfy*oFvf6qaTjn)C2Lx9Gu+qrqE zkB_r$q_(8l(cXbo4V0@vZNPhvj}%XH5kE`y6e|Y^wWd0w33Q3;Cz@&l0M-hpy9E=~ zVO%}VrO-Bwb`j-LWqrVj@c6CgcPS*$nu^whTBDug>e0sk0IYCwUy(oMDq*OpR70vk zh&G1B`6ZN6wSi3k4dRSaDzS6#lbcW*{my>}0KZ8PgQg|FApigX07*qoM6N<$g0w0Q Ao&W#< literal 0 HcmV?d00001 diff --git a/ICSharpCode.TextEditor/Project/Resources/sc_cancel.png b/ICSharpCode.TextEditor/Project/Resources/sc_cancel.png new file mode 100644 index 0000000000000000000000000000000000000000..cf6fa4c0890850976a04352998cdefab94cbd24d GIT binary patch literal 510 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`oCO|{#S9GG!XV7ZFl&wkP>?0v z(btiI;l}H)G9s&he1&9>AYTTCJOu`ZqBjf--#r-^T7EGwkUkkDXZV078Y$iT>Ao*=2b_;QxpvHuD@DbtOB z;DA9#zsmEL90mu5gMT-*FnYYb%Y97YTMb)V&;*+fx9QG&%nb}ljgnuOU3B94(%ki< zju@Eht;@EW@OOISLT$zq?I-^_d}adb_SH&Dm|-EAvTLFs%l0jQ*grR#`0yNIU+G-N z=)j;T!Nc?Dro&0T32Jq99OurrZ+3j7P&vUWg_(hg<0iufMNbFUtqRT)3_9ksT=W{1!v6~DH3{A!jW>c0mKDl)H|NrzPrep*8Q*52f z9bV2kHKY1ntHM%X)P~m1^kzm-$TU0X5v-UJFt{6OB{an^LB{Ts5=H01p literal 0 HcmV?d00001 diff --git a/ICSharpCode.TextEditor/Project/Resources/sc_copy.png b/ICSharpCode.TextEditor/Project/Resources/sc_copy.png new file mode 100644 index 0000000000000000000000000000000000000000..174d577794bdfe40d16138e6e1b2942a337c78f4 GIT binary patch literal 600 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`oCO|{#S9GG!XV7ZFl&wkP>?0v z(btiI;l}H)G9s&he1&9>AYTTCJOu`ZqBjf--#r-^T7EGw<#f8UNRpINevN8PmT{r);bpV_-v zB4woo>W`-}fQ?V8-zlXznq7?^fF5N9?vHa@&h%YO%BPY(}| zvDlrI6CM1n8*^{JW9FIIIFHBZk<)RLQeNH0wmCp!H;6|>&X9N$y-jhaLtA6_i36-0 z3I#eJ3JVS9x>4syiEo4dE=$J-n4sr^1L@$9j; z35E>}Ni*$t9%hMtA$I-y97p~bfe+WFCwXiNOGrp%J*sn9ALu^S37c>5oLHZCoFgWM z!MC%Xk*R|<{7({hi~xtp#B_}h7uZfW0|Vrt2}Ae;B?!}y!MI=}BhYY`pRB7{louN+ go?Y;PnZtnL@a^}OI%y|ufT@7N)78&qol`;+0PL~s?f?J) literal 0 HcmV?d00001 diff --git a/ICSharpCode.TextEditor/Project/Resources/sc_cut.png b/ICSharpCode.TextEditor/Project/Resources/sc_cut.png new file mode 100644 index 0000000000000000000000000000000000000000..001180acddd024ed4d16cf21e6f450880dd6f048 GIT binary patch literal 802 zcmV+-1Ks?IP)-TNCQEY@ z267Am=}}izVE_mq1}~q$zJrI4{wKu{klG!)_x}g!1qdJzUn(*tanGsK=ZP`|q~_3( z zF#P(%!0`PO!?CSr7;Zgdj0G725I~Gz%`#kzoxC&|j_*7Qlwe@6GiP9U38Z&k;b2hZ z{lVaF#lZ0KI|IX^GYkyh{xUEm#_2Jf+jEKG$}^5wfB*nN0KWeM`~Co9LPa3}$F;=( zI-3ANGJqv z0*Hl`iNS&4{XK?n-@snkAK=W#uxRHih8Y(b+b+NTJqhGRCI%LU?|T`miah@_X!A4d z|INzF!20Pi!#@TF2Y>)#c9LcGk>UCxrEeg>V4%XvuyWU1hKc8y+OE9)57PV+h`(NZ z^Y5w{7wfNkui0%A{6raa75*_idia6i!E=T)00G3j g*{^{38bE*n0PtlMNF)u)_W%F@07*qoM6N<$f*svtmH+?% literal 0 HcmV?d00001 diff --git a/ICSharpCode.TextEditor/Project/Resources/sc_linearrowend.png b/ICSharpCode.TextEditor/Project/Resources/sc_linearrowend.png new file mode 100644 index 0000000000000000000000000000000000000000..683bf37f2b3a0027ff5e289d34d8851f642e350b GIT binary patch literal 539 zcmV+$0_6RPP)01;)tkDq^J_n-eT`OwApPIHdGK`{g%fQT~S z%hz8FQt})O&;Bz`zVPsi(}X?G;D!JM5F?0a+;E?dfEhACEW7p6uN~~%>wJW^f|LC7-_km;K7dTlV$MX9gD?Mh~FSV}Jl+dH>;C>?_l9%W8h@`oy)_)uz^Q>-8KIssI20AY({UO#lFHAOHYz>;M4wNdN$Z`TziL zg8%^NApig|x&Qzdi4RZnl>h(&k4Z#9RCwBA{Qv(y13K{V@|gytGNIh*JGO1fV!#0a z0*C=*06KPbcKQGH`wuV%v4QI3u&D!KfB<5g7tPT9gOMTYHvu>A~d!Cyx%$+xn z;lPn&U<({54IYd%m5)*Z4O>Oo$!B{pWvCyvZjZ#9(acz_926{5c zhzrfq)HS>b5I`*7e=;!q`~3lK8#@!wAPEKrW)41v^S>n+6qMwE94@fjw_kr5zJB|` z@cP471{X&=hQI&Y4gv%a%a30S4FCWB#EhMPzZiZ1UB}46g(Cj_*Dr=?bCd2U+ZFaii5mJN3qN`Yb%fmjq2sBl$x zo-w4D{W-+U$jDIasmle#7%uqx_b&rL05M|AjWCduSBYB^3IGBKuK|pVjK+iv00=Mu Xq$mCuLp24<00000NkvXXu0mjfjo?P3 literal 0 HcmV?d00001 diff --git a/ICSharpCode.TextEditor/Project/Resources/sc_redo.png b/ICSharpCode.TextEditor/Project/Resources/sc_redo.png new file mode 100644 index 0000000000000000000000000000000000000000..b652bf267b0cc9932f7d55b6d700babf49320290 GIT binary patch literal 894 zcmV-^1A+XBP)z^Q>-8KIssI20AY({UO#lFHAOHYz>;M4wNdN$Z`TziL zg8%^NApig|x&Qzdi4RZnl>h(&^hrcPRCwBA`v3nw10@(BfEXE&fJS0(I0y5;dQRs5 zs%%XESwLdH{xSaf{+IFgkH1WfH|7_wG)U>b!q56o8zlGfH`7&s0D>E!lhAokOPbFv zH`<3mN<@Hxm5B)~{`=oQhG%a+Fic*$h2h%MPlp8A{@N{V%LmIvS50I92q0zI!_R+=3?IJ!W_bVY7Z`u~{+ofBg@wV*(tzR5uW$1A9zSI;GSCCd?Kygi z0U&@_6vG?Bl|{Mi9Ieb4ZoT-#@aFRmh69I>F+8|`4{VEqq7s9Htrdd^F9!oi&C?gJ z8Qy;R36}f&`!@qX0I@Lt{nixf7tZkb?N_ko)ytPKJbwOa>Z@a`mVgvJunLR0d;4Z~ zXk-l7kU(!YhIe0nferZc`xgU10I{(B`>p)(H!}kx!ykr&2M;nlc>HwgtAmSLfbuVu z0*Ws4vi#Tn`Qrz}+4C0}Y|M=rzW(~d!0?{|YQS#>fB<4){PUCf_us$3C}Ci@f8z=R zRPzgv0?vOww0jzA!HQpg`oZw>;}7IW1BS^@27mx!`ThGRSTi`pe}nz@0;T}Ozx4P6 z12*ve_dkZOKY#rO2q2d4KY#!I@#jA?2MZ&Eww4yd>n{nF?+-6%2Pu00<;T6Ly=_X^ z4EX(z@%NYSzYYTg5X+Zu-ygks|3OhsT$sVl-IL+sg>!9xEFu{`pIF}h`tVZU*Fc{z zULjAq~jdPRhrg@KoyiQ(s;e++@4k$>Nv+scB&6$}6Y z1d@3F{ql~eeZT)KyMN=NQEG7=0}H1V!;e2e1Aqbb_s?$zd;lPTkTa7yql{q?D~~`b z8#ALE3*&$0KmQs3{`mFh#Gl)TY;YL>5I~G*d_f?V0%CTk94MeZ0r4GN1^@&Y0Gv!` U-T${XCjbBd07*qoM6N<$f<`l&`Tzg` literal 0 HcmV?d00001 diff --git a/ICSharpCode.TextEditor/Project/Resources/sc_searchdialog.png b/ICSharpCode.TextEditor/Project/Resources/sc_searchdialog.png new file mode 100644 index 0000000000000000000000000000000000000000..f16ee75f9591c55918a26de8393fcb9785b3de65 GIT binary patch literal 680 zcmV;Z0$2TsP)9LdPYw1I4aVGy4fIe-9SM0VnwIdd3x@7~Su zMGZ^q8JPf4Z-5)&!1;lwrm+gWMm{*4Ke@-AQo)i`Sk z36}u^h>4MrQTO4)hYXJ&KL#u2n&rBM5`oATf{}2!Par^nmmN1P~Lt zpFqwB*#rd#pcpI$l0ydo0mQ^WApi&W)Kn*!cYhkg9ad2 z4+sDR5X+Y@Ul`V{TL(@oA3l6w`19uvSP~=-10eRHLx;c?u&}Uz4Y+dU3RoN-NC8s(W6J;s1gwo0i`sC&!0bo4FIXp(a~Xe^X3gW9OUHW7__yu8IB)6&HxZV zjKFBw3>5T1cQVkREx;)C1+np|0SF*adR7BsDJz^Q>-8KIssI20AY({UO#lFHAOHYz>;M4wNdN$Z`TziL zg8%^NApig|x&Qzdi4RZnl>h(%$w@>(RCwBA{Qv(y0|<0XTLNL=0Nk7$3}3(hVEFy} zS3q4s@yH8(n;x$|L{{Qp$-~W}H_x^9_o9z!X03d)^U^e{w z_mAQ0k6#Qwe*MPkg0~+&F}(ipmBG%+45Vg5UC+z_pzub30Ahk0@b@p!NCuqv=dV8u zZ$EtpV`C#D24*Ja4Iq600R(r!-+zA@7=gw;{8ot71>b+TGpt&>9zz@;fDi`!0U7{= z-+q2(Xj~YMqNqN8C4;Yr6T{a}9~pl9_zo6ZwQ(ny1_&U80e}B6FflPPeE;=^p)qk8 ziUD7Lyl1%gEuDb@q?(6;L6KuR13NnhSPme7&6w1*|O0VD%sX5I`s{067f=T34pwG$7x<6U7Ap0fdwe{(%D# z1ggVl5ex&6dJq5zAQt45$OKFWfqF+7hynkgUH}Lnq`dH#A-$-Yfm8qxK#Z{L%*e=S hOsZy}-m?G!1^{cB-lhmMOTYjC002ovPDHLkV1g`@<;Va4 literal 0 HcmV?d00001 diff --git a/ICSharpCode.TextEditor/Project/Resources/sc_undo.png b/ICSharpCode.TextEditor/Project/Resources/sc_undo.png new file mode 100644 index 0000000000000000000000000000000000000000..c6df3a1643f8fa21034e2d4ee899d2b46008d38b GIT binary patch literal 888 zcmV-;1Bd*HP)z^Q>-8KIssI20AY({UO#lFHAOHYz>;M4wNdN$Z`TziL zg8%^NApig|x&Qzdi4RZnl>h(&?ny*JRCwBA{Qv(y10?_;fEW!@y07rF{?i6=KK^F9 zdSOnfjz(f{I0y5;dQRs5s%%XESwM2X{xSaf{+IFgkH1WfH|7_wgc$%3K#aC&ZU2|H z<%2oVRTCNB|6)F*CCz7-8|}j&B_hDU%ESbg`~B}9!?QOZ7$z^>!f@^Br$bj3l-q;E z0Ro8S@891H_g;SiD+00ot##}oJnR@ge*eYr`0Y0o=d!S{Gt_28GOXBf$nM|YmV?(8 z*VqFD5X;}+zZu?s`3Y8_rXa)MXl2H5>%}LAH=lnn95{T8;lcfTU^xXvB?bpuD+Uo> z4zQX_*YDVU4{rzu2mnC*H5tR$iTAY6is+D6U_;I=HCiK0ixq0Ro8S>(5`mzyJQn%E`(E zHvr@f5C(_-@1MwKzku;U$p^#+2q2b^pS~QPIib^*mFYh#wuJKe=f8U}wxDf7rM8w9 znEm6=e}?ZrfByvtAeQ&XSERjXVBiO(2^@*-JBZ-5h^!Q3``zZ|?#b}uKNG{7_iq`# zeEa?gAb>!r>@Kck%4-t5m6Q3O8w(Qyi;0;YLv&;W!{_8KMUSW9i^$!CR^M8hI>y|RyyMO=O&zlG50R#|U1OEQ`&G7m+ zCj&bJ6T=TsQhxW0VRmm5!~J^?&;7iyCkm+KJwO04;xm9n)$Rxf8;b=CovNDV*$F%rnFDnQH!RR>B7pg4R6vKgiTAiw|~8AvwYG}1Q! O0000z@;j(q!3lK=n!AY({UO#lFTB>(_`g8%^e{{R4h=>PzA zFaQARU;qF*m;eA5Z<1fdMgRZ-+(|@1RCwB~lD%uwQ51%s`)eZ=ELNoW5u2zfibWI! zvpTpH2M0I7segc=PCDvj2IVFCYIg<^K}2}+JS+Wl=ixI+PEN_a z+1IyP`PK_sAm<$Y6ow%;u3ch!CVl1p+?!OV7j)L`r>#Q~uHU>3!1PS|a;G=g1{i|? zs%t!y;^Mi}h!<)N&D{r&=-EKLN4#etTfloyZfOx A%>V!Z literal 0 HcmV?d00001 diff --git a/ICSharpCode.TextEditor/Project/Src/Actions/BookmarkActions.cs b/ICSharpCode.TextEditor/Project/Src/Actions/BookmarkActions.cs new file mode 100644 index 0000000..bc4f8d5 --- /dev/null +++ b/ICSharpCode.TextEditor/Project/Src/Actions/BookmarkActions.cs @@ -0,0 +1,80 @@ +// +// +// +// +// $Revision$ +// + +using System; +using ICSharpCode.TextEditor.Document; + +namespace ICSharpCode.TextEditor.Actions +{ + public class ToggleBookmark : AbstractEditAction + { + public override void Execute(TextArea textArea) + { + textArea.Document.BookmarkManager.ToggleMarkAt(textArea.Caret.Position); + textArea.Document.RequestUpdate(new TextAreaUpdate(TextAreaUpdateType.SingleLine, textArea.Caret.Line)); + textArea.Document.CommitUpdate(); + + } + } + + public class GotoPrevBookmark : AbstractEditAction + { + Predicate predicate = null; + + public GotoPrevBookmark(Predicate predicate) + { + this.predicate = predicate; + } + + public override void Execute(TextArea textArea) + { + Bookmark mark = textArea.Document.BookmarkManager.GetPrevMark(textArea.Caret.Line, predicate); + if (mark != null) { + textArea.Caret.Position = mark.Location; + textArea.SelectionManager.ClearSelection(); + textArea.SetDesiredColumn(); + } + } + } + + public class GotoNextBookmark : AbstractEditAction + { + Predicate predicate = null; + + public GotoNextBookmark(Predicate predicate) + { + this.predicate = predicate; + } + + public override void Execute(TextArea textArea) + { + Bookmark mark = textArea.Document.BookmarkManager.GetNextMark(textArea.Caret.Line, predicate); + if (mark != null) { + textArea.Caret.Position = mark.Location; + textArea.SelectionManager.ClearSelection(); + textArea.SetDesiredColumn(); + } + } + } + + public class ClearAllBookmarks : AbstractEditAction + { + Predicate predicate = null; + + public ClearAllBookmarks(Predicate predicate) + { + this.predicate = predicate; + } + + public override void Execute(TextArea textArea) + { + textArea.Document.BookmarkManager.RemoveMarks(predicate); + textArea.Document.RequestUpdate(new TextAreaUpdate(TextAreaUpdateType.WholeTextArea)); + textArea.Document.CommitUpdate(); + } + } +} diff --git a/ICSharpCode.TextEditor/Project/Src/Actions/CaretActions.cs b/ICSharpCode.TextEditor/Project/Src/Actions/CaretActions.cs new file mode 100644 index 0000000..f6738b8 --- /dev/null +++ b/ICSharpCode.TextEditor/Project/Src/Actions/CaretActions.cs @@ -0,0 +1,203 @@ +// +// +// +// +// $Revision$ +// + +using System; +using System.Collections.Generic; +using System.Drawing; + +using ICSharpCode.TextEditor.Document; + +namespace ICSharpCode.TextEditor.Actions +{ + public class CaretLeft : AbstractEditAction + { + public override void Execute(TextArea textArea) + { + TextLocation position = textArea.Caret.Position; + List foldings = textArea.Document.FoldingManager.GetFoldedFoldingsWithEnd(position.Y); + FoldMarker justBeforeCaret = null; + foreach (FoldMarker fm in foldings) { + if (fm.EndColumn == position.X) { + justBeforeCaret = fm; + break; // the first folding found is the folding with the smallest Startposition + } + } + + if (justBeforeCaret != null) { + position.Y = justBeforeCaret.StartLine; + position.X = justBeforeCaret.StartColumn; + } else { + if (position.X > 0) { + --position.X; + } else if (position.Y > 0) { + LineSegment lineAbove = textArea.Document.GetLineSegment(position.Y - 1); + position = new TextLocation(lineAbove.Length, position.Y - 1); + } + } + + textArea.Caret.Position = position; + textArea.SetDesiredColumn(); + } + } + + public class CaretRight : AbstractEditAction + { + public override void Execute(TextArea textArea) + { + LineSegment curLine = textArea.Document.GetLineSegment(textArea.Caret.Line); + TextLocation position = textArea.Caret.Position; + List foldings = textArea.Document.FoldingManager.GetFoldedFoldingsWithStart(position.Y); + FoldMarker justBehindCaret = null; + foreach (FoldMarker fm in foldings) { + if (fm.StartColumn == position.X) { + justBehindCaret = fm; + break; + } + } + if (justBehindCaret != null) { + position.Y = justBehindCaret.EndLine; + position.X = justBehindCaret.EndColumn; + } else { // no folding is interesting + if (position.X < curLine.Length || textArea.TextEditorProperties.AllowCaretBeyondEOL) { + ++position.X; + } else if (position.Y + 1 < textArea.Document.TotalNumberOfLines) { + ++position.Y; + position.X = 0; + } + } + textArea.Caret.Position = position; + textArea.SetDesiredColumn(); + } + } + + public class CaretUp : AbstractEditAction + { + public override void Execute(TextArea textArea) + { + TextLocation position = textArea.Caret.Position; + int lineNr = position.Y; + int visualLine = textArea.Document.GetVisibleLine(lineNr); + if (visualLine > 0) { + Point pos = new Point(textArea.TextView.GetDrawingXPos(lineNr, position.X), + textArea.TextView.DrawingPosition.Y + (visualLine - 1) * textArea.TextView.FontHeight - textArea.TextView.TextArea.VirtualTop.Y); + textArea.Caret.Position = textArea.TextView.GetLogicalPosition(pos); + textArea.SetCaretToDesiredColumn(); + } +// if (textArea.Caret.Line > 0) { +// textArea.SetCaretToDesiredColumn(textArea.Caret.Line - 1); +// } + } + } + + public class CaretDown : AbstractEditAction + { + public override void Execute(TextArea textArea) + { + TextLocation position = textArea.Caret.Position; + int lineNr = position.Y; + int visualLine = textArea.Document.GetVisibleLine(lineNr); + if (visualLine < textArea.Document.GetVisibleLine(textArea.Document.TotalNumberOfLines)) { + Point pos = new Point(textArea.TextView.GetDrawingXPos(lineNr, position.X), + textArea.TextView.DrawingPosition.Y + + (visualLine + 1) * textArea.TextView.FontHeight + - textArea.TextView.TextArea.VirtualTop.Y); + textArea.Caret.Position = textArea.TextView.GetLogicalPosition(pos); + textArea.SetCaretToDesiredColumn(); + } +// if (textArea.Caret.Line + 1 < textArea.Document.TotalNumberOfLines) { +// textArea.SetCaretToDesiredColumn(textArea.Caret.Line + 1); +// } + } + } + + public class WordRight : CaretRight + { + public override void Execute(TextArea textArea) + { + LineSegment line = textArea.Document.GetLineSegment(textArea.Caret.Position.Y); + TextLocation oldPos = textArea.Caret.Position; + TextLocation newPos; + if (textArea.Caret.Column >= line.Length) { + newPos = new TextLocation(0, textArea.Caret.Line + 1); + } else { + int nextWordStart = TextUtilities.FindNextWordStart(textArea.Document, textArea.Caret.Offset); + newPos = textArea.Document.OffsetToPosition(nextWordStart); + } + + // handle fold markers + List foldings = textArea.Document.FoldingManager.GetFoldingsFromPosition(newPos.Y, newPos.X); + foreach (FoldMarker marker in foldings) { + if (marker.IsFolded) { + if (oldPos.X == marker.StartColumn && oldPos.Y == marker.StartLine) { + newPos = new TextLocation(marker.EndColumn, marker.EndLine); + } else { + newPos = new TextLocation(marker.StartColumn, marker.StartLine); + } + break; + } + } + + textArea.Caret.Position = newPos; + textArea.SetDesiredColumn(); + } + } + + public class WordLeft : CaretLeft + { + public override void Execute(TextArea textArea) + { + TextLocation oldPos = textArea.Caret.Position; + if (textArea.Caret.Column == 0) { + base.Execute(textArea); + } else { + LineSegment line = textArea.Document.GetLineSegment(textArea.Caret.Position.Y); + + int prevWordStart = TextUtilities.FindPrevWordStart(textArea.Document, textArea.Caret.Offset); + + TextLocation newPos = textArea.Document.OffsetToPosition(prevWordStart); + + // handle fold markers + List foldings = textArea.Document.FoldingManager.GetFoldingsFromPosition(newPos.Y, newPos.X); + foreach (FoldMarker marker in foldings) { + if (marker.IsFolded) { + if (oldPos.X == marker.EndColumn && oldPos.Y == marker.EndLine) { + newPos = new TextLocation(marker.StartColumn, marker.StartLine); + } else { + newPos = new TextLocation(marker.EndColumn, marker.EndLine); + } + break; + } + } + textArea.Caret.Position = newPos; + textArea.SetDesiredColumn(); + } + + + } + } + + public class ScrollLineUp : AbstractEditAction + { + public override void Execute(TextArea textArea) + { + textArea.AutoClearSelection = false; + + textArea.MotherTextAreaControl.VScrollBar.Value = Math.Max(textArea.MotherTextAreaControl.VScrollBar.Minimum, + textArea.VirtualTop.Y - textArea.TextView.FontHeight); + } + } + + public class ScrollLineDown : AbstractEditAction + { + public override void Execute(TextArea textArea) + { + textArea.AutoClearSelection = false; + textArea.MotherTextAreaControl.VScrollBar.Value = Math.Min(textArea.MotherTextAreaControl.VScrollBar.Maximum, + textArea.VirtualTop.Y + textArea.TextView.FontHeight); + } + } +} diff --git a/ICSharpCode.TextEditor/Project/Src/Actions/ClipBoardActions.cs b/ICSharpCode.TextEditor/Project/Src/Actions/ClipBoardActions.cs new file mode 100644 index 0000000..d5387e9 --- /dev/null +++ b/ICSharpCode.TextEditor/Project/Src/Actions/ClipBoardActions.cs @@ -0,0 +1,42 @@ +// +// +// +// +// $Revision$ +// + +using System; + +namespace ICSharpCode.TextEditor.Actions +{ + public class Cut : AbstractEditAction + { + public override void Execute(TextArea textArea) + { + if (textArea.Document.ReadOnly) { + return; + } + textArea.ClipboardHandler.Cut(null, null); + } + } + + public class Copy : AbstractEditAction + { + public override void Execute(TextArea textArea) + { + textArea.AutoClearSelection = false; + textArea.ClipboardHandler.Copy(null, null); + } + } + + public class Paste : AbstractEditAction + { + public override void Execute(TextArea textArea) + { + if (textArea.Document.ReadOnly) { + return; + } + textArea.ClipboardHandler.Paste(null, null); + } + } +} diff --git a/ICSharpCode.TextEditor/Project/Src/Actions/FindAndReplaceFormActions.cs b/ICSharpCode.TextEditor/Project/Src/Actions/FindAndReplaceFormActions.cs new file mode 100644 index 0000000..9230085 --- /dev/null +++ b/ICSharpCode.TextEditor/Project/Src/Actions/FindAndReplaceFormActions.cs @@ -0,0 +1,69 @@ +using ICSharpCode.TextEditor.Actions; +using ICSharpCode.TextEditor.UserControls; + +namespace ICSharpCode.TextEditor.Src.Actions +{ + abstract class FindAndReplaceFormActions : AbstractEditAction + { + protected readonly TextEditorControlEx Control; + protected readonly FindAndReplaceForm FindForm; + + protected FindAndReplaceFormActions(FindAndReplaceForm findForm, TextEditorControlEx control) + { + FindForm = findForm; + Control = control; + } + } + + class FindAgainReverseAction : FindAndReplaceFormActions + { + public FindAgainReverseAction(FindAndReplaceForm findForm, TextEditorControlEx control) + : base(findForm, control) + { + } + + public override void Execute(TextArea textArea) + { + FindForm.FindNext(true, true, string.Format("Search text «{0}» not found.", FindForm.LookFor)); + } + } + + class FindAgainAction : FindAndReplaceFormActions + { + public FindAgainAction(FindAndReplaceForm findForm, TextEditorControlEx control) + : base(findForm, control) + { + } + + public override void Execute(TextArea textArea) + { + FindForm.FindNext(true, false, string.Format("Search text «{0}» not found.", FindForm.LookFor)); + } + } + + class EditReplaceAction : FindAndReplaceFormActions + { + public EditReplaceAction(FindAndReplaceForm findForm, TextEditorControlEx control) + : base(findForm, control) + { + } + + public override void Execute(TextArea textArea) + { + FindForm.ShowFor(Control, true); + } + } + + class EditFindAction : FindAndReplaceFormActions + { + public EditFindAction(FindAndReplaceForm findForm, TextEditorControlEx control) + : base(findForm, control) + { + } + + public override void Execute(TextArea textArea) + { + FindForm.ShowFor(Control, false); + } + } +} \ No newline at end of file diff --git a/ICSharpCode.TextEditor/Project/Src/Actions/FoldActions.cs b/ICSharpCode.TextEditor/Project/Src/Actions/FoldActions.cs new file mode 100644 index 0000000..1f66438 --- /dev/null +++ b/ICSharpCode.TextEditor/Project/Src/Actions/FoldActions.cs @@ -0,0 +1,68 @@ +// +// +// +// +// $Revision$ +// + +using System; +using System.Collections.Generic; +using ICSharpCode.TextEditor.Document; + +namespace ICSharpCode.TextEditor.Actions +{ + public class ToggleFolding : AbstractEditAction + { + public override void Execute(TextArea textArea) + { + List foldMarkers = textArea.Document.FoldingManager.GetFoldingsWithStart(textArea.Caret.Line); + if (foldMarkers.Count != 0) { + foreach (FoldMarker fm in foldMarkers) + fm.IsFolded = !fm.IsFolded; + } else { + foldMarkers = textArea.Document.FoldingManager.GetFoldingsContainsLineNumber(textArea.Caret.Line); + if (foldMarkers.Count != 0) { + FoldMarker innerMost = foldMarkers[0]; + for (int i = 1; i < foldMarkers.Count; i++) { + if (new TextLocation(foldMarkers[i].StartColumn, foldMarkers[i].StartLine) > + new TextLocation(innerMost.StartColumn, innerMost.StartLine)) + { + innerMost = foldMarkers[i]; + } + } + innerMost.IsFolded = !innerMost.IsFolded; + } + } + textArea.Document.FoldingManager.NotifyFoldingsChanged(EventArgs.Empty); + } + } + + public class ToggleAllFoldings : AbstractEditAction + { + public override void Execute(TextArea textArea) + { + bool doFold = true; + foreach (FoldMarker fm in textArea.Document.FoldingManager.FoldMarker) { + if (fm.IsFolded) { + doFold = false; + break; + } + } + foreach (FoldMarker fm in textArea.Document.FoldingManager.FoldMarker) { + fm.IsFolded = doFold; + } + textArea.Document.FoldingManager.NotifyFoldingsChanged(EventArgs.Empty); + } + } + + public class ShowDefinitionsOnly : AbstractEditAction + { + public override void Execute(TextArea textArea) + { + foreach (FoldMarker fm in textArea.Document.FoldingManager.FoldMarker) { + fm.IsFolded = fm.FoldType == FoldType.MemberBody || fm.FoldType == FoldType.Region; + } + textArea.Document.FoldingManager.NotifyFoldingsChanged(EventArgs.Empty); + } + } +} diff --git a/ICSharpCode.TextEditor/Project/Src/Actions/FormatActions.cs b/ICSharpCode.TextEditor/Project/Src/Actions/FormatActions.cs new file mode 100644 index 0000000..6907b2f --- /dev/null +++ b/ICSharpCode.TextEditor/Project/Src/Actions/FormatActions.cs @@ -0,0 +1,219 @@ +// +// +// +// +// $Revision$ +// + +using System; +using System.Text; +using ICSharpCode.TextEditor.Document; + +namespace ICSharpCode.TextEditor.Actions +{ + public abstract class AbstractLineFormatAction : AbstractEditAction + { + protected TextArea textArea; + abstract protected void Convert(IDocument document, int startLine, int endLine); + + public override void Execute(TextArea textArea) + { + if (textArea.SelectionManager.SelectionIsReadonly) { + return; + } + this.textArea = textArea; + textArea.BeginUpdate(); + textArea.Document.UndoStack.StartUndoGroup(); + if (textArea.SelectionManager.HasSomethingSelected) { + foreach (ISelection selection in textArea.SelectionManager.SelectionCollection) { + Convert(textArea.Document, selection.StartPosition.Y, selection.EndPosition.Y); + } + } else { + Convert(textArea.Document, 0, textArea.Document.TotalNumberOfLines - 1); + } + textArea.Document.UndoStack.EndUndoGroup(); + textArea.Caret.ValidateCaretPos(); + textArea.EndUpdate(); + textArea.Refresh(); + } + } + + public abstract class AbstractSelectionFormatAction : AbstractEditAction + { + protected TextArea textArea; + abstract protected void Convert(IDocument document, int offset, int length); + + public override void Execute(TextArea textArea) + { + if (textArea.SelectionManager.SelectionIsReadonly) { + return; + } + this.textArea = textArea; + textArea.BeginUpdate(); + if (textArea.SelectionManager.HasSomethingSelected) { + foreach (ISelection selection in textArea.SelectionManager.SelectionCollection) { + Convert(textArea.Document, selection.Offset, selection.Length); + } + } else { + Convert(textArea.Document, 0, textArea.Document.TextLength); + } + textArea.Caret.ValidateCaretPos(); + textArea.EndUpdate(); + textArea.Refresh(); + } + } + + public class RemoveLeadingWS : AbstractLineFormatAction + { + protected override void Convert(IDocument document, int y1, int y2) + { + for (int i = y1; i < y2; ++i) { + LineSegment line = document.GetLineSegment(i); + int removeNumber = 0; + for (int x = line.Offset; x < line.Offset + line.Length && char.IsWhiteSpace(document.GetCharAt(x)); ++x) { + ++removeNumber; + } + if (removeNumber > 0) { + document.Remove(line.Offset, removeNumber); + } + } + } + } + + public class RemoveTrailingWS : AbstractLineFormatAction + { + protected override void Convert(IDocument document, int y1, int y2) + { + for (int i = y2 - 1; i >= y1; --i) { + LineSegment line = document.GetLineSegment(i); + int removeNumber = 0; + for (int x = line.Offset + line.Length - 1; x >= line.Offset && char.IsWhiteSpace(document.GetCharAt(x)); --x) { + ++removeNumber; + } + if (removeNumber > 0) { + document.Remove(line.Offset + line.Length - removeNumber, removeNumber); + } + } + } + } + + + public class ToUpperCase : AbstractSelectionFormatAction + { + protected override void Convert(IDocument document, int startOffset, int length) + { + string what = document.GetText(startOffset, length).ToUpper(); + document.Replace(startOffset, length, what); + } + } + + public class ToLowerCase : AbstractSelectionFormatAction + { + protected override void Convert(IDocument document, int startOffset, int length) + { + string what = document.GetText(startOffset, length).ToLower(); + document.Replace(startOffset, length, what); + } + } + + public class InvertCaseAction : AbstractSelectionFormatAction + { + protected override void Convert(IDocument document, int startOffset, int length) + { + StringBuilder what = new StringBuilder(document.GetText(startOffset, length)); + + for (int i = 0; i < what.Length; ++i) { + what[i] = char.IsUpper(what[i]) ? char.ToLower(what[i]) : char.ToUpper(what[i]); + } + + document.Replace(startOffset, length, what.ToString()); + } + } + + public class CapitalizeAction : AbstractSelectionFormatAction + { + protected override void Convert(IDocument document, int startOffset, int length) + { + StringBuilder what = new StringBuilder(document.GetText(startOffset, length)); + + for (int i = 0; i < what.Length; ++i) { + if (!char.IsLetter(what[i]) && i < what.Length - 1) { + what[i + 1] = char.ToUpper(what[i + 1]); + } + } + document.Replace(startOffset, length, what.ToString()); + } + + } + + public class ConvertTabsToSpaces : AbstractSelectionFormatAction + { + protected override void Convert(IDocument document, int startOffset, int length) + { + string what = document.GetText(startOffset, length); + string spaces = new string(' ', document.TextEditorProperties.TabIndent); + document.Replace(startOffset, length, what.Replace("\t", spaces)); + } + } + + public class ConvertSpacesToTabs : AbstractSelectionFormatAction + { + protected override void Convert(IDocument document, int startOffset, int length) + { + string what = document.GetText(startOffset, length); + string spaces = new string(' ', document.TextEditorProperties.TabIndent); + document.Replace(startOffset, length, what.Replace(spaces, "\t")); + } + } + + public class ConvertLeadingTabsToSpaces : AbstractLineFormatAction + { + protected override void Convert(IDocument document, int y1, int y2) + { + for (int i = y2; i >= y1; --i) { + LineSegment line = document.GetLineSegment(i); + + if(line.Length > 0) { + // count how many whitespace characters there are at the start + int whiteSpace = 0; + for(whiteSpace = 0; whiteSpace < line.Length && char.IsWhiteSpace(document.GetCharAt(line.Offset + whiteSpace)); whiteSpace++) { + // deliberately empty + } + if(whiteSpace > 0) { + string newLine = document.GetText(line.Offset,whiteSpace); + string newPrefix = newLine.Replace("\t",new string(' ', document.TextEditorProperties.TabIndent)); + document.Replace(line.Offset,whiteSpace,newPrefix); + } + } + } + } + } + + public class ConvertLeadingSpacesToTabs : AbstractLineFormatAction + { + protected override void Convert(IDocument document, int y1, int y2) + { + for (int i = y2; i >= y1; --i) { + LineSegment line = document.GetLineSegment(i); + if(line.Length > 0) { + // note: some users may prefer a more radical ConvertLeadingSpacesToTabs that + // means there can be no spaces before the first character even if the spaces + // didn't add up to a whole number of tabs + string newLine = TextUtilities.LeadingWhiteSpaceToTabs(document.GetText(line.Offset,line.Length), document.TextEditorProperties.TabIndent); + document.Replace(line.Offset,line.Length,newLine); + } + } + } + } + + /// + /// This is a sample editaction plugin, it indents the selected area. + /// + public class IndentSelection : AbstractLineFormatAction + { + protected override void Convert(IDocument document, int startLine, int endLine) + { + document.FormattingStrategy.IndentLines(textArea, startLine, endLine); + } + } +} diff --git a/ICSharpCode.TextEditor/Project/Src/Actions/GoToLineNumberAction.cs b/ICSharpCode.TextEditor/Project/Src/Actions/GoToLineNumberAction.cs new file mode 100644 index 0000000..7896027 --- /dev/null +++ b/ICSharpCode.TextEditor/Project/Src/Actions/GoToLineNumberAction.cs @@ -0,0 +1,28 @@ +using System.Windows.Forms; +using ICSharpCode.TextEditor.Actions; +using ICSharpCode.TextEditor.UserControls; + +namespace ICSharpCode.TextEditor.Src.Actions +{ + class GoToLineNumberAction : AbstractEditAction + { + private readonly GotoForm _gotoForm; + + public GoToLineNumberAction() + { + _gotoForm = new GotoForm(); + } + + public override void Execute(TextArea textArea) + { + _gotoForm.FirstLineNumber = 1; + _gotoForm.LastLineNumber = textArea.Document.TotalNumberOfLines; + _gotoForm.SelectedLineNumber = textArea.Caret.Line + 1; + + if (DialogResult.OK == _gotoForm.ShowDialogEx() && _gotoForm.SelectedLineNumber > 0) + { + textArea.Caret.Position = new TextLocation(0, _gotoForm.SelectedLineNumber - 1); + } + } + } +} \ No newline at end of file diff --git a/ICSharpCode.TextEditor/Project/Src/Actions/HomeEndActions.cs b/ICSharpCode.TextEditor/Project/Src/Actions/HomeEndActions.cs new file mode 100644 index 0000000..c200fc3 --- /dev/null +++ b/ICSharpCode.TextEditor/Project/Src/Actions/HomeEndActions.cs @@ -0,0 +1,114 @@ +// +// +// +// +// $Revision$ +// + +using System; +using System.Collections.Generic; +using System.Drawing; + +using ICSharpCode.TextEditor.Document; + +namespace ICSharpCode.TextEditor.Actions +{ + public class Home : AbstractEditAction + { + public override void Execute(TextArea textArea) + { + LineSegment curLine; + TextLocation newPos = textArea.Caret.Position; + bool jumpedIntoFolding = false; + do { + curLine = textArea.Document.GetLineSegment(newPos.Y); + + if (TextUtilities.IsEmptyLine(textArea.Document, newPos.Y)) { + if (newPos.X != 0) { + newPos.X = 0; + } else { + newPos.X = curLine.Length; + } + } else { + int firstCharOffset = TextUtilities.GetFirstNonWSChar(textArea.Document, curLine.Offset); + int firstCharColumn = firstCharOffset - curLine.Offset; + + if (newPos.X == firstCharColumn) { + newPos.X = 0; + } else { + newPos.X = firstCharColumn; + } + } + List foldings = textArea.Document.FoldingManager.GetFoldingsFromPosition(newPos.Y, newPos.X); + jumpedIntoFolding = false; + foreach (FoldMarker foldMarker in foldings) { + if (foldMarker.IsFolded) { + newPos = new TextLocation(foldMarker.StartColumn, foldMarker.StartLine); + jumpedIntoFolding = true; + break; + } + } + + } while (jumpedIntoFolding); + + if (newPos != textArea.Caret.Position) { + textArea.Caret.Position = newPos; + textArea.SetDesiredColumn(); + } + } + } + + public class End : AbstractEditAction + { + public override void Execute(TextArea textArea) + { + LineSegment curLine; + TextLocation newPos = textArea.Caret.Position; + bool jumpedIntoFolding = false; + do { + curLine = textArea.Document.GetLineSegment(newPos.Y); + newPos.X = curLine.Length; + + List foldings = textArea.Document.FoldingManager.GetFoldingsFromPosition(newPos.Y, newPos.X); + jumpedIntoFolding = false; + foreach (FoldMarker foldMarker in foldings) { + if (foldMarker.IsFolded) { + newPos = new TextLocation(foldMarker.EndColumn, foldMarker.EndLine); + jumpedIntoFolding = true; + break; + } + } + } while (jumpedIntoFolding); + + if (newPos != textArea.Caret.Position) { + textArea.Caret.Position = newPos; + textArea.SetDesiredColumn(); + } + } + } + + + public class MoveToStart : AbstractEditAction + { + public override void Execute(TextArea textArea) + { + if (textArea.Caret.Line != 0 || textArea.Caret.Column != 0) { + textArea.Caret.Position = new TextLocation(0, 0); + textArea.SetDesiredColumn(); + } + } + } + + + public class MoveToEnd : AbstractEditAction + { + public override void Execute(TextArea textArea) + { + TextLocation endPos = textArea.Document.OffsetToPosition(textArea.Document.TextLength); + if (textArea.Caret.Position != endPos) { + textArea.Caret.Position = endPos; + textArea.SetDesiredColumn(); + } + } + } +} diff --git a/ICSharpCode.TextEditor/Project/Src/Actions/IEditAction.cs b/ICSharpCode.TextEditor/Project/Src/Actions/IEditAction.cs new file mode 100644 index 0000000..b194a5e --- /dev/null +++ b/ICSharpCode.TextEditor/Project/Src/Actions/IEditAction.cs @@ -0,0 +1,58 @@ +// +// +// +// +// $Revision$ +// + +using System; +using System.Windows.Forms; + +namespace ICSharpCode.TextEditor.Actions +{ + /// + /// To define a new key for the textarea, you must write a class which + /// implements this interface. + /// + public interface IEditAction + { + /// + /// An array of keys on which this edit action occurs. + /// + Keys[] Keys { + get; + set; + } + + /// + /// When the key which is defined per XML is pressed, this method will be launched. + /// + void Execute(TextArea textArea); + } + + /// + /// To define a new key for the textarea, you must write a class which + /// implements this interface. + /// + public abstract class AbstractEditAction : IEditAction + { + Keys[] keys = null; + + /// + /// An array of keys on which this edit action occurs. + /// + public Keys[] Keys { + get { + return keys; + } + set { + keys = value; + } + } + + /// + /// When the key which is defined per XML is pressed, this method will be launched. + /// + public abstract void Execute(TextArea textArea); + } +} diff --git a/ICSharpCode.TextEditor/Project/Src/Actions/MiscActions.cs b/ICSharpCode.TextEditor/Project/Src/Actions/MiscActions.cs new file mode 100644 index 0000000..9527326 --- /dev/null +++ b/ICSharpCode.TextEditor/Project/Src/Actions/MiscActions.cs @@ -0,0 +1,902 @@ +// +// +// +// +// $Revision$ +// + +using System; +using System.Diagnostics; +using System.Drawing; +using System.Text; + +using ICSharpCode.TextEditor.Document; + +namespace ICSharpCode.TextEditor.Actions +{ + public class Tab : AbstractEditAction + { + public static string GetIndentationString(IDocument document) + { + return GetIndentationString(document, null); + } + + public static string GetIndentationString(IDocument document, TextArea textArea) + { + StringBuilder indent = new StringBuilder(); + + if (document.TextEditorProperties.ConvertTabsToSpaces) { + int tabIndent = document.TextEditorProperties.IndentationSize; + if (textArea != null) { + int column = textArea.TextView.GetVisualColumn(textArea.Caret.Line, textArea.Caret.Column); + indent.Append(new string(' ', tabIndent - column % tabIndent)); + } else { + indent.Append(new string(' ', tabIndent)); + } + } else { + indent.Append('\t'); + } + return indent.ToString(); + } + + void InsertTabs(IDocument document, ISelection selection, int y1, int y2) + { + string indentationString = GetIndentationString(document); + for (int i = y2; i >= y1; --i) { + LineSegment line = document.GetLineSegment(i); + if (i == y2 && i == selection.EndPosition.Y && selection.EndPosition.X == 0) { + continue; + } + + // this bit is optional - but useful if you are using block tabbing to sort out + // a source file with a mixture of tabs and spaces +// string newLine = document.GetText(line.Offset,line.Length); +// document.Replace(line.Offset,line.Length,newLine); + + document.Insert(line.Offset, indentationString); + } + } + + void InsertTabAtCaretPosition(TextArea textArea) + { + switch (textArea.Caret.CaretMode) { + case CaretMode.InsertMode: + textArea.InsertString(GetIndentationString(textArea.Document, textArea)); + break; + case CaretMode.OverwriteMode: + string indentStr = GetIndentationString(textArea.Document, textArea); + textArea.ReplaceChar(indentStr[0]); + if (indentStr.Length > 1) { + textArea.InsertString(indentStr.Substring(1)); + } + break; + } + textArea.SetDesiredColumn(); + } + /// + /// Executes this edit action + /// + /// The which is used for callback purposes + public override void Execute(TextArea textArea) + { + if (textArea.SelectionManager.SelectionIsReadonly) { + return; + } + textArea.Document.UndoStack.StartUndoGroup(); + if (textArea.SelectionManager.HasSomethingSelected) { + foreach (ISelection selection in textArea.SelectionManager.SelectionCollection) { + int startLine = selection.StartPosition.Y; + int endLine = selection.EndPosition.Y; + if (startLine != endLine) { + textArea.BeginUpdate(); + InsertTabs(textArea.Document, selection, startLine, endLine); + textArea.Document.RequestUpdate(new TextAreaUpdate(TextAreaUpdateType.LinesBetween, startLine, endLine)); + textArea.EndUpdate(); + } else { + InsertTabAtCaretPosition(textArea); + break; + } + } + textArea.Document.CommitUpdate(); + textArea.AutoClearSelection = false; + } else { + InsertTabAtCaretPosition(textArea); + } + textArea.Document.UndoStack.EndUndoGroup(); + } + } + + public class ShiftTab : AbstractEditAction + { + void RemoveTabs(IDocument document, ISelection selection, int y1, int y2) + { + document.UndoStack.StartUndoGroup(); + for (int i = y2; i >= y1; --i) { + LineSegment line = document.GetLineSegment(i); + if (i == y2 && line.Offset == selection.EndOffset) { + continue; + } + if (line.Length > 0) { + /**** TextPad Strategy: + /// first convert leading whitespace to tabs (controversial! - not all editors work like this) + string newLine = TextUtilities.LeadingWhiteSpaceToTabs(document.GetText(line.Offset,line.Length),document.Properties.Get("TabIndent", 4)); + if(newLine.Length > 0 && newLine[0] == '\t') { + document.Replace(line.Offset,line.Length,newLine.Substring(1)); + ++redocounter; + } + else if(newLine.Length > 0 && newLine[0] == ' ') { + /// there were just some leading spaces but less than TabIndent of them + int leadingSpaces = 1; + for(leadingSpaces = 1; leadingSpaces < newLine.Length && newLine[leadingSpaces] == ' '; leadingSpaces++) { + /// deliberately empty + } + document.Replace(line.Offset,line.Length,newLine.Substring(leadingSpaces)); + ++redocounter; + } + /// else + /// there were no leading tabs or spaces on this line so do nothing + /// MS Visual Studio 6 strategy: + ****/ +// string temp = document.GetText(line.Offset,line.Length); + if (line.Length > 0) { + int charactersToRemove = 0; + if(document.GetCharAt(line.Offset) == '\t') { // first character is a tab - just remove it + charactersToRemove = 1; + } else if(document.GetCharAt(line.Offset) == ' ') { + int leadingSpaces = 1; + int tabIndent = document.TextEditorProperties.IndentationSize; + for (leadingSpaces = 1; leadingSpaces < line.Length && document.GetCharAt(line.Offset + leadingSpaces) == ' '; leadingSpaces++) { + // deliberately empty + } + if(leadingSpaces >= tabIndent) { + // just remove tabIndent + charactersToRemove = tabIndent; + } + else if(line.Length > leadingSpaces && document.GetCharAt(line.Offset + leadingSpaces) == '\t') { + // remove the leading spaces and the following tab as they add up + // to just one tab stop + charactersToRemove = leadingSpaces+1; + } + else { + // just remove the leading spaces + charactersToRemove = leadingSpaces; + } + } + if (charactersToRemove > 0) { + document.Remove(line.Offset,charactersToRemove); + } + } + } + } + document.UndoStack.EndUndoGroup(); + } + + /// + /// Executes this edit action + /// + /// The which is used for callback purposes + public override void Execute(TextArea textArea) + { + if (textArea.SelectionManager.HasSomethingSelected) { + foreach (ISelection selection in textArea.SelectionManager.SelectionCollection) { + int startLine = selection.StartPosition.Y; + int endLine = selection.EndPosition.Y; + textArea.BeginUpdate(); + RemoveTabs(textArea.Document, selection, startLine, endLine); + textArea.Document.UpdateQueue.Clear(); + textArea.Document.RequestUpdate(new TextAreaUpdate(TextAreaUpdateType.LinesBetween, startLine, endLine)); + textArea.EndUpdate(); + + } + textArea.AutoClearSelection = false; + } else { + // Pressing Shift-Tab with nothing selected the cursor will move back to the + // previous tab stop. It will stop at the beginning of the line. Also, the desired + // column is updated to that column. + LineSegment line = textArea.Document.GetLineSegmentForOffset(textArea.Caret.Offset); + string startOfLine = textArea.Document.GetText(line.Offset,textArea.Caret.Offset - line.Offset); + int tabIndent = textArea.Document.TextEditorProperties.IndentationSize; + int currentColumn = textArea.Caret.Column; + int remainder = currentColumn % tabIndent; + if (remainder == 0) { + textArea.Caret.DesiredColumn = Math.Max(0, currentColumn - tabIndent); + } else { + textArea.Caret.DesiredColumn = Math.Max(0, currentColumn - remainder); + } + textArea.SetCaretToDesiredColumn(); + } + } + } + + public class ToggleComment : AbstractEditAction + { + /// + /// Executes this edit action + /// + /// The which is used for callback purposes + public override void Execute(TextArea textArea) + { + if (textArea.Document.ReadOnly) { + return; + } + + if (textArea.Document.HighlightingStrategy.Properties.ContainsKey("LineComment")) { + new ToggleLineComment().Execute(textArea); + } else if (textArea.Document.HighlightingStrategy.Properties.ContainsKey("BlockCommentBegin") && + textArea.Document.HighlightingStrategy.Properties.ContainsKey("BlockCommentBegin")) { + new ToggleBlockComment().Execute(textArea); + } + } + } + + public class ToggleLineComment : AbstractEditAction + { + int firstLine; + int lastLine; + + void RemoveCommentAt(IDocument document, string comment, ISelection selection, int y1, int y2) + { + firstLine = y1; + lastLine = y2; + + for (int i = y2; i >= y1; --i) { + LineSegment line = document.GetLineSegment(i); + if (selection != null && i == y2 && line.Offset == selection.Offset + selection.Length) { + --lastLine; + continue; + } + + string lineText = document.GetText(line.Offset, line.Length); + if (lineText.Trim().StartsWith(comment)) { + document.Remove(line.Offset + lineText.IndexOf(comment), comment.Length); + } + } + } + + void SetCommentAt(IDocument document, string comment, ISelection selection, int y1, int y2) + { + firstLine = y1; + lastLine = y2; + + for (int i = y2; i >= y1; --i) { + LineSegment line = document.GetLineSegment(i); + if (selection != null && i == y2 && line.Offset == selection.Offset + selection.Length) { + --lastLine; + continue; + } + + string lineText = document.GetText(line.Offset, line.Length); + document.Insert(line.Offset, comment); + } + } + + bool ShouldComment(IDocument document, string comment, ISelection selection, int startLine, int endLine) + { + for (int i = endLine; i >= startLine; --i) { + LineSegment line = document.GetLineSegment(i); + if (selection != null && i == endLine && line.Offset == selection.Offset + selection.Length) { + --lastLine; + continue; + } + string lineText = document.GetText(line.Offset, line.Length); + if (!lineText.Trim().StartsWith(comment)) { + return true; + } + } + return false; + } + + /// + /// Executes this edit action + /// + /// The which is used for callback purposes + public override void Execute(TextArea textArea) + { + if (textArea.Document.ReadOnly) { + return; + } + + string comment = null; + if (textArea.Document.HighlightingStrategy.Properties.ContainsKey("LineComment")) { + comment = textArea.Document.HighlightingStrategy.Properties["LineComment"].ToString(); + } + + if (comment == null || comment.Length == 0) { + return; + } + + textArea.Document.UndoStack.StartUndoGroup(); + if (textArea.SelectionManager.HasSomethingSelected) { + bool shouldComment = true; + foreach (ISelection selection in textArea.SelectionManager.SelectionCollection) { + if (!ShouldComment(textArea.Document, comment, selection, selection.StartPosition.Y, selection.EndPosition.Y)) { + shouldComment = false; + break; + } + } + + foreach (ISelection selection in textArea.SelectionManager.SelectionCollection) { + textArea.BeginUpdate(); + if (shouldComment) { + SetCommentAt(textArea.Document, comment, selection, selection.StartPosition.Y, selection.EndPosition.Y); + } else { + RemoveCommentAt(textArea.Document, comment, selection, selection.StartPosition.Y, selection.EndPosition.Y); + } + textArea.Document.UpdateQueue.Clear(); + textArea.Document.RequestUpdate(new TextAreaUpdate(TextAreaUpdateType.LinesBetween, firstLine, lastLine)); + textArea.EndUpdate(); + } + textArea.Document.CommitUpdate(); + textArea.AutoClearSelection = false; + } else { + textArea.BeginUpdate(); + int caretLine = textArea.Caret.Line; + if (ShouldComment(textArea.Document, comment, null, caretLine, caretLine)) { + SetCommentAt(textArea.Document, comment, null, caretLine, caretLine); + } else { + RemoveCommentAt(textArea.Document, comment, null, caretLine, caretLine); + } + textArea.Document.UpdateQueue.Clear(); + textArea.Document.RequestUpdate(new TextAreaUpdate(TextAreaUpdateType.SingleLine, caretLine)); + textArea.EndUpdate(); + } + textArea.Document.UndoStack.EndUndoGroup(); + } + } + + public class ToggleBlockComment : AbstractEditAction + { + /// + /// Executes this edit action + /// + /// The which is used for callback purposes + public override void Execute(TextArea textArea) + { + if (textArea.Document.ReadOnly) { + return; + } + + string commentStart = null; + if (textArea.Document.HighlightingStrategy.Properties.ContainsKey("BlockCommentBegin")) { + commentStart = textArea.Document.HighlightingStrategy.Properties["BlockCommentBegin"].ToString(); + } + + string commentEnd = null; + if (textArea.Document.HighlightingStrategy.Properties.ContainsKey("BlockCommentEnd")) { + commentEnd = textArea.Document.HighlightingStrategy.Properties["BlockCommentEnd"].ToString(); + } + + if (commentStart == null || commentStart.Length == 0 || commentEnd == null || commentEnd.Length == 0) { + return; + } + + int selectionStartOffset; + int selectionEndOffset; + + if (textArea.SelectionManager.HasSomethingSelected) { + selectionStartOffset = textArea.SelectionManager.SelectionCollection[0].Offset; + selectionEndOffset = textArea.SelectionManager.SelectionCollection[textArea.SelectionManager.SelectionCollection.Count - 1].EndOffset; + } else { + selectionStartOffset = textArea.Caret.Offset; + selectionEndOffset = selectionStartOffset; + } + + BlockCommentRegion commentRegion = FindSelectedCommentRegion(textArea.Document, commentStart, commentEnd, selectionStartOffset, selectionEndOffset); + + textArea.Document.UndoStack.StartUndoGroup(); + if (commentRegion != null) { + RemoveComment(textArea.Document, commentRegion); + } else if (textArea.SelectionManager.HasSomethingSelected) { + SetCommentAt(textArea.Document, selectionStartOffset, selectionEndOffset, commentStart, commentEnd); + } + textArea.Document.UndoStack.EndUndoGroup(); + + textArea.Document.CommitUpdate(); + textArea.AutoClearSelection = false; + } + + public static BlockCommentRegion FindSelectedCommentRegion(IDocument document, string commentStart, string commentEnd, int selectionStartOffset, int selectionEndOffset) + { + if (document.TextLength == 0) { + return null; + } + + // Find start of comment in selected text. + + int commentEndOffset = -1; + string selectedText = document.GetText(selectionStartOffset, selectionEndOffset - selectionStartOffset); + + int commentStartOffset = selectedText.IndexOf(commentStart); + if (commentStartOffset >= 0) { + commentStartOffset += selectionStartOffset; + } + + // Find end of comment in selected text. + + if (commentStartOffset >= 0) { + commentEndOffset = selectedText.IndexOf(commentEnd, commentStartOffset + commentStart.Length - selectionStartOffset); + } else { + commentEndOffset = selectedText.IndexOf(commentEnd); + } + + if (commentEndOffset >= 0) { + commentEndOffset += selectionStartOffset; + } + + // Find start of comment before or partially inside the + // selected text. + + int commentEndBeforeStartOffset = -1; + if (commentStartOffset == -1) { + int offset = selectionEndOffset + commentStart.Length - 1; + if (offset > document.TextLength) { + offset = document.TextLength; + } + string text = document.GetText(0, offset); + commentStartOffset = text.LastIndexOf(commentStart); + if (commentStartOffset >= 0) { + // Find end of comment before comment start. + commentEndBeforeStartOffset = text.IndexOf(commentEnd, commentStartOffset, selectionStartOffset - commentStartOffset); + if (commentEndBeforeStartOffset > commentStartOffset) { + commentStartOffset = -1; + } + } + } + + // Find end of comment after or partially after the + // selected text. + + if (commentEndOffset == -1) { + int offset = selectionStartOffset + 1 - commentEnd.Length; + if (offset < 0) { + offset = selectionStartOffset; + } + string text = document.GetText(offset, document.TextLength - offset); + commentEndOffset = text.IndexOf(commentEnd); + if (commentEndOffset >= 0) { + commentEndOffset += offset; + } + } + + if (commentStartOffset != -1 && commentEndOffset != -1) { + return new BlockCommentRegion(commentStart, commentEnd, commentStartOffset, commentEndOffset); + } + + return null; + } + + + void SetCommentAt(IDocument document, int offsetStart, int offsetEnd, string commentStart, string commentEnd) + { + document.Insert(offsetEnd, commentEnd); + document.Insert(offsetStart, commentStart); + } + + void RemoveComment(IDocument document, BlockCommentRegion commentRegion) + { + document.Remove(commentRegion.EndOffset, commentRegion.CommentEnd.Length); + document.Remove(commentRegion.StartOffset, commentRegion.CommentStart.Length); + } + } + + public class BlockCommentRegion + { + string commentStart = string.Empty; + string commentEnd = string.Empty; + int startOffset = -1; + int endOffset = -1; + + /// + /// The end offset is the offset where the comment end string starts from. + /// + public BlockCommentRegion(string commentStart, string commentEnd, int startOffset, int endOffset) + { + this.commentStart = commentStart; + this.commentEnd = commentEnd; + this.startOffset = startOffset; + this.endOffset = endOffset; + } + + public string CommentStart { + get { + return commentStart; + } + } + + public string CommentEnd { + get { + return commentEnd; + } + } + + public int StartOffset { + get { + return startOffset; + } + } + + public int EndOffset { + get { + return endOffset; + } + } + + public override int GetHashCode() + { + int hashCode = 0; + unchecked { + if (commentStart != null) hashCode += 1000000007 * commentStart.GetHashCode(); + if (commentEnd != null) hashCode += 1000000009 * commentEnd.GetHashCode(); + hashCode += 1000000021 * startOffset.GetHashCode(); + hashCode += 1000000033 * endOffset.GetHashCode(); + } + return hashCode; + } + + public override bool Equals(object obj) + { + BlockCommentRegion other = obj as BlockCommentRegion; + if (other == null) return false; + return this.commentStart == other.commentStart && this.commentEnd == other.commentEnd && this.startOffset == other.startOffset && this.endOffset == other.endOffset; + } + } + + public class Backspace : AbstractEditAction + { + /// + /// Executes this edit action + /// + /// The which is used for callback purposes + public override void Execute(TextArea textArea) + { + if (textArea.SelectionManager.HasSomethingSelected) { + Delete.DeleteSelection(textArea); + } else { + if (textArea.Caret.Offset > 0 && !textArea.IsReadOnly(textArea.Caret.Offset - 1)) { + textArea.BeginUpdate(); + int curLineNr = textArea.Document.GetLineNumberForOffset(textArea.Caret.Offset); + int curLineOffset = textArea.Document.GetLineSegment(curLineNr).Offset; + + if (curLineOffset == textArea.Caret.Offset) { + LineSegment line = textArea.Document.GetLineSegment(curLineNr - 1); + bool lastLine = curLineNr == textArea.Document.TotalNumberOfLines; + int lineEndOffset = line.Offset + line.Length; + int lineLength = line.Length; + textArea.Document.Remove(lineEndOffset, curLineOffset - lineEndOffset); + textArea.Caret.Position = new TextLocation(lineLength, curLineNr - 1); + textArea.Document.RequestUpdate(new TextAreaUpdate(TextAreaUpdateType.PositionToEnd, new TextLocation(0, curLineNr - 1))); + } else { + int caretOffset = textArea.Caret.Offset - 1; + textArea.Caret.Position = textArea.Document.OffsetToPosition(caretOffset); + textArea.Document.Remove(caretOffset, 1); + + textArea.Document.RequestUpdate(new TextAreaUpdate(TextAreaUpdateType.PositionToLineEnd, new TextLocation(textArea.Caret.Offset - textArea.Document.GetLineSegment(curLineNr).Offset, curLineNr))); + } + textArea.EndUpdate(); + } + } + } + } + + public class Delete : AbstractEditAction + { + internal static void DeleteSelection(TextArea textArea) + { + Debug.Assert(textArea.SelectionManager.HasSomethingSelected); + if (textArea.SelectionManager.SelectionIsReadonly) + return; + textArea.BeginUpdate(); + textArea.Caret.Position = textArea.SelectionManager.SelectionCollection[0].StartPosition; + textArea.SelectionManager.RemoveSelectedText(); + textArea.ScrollToCaret(); + textArea.EndUpdate(); + } + + /// + /// Executes this edit action + /// + /// The which is used for callback purposes + public override void Execute(TextArea textArea) + { + if (textArea.SelectionManager.HasSomethingSelected) { + DeleteSelection(textArea); + } else { + if (textArea.IsReadOnly(textArea.Caret.Offset)) + return; + + if (textArea.Caret.Offset < textArea.Document.TextLength) { + textArea.BeginUpdate(); + int curLineNr = textArea.Document.GetLineNumberForOffset(textArea.Caret.Offset); + LineSegment curLine = textArea.Document.GetLineSegment(curLineNr); + + if (curLine.Offset + curLine.Length == textArea.Caret.Offset) { + if (curLineNr + 1 < textArea.Document.TotalNumberOfLines) { + LineSegment nextLine = textArea.Document.GetLineSegment(curLineNr + 1); + + textArea.Document.Remove(textArea.Caret.Offset, nextLine.Offset - textArea.Caret.Offset); + textArea.Document.RequestUpdate(new TextAreaUpdate(TextAreaUpdateType.PositionToEnd, new TextLocation(0, curLineNr))); + } + } else { + textArea.Document.Remove(textArea.Caret.Offset, 1); +// textArea.Document.RequestUpdate(new TextAreaUpdate(TextAreaUpdateType.PositionToLineEnd, new TextLocation(textArea.Caret.Offset - textArea.Document.GetLineSegment(curLineNr).Offset, curLineNr))); + } + textArea.UpdateMatchingBracket(); + textArea.EndUpdate(); + } + } + } + } + + public class MovePageDown : AbstractEditAction + { + /// + /// Executes this edit action + /// + /// The which is used for callback purposes + public override void Execute(TextArea textArea) + { + int curLineNr = textArea.Caret.Line; + int requestedLineNumber = Math.Min(textArea.Document.GetNextVisibleLineAbove(curLineNr, textArea.TextView.VisibleLineCount), textArea.Document.TotalNumberOfLines - 1); + + if (curLineNr != requestedLineNumber) { + textArea.Caret.Position = new TextLocation(0, requestedLineNumber); + textArea.SetCaretToDesiredColumn(); + } + } + } + + public class MovePageUp : AbstractEditAction + { + /// + /// Executes this edit action + /// + /// The which is used for callback purposes + public override void Execute(TextArea textArea) + { + int curLineNr = textArea.Caret.Line; + int requestedLineNumber = Math.Max(textArea.Document.GetNextVisibleLineBelow(curLineNr, textArea.TextView.VisibleLineCount), 0); + + if (curLineNr != requestedLineNumber) { + textArea.Caret.Position = new TextLocation(0, requestedLineNumber); + textArea.SetCaretToDesiredColumn(); + } + } + } + public class Return : AbstractEditAction + { + /// + /// Executes this edit action + /// + /// The which is used for callback purposes + public override void Execute(TextArea textArea) + { + if (textArea.Document.ReadOnly) { + return; + } + textArea.BeginUpdate(); + textArea.Document.UndoStack.StartUndoGroup(); + try { + if (textArea.HandleKeyPress('\n')) + return; + + textArea.InsertString(Environment.NewLine); + + int curLineNr = textArea.Caret.Line; + textArea.Document.FormattingStrategy.FormatLine(textArea, curLineNr, textArea.Caret.Offset, '\n'); + textArea.SetDesiredColumn(); + + textArea.Document.UpdateQueue.Clear(); + textArea.Document.RequestUpdate(new TextAreaUpdate(TextAreaUpdateType.PositionToEnd, new TextLocation(0, curLineNr - 1))); + } finally { + textArea.Document.UndoStack.EndUndoGroup(); + textArea.EndUpdate(); + } + } + } + + public class ToggleEditMode : AbstractEditAction + { + /// + /// Executes this edit action + /// + /// The which is used for callback purposes + public override void Execute(TextArea textArea) + { + if (textArea.Document.ReadOnly) { + return; + } + switch (textArea.Caret.CaretMode) { + case CaretMode.InsertMode: + textArea.Caret.CaretMode = CaretMode.OverwriteMode; + break; + case CaretMode.OverwriteMode: + textArea.Caret.CaretMode = CaretMode.InsertMode; + break; + } + } + } + + public class Undo : AbstractEditAction + { + /// + /// Executes this edit action + /// + /// The which is used for callback purposes + public override void Execute(TextArea textArea) + { + textArea.MotherTextEditorControl.Undo(); + } + } + + public class Redo : AbstractEditAction + { + /// + /// Executes this edit action + /// + /// The which is used for callback purposes + public override void Execute(TextArea textArea) + { + textArea.MotherTextEditorControl.Redo(); + } + } + + /// + /// handles the ctrl-backspace key + /// functionality attempts to roughly mimic MS Developer studio + /// I will implement this as deleting back to the point that ctrl-leftarrow would + /// take you to + /// + public class WordBackspace : AbstractEditAction + { + /// + /// Executes this edit action + /// + /// The which is used for callback purposes + public override void Execute(TextArea textArea) + { + // if anything is selected we will just delete it first + if (textArea.SelectionManager.HasSomethingSelected) { + Delete.DeleteSelection(textArea); + return; + } + textArea.BeginUpdate(); + // now delete from the caret to the beginning of the word + LineSegment line = + textArea.Document.GetLineSegmentForOffset(textArea.Caret.Offset); + // if we are not at the beginning of a line + if (textArea.Caret.Offset > line.Offset) { + int prevWordStart = TextUtilities.FindPrevWordStart(textArea.Document, + textArea.Caret.Offset); + if (prevWordStart < textArea.Caret.Offset) { + if (!textArea.IsReadOnly(prevWordStart, textArea.Caret.Offset - prevWordStart)) { + textArea.Document.Remove(prevWordStart, + textArea.Caret.Offset - prevWordStart); + textArea.Caret.Position = textArea.Document.OffsetToPosition(prevWordStart); + } + } + } + // if we are now at the beginning of a line + if (textArea.Caret.Offset == line.Offset) { + // if we are not on the first line + int curLineNr = textArea.Document.GetLineNumberForOffset(textArea.Caret.Offset); + if (curLineNr > 0) { + // move to the end of the line above + LineSegment lineAbove = textArea.Document.GetLineSegment(curLineNr - 1); + int endOfLineAbove = lineAbove.Offset + lineAbove.Length; + int charsToDelete = textArea.Caret.Offset - endOfLineAbove; + if (!textArea.IsReadOnly(endOfLineAbove, charsToDelete)) { + textArea.Document.Remove(endOfLineAbove, charsToDelete); + textArea.Caret.Position = textArea.Document.OffsetToPosition(endOfLineAbove); + } + } + } + textArea.SetDesiredColumn(); + textArea.EndUpdate(); + // if there are now less lines, we need this or there are redraw problems + textArea.Document.RequestUpdate(new TextAreaUpdate(TextAreaUpdateType.PositionToEnd, new TextLocation(0, textArea.Document.GetLineNumberForOffset(textArea.Caret.Offset)))); + textArea.Document.CommitUpdate(); + } + } + + /// + /// handles the ctrl-delete key + /// functionality attempts to mimic MS Developer studio + /// I will implement this as deleting forwardto the point that + /// ctrl-leftarrow would take you to + /// + public class DeleteWord : Delete + { + /// + /// Executes this edit action + /// + /// The which is used for callback purposes + public override void Execute(TextArea textArea) + { + if (textArea.SelectionManager.HasSomethingSelected) { + DeleteSelection(textArea); + return; + } + // if anything is selected we will just delete it first + textArea.BeginUpdate(); + // now delete from the caret to the beginning of the word + LineSegment line = textArea.Document.GetLineSegmentForOffset(textArea.Caret.Offset); + if(textArea.Caret.Offset == line.Offset + line.Length) { + // if we are at the end of a line + base.Execute(textArea); + } else { + int nextWordStart = TextUtilities.FindNextWordStart(textArea.Document, + textArea.Caret.Offset); + if(nextWordStart > textArea.Caret.Offset) { + if (!textArea.IsReadOnly(textArea.Caret.Offset, nextWordStart - textArea.Caret.Offset)) { + textArea.Document.Remove(textArea.Caret.Offset, nextWordStart - textArea.Caret.Offset); + // cursor never moves with this command + } + } + } + textArea.UpdateMatchingBracket(); + textArea.EndUpdate(); + // if there are now less lines, we need this or there are redraw problems + textArea.Document.RequestUpdate(new TextAreaUpdate(TextAreaUpdateType.PositionToEnd, new TextLocation(0, textArea.Document.GetLineNumberForOffset(textArea.Caret.Offset)))); + textArea.Document.CommitUpdate(); + } + } + + public class DeleteLine : AbstractEditAction + { + public override void Execute(TextArea textArea) + { + int lineNr = textArea.Caret.Line; + LineSegment line = textArea.Document.GetLineSegment(lineNr); + if (textArea.IsReadOnly(line.Offset, line.Length)) + return; + textArea.Document.Remove(line.Offset, line.TotalLength); + textArea.Caret.Position = textArea.Document.OffsetToPosition(line.Offset); + + textArea.Document.RequestUpdate(new TextAreaUpdate(TextAreaUpdateType.PositionToEnd, new TextLocation(0, lineNr))); + textArea.UpdateMatchingBracket(); + textArea.Document.CommitUpdate(); + } + } + + public class DeleteToLineEnd : AbstractEditAction + { + public override void Execute(TextArea textArea) + { + int lineNr = textArea.Caret.Line; + LineSegment line = textArea.Document.GetLineSegment(lineNr); + + int numRemove = (line.Offset + line.Length) - textArea.Caret.Offset; + if (numRemove > 0 && !textArea.IsReadOnly(textArea.Caret.Offset, numRemove)) { + textArea.Document.Remove(textArea.Caret.Offset, numRemove); + textArea.Document.RequestUpdate(new TextAreaUpdate(TextAreaUpdateType.SingleLine, new TextLocation(0, lineNr))); + textArea.Document.CommitUpdate(); + } + } + } + + public class GotoMatchingBrace : AbstractEditAction + { + public override void Execute(TextArea textArea) + { + Highlight highlight = textArea.FindMatchingBracketHighlight(); + if (highlight != null) { + TextLocation p1 = new TextLocation(highlight.CloseBrace.X + 1, highlight.CloseBrace.Y); + TextLocation p2 = new TextLocation(highlight.OpenBrace.X + 1, highlight.OpenBrace.Y); + if (p1 == textArea.Caret.Position) { + if (textArea.Document.TextEditorProperties.BracketMatchingStyle == BracketMatchingStyle.After) { + textArea.Caret.Position = p2; + } else { + textArea.Caret.Position = new TextLocation(p2.X - 1, p2.Y); + } + } else { + if (textArea.Document.TextEditorProperties.BracketMatchingStyle == BracketMatchingStyle.After) { + textArea.Caret.Position = p1; + } else { + textArea.Caret.Position = new TextLocation(p1.X - 1, p1.Y); + } + } + textArea.SetDesiredColumn(); + } + } + } +} diff --git a/ICSharpCode.TextEditor/Project/Src/Actions/SelectionActions.cs b/ICSharpCode.TextEditor/Project/Src/Actions/SelectionActions.cs new file mode 100644 index 0000000..5b2e719 --- /dev/null +++ b/ICSharpCode.TextEditor/Project/Src/Actions/SelectionActions.cs @@ -0,0 +1,176 @@ +// +// +// +// +// $Revision$ +// + +using System; +using System.Drawing; +using ICSharpCode.TextEditor.Document; + +namespace ICSharpCode.TextEditor.Actions +{ + public class ShiftCaretRight : CaretRight + { + public override void Execute(TextArea textArea) + { + TextLocation oldCaretPos = textArea.Caret.Position; + base.Execute(textArea); + textArea.AutoClearSelection = false; + textArea.SelectionManager.ExtendSelection(oldCaretPos, textArea.Caret.Position); + } + } + + public class ShiftCaretLeft : CaretLeft + { + public override void Execute(TextArea textArea) + { + TextLocation oldCaretPos = textArea.Caret.Position; + base.Execute(textArea); + textArea.AutoClearSelection = false; + textArea.SelectionManager.ExtendSelection(oldCaretPos, textArea.Caret.Position); + } + } + + public class ShiftCaretUp : CaretUp + { + public override void Execute(TextArea textArea) + { + TextLocation oldCaretPos = textArea.Caret.Position; + base.Execute(textArea); + textArea.AutoClearSelection = false; + textArea.SelectionManager.ExtendSelection(oldCaretPos, textArea.Caret.Position); + } + } + + public class ShiftCaretDown : CaretDown + { + public override void Execute(TextArea textArea) + { + TextLocation oldCaretPos = textArea.Caret.Position; + base.Execute(textArea); + textArea.AutoClearSelection = false; + textArea.SelectionManager.ExtendSelection(oldCaretPos, textArea.Caret.Position); + } + } + + public class ShiftWordRight : WordRight + { + public override void Execute(TextArea textArea) + { + TextLocation oldCaretPos = textArea.Caret.Position; + base.Execute(textArea); + textArea.AutoClearSelection = false; + textArea.SelectionManager.ExtendSelection(oldCaretPos, textArea.Caret.Position); + } + } + + public class ShiftWordLeft : WordLeft + { + public override void Execute(TextArea textArea) + { + TextLocation oldCaretPos = textArea.Caret.Position; + base.Execute(textArea); + textArea.AutoClearSelection = false; + textArea.SelectionManager.ExtendSelection(oldCaretPos, textArea.Caret.Position); + } + } + + public class ShiftHome : Home + { + public override void Execute(TextArea textArea) + { + TextLocation oldCaretPos = textArea.Caret.Position; + base.Execute(textArea); + textArea.AutoClearSelection = false; + textArea.SelectionManager.ExtendSelection(oldCaretPos, textArea.Caret.Position); + } + } + + public class ShiftEnd : End + { + public override void Execute(TextArea textArea) + { + TextLocation oldCaretPos = textArea.Caret.Position; + base.Execute(textArea); + textArea.AutoClearSelection = false; + textArea.SelectionManager.ExtendSelection(oldCaretPos, textArea.Caret.Position); + } + } + + public class ShiftMoveToStart : MoveToStart + { + public override void Execute(TextArea textArea) + { + TextLocation oldCaretPos = textArea.Caret.Position; + base.Execute(textArea); + textArea.AutoClearSelection = false; + textArea.SelectionManager.ExtendSelection(oldCaretPos, textArea.Caret.Position); + } + } + + public class ShiftMoveToEnd : MoveToEnd + { + public override void Execute(TextArea textArea) + { + TextLocation oldCaretPos = textArea.Caret.Position; + base.Execute(textArea); + textArea.AutoClearSelection = false; + textArea.SelectionManager.ExtendSelection(oldCaretPos, textArea.Caret.Position); + } + } + + public class ShiftMovePageUp : MovePageUp + { + public override void Execute(TextArea textArea) + { + TextLocation oldCaretPos = textArea.Caret.Position; + base.Execute(textArea); + textArea.AutoClearSelection = false; + textArea.SelectionManager.ExtendSelection(oldCaretPos, textArea.Caret.Position); + } + } + + public class ShiftMovePageDown : MovePageDown + { + public override void Execute(TextArea textArea) + { + TextLocation oldCaretPos = textArea.Caret.Position; + base.Execute(textArea); + textArea.AutoClearSelection = false; + textArea.SelectionManager.ExtendSelection(oldCaretPos, textArea.Caret.Position); + } + } + + public class SelectWholeDocument : AbstractEditAction + { + public override void Execute(TextArea textArea) + { + textArea.AutoClearSelection = false; + TextLocation startPoint = new TextLocation(0, 0); + TextLocation endPoint = textArea.Document.OffsetToPosition(textArea.Document.TextLength); + if (textArea.SelectionManager.HasSomethingSelected) { + if (textArea.SelectionManager.SelectionCollection[0].StartPosition == startPoint && + textArea.SelectionManager.SelectionCollection[0].EndPosition == endPoint) { + return; + } + } + textArea.Caret.Position = textArea.SelectionManager.NextValidPosition(endPoint.Y); + textArea.SelectionManager.ExtendSelection(startPoint, endPoint); + // after a SelectWholeDocument selection, the caret is placed correctly, + // but it is not positioned internally. The effect is when the cursor + // is moved up or down a line, the caret will take on the column that + // it was in before the SelectWholeDocument + textArea.SetDesiredColumn(); + } + } + + public class ClearAllSelections : AbstractEditAction + { + public override void Execute(TextArea textArea) + { + textArea.SelectionManager.ClearSelection(); + } + } +} diff --git a/ICSharpCode.TextEditor/Project/Src/Document/AbstractSegment.cs b/ICSharpCode.TextEditor/Project/Src/Document/AbstractSegment.cs new file mode 100644 index 0000000..ddefec4 --- /dev/null +++ b/ICSharpCode.TextEditor/Project/Src/Document/AbstractSegment.cs @@ -0,0 +1,52 @@ +// +// +// +// +// $Revision$ +// + +using System; + +namespace ICSharpCode.TextEditor.Document +{ + /// + /// This interface is used to describe a span inside a text sequence + /// + public class AbstractSegment : ISegment + { + [CLSCompliant(false)] + protected int offset = -1; + [CLSCompliant(false)] + protected int length = -1; + + #region ICSharpCode.TextEditor.Document.ISegment interface implementation + public virtual int Offset { + get { + return offset; + } + set { + offset = value; + } + } + + public virtual int Length { + get { + return length; + } + set { + length = value; + } + } + + #endregion + + public override string ToString() + { + return string.Format("[AbstractSegment: Offset = {0}, Length = {1}]", + Offset, + Length); + } + + + } +} diff --git a/ICSharpCode.TextEditor/Project/Src/Document/BookmarkManager/Bookmark.cs b/ICSharpCode.TextEditor/Project/Src/Document/BookmarkManager/Bookmark.cs new file mode 100644 index 0000000..ed5e251 --- /dev/null +++ b/ICSharpCode.TextEditor/Project/Src/Document/BookmarkManager/Bookmark.cs @@ -0,0 +1,164 @@ +// +// +// +// +// $Revision$ +// + +using System; +using System.Drawing; +using SWF = System.Windows.Forms; + +namespace ICSharpCode.TextEditor.Document +{ + /// + /// Description of Bookmark. + /// + public class Bookmark + { + IDocument document; + TextAnchor anchor; + TextLocation location; + bool isEnabled = true; + + public IDocument Document { + get { + return document; + } + set { + if (document != value) { + if (anchor != null) { + location = anchor.Location; + anchor = null; + } + document = value; + CreateAnchor(); + OnDocumentChanged(EventArgs.Empty); + } + } + } + + void CreateAnchor() + { + if (document != null) { + LineSegment line = document.GetLineSegment(Math.Max(0, Math.Min(location.Line, document.TotalNumberOfLines-1))); + anchor = line.CreateAnchor(Math.Max(0, Math.Min(location.Column, line.Length))); + // after insertion: keep bookmarks after the initial whitespace (see DefaultFormattingStrategy.SmartReplaceLine) + anchor.MovementType = AnchorMovementType.AfterInsertion; + anchor.Deleted += AnchorDeleted; + } + } + + void AnchorDeleted(object sender, EventArgs e) + { + document.BookmarkManager.RemoveMark(this); + } + + /// + /// Gets the TextAnchor used for this bookmark. + /// Is null if the bookmark is not connected to a document. + /// + public TextAnchor Anchor { + get { return anchor; } + } + + public TextLocation Location { + get { + if (anchor != null) + return anchor.Location; + else + return location; + } + set { + location = value; + CreateAnchor(); + } + } + + public event EventHandler DocumentChanged; + + protected virtual void OnDocumentChanged(EventArgs e) + { + if (DocumentChanged != null) { + DocumentChanged(this, e); + } + } + + public bool IsEnabled { + get { + return isEnabled; + } + set { + if (isEnabled != value) { + isEnabled = value; + if (document != null) { + document.RequestUpdate(new TextAreaUpdate(TextAreaUpdateType.SingleLine, LineNumber)); + document.CommitUpdate(); + } + OnIsEnabledChanged(EventArgs.Empty); + } + } + } + + public event EventHandler IsEnabledChanged; + + protected virtual void OnIsEnabledChanged(EventArgs e) + { + if (IsEnabledChanged != null) { + IsEnabledChanged(this, e); + } + } + + public int LineNumber { + get { + if (anchor != null) + return anchor.LineNumber; + else + return location.Line; + } + } + + public int ColumnNumber { + get { + if (anchor != null) + return anchor.ColumnNumber; + else + return location.Column; + } + } + + /// + /// Gets if the bookmark can be toggled off using the 'set/unset bookmark' command. + /// + public virtual bool CanToggle { + get { + return true; + } + } + + public Bookmark(IDocument document, TextLocation location) : this(document, location, true) + { + } + + public Bookmark(IDocument document, TextLocation location, bool isEnabled) + { + this.document = document; + this.isEnabled = isEnabled; + this.Location = location; + } + + public virtual bool Click(SWF.Control parent, SWF.MouseEventArgs e) + { + if (e.Button == SWF.MouseButtons.Left && CanToggle) { + document.BookmarkManager.RemoveMark(this); + return true; + } + return false; + } + + public virtual void Draw(IconBarMargin margin, Graphics g, Point p) + { + margin.DrawBookmark(g, p.Y, isEnabled); + } + } +} diff --git a/ICSharpCode.TextEditor/Project/Src/Document/BookmarkManager/BookmarkEventHandler.cs b/ICSharpCode.TextEditor/Project/Src/Document/BookmarkManager/BookmarkEventHandler.cs new file mode 100644 index 0000000..39493eb --- /dev/null +++ b/ICSharpCode.TextEditor/Project/Src/Document/BookmarkManager/BookmarkEventHandler.cs @@ -0,0 +1,32 @@ +// +// +// +// +// $Revision$ +// + +using System; + +namespace ICSharpCode.TextEditor.Document +{ + public delegate void BookmarkEventHandler(object sender, BookmarkEventArgs e); + + /// + /// Description of BookmarkEventHandler. + /// + public class BookmarkEventArgs : EventArgs + { + Bookmark bookmark; + + public Bookmark Bookmark { + get { + return bookmark; + } + } + + public BookmarkEventArgs(Bookmark bookmark) + { + this.bookmark = bookmark; + } + } +} diff --git a/ICSharpCode.TextEditor/Project/Src/Document/BookmarkManager/BookmarkManager.cs b/ICSharpCode.TextEditor/Project/Src/Document/BookmarkManager/BookmarkManager.cs new file mode 100644 index 0000000..7105065 --- /dev/null +++ b/ICSharpCode.TextEditor/Project/Src/Document/BookmarkManager/BookmarkManager.cs @@ -0,0 +1,246 @@ +// +// +// +// +// $Revision$ +// + +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using ICSharpCode.TextEditor.Util; + +namespace ICSharpCode.TextEditor.Document +{ + public interface IBookmarkFactory + { + Bookmark CreateBookmark(IDocument document, TextLocation location); + } + + /// + /// This class handles the bookmarks for a buffer + /// + public class BookmarkManager + { + IDocument document; + #if DEBUG + IList bookmark = new CheckedList(); + #else + List bookmark = new List(); + #endif + + /// + /// Contains all bookmarks + /// + public ReadOnlyCollection Marks { + get { + return new ReadOnlyCollection(bookmark); + } + } + + public IDocument Document { + get { + return document; + } + } + + /// + /// Creates a new instance of + /// + internal BookmarkManager(IDocument document, LineManager lineTracker) + { + this.document = document; + } + + /// + /// Gets/Sets the bookmark factory used to create bookmarks for "ToggleMarkAt". + /// + public IBookmarkFactory Factory { get; set;} + + /// + /// Sets the mark at the line location.Line if it is not set, if the + /// line is already marked the mark is cleared. + /// + public void ToggleMarkAt(TextLocation location) + { + Bookmark newMark; + if (Factory != null) { + newMark = Factory.CreateBookmark(document, location); + } else { + newMark = new Bookmark(document, location); + } + + Type newMarkType = newMark.GetType(); + + for (int i = 0; i < bookmark.Count; ++i) { + Bookmark mark = bookmark[i]; + + if (mark.LineNumber == location.Line && mark.CanToggle && mark.GetType() == newMarkType) { + bookmark.RemoveAt(i); + OnRemoved(new BookmarkEventArgs(mark)); + return; + } + } + + bookmark.Add(newMark); + OnAdded(new BookmarkEventArgs(newMark)); + } + + public void AddMark(Bookmark mark) + { + bookmark.Add(mark); + OnAdded(new BookmarkEventArgs(mark)); + } + + public void RemoveMark(Bookmark mark) + { + bookmark.Remove(mark); + OnRemoved(new BookmarkEventArgs(mark)); + } + + public void RemoveMarks(Predicate predicate) + { + for (int i = 0; i < bookmark.Count; ++i) { + Bookmark bm = bookmark[i]; + if (predicate(bm)) { + bookmark.RemoveAt(i--); + OnRemoved(new BookmarkEventArgs(bm)); + } + } + } + + /// + /// true, if a mark at mark exists, otherwise false + /// + public bool IsMarked(int lineNr) + { + for (int i = 0; i < bookmark.Count; ++i) { + if (bookmark[i].LineNumber == lineNr) { + return true; + } + } + return false; + } + + /// + /// Clears all bookmark + /// + public void Clear() + { + foreach (Bookmark mark in bookmark) { + OnRemoved(new BookmarkEventArgs(mark)); + } + bookmark.Clear(); + } + + /// + /// The lowest mark, if no marks exists it returns -1 + /// + public Bookmark GetFirstMark(Predicate predicate) + { + if (bookmark.Count < 1) { + return null; + } + Bookmark first = null; + for (int i = 0; i < bookmark.Count; ++i) { + if (predicate(bookmark[i]) && bookmark[i].IsEnabled && (first == null || bookmark[i].LineNumber < first.LineNumber)) { + first = bookmark[i]; + } + } + return first; + } + + /// + /// The highest mark, if no marks exists it returns -1 + /// + public Bookmark GetLastMark(Predicate predicate) + { + if (bookmark.Count < 1) { + return null; + } + Bookmark last = null; + for (int i = 0; i < bookmark.Count; ++i) { + if (predicate(bookmark[i]) && bookmark[i].IsEnabled && (last == null || bookmark[i].LineNumber > last.LineNumber)) { + last = bookmark[i]; + } + } + return last; + } + bool AcceptAnyMarkPredicate(Bookmark mark) + { + return true; + } + public Bookmark GetNextMark(int curLineNr) + { + return GetNextMark(curLineNr, AcceptAnyMarkPredicate); + } + + /// + /// returns first mark higher than lineNr + /// + /// + /// returns the next mark > cur, if it not exists it returns FirstMark() + /// + public Bookmark GetNextMark(int curLineNr, Predicate predicate) + { + if (bookmark.Count == 0) { + return null; + } + + Bookmark next = GetFirstMark(predicate); + foreach (Bookmark mark in bookmark) { + if (predicate(mark) && mark.IsEnabled && mark.LineNumber > curLineNr) { + if (mark.LineNumber < next.LineNumber || next.LineNumber <= curLineNr) { + next = mark; + } + } + } + return next; + } + + public Bookmark GetPrevMark(int curLineNr) + { + return GetPrevMark(curLineNr, AcceptAnyMarkPredicate); + } + /// + /// returns first mark lower than lineNr + /// + /// + /// returns the next mark lower than cur, if it not exists it returns LastMark() + /// + public Bookmark GetPrevMark(int curLineNr, Predicate predicate) + { + if (bookmark.Count == 0) { + return null; + } + + Bookmark prev = GetLastMark(predicate); + + foreach (Bookmark mark in bookmark) { + if (predicate(mark) && mark.IsEnabled && mark.LineNumber < curLineNr) { + if (mark.LineNumber > prev.LineNumber || prev.LineNumber >= curLineNr) { + prev = mark; + } + } + } + return prev; + } + + protected virtual void OnRemoved(BookmarkEventArgs e) + { + if (Removed != null) { + Removed(this, e); + } + } + + protected virtual void OnAdded(BookmarkEventArgs e) + { + if (Added != null) { + Added(this, e); + } + } + + public event BookmarkEventHandler Removed; + public event BookmarkEventHandler Added; + } +} diff --git a/ICSharpCode.TextEditor/Project/Src/Document/BookmarkManager/BookmarkManagerMemento.cs b/ICSharpCode.TextEditor/Project/Src/Document/BookmarkManager/BookmarkManagerMemento.cs new file mode 100644 index 0000000..2f26c31 --- /dev/null +++ b/ICSharpCode.TextEditor/Project/Src/Document/BookmarkManager/BookmarkManagerMemento.cs @@ -0,0 +1,101 @@ +// +// +// +// +// $Revision$ +// + +using System; +using System.Collections.Generic; +using System.Xml; + +namespace ICSharpCode.TextEditor.Document +{ + /// + /// This class is used for storing the state of a bookmark manager + /// + public class BookmarkManagerMemento + { + List bookmarks = new List(); + + /// + /// Contains all bookmarks as int values + /// + public List Bookmarks { + get { + return bookmarks; + } + set { + bookmarks = value; + } + } + + /// + /// Validates all bookmarks if they're in range of the document. + /// (removing all bookmarks < 0 and bookmarks > max. line number + /// + public void CheckMemento(IDocument document) + { + for (int i = 0; i < bookmarks.Count; ++i) { + int mark = (int)bookmarks[i]; + if (mark < 0 || mark >= document.TotalNumberOfLines) { + bookmarks.RemoveAt(i); + --i; + } + } + } + + /// + /// Creates a new instance of + /// + public BookmarkManagerMemento() + { + } + + /// + /// Creates a new instance of + /// + public BookmarkManagerMemento(XmlElement element) + { + foreach (XmlElement el in element.ChildNodes) { + bookmarks.Add(int.Parse(el.Attributes["line"].InnerText)); + } + } + + /// + /// Creates a new instance of + /// + public BookmarkManagerMemento(List bookmarks) + { + this.bookmarks = bookmarks; + } + + /// + /// Converts a xml element to a object + /// + public object FromXmlElement(XmlElement element) + { + return new BookmarkManagerMemento(element); + } + + /// + /// Converts this to a xml element + /// + public XmlElement ToXmlElement(XmlDocument doc) + { + XmlElement bookmarknode = doc.CreateElement("Bookmarks"); + + foreach (int line in bookmarks) { + XmlElement markNode = doc.CreateElement("Mark"); + + XmlAttribute lineAttr = doc.CreateAttribute("line"); + lineAttr.InnerText = line.ToString(); + markNode.Attributes.Append(lineAttr); + + bookmarknode.AppendChild(markNode); + } + + return bookmarknode; + } + } +} diff --git a/ICSharpCode.TextEditor/Project/Src/Document/DefaultDocument.cs b/ICSharpCode.TextEditor/Project/Src/Document/DefaultDocument.cs new file mode 100644 index 0000000..3ab801e --- /dev/null +++ b/ICSharpCode.TextEditor/Project/Src/Document/DefaultDocument.cs @@ -0,0 +1,457 @@ +// +// +// +// +// $Revision$ +// + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Drawing; + +using ICSharpCode.TextEditor.Undo; + +namespace ICSharpCode.TextEditor.Document +{ + /// + /// Describes the caret marker + /// + public enum LineViewerStyle { + /// + /// No line viewer will be displayed + /// + None, + + /// + /// The row in which the caret is will be marked + /// + FullRow + } + + /// + /// Describes the indent style + /// + public enum IndentStyle { + /// + /// No indentation occurs + /// + None, + + /// + /// The indentation from the line above will be + /// taken to indent the curent line + /// + Auto, + + /// + /// Inteligent, context sensitive indentation will occur + /// + Smart + } + + /// + /// Describes the bracket highlighting style + /// + public enum BracketHighlightingStyle { + + /// + /// Brackets won't be highlighted + /// + None, + + /// + /// Brackets will be highlighted if the caret is on the bracket + /// + OnBracket, + + /// + /// Brackets will be highlighted if the caret is after the bracket + /// + AfterBracket + } + + /// + /// Describes the selection mode of the text area + /// + public enum DocumentSelectionMode { + /// + /// The 'normal' selection mode. + /// + Normal, + + /// + /// Selections will be added to the current selection or new + /// ones will be created (multi-select mode) + /// + Additive + } + + /// + /// The default implementation. + /// + internal sealed class DefaultDocument : IDocument + { + bool readOnly = false; + + LineManager lineTrackingStrategy; + BookmarkManager bookmarkManager; + ITextBufferStrategy textBufferStrategy; + IFormattingStrategy formattingStrategy; + FoldingManager foldingManager; + UndoStack undoStack = new UndoStack(); + ITextEditorProperties textEditorProperties = new DefaultTextEditorProperties(); + MarkerStrategy markerStrategy; + + public LineManager LineManager { + get { return lineTrackingStrategy; } + set { lineTrackingStrategy = value; } + } + + public event EventHandler LineLengthChanged { + add { lineTrackingStrategy.LineLengthChanged += value; } + remove { lineTrackingStrategy.LineLengthChanged -= value; } + } + public event EventHandler LineCountChanged { + add { lineTrackingStrategy.LineCountChanged += value; } + remove { lineTrackingStrategy.LineCountChanged -= value; } + } + public event EventHandler LineDeleted { + add { lineTrackingStrategy.LineDeleted += value; } + remove { lineTrackingStrategy.LineDeleted -= value; } + } + + public MarkerStrategy MarkerStrategy { + get { return markerStrategy; } + set { markerStrategy = value; } + } + + public ITextEditorProperties TextEditorProperties { + get { + return textEditorProperties; + } + set { + textEditorProperties = value; + } + } + + public UndoStack UndoStack { + get { + return undoStack; + } + } + + public IList LineSegmentCollection { + get { + return lineTrackingStrategy.LineSegmentCollection; + } + } + + public bool ReadOnly { + get { + return readOnly; + } + set { + readOnly = value; + } + } + + public ITextBufferStrategy TextBufferStrategy { + get { + return textBufferStrategy; + } + set { + textBufferStrategy = value; + } + } + + public IFormattingStrategy FormattingStrategy { + get { + return formattingStrategy; + } + set { + formattingStrategy = value; + } + } + + public FoldingManager FoldingManager { + get { + return foldingManager; + } + set { + foldingManager = value; + } + } + + public IHighlightingStrategy HighlightingStrategy { + get { + return lineTrackingStrategy.HighlightingStrategy; + } + set { + lineTrackingStrategy.HighlightingStrategy = value; + } + } + + public int TextLength { + get { + return textBufferStrategy.Length; + } + } + + public BookmarkManager BookmarkManager { + get { + return bookmarkManager; + } + set { + bookmarkManager = value; + } + } + + + public string TextContent { + get { + return GetText(0, textBufferStrategy.Length); + } + set { + Debug.Assert(textBufferStrategy != null); + Debug.Assert(lineTrackingStrategy != null); + OnDocumentAboutToBeChanged(new DocumentEventArgs(this, 0, 0, value)); + textBufferStrategy.SetContent(value); + lineTrackingStrategy.SetContent(value); + undoStack.ClearAll(); + + OnDocumentChanged(new DocumentEventArgs(this, 0, 0, value)); + OnTextContentChanged(EventArgs.Empty); + } + } + + public void Insert(int offset, string text) + { + if (readOnly) { + return; + } + OnDocumentAboutToBeChanged(new DocumentEventArgs(this, offset, -1, text)); + + textBufferStrategy.Insert(offset, text); + lineTrackingStrategy.Insert(offset, text); + + undoStack.Push(new UndoableInsert(this, offset, text)); + + OnDocumentChanged(new DocumentEventArgs(this, offset, -1, text)); + } + + public void Remove(int offset, int length) + { + if (readOnly) { + return; + } + OnDocumentAboutToBeChanged(new DocumentEventArgs(this, offset, length)); + undoStack.Push(new UndoableDelete(this, offset, GetText(offset, length))); + + textBufferStrategy.Remove(offset, length); + lineTrackingStrategy.Remove(offset, length); + + OnDocumentChanged(new DocumentEventArgs(this, offset, length)); + } + + public void Replace(int offset, int length, string text) + { + if (readOnly) { + return; + } + OnDocumentAboutToBeChanged(new DocumentEventArgs(this, offset, length, text)); + undoStack.Push(new UndoableReplace(this, offset, GetText(offset, length), text)); + + textBufferStrategy.Replace(offset, length, text); + lineTrackingStrategy.Replace(offset, length, text); + + OnDocumentChanged(new DocumentEventArgs(this, offset, length, text)); + } + + public char GetCharAt(int offset) + { + return textBufferStrategy.GetCharAt(offset); + } + + public string GetText(int offset, int length) + { + #if DEBUG + if (length < 0) throw new ArgumentOutOfRangeException("length", length, "length < 0"); + #endif + return textBufferStrategy.GetText(offset, length); + } + public string GetText(ISegment segment) + { + return GetText(segment.Offset, segment.Length); + } + + public int TotalNumberOfLines { + get { + return lineTrackingStrategy.TotalNumberOfLines; + } + } + + public int GetLineNumberForOffset(int offset) + { + return lineTrackingStrategy.GetLineNumberForOffset(offset); + } + + public LineSegment GetLineSegmentForOffset(int offset) + { + return lineTrackingStrategy.GetLineSegmentForOffset(offset); + } + + public LineSegment GetLineSegment(int line) + { + return lineTrackingStrategy.GetLineSegment(line); + } + + public int GetFirstLogicalLine(int lineNumber) + { + return lineTrackingStrategy.GetFirstLogicalLine(lineNumber); + } + + public int GetLastLogicalLine(int lineNumber) + { + return lineTrackingStrategy.GetLastLogicalLine(lineNumber); + } + + public int GetVisibleLine(int lineNumber) + { + return lineTrackingStrategy.GetVisibleLine(lineNumber); + } + +// public int GetVisibleColumn(int logicalLine, int logicalColumn) +// { +// return lineTrackingStrategy.GetVisibleColumn(logicalLine, logicalColumn); +// } +// + public int GetNextVisibleLineAbove(int lineNumber, int lineCount) + { + return lineTrackingStrategy.GetNextVisibleLineAbove(lineNumber, lineCount); + } + + public int GetNextVisibleLineBelow(int lineNumber, int lineCount) + { + return lineTrackingStrategy.GetNextVisibleLineBelow(lineNumber, lineCount); + } + + public TextLocation OffsetToPosition(int offset) + { + int lineNr = GetLineNumberForOffset(offset); + LineSegment line = GetLineSegment(lineNr); + return new TextLocation(offset - line.Offset, lineNr); + } + + public int PositionToOffset(TextLocation p) + { + if (p.Y >= this.TotalNumberOfLines) { + return 0; + } + LineSegment line = GetLineSegment(p.Y); + return Math.Min(this.TextLength, line.Offset + Math.Min(line.Length, p.X)); + } + + public void UpdateSegmentListOnDocumentChange(List list, DocumentEventArgs e) where T : ISegment + { + int removedCharacters = e.Length > 0 ? e.Length : 0; + int insertedCharacters = e.Text != null ? e.Text.Length : 0; + for (int i = 0; i < list.Count; ++i) { + ISegment s = list[i]; + int segmentStart = s.Offset; + int segmentEnd = s.Offset + s.Length; + + if (e.Offset <= segmentStart) { + segmentStart -= removedCharacters; + if (segmentStart < e.Offset) + segmentStart = e.Offset; + } + if (e.Offset < segmentEnd) { + segmentEnd -= removedCharacters; + if (segmentEnd < e.Offset) + segmentEnd = e.Offset; + } + + Debug.Assert(segmentStart <= segmentEnd); + + if (segmentStart == segmentEnd) { + list.RemoveAt(i); + --i; + continue; + } + + if (e.Offset <= segmentStart) + segmentStart += insertedCharacters; + if (e.Offset < segmentEnd) + segmentEnd += insertedCharacters; + + Debug.Assert(segmentStart < segmentEnd); + + s.Offset = segmentStart; + s.Length = segmentEnd - segmentStart; + } + } + + void OnDocumentAboutToBeChanged(DocumentEventArgs e) + { + if (DocumentAboutToBeChanged != null) { + DocumentAboutToBeChanged(this, e); + } + } + + void OnDocumentChanged(DocumentEventArgs e) + { + if (DocumentChanged != null) { + DocumentChanged(this, e); + } + } + + public event DocumentEventHandler DocumentAboutToBeChanged; + public event DocumentEventHandler DocumentChanged; + + // UPDATE STUFF + List updateQueue = new List(); + + public List UpdateQueue { + get { + return updateQueue; + } + } + + public void RequestUpdate(TextAreaUpdate update) + { + if (updateQueue.Count == 1 && updateQueue[0].TextAreaUpdateType == TextAreaUpdateType.WholeTextArea) { + // if we're going to update the whole text area, we don't need to store detail updates + return; + } + if (update.TextAreaUpdateType == TextAreaUpdateType.WholeTextArea) { + // if we're going to update the whole text area, we don't need to store detail updates + updateQueue.Clear(); + } + updateQueue.Add(update); + } + + public void CommitUpdate() + { + if (UpdateCommited != null) { + UpdateCommited(this, EventArgs.Empty); + } + } + + void OnTextContentChanged(EventArgs e) + { + if (TextContentChanged != null) { + TextContentChanged(this, e); + } + } + + public event EventHandler UpdateCommited; + public event EventHandler TextContentChanged; + + [Conditional("DEBUG")] + internal static void ValidatePosition(IDocument document, TextLocation position) + { + document.GetLineSegment(position.Line); + } + } +} diff --git a/ICSharpCode.TextEditor/Project/Src/Document/DefaultTextEditorProperties.cs b/ICSharpCode.TextEditor/Project/Src/Document/DefaultTextEditorProperties.cs new file mode 100644 index 0000000..70c5171 --- /dev/null +++ b/ICSharpCode.TextEditor/Project/Src/Document/DefaultTextEditorProperties.cs @@ -0,0 +1,321 @@ +// +// +// +// +// $Revision$ +// + +using System; +using System.Drawing; +using System.Text; + +namespace ICSharpCode.TextEditor.Document +{ + public enum BracketMatchingStyle { + Before, + After + } + + public class DefaultTextEditorProperties : ITextEditorProperties + { + int tabIndent = 4; + int indentationSize = 4; + IndentStyle indentStyle = IndentStyle.Smart; + DocumentSelectionMode documentSelectionMode = DocumentSelectionMode.Normal; + Encoding encoding = System.Text.Encoding.UTF8; + BracketMatchingStyle bracketMatchingStyle = BracketMatchingStyle.After; + FontContainer fontContainer; + static Font DefaultFont; + + public DefaultTextEditorProperties() + { + if (DefaultFont == null) { + DefaultFont = new Font("Courier New", 10); + } + this.fontContainer = new FontContainer(DefaultFont); + } + + bool allowCaretBeyondEOL = false; + + bool caretLine = false; + + bool showMatchingBracket = true; + bool showLineNumbers = true; + + bool showSpaces = false; + bool showTabs = false; + bool showEOLMarker = false; + + bool showInvalidLines = false; + + bool isIconBarVisible = false; + bool enableFolding = true; + bool showHorizontalRuler = false; + bool showVerticalRuler = true; + bool convertTabsToSpaces = false; + System.Drawing.Text.TextRenderingHint textRenderingHint = System.Drawing.Text.TextRenderingHint.SystemDefault; + bool mouseWheelScrollDown = true; + bool mouseWheelTextZoom = true; + + bool hideMouseCursor = false; + bool cutCopyWholeLine = true; + + int verticalRulerRow = 80; + LineViewerStyle lineViewerStyle = LineViewerStyle.None; + string lineTerminator = "\r\n"; + bool autoInsertCurlyBracket = true; + bool supportReadOnlySegments = false; + + public int TabIndent { + get { + return tabIndent; + } + set { + tabIndent = value; + } + } + + public int IndentationSize { + get { return indentationSize; } + set { indentationSize = value; } + } + + public IndentStyle IndentStyle { + get { + return indentStyle; + } + set { + indentStyle = value; + } + } + + public bool CaretLine { + get + { + return caretLine; + } + set + { + caretLine = value; + } + } + + public DocumentSelectionMode DocumentSelectionMode { + get { + return documentSelectionMode; + } + set { + documentSelectionMode = value; + } + } + public bool AllowCaretBeyondEOL { + get { + return allowCaretBeyondEOL; + } + set { + allowCaretBeyondEOL = value; + } + } + public bool ShowMatchingBracket { + get { + return showMatchingBracket; + } + set { + showMatchingBracket = value; + } + } + public bool ShowLineNumbers { + get { + return showLineNumbers; + } + set { + showLineNumbers = value; + } + } + public bool ShowSpaces { + get { + return showSpaces; + } + set { + showSpaces = value; + } + } + public bool ShowTabs { + get { + return showTabs; + } + set { + showTabs = value; + } + } + public bool ShowEOLMarker { + get { + return showEOLMarker; + } + set { + showEOLMarker = value; + } + } + public bool ShowInvalidLines { + get { + return showInvalidLines; + } + set { + showInvalidLines = value; + } + } + public bool IsIconBarVisible { + get { + return isIconBarVisible; + } + set { + isIconBarVisible = value; + } + } + public bool EnableFolding { + get { + return enableFolding; + } + set { + enableFolding = value; + } + } + public bool ShowHorizontalRuler { + get { + return showHorizontalRuler; + } + set { + showHorizontalRuler = value; + } + } + public bool ShowVerticalRuler { + get { + return showVerticalRuler; + } + set { + showVerticalRuler = value; + } + } + public bool ConvertTabsToSpaces { + get { + return convertTabsToSpaces; + } + set { + convertTabsToSpaces = value; + } + } + public System.Drawing.Text.TextRenderingHint TextRenderingHint { + get { return textRenderingHint; } + set { textRenderingHint = value; } + } + + public bool MouseWheelScrollDown { + get { + return mouseWheelScrollDown; + } + set { + mouseWheelScrollDown = value; + } + } + public bool MouseWheelTextZoom { + get { + return mouseWheelTextZoom; + } + set { + mouseWheelTextZoom = value; + } + } + + public bool HideMouseCursor { + get { + return hideMouseCursor; + } + set { + hideMouseCursor = value; + } + } + + public bool CutCopyWholeLine { + get { + return cutCopyWholeLine; + } + set { + cutCopyWholeLine = value; + } + } + + public Encoding Encoding { + get { + return encoding; + } + set { + encoding = value; + } + } + public int VerticalRulerRow { + get { + return verticalRulerRow; + } + set { + verticalRulerRow = value; + } + } + public LineViewerStyle LineViewerStyle { + get { + return lineViewerStyle; + } + set { + lineViewerStyle = value; + } + } + public string LineTerminator { + get { + return lineTerminator; + } + set { + lineTerminator = value; + } + } + public bool AutoInsertCurlyBracket { + get { + return autoInsertCurlyBracket; + } + set { + autoInsertCurlyBracket = value; + } + } + + public Font Font { + get { + return fontContainer.DefaultFont; + } + set { + fontContainer.DefaultFont = value; + } + } + + public FontContainer FontContainer { + get { + return fontContainer; + } + } + + public BracketMatchingStyle BracketMatchingStyle { + get { + return bracketMatchingStyle; + } + set { + bracketMatchingStyle = value; + } + } + + public bool SupportReadOnlySegments { + get { + return supportReadOnlySegments; + } + set { + supportReadOnlySegments = value; + } + } + } +} diff --git a/ICSharpCode.TextEditor/Project/Src/Document/DocumentEventArgs.cs b/ICSharpCode.TextEditor/Project/Src/Document/DocumentEventArgs.cs new file mode 100644 index 0000000..b7a97cf --- /dev/null +++ b/ICSharpCode.TextEditor/Project/Src/Document/DocumentEventArgs.cs @@ -0,0 +1,103 @@ +// +// +// +// +// $Revision$ +// + +using System; + +namespace ICSharpCode.TextEditor.Document +{ + /// + /// This delegate is used for document events. + /// + public delegate void DocumentEventHandler(object sender, DocumentEventArgs e); + + /// + /// This class contains more information on a document event + /// + public class DocumentEventArgs : EventArgs + { + IDocument document; + int offset; + int length; + string text; + + /// + /// always a valid Document which is related to the Event. + /// + public IDocument Document { + get { + return document; + } + } + + /// + /// -1 if no offset was specified for this event + /// + public int Offset { + get { + return offset; + } + } + + /// + /// null if no text was specified for this event + /// + public string Text { + get { + return text; + } + } + + /// + /// -1 if no length was specified for this event + /// + public int Length { + get { + return length; + } + } + + /// + /// Creates a new instance off + /// + public DocumentEventArgs(IDocument document) : this(document, -1, -1, null) + { + } + + /// + /// Creates a new instance off + /// + public DocumentEventArgs(IDocument document, int offset) : this(document, offset, -1, null) + { + } + + /// + /// Creates a new instance off + /// + public DocumentEventArgs(IDocument document, int offset, int length) : this(document, offset, length, null) + { + } + + /// + /// Creates a new instance off + /// + public DocumentEventArgs(IDocument document, int offset, int length, string text) + { + this.document = document; + this.offset = offset; + this.length = length; + this.text = text; + } + public override string ToString() + { + return string.Format("[DocumentEventArgs: Document = {0}, Offset = {1}, Text = {2}, Length = {3}]", + Document, + Offset, + Text, + Length); + } + } +} diff --git a/ICSharpCode.TextEditor/Project/Src/Document/DocumentFactory.cs b/ICSharpCode.TextEditor/Project/Src/Document/DocumentFactory.cs new file mode 100644 index 0000000..b60ce07 --- /dev/null +++ b/ICSharpCode.TextEditor/Project/Src/Document/DocumentFactory.cs @@ -0,0 +1,57 @@ +// +// +// +// +// $Revision$ +// + +using System; +using System.Text; + +namespace ICSharpCode.TextEditor.Document +{ + /// + /// This interface represents a container which holds a text sequence and + /// all necessary information about it. It is used as the base for a text editor. + /// + public class DocumentFactory + { + /// + /// Creates a new object. Only create + /// with this method. + /// + public IDocument CreateDocument() + { + DefaultDocument doc = new DefaultDocument(); + doc.TextBufferStrategy = new GapTextBufferStrategy(); + doc.FormattingStrategy = new DefaultFormattingStrategy(); + doc.LineManager = new LineManager(doc, null); + doc.FoldingManager = new FoldingManager(doc, doc.LineManager); + doc.FoldingManager.FoldingStrategy = null; //new ParserFoldingStrategy(); + doc.MarkerStrategy = new MarkerStrategy(doc); + doc.BookmarkManager = new BookmarkManager(doc, doc.LineManager); + return doc; + } + + /// + /// Creates a new document and loads the given file + /// + public IDocument CreateFromTextBuffer(ITextBufferStrategy textBuffer) + { + DefaultDocument doc = (DefaultDocument)CreateDocument(); + doc.TextContent = textBuffer.GetText(0, textBuffer.Length); + doc.TextBufferStrategy = textBuffer; + return doc; + } + + /// + /// Creates a new document and loads the given file + /// + public IDocument CreateFromFile(string fileName) + { + IDocument document = CreateDocument(); + document.TextContent = Util.FileReader.ReadFileContent(fileName, Encoding.Default); + return document; + } + } +} diff --git a/ICSharpCode.TextEditor/Project/Src/Document/FoldingStrategy/CSharpFoldingStrategy.cs b/ICSharpCode.TextEditor/Project/Src/Document/FoldingStrategy/CSharpFoldingStrategy.cs new file mode 100644 index 0000000..0abe64b --- /dev/null +++ b/ICSharpCode.TextEditor/Project/Src/Document/FoldingStrategy/CSharpFoldingStrategy.cs @@ -0,0 +1,107 @@ +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// This file is part of CodingEditor. +// Note: This project is derived from Peter Project +// (hosted on sourceforge and codeplex) +// +// Copyright (c) 2008-2009, CE Team +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +using System.Collections.Generic; +using ICSharpCode.TextEditor.Document; + +namespace ICSharpCode.TextEditor.Src.Document.FoldingStrategy +{ + public class CSharpFoldingStrategy : IFoldingStrategy + { + #region Methods + + /// + /// Generates the foldings for our document. + /// + /// The current document. + /// The filename of the document. + /// Extra parse information, not used in this sample. + /// A list of FoldMarkers. + public List GenerateFoldMarkers(IDocument document, string fileName, object parseInformation) + { + var list = new List(); + var startLines = new Stack(); + + // Create foldmarkers for the whole document, enumerate through every line. + for (int i = 0; i < document.TotalNumberOfLines; i++) + { + var seg = document.GetLineSegment(i); + int offs, end = document.TextLength; + char c; + for (offs = seg.Offset; offs < end && ((c = document.GetCharAt(offs)) == ' ' || c == '\t'); offs++) + { + } + if (offs == end) + break; + int spaceCount = offs - seg.Offset; + + // now offs points to the first non-whitespace char on the line + if (document.GetCharAt(offs) == '#') + { + string text = document.GetText(offs, seg.Length - spaceCount); + if (text.StartsWith("#region")) + startLines.Push(i); + if (text.StartsWith("#endregion") && startLines.Count > 0) + { + // Add a new FoldMarker to the list. + int start = startLines.Pop(); + list.Add(new FoldMarker(document, start, + document.GetLineSegment(start).Length, + i, spaceCount + "#endregion".Length, FoldType.Region, "{...}")); + } + } + + // { } + if (document.GetCharAt(offs) == '{') + { + int offsetOfClosingBracket = document.FormattingStrategy.SearchBracketForward(document, offs + 1, '{', '}'); + if (offsetOfClosingBracket > 0) + { + int length = offsetOfClosingBracket - offs + 1; + list.Add(new FoldMarker(document, offs, length, "{...}", false)); + } + } + + if (document.GetCharAt(offs) == '/') + { + string text = document.GetText(offs, seg.Length - spaceCount); + if (text.StartsWith("/// ")) + startLines.Push(i); + if ((text.StartsWith("/// ") || text.StartsWith("/// ")) + && startLines.Count > 0) + { + // Add a new FoldMarker to the list. + int start = startLines.Pop(); + list.Add(new FoldMarker(document, start, + document.GetLineSegment(start).Length, + i, spaceCount + "/// ".Length, FoldType.TypeBody, "/// ...")); + } + + } + } + + return list; + } + + #endregion Methods + } +} \ No newline at end of file diff --git a/ICSharpCode.TextEditor/Project/Src/Document/FoldingStrategy/FoldMarker.cs b/ICSharpCode.TextEditor/Project/Src/Document/FoldingStrategy/FoldMarker.cs new file mode 100644 index 0000000..9f1b89b --- /dev/null +++ b/ICSharpCode.TextEditor/Project/Src/Document/FoldingStrategy/FoldMarker.cs @@ -0,0 +1,174 @@ +// +// +// +// +// $Revision$ +// + +using System; + +namespace ICSharpCode.TextEditor.Document +{ + public enum FoldType { + Unspecified, + MemberBody, + Region, + TypeBody + } + + public class FoldMarker : AbstractSegment, IComparable + { + bool isFolded = false; + string foldText = "..."; + FoldType foldType = FoldType.Unspecified; + IDocument document = null; + int startLine = -1, startColumn, endLine = -1, endColumn; + + static void GetPointForOffset(IDocument document, int offset, out int line, out int column) + { + if (offset > document.TextLength) { + line = document.TotalNumberOfLines + 1; + column = 1; + } else if (offset < 0) { + line = -1; + column = -1; + } else { + line = document.GetLineNumberForOffset(offset); + column = offset - document.GetLineSegment(line).Offset; + } + } + + public FoldType FoldType { + get { return foldType; } + set { foldType = value; } + } + + public int StartLine { + get { + if (startLine < 0) { + GetPointForOffset(document, offset, out startLine, out startColumn); + } + return startLine; + } + } + + public int StartColumn { + get { + if (startLine < 0) { + GetPointForOffset(document, offset, out startLine, out startColumn); + } + return startColumn; + } + } + + public int EndLine { + get { + if (endLine < 0) { + GetPointForOffset(document, offset + length, out endLine, out endColumn); + } + return endLine; + } + } + + public int EndColumn { + get { + if (endLine < 0) { + GetPointForOffset(document, offset + length, out endLine, out endColumn); + } + return endColumn; + } + } + + public override int Offset { + get { return base.Offset; } + set { + base.Offset = value; + startLine = -1; endLine = -1; + } + } + public override int Length { + get { return base.Length; } + set { + base.Length = value; + endLine = -1; + } + } + + public bool IsFolded { + get { + return isFolded; + } + set { + isFolded = value; + } + } + + public string FoldText { + get { + return foldText; + } + } + + public string InnerText { + get { + return document.GetText(offset, length); + } + } + + public FoldMarker(IDocument document, int offset, int length, string foldText, bool isFolded) + { + this.document = document; + this.offset = offset; + this.length = length; + this.foldText = foldText; + this.isFolded = isFolded; + } + + public FoldMarker(IDocument document, int startLine, int startColumn, int endLine, int endColumn) : this(document, startLine, startColumn, endLine, endColumn, FoldType.Unspecified) + { + } + + public FoldMarker(IDocument document, int startLine, int startColumn, int endLine, int endColumn, FoldType foldType) : this(document, startLine, startColumn, endLine, endColumn, foldType, "...") + { + } + + public FoldMarker(IDocument document, int startLine, int startColumn, int endLine, int endColumn, FoldType foldType, string foldText) : this(document, startLine, startColumn, endLine, endColumn, foldType, foldText, false) + { + } + + public FoldMarker(IDocument document, int startLine, int startColumn, int endLine, int endColumn, FoldType foldType, string foldText, bool isFolded) + { + this.document = document; + + startLine = Math.Min(document.TotalNumberOfLines - 1, Math.Max(startLine, 0)); + ISegment startLineSegment = document.GetLineSegment(startLine); + + endLine = Math.Min(document.TotalNumberOfLines - 1, Math.Max(endLine, 0)); + ISegment endLineSegment = document.GetLineSegment(endLine); + + // Prevent the region from completely disappearing + if (string.IsNullOrEmpty(foldText)) { + foldText = "..."; + } + + this.FoldType = foldType; + this.foldText = foldText; + this.offset = startLineSegment.Offset + Math.Min(startColumn, startLineSegment.Length); + this.length = (endLineSegment.Offset + Math.Min(endColumn, endLineSegment.Length)) - this.offset; + this.isFolded = isFolded; + } + + public int CompareTo(object o) + { + if (!(o is FoldMarker)) { + throw new ArgumentException(); + } + FoldMarker f = (FoldMarker)o; + if (offset != f.offset) { + return offset.CompareTo(f.offset); + } + + return length.CompareTo(f.length); + } + } +} diff --git a/ICSharpCode.TextEditor/Project/Src/Document/FoldingStrategy/FoldingManager.cs b/ICSharpCode.TextEditor/Project/Src/Document/FoldingStrategy/FoldingManager.cs new file mode 100644 index 0000000..1501b73 --- /dev/null +++ b/ICSharpCode.TextEditor/Project/Src/Document/FoldingStrategy/FoldingManager.cs @@ -0,0 +1,362 @@ +// +// +// +// +// $Revision$ +// + +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Text; + +namespace ICSharpCode.TextEditor.Document +{ + public class FoldingManager + { + List foldMarker = new List(); + List foldMarkerByEnd = new List(); + IDocument document; + + public IList FoldMarker => foldMarker.AsReadOnly(); + + public IFoldingStrategy? FoldingStrategy { get; set; } + + internal FoldingManager(IDocument document, LineManager lineTracker) + { + this.document = document; + document.DocumentChanged += new DocumentEventHandler(DocumentChanged); + + // lineTracker.LineCountChanged += new LineManagerEventHandler(LineManagerLineCountChanged); + // lineTracker.LineLengthChanged += new LineLengthEventHandler(LineManagerLineLengthChanged); + // foldMarker.Add(new FoldMarker(0, 5, 3, 5)); + // + // foldMarker.Add(new FoldMarker(5, 5, 10, 3)); + // foldMarker.Add(new FoldMarker(6, 0, 8, 2)); + // + // FoldMarker fm1 = new FoldMarker(10, 4, 10, 7); + // FoldMarker fm2 = new FoldMarker(10, 10, 10, 14); + // + // fm1.IsFolded = true; + // fm2.IsFolded = true; + // + // foldMarker.Add(fm1); + // foldMarker.Add(fm2); + // foldMarker.Sort(); + } + + void DocumentChanged(object sender, DocumentEventArgs e) + { + int oldCount = foldMarker.Count; + document.UpdateSegmentListOnDocumentChange(foldMarker, e); + if (oldCount != foldMarker.Count) + { + document.RequestUpdate(new TextAreaUpdate(TextAreaUpdateType.WholeTextArea)); + } + } + + public List GetFoldingsFromPosition(int line, int column) + { + List foldings = new List(); + if (foldMarker != null) + { + for (int i = 0; i < foldMarker.Count; ++i) + { + FoldMarker fm = foldMarker[i]; + if ((fm.StartLine == line && column > fm.StartColumn && !(fm.EndLine == line && column >= fm.EndColumn)) || + (fm.EndLine == line && column < fm.EndColumn && !(fm.StartLine == line && column <= fm.StartColumn)) || + (line > fm.StartLine && line < fm.EndLine)) + { + foldings.Add(fm); + } + } + } + return foldings; + } + + class StartComparer : IComparer + { + public readonly static StartComparer Instance = new StartComparer(); + + public int Compare(FoldMarker x, FoldMarker y) + { + if (x.StartLine < y.StartLine) + return -1; + else if (x.StartLine == y.StartLine) + return x.StartColumn.CompareTo(y.StartColumn); + else + return 1; + } + } + + class EndComparer : IComparer + { + public readonly static EndComparer Instance = new EndComparer(); + + public int Compare(FoldMarker x, FoldMarker y) + { + if (x.EndLine < y.EndLine) + return -1; + else if (x.EndLine == y.EndLine) + return x.EndColumn.CompareTo(y.EndColumn); + else + return 1; + } + } + + List GetFoldingsByStartAfterColumn(int lineNumber, int column, bool forceFolded) + { + List foldings = new List(); + + if (foldMarker != null) + { + int index = foldMarker.BinarySearch( + new FoldMarker(document, lineNumber, column, lineNumber, column), + StartComparer.Instance); + if (index < 0) index = ~index; + + for (; index < foldMarker.Count; index++) + { + FoldMarker fm = foldMarker[index]; + if (fm.StartLine > lineNumber) + break; + if (fm.StartColumn <= column) + continue; + if (!forceFolded || fm.IsFolded) + foldings.Add(fm); + } + } + return foldings; + } + + public List GetFoldingsWithStart(int lineNumber) + { + return GetFoldingsByStartAfterColumn(lineNumber, -1, false); + } + + public List GetFoldedFoldingsWithStart(int lineNumber) + { + return GetFoldingsByStartAfterColumn(lineNumber, -1, true); + } + + public List GetFoldedFoldingsWithStartAfterColumn(int lineNumber, int column) + { + return GetFoldingsByStartAfterColumn(lineNumber, column, true); + } + + List GetFoldingsByEndAfterColumn(int lineNumber, int column, bool forceFolded) + { + List foldings = new List(); + + if (foldMarker != null) + { + int index = foldMarkerByEnd.BinarySearch( + new FoldMarker(document, lineNumber, column, lineNumber, column), + EndComparer.Instance); + if (index < 0) index = ~index; + + for (; index < foldMarkerByEnd.Count; index++) + { + FoldMarker fm = foldMarkerByEnd[index]; + if (fm.EndLine > lineNumber) + break; + if (fm.EndColumn <= column) + continue; + if (!forceFolded || fm.IsFolded) + foldings.Add(fm); + } + } + return foldings; + } + + public List GetFoldingsWithEnd(int lineNumber) + { + return GetFoldingsByEndAfterColumn(lineNumber, -1, false); + } + + public List GetFoldedFoldingsWithEnd(int lineNumber) + { + return GetFoldingsByEndAfterColumn(lineNumber, -1, true); + } + + public bool IsFoldStart(int lineNumber) + { + return GetFoldingsWithStart(lineNumber).Count > 0; + } + + public bool IsFoldEnd(int lineNumber) + { + return GetFoldingsWithEnd(lineNumber).Count > 0; + } + + public List GetFoldingsContainsLineNumber(int lineNumber) + { + List foldings = new List(); + if (foldMarker != null) + { + foreach (FoldMarker fm in foldMarker) + { + if (fm.StartLine < lineNumber && lineNumber < fm.EndLine) + { + foldings.Add(fm); + } + } + } + return foldings; + } + + public bool IsBetweenFolding(int lineNumber) + { + return GetFoldingsContainsLineNumber(lineNumber).Count > 0; + } + + public bool IsLineVisible(int lineNumber) + { + foreach (FoldMarker fm in GetFoldingsContainsLineNumber(lineNumber)) + { + if (fm.IsFolded) + return false; + } + return true; + } + + public List GetTopLevelFoldedFoldings() + { + List foldings = new List(); + if (foldMarker != null) + { + Point end = new Point(0, 0); + foreach (FoldMarker fm in foldMarker) + { + if (fm.IsFolded && (fm.StartLine > end.Y || fm.StartLine == end.Y && fm.StartColumn >= end.X)) + { + foldings.Add(fm); + end = new Point(fm.EndColumn, fm.EndLine); + } + } + } + return foldings; + } + + public void UpdateFoldings(string fileName, object parseInfo) + { + UpdateFoldings(FoldingStrategy?.GenerateFoldMarkers(document, fileName, parseInfo)); + } + + public void UpdateFoldings(List newFoldings) + { + int oldFoldingsCount = foldMarker.Count; + lock (this) + { + if (newFoldings != null && newFoldings.Count != 0) + { + newFoldings.Sort(); + if (foldMarker.Count == newFoldings.Count) + { + for (int i = 0; i < foldMarker.Count; ++i) + { + newFoldings[i].IsFolded = foldMarker[i].IsFolded; + } + foldMarker = newFoldings; + } + else + { + for (int i = 0, j = 0; i < foldMarker.Count && j < newFoldings.Count;) + { + int n = newFoldings[j].CompareTo(foldMarker[i]); + if (n > 0) + { + ++i; + } + else + { + if (n == 0) + { + newFoldings[j].IsFolded = foldMarker[i].IsFolded; + } + ++j; + } + } + } + } + if (newFoldings != null) + { + foldMarker = newFoldings; + foldMarkerByEnd = new List(newFoldings); + foldMarkerByEnd.Sort(EndComparer.Instance); + } + else + { + foldMarker.Clear(); + foldMarkerByEnd.Clear(); + } + } + if (oldFoldingsCount != foldMarker.Count) + { + document.RequestUpdate(new TextAreaUpdate(TextAreaUpdateType.WholeTextArea)); + document.CommitUpdate(); + } + } + + public string SerializeToString() + { + StringBuilder sb = new StringBuilder(); + foreach (FoldMarker marker in this.foldMarker) + { + sb.Append(marker.Offset); sb.Append("\n"); + sb.Append(marker.Length); sb.Append("\n"); + sb.Append(marker.FoldText); sb.Append("\n"); + sb.Append(marker.IsFolded); sb.Append("\n"); + } + return sb.ToString(); + } + + public void DeserializeFromString(string str) + { + try + { + string[] lines = str.Split('\n'); + for (int i = 0; i < lines.Length && lines[i].Length > 0; i += 4) + { + int offset = int.Parse(lines[i]); + int length = int.Parse(lines[i + 1]); + string text = lines[i + 2]; + bool isFolded = bool.Parse(lines[i + 3]); + bool found = false; + foreach (FoldMarker marker in foldMarker) + { + if (marker.Offset == offset && marker.Length == length) + { + marker.IsFolded = isFolded; + found = true; + break; + } + } + if (!found) + { + foldMarker.Add(new FoldMarker(document, offset, length, text, isFolded)); + } + } + if (lines.Length > 0) + { + NotifyFoldingsChanged(EventArgs.Empty); + } + } + catch (Exception) + { + // Empty catch + } + } + + public void NotifyFoldingsChanged(EventArgs e) + { + if (FoldingsChanged != null) + { + FoldingsChanged(this, e); + } + } + + + public event EventHandler FoldingsChanged; + } +} diff --git a/ICSharpCode.TextEditor/Project/Src/Document/FoldingStrategy/IFoldingStrategy.cs b/ICSharpCode.TextEditor/Project/Src/Document/FoldingStrategy/IFoldingStrategy.cs new file mode 100644 index 0000000..5c447ba --- /dev/null +++ b/ICSharpCode.TextEditor/Project/Src/Document/FoldingStrategy/IFoldingStrategy.cs @@ -0,0 +1,24 @@ +// +// +// +// +// $Revision$ +// + +using System; +using System.Collections.Generic; + +namespace ICSharpCode.TextEditor.Document +{ + /// + /// This interface is used for the folding capabilities + /// of the textarea. + /// + public interface IFoldingStrategy + { + /// + /// Calculates the fold level of a specific line. + /// + List GenerateFoldMarkers(IDocument document, string fileName, object parseInformation); + } +} diff --git a/ICSharpCode.TextEditor/Project/Src/Document/FoldingStrategy/IFoldingStrategyEx.cs b/ICSharpCode.TextEditor/Project/Src/Document/FoldingStrategy/IFoldingStrategyEx.cs new file mode 100644 index 0000000..f6c20e5 --- /dev/null +++ b/ICSharpCode.TextEditor/Project/Src/Document/FoldingStrategy/IFoldingStrategyEx.cs @@ -0,0 +1,10 @@ +using System.Collections.Generic; +using ICSharpCode.TextEditor.Document; + +namespace ICSharpCode.TextEditor.Src.Document.FoldingStrategy +{ + public interface IFoldingStrategyEx : IFoldingStrategy + { + List GetFoldingErrors(); + } +} \ No newline at end of file diff --git a/ICSharpCode.TextEditor/Project/Src/Document/FoldingStrategy/IndentFoldingStrategy.cs b/ICSharpCode.TextEditor/Project/Src/Document/FoldingStrategy/IndentFoldingStrategy.cs new file mode 100644 index 0000000..e6dc6cc --- /dev/null +++ b/ICSharpCode.TextEditor/Project/Src/Document/FoldingStrategy/IndentFoldingStrategy.cs @@ -0,0 +1,47 @@ +// +// +// +// +// $Revision$ +// + +using System; +using System.Collections.Generic; + +namespace ICSharpCode.TextEditor.Document +{ + /// + /// A simple folding strategy which calculates the folding level + /// using the indent level of the line. + /// + public class IndentFoldingStrategy : IFoldingStrategy + { + public List GenerateFoldMarkers(IDocument document, string fileName, object parseInformation) + { + List l = new List(); + Stack offsetStack = new Stack(); + Stack textStack = new Stack(); + //int level = 0; + //foreach (LineSegment segment in document.LineSegmentCollection) { + // + //} + return l; + } + + int GetLevel(IDocument document, int offset) + { + int level = 0; + int spaces = 0; + for (int i = offset; i < document.TextLength; ++i) { + char c = document.GetCharAt(i); + if (c == '\t' || (c == ' ' && ++spaces == 4)) { + spaces = 0; + ++level; + } else { + break; + } + } + return level; + } + } +} diff --git a/ICSharpCode.TextEditor/Project/Src/Document/FoldingStrategy/JSONFoldingStrategy.cs b/ICSharpCode.TextEditor/Project/Src/Document/FoldingStrategy/JSONFoldingStrategy.cs new file mode 100644 index 0000000..19d5543 --- /dev/null +++ b/ICSharpCode.TextEditor/Project/Src/Document/FoldingStrategy/JSONFoldingStrategy.cs @@ -0,0 +1,129 @@ +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// This file is part of CodingEditor. +// Note: This project is derived from Peter Project +// (hosted on sourceforge and codeplex) +// +// Copyright (c) 2008-2009, CE Team +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +using System.Collections.Generic; +using System.Linq; +using ICSharpCode.TextEditor.Document; + +namespace ICSharpCode.TextEditor.Src.Document.FoldingStrategy +{ + public class JSONFoldingStrategy : IFoldingStrategy + { + #region Methods + + /// + /// Generates the foldings for our document. + /// + /// The current document. + /// The filename of the document. + /// Extra parse information, not used in this sample. + /// A list of FoldMarkers. + public List GenerateFoldMarkers(IDocument document, string fileName, object parseInformation) + { + var list = new List(); + var startLines = new Stack(); + + // Create foldmarkers for the whole document, enumerate through every line. + for (int i = 0; i < document.TotalNumberOfLines; i++) + { + var seg = document.GetLineSegment(i); + int offs, end = seg.Length + seg.Offset; + char c; + for ( + offs = seg.Offset; + offs < end; + offs++) + { + + c = document.GetCharAt(offs); + if (seg.Words.Any(w => w.IsDelimiter && w.Offset == offs - seg.Offset && w.Word == c.ToString())) + { + if (c == '{') + { + int offsetOfClosingBracket = SearchBracketForward(document, i, offs, '{', '}'); + if (offsetOfClosingBracket > 0) + { + int length = offsetOfClosingBracket - offs + 1; + list.Add(new FoldMarker(document, offs, length, "{...}", false)); + } + } + if (c == '[') + { + int offsetOfClosingBracket = SearchBracketForward(document, i, offs, '[', ']'); + if (offsetOfClosingBracket > 0) + { + int length = offsetOfClosingBracket - offs + 1; + list.Add(new FoldMarker(document, offs, length, "[...]", false)); + } + } + } + } + } + + return list; + } + + + private int SearchBracketForward(IDocument document, int currLine, int currOffset, char openBracket, char closingBracket) + { + // Create foldmarkers for the whole document, enumerate through every line. + + int brackets = 1, spaceCount = 0; + for (int i = currLine; i < document.TotalNumberOfLines; i++) + { + var seg = document.GetLineSegment(i); + int offs, end = seg.Length + seg.Offset; + char c; + for ( + offs = i == currLine ? currOffset + 1 : seg.Offset; + offs < end; + offs++) + { + + c = document.GetCharAt(offs); + if (c == ' ' || c == '\t' || c == '\n' || c == '\r') spaceCount++; + if (seg.Words.Any(w => w.IsDelimiter && w.Offset == offs - seg.Offset && w.Word == c.ToString())) + { + if (c == openBracket) + { + ++brackets; + } + else if (c == closingBracket) + { + --brackets; + if (brackets == 0) + { + if (offs - spaceCount - 1 == currOffset) + { + return -1; + } + return offs; + } + } + } + } + } + return -1; + } + #endregion Methods + } +} \ No newline at end of file diff --git a/ICSharpCode.TextEditor/Project/Src/Document/FoldingStrategy/XmlFoldingStrategy.cs b/ICSharpCode.TextEditor/Project/Src/Document/FoldingStrategy/XmlFoldingStrategy.cs new file mode 100644 index 0000000..238934e --- /dev/null +++ b/ICSharpCode.TextEditor/Project/Src/Document/FoldingStrategy/XmlFoldingStrategy.cs @@ -0,0 +1,310 @@ +// Copied from http://codingeditor.googlecode.com/svn/trunk/libs/ICSharpCode.TextEditor/Project/Src/Document/FoldingStrategy/ +#region Header + +// +// +// +// +// $Revision: 1971 $ +// + +#endregion Header + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Text; +using System.Xml; +using ICSharpCode.TextEditor.Document; + +namespace ICSharpCode.TextEditor.Src.Document.FoldingStrategy +{ + /// + /// Holds information about the start of a fold in an xml string. + /// + class XmlFoldStart + { + #region Fields + + readonly int _col; + string _foldText = string.Empty; + readonly int _line; + readonly string _name = string.Empty; + readonly string _prefix = string.Empty; + + #endregion Fields + + #region Constructors + + public XmlFoldStart(string prefix, string name, int line, int col) + { + _line = line; + _col = col; + _prefix = prefix; + _name = name; + } + + #endregion Constructors + + #region Properties + + /// + /// The column where the fold should start. Columns start from 0. + /// + public int Column + { + get + { + return _col; + } + } + + /// + /// The text to be displayed when the item is folded. + /// + public string FoldText + { + get + { + return _foldText; + } + + set + { + _foldText = value; + } + } + + /// + /// The line where the fold should start. Lines start from 0. + /// + public int Line + { + get + { + return _line; + } + } + + /// + /// The name of the xml item with its prefix if it has one. + /// + public string Name + { + get + { + return _prefix.Length > 0 ? string.Concat(_prefix, ":", _name) : _name; + } + } + + #endregion Properties + } + + /// + /// Determines folds for an xml string in the editor. + /// + public class XmlFoldingStrategy : IFoldingStrategyEx + { + #region Fields + + /// + /// Flag indicating whether attributes should be displayed on folded elements. + /// + public bool ShowAttributesWhenFolded = false; + + private List _foldingErrors = new List(); + + #endregion Fields + + #region Methods + + public List GetFoldingErrors() + { + return _foldingErrors; + } + + /// + /// Adds folds to the text editor around each start-end element pair. + /// + /// + /// If the xml is not well formed then no folds are created. + /// Note that the xml text reader lines and positions start + /// from 1 and the SharpDevelop text editor line information starts from 0. + /// + public List GenerateFoldMarkers(IDocument document, string fileName, object parseInformation) + { + _foldingErrors = new List(); + //showAttributesWhenFolded = XmlEditorAddInOptions.ShowAttributesWhenFolded; + + var foldMarkers = new List(); + var stack = new Stack(); + + try + { + string xml = document.TextContent; + var reader = new XmlTextReader(new StringReader(xml)); + while (reader.Read()) + { + switch (reader.NodeType) + { + case XmlNodeType.Element: + if (!reader.IsEmptyElement) + { + XmlFoldStart newFoldStart = CreateElementFoldStart(reader); + stack.Push(newFoldStart); + } + break; + + case XmlNodeType.EndElement: + var foldStart = (XmlFoldStart)stack.Pop(); + CreateElementFold(document, foldMarkers, reader, foldStart); + break; + + case XmlNodeType.Comment: + CreateCommentFold(document, foldMarkers, reader); + break; + } + } + } + catch (Exception ex) + { + _foldingErrors.Add(ex.Message); + + // If the xml is not well formed keep the foldings that already exist in the document. + return new List(document.FoldingManager.FoldMarker); + } + + return foldMarkers; + } + + /// + /// Xml encode the attribute string since the string returned from + /// the XmlTextReader is the plain unencoded string and .NET + /// does not provide us with an xml encode method. + /// + static string XmlEncodeAttributeValue(string attributeValue, char quoteChar) + { + var encodedValue = new StringBuilder(attributeValue); + + encodedValue.Replace("&", "&"); + encodedValue.Replace("<", "<"); + encodedValue.Replace(">", ">"); + + if (quoteChar == '"') + { + encodedValue.Replace("\"", """); + } + else + { + encodedValue.Replace("'", "'"); + } + + return encodedValue.ToString(); + } + + /// + /// Creates a comment fold if the comment spans more than one line. + /// + /// The text displayed when the comment is folded is the first + /// line of the comment. + void CreateCommentFold(IDocument document, List foldMarkers, XmlTextReader reader) + { + if (reader.Value != null) + { + string comment = reader.Value.Replace("\r\n", "\n"); + string[] lines = comment.Split('\n'); + if (lines.Length > 1) + { + + // Take off 5 chars to get the actual comment start (takes + // into account the ' + int endCol = lines[lines.Length - 1].Length + startCol + 3; + int endLine = startLine + lines.Length - 1; + string foldText = string.Concat(""); + var foldMarker = new FoldMarker(document, startLine, startCol, endLine, endCol, FoldType.TypeBody, foldText); + foldMarkers.Add(foldMarker); + } + } + } + + /// + /// Create an element fold if the start and end tag are on + /// different lines. + /// + void CreateElementFold(IDocument document, List foldMarkers, XmlTextReader reader, XmlFoldStart foldStart) + { + int endLine = reader.LineNumber - 1; + if (endLine > foldStart.Line) + { + int endCol = reader.LinePosition + foldStart.Name.Length; + var foldMarker = new FoldMarker(document, foldStart.Line, foldStart.Column, endLine, endCol, FoldType.TypeBody, foldStart.FoldText); + foldMarkers.Add(foldMarker); + } + } + + /// + /// Creates an XmlFoldStart for the start tag of an element. + /// + XmlFoldStart CreateElementFoldStart(XmlTextReader reader) + { + // Take off 2 from the line position returned + // from the xml since it points to the start + // of the element name and not the beginning + // tag. + var newFoldStart = new XmlFoldStart(reader.Prefix, reader.LocalName, reader.LineNumber - 1, reader.LinePosition - 2); + + if (ShowAttributesWhenFolded && reader.HasAttributes) + { + newFoldStart.FoldText = string.Concat("<", newFoldStart.Name, " ", GetAttributeFoldText(reader), ">"); + } + else + { + newFoldStart.FoldText = string.Concat("<", newFoldStart.Name, ">"); + } + + return newFoldStart; + } + + /// + /// Gets the element's attributes as a string on one line that will + /// be displayed when the element is folded. + /// + /// + /// Currently this puts all attributes from an element on the same + /// line of the start tag. It does not cater for elements where attributes + /// are not on the same line as the start tag. + /// + string GetAttributeFoldText(XmlTextReader reader) + { + var text = new StringBuilder(); + + for (int i = 0; i < reader.AttributeCount; ++i) + { + reader.MoveToAttribute(i); + + text.Append(reader.Name); + text.Append("="); + text.Append(reader.QuoteChar.ToString(CultureInfo.InvariantCulture)); + text.Append(XmlEncodeAttributeValue(reader.Value, reader.QuoteChar)); + text.Append(reader.QuoteChar.ToString(CultureInfo.InvariantCulture)); + + // Append a space if this is not the + // last attribute. + if (i < reader.AttributeCount - 1) + { + text.Append(" "); + } + } + + return text.ToString(); + } + + #endregion Methods + } +} \ No newline at end of file diff --git a/ICSharpCode.TextEditor/Project/Src/Document/FormattingStrategy/DefaultFormattingStrategy.cs b/ICSharpCode.TextEditor/Project/Src/Document/FormattingStrategy/DefaultFormattingStrategy.cs new file mode 100644 index 0000000..4f1312e --- /dev/null +++ b/ICSharpCode.TextEditor/Project/Src/Document/FormattingStrategy/DefaultFormattingStrategy.cs @@ -0,0 +1,218 @@ +// +// +// +// +// $Revision$ +// + +using System; +using System.Text; + +namespace ICSharpCode.TextEditor.Document +{ + /// + /// This class handles the auto and smart indenting in the textbuffer while + /// you type. + /// + public class DefaultFormattingStrategy : IFormattingStrategy + { + /// + /// Creates a new instance off + /// + public DefaultFormattingStrategy() + { + } + + /// + /// returns the whitespaces which are before a non white space character in the line line + /// as a string. + /// + protected string GetIndentation(TextArea textArea, int lineNumber) + { + if (lineNumber < 0 || lineNumber > textArea.Document.TotalNumberOfLines) { + throw new ArgumentOutOfRangeException("lineNumber"); + } + + string lineText = TextUtilities.GetLineAsString(textArea.Document, lineNumber); + StringBuilder whitespaces = new StringBuilder(); + + foreach (char ch in lineText) { + if (char.IsWhiteSpace(ch)) { + whitespaces.Append(ch); + } else { + break; + } + } + return whitespaces.ToString(); + } + + /// + /// Could be overwritten to define more complex indenting. + /// + protected virtual int AutoIndentLine(TextArea textArea, int lineNumber) + { + string indentation = lineNumber != 0 ? GetIndentation(textArea, lineNumber - 1) : ""; + if(indentation.Length > 0) { + string newLineText = indentation + TextUtilities.GetLineAsString(textArea.Document, lineNumber).Trim(); + LineSegment oldLine = textArea.Document.GetLineSegment(lineNumber); + SmartReplaceLine(textArea.Document, oldLine, newLineText); + } + return indentation.Length; + } + + static readonly char[] whitespaceChars = {' ', '\t'}; + + /// + /// Replaces the text in a line. + /// If only whitespace at the beginning and end of the line was changed, this method + /// only adjusts the whitespace and doesn't replace the other text. + /// + public static void SmartReplaceLine(IDocument document, LineSegment line, string newLineText) + { + if (document == null) + throw new ArgumentNullException("document"); + if (line == null) + throw new ArgumentNullException("line"); + if (newLineText == null) + throw new ArgumentNullException("newLineText"); + string newLineTextTrim = newLineText.Trim(whitespaceChars); + string oldLineText = document.GetText(line); + if (oldLineText == newLineText) + return; + int pos = oldLineText.IndexOf(newLineTextTrim); + if (newLineTextTrim.Length > 0 && pos >= 0) { + document.UndoStack.StartUndoGroup(); + try { + // find whitespace at beginning + int startWhitespaceLength = 0; + while (startWhitespaceLength < newLineText.Length) { + char c = newLineText[startWhitespaceLength]; + if (c != ' ' && c != '\t') + break; + startWhitespaceLength++; + } + // find whitespace at end + int endWhitespaceLength = newLineText.Length - newLineTextTrim.Length - startWhitespaceLength; + + // replace whitespace sections + int lineOffset = line.Offset; + document.Replace(lineOffset + pos + newLineTextTrim.Length, line.Length - pos - newLineTextTrim.Length, newLineText.Substring(newLineText.Length - endWhitespaceLength)); + document.Replace(lineOffset, pos, newLineText.Substring(0, startWhitespaceLength)); + } finally { + document.UndoStack.EndUndoGroup(); + } + } else { + document.Replace(line.Offset, line.Length, newLineText); + } + } + + /// + /// Could be overwritten to define more complex indenting. + /// + protected virtual int SmartIndentLine(TextArea textArea, int line) + { + return AutoIndentLine(textArea, line); // smart = autoindent in normal texts + } + + /// + /// This function formats a specific line after ch is pressed. + /// + /// + /// the caret delta position the caret will be moved this number + /// of bytes (e.g. the number of bytes inserted before the caret, or + /// removed, if this number is negative) + /// + public virtual void FormatLine(TextArea textArea, int line, int cursorOffset, char ch) + { + if (ch == '\n') { + textArea.Caret.Column = IndentLine(textArea, line); + } + } + + /// + /// This function sets the indentation level in a specific line + /// + /// + /// the number of inserted characters. + /// + public int IndentLine(TextArea textArea, int line) + { + textArea.Document.UndoStack.StartUndoGroup(); + int result; + switch (textArea.Document.TextEditorProperties.IndentStyle) { + case IndentStyle.None: + result = 0; + break; + case IndentStyle.Auto: + result = AutoIndentLine(textArea, line); + break; + case IndentStyle.Smart: + result = SmartIndentLine(textArea, line); + break; + default: + throw new NotSupportedException("Unsupported value for IndentStyle: " + textArea.Document.TextEditorProperties.IndentStyle); + } + textArea.Document.UndoStack.EndUndoGroup(); + return result; + } + + /// + /// This function sets the indentlevel in a range of lines. + /// + public virtual void IndentLines(TextArea textArea, int begin, int end) + { + textArea.Document.UndoStack.StartUndoGroup(); + for (int i = begin; i <= end; ++i) { + IndentLine(textArea, i); + } + textArea.Document.UndoStack.EndUndoGroup(); + } + + public virtual int SearchBracketBackward(IDocument document, int offset, char openBracket, char closingBracket) + { + int brackets = -1; + // first try "quick find" - find the matching bracket if there is no string/comment in the way + for (int i = offset; i >= 0; --i) { + char ch = document.GetCharAt(i); + if (ch == openBracket) { + ++brackets; + if (brackets == 0) return i; + } else if (ch == closingBracket) { + --brackets; + } else if (ch == '"') { + break; + } else if (ch == '\'') { + break; + } else if (ch == '/' && i > 0) { + if (document.GetCharAt(i - 1) == '/') break; + if (document.GetCharAt(i - 1) == '*') break; + } + } + return -1; + } + + public virtual int SearchBracketForward(IDocument document, int offset, char openBracket, char closingBracket) + { + int brackets = 1; + // try "quick find" - find the matching bracket if there is no string/comment in the way + for (int i = offset; i < document.TextLength; ++i) { + char ch = document.GetCharAt(i); + if (ch == openBracket) { + ++brackets; + } else if (ch == closingBracket) { + --brackets; + if (brackets == 0) return i; + } else if (ch == '"') { + break; + } else if (ch == '\'') { + break; + } else if (ch == '/' && i > 0) { + if (document.GetCharAt(i - 1) == '/') break; + } else if (ch == '*' && i > 0) { + if (document.GetCharAt(i - 1) == '/') break; + } + } + return -1; + } + } +} diff --git a/ICSharpCode.TextEditor/Project/Src/Document/FormattingStrategy/IFormattingStrategy.cs b/ICSharpCode.TextEditor/Project/Src/Document/FormattingStrategy/IFormattingStrategy.cs new file mode 100644 index 0000000..cd82576 --- /dev/null +++ b/ICSharpCode.TextEditor/Project/Src/Document/FormattingStrategy/IFormattingStrategy.cs @@ -0,0 +1,59 @@ +// +// +// +// +// $Revision$ +// + +using System; + +namespace ICSharpCode.TextEditor.Document +{ + /// + /// This interface handles the auto and smart indenting and formating + /// in the document while you type. Language bindings could overwrite this + /// interface and define their own indentation/formating. + /// + public interface IFormattingStrategy + { + /// + /// This function formats a specific line after ch is pressed. + /// + void FormatLine(TextArea textArea, int line, int caretOffset, char charTyped); + + /// + /// This function sets the indentation level in a specific line + /// + /// + /// The target caret position (length of new indentation). + /// + int IndentLine(TextArea textArea, int line); + + /// + /// This function sets the indentlevel in a range of lines. + /// + void IndentLines(TextArea textArea, int begin, int end); + + /// + /// Finds the offset of the opening bracket in the block defined by offset skipping + /// brackets in strings and comments. + /// + /// The document to search in. + /// The offset of an position in the block or the offset of the closing bracket. + /// The character for the opening bracket. + /// The character for the closing bracket. + /// Returns the offset of the opening bracket or -1 if no matching bracket was found. + int SearchBracketBackward(IDocument document, int offset, char openBracket, char closingBracket); + + /// + /// Finds the offset of the closing bracket in the block defined by offset skipping + /// brackets in strings and comments. + /// + /// The document to search in. + /// The offset of an position in the block or the offset of the opening bracket. + /// The character for the opening bracket. + /// The character for the closing bracket. + /// Returns the offset of the closing bracket or -1 if no matching bracket was found. + int SearchBracketForward(IDocument document, int offset, char openBracket, char closingBracket); + } +} diff --git a/ICSharpCode.TextEditor/Project/Src/Document/HighlightingStrategy/DefaultHighlightingStrategy.cs b/ICSharpCode.TextEditor/Project/Src/Document/HighlightingStrategy/DefaultHighlightingStrategy.cs new file mode 100644 index 0000000..ba903fc --- /dev/null +++ b/ICSharpCode.TextEditor/Project/Src/Document/HighlightingStrategy/DefaultHighlightingStrategy.cs @@ -0,0 +1,917 @@ +// +// +// +// +// $Revision$ +// + +using System; +using System.Collections.Generic; +using System.Text; +using System.Drawing; +using System.Windows.Forms; + +namespace ICSharpCode.TextEditor.Document +{ + public class DefaultHighlightingStrategy : IHighlightingStrategyUsingRuleSets + { + string name; + List rules = new List(); + + Dictionary environmentColors = new Dictionary(); + Dictionary properties = new Dictionary(); + string[] extensions; + + HighlightColor digitColor; + HighlightRuleSet defaultRuleSet = null; + + public HighlightColor DigitColor { + get { + return digitColor; + } + set { + digitColor = value; + } + } + + public IEnumerable> EnvironmentColors { + get { + return environmentColors; + } + } + + protected void ImportSettingsFrom(DefaultHighlightingStrategy source) + { + if (source == null) + throw new ArgumentNullException("source"); + properties = source.properties; + extensions = source.extensions; + digitColor = source.digitColor; + defaultRuleSet = source.defaultRuleSet; + name = source.name; + rules = source.rules; + environmentColors = source.environmentColors; + defaultTextColor = source.defaultTextColor; + } + + public DefaultHighlightingStrategy() : this("Default") + { + } + + public DefaultHighlightingStrategy(string name) + { + this.name = name; + + digitColor = new HighlightColor(SystemColors.WindowText, false, false); + defaultTextColor = new HighlightColor(SystemColors.WindowText, false, false); + + // set small 'default color environment' + environmentColors["Default"] = new HighlightBackground("WindowText", "Window", false, false); + environmentColors["Selection"] = new HighlightColor("HighlightText", "Highlight", false, false); + environmentColors["VRuler"] = new HighlightColor("ControlLight", "Window", false, false); + environmentColors["InvalidLines"] = new HighlightColor(Color.Red, false, false); + environmentColors["CaretMarker"] = new HighlightColor(Color.Yellow, false, false); + environmentColors["CaretLine"] = new HighlightBackground("ControlLight", "Window", false, false); + environmentColors["LineNumbers"] = new HighlightBackground("ControlDark", "Window", false, false); + + environmentColors["FoldLine"] = new HighlightColor("ControlDark", false, false); + environmentColors["FoldMarker"] = new HighlightColor("WindowText", "Window", false, false); + environmentColors["SelectedFoldLine"] = new HighlightColor("WindowText", false, false); + environmentColors["EOLMarkers"] = new HighlightColor("ControlLight", "Window", false, false); + environmentColors["SpaceMarkers"] = new HighlightColor("ControlLight", "Window", false, false); + environmentColors["TabMarkers"] = new HighlightColor("ControlLight", "Window", false, false); + + } + + public Dictionary Properties { + get { + return properties; + } + } + + public string Name + { + get { + return name; + } + } + + public string[] Extensions + { + set { + extensions = value; + } + get { + return extensions; + } + } + + public List Rules { + get { + return rules; + } + } + + public HighlightRuleSet FindHighlightRuleSet(string name) + { + foreach(HighlightRuleSet ruleSet in rules) { + if (ruleSet.Name == name) { + return ruleSet; + } + } + return null; + } + + public void AddRuleSet(HighlightRuleSet aRuleSet) + { + HighlightRuleSet existing = FindHighlightRuleSet(aRuleSet.Name); + if (existing != null) { + existing.MergeFrom(aRuleSet); + } else { + rules.Add(aRuleSet); + } + } + + public void ResolveReferences() + { + // Resolve references from Span definitions to RuleSets + ResolveRuleSetReferences(); + // Resolve references from RuleSet defintitions to Highlighters defined in an external mode file + ResolveExternalReferences(); + } + + void ResolveRuleSetReferences() + { + foreach (HighlightRuleSet ruleSet in Rules) { + if (ruleSet.Name == null) { + defaultRuleSet = ruleSet; + } + + foreach (Span aSpan in ruleSet.Spans) { + if (aSpan.Rule != null) { + bool found = false; + foreach (HighlightRuleSet refSet in Rules) { + if (refSet.Name == aSpan.Rule) { + found = true; + aSpan.RuleSet = refSet; + break; + } + } + if (!found) { + aSpan.RuleSet = null; + throw new HighlightingDefinitionInvalidException("The RuleSet " + aSpan.Rule + " could not be found in mode definition " + this.Name); + } + } else { + aSpan.RuleSet = null; + } + } + } + + if (defaultRuleSet == null) { + throw new HighlightingDefinitionInvalidException("No default RuleSet is defined for mode definition " + this.Name); + } + } + + void ResolveExternalReferences() + { + foreach (HighlightRuleSet ruleSet in Rules) { + ruleSet.Highlighter = this; + if (ruleSet.Reference != null) { + IHighlightingStrategy highlighter = HighlightingManager.Manager.FindHighlighter (ruleSet.Reference); + + if (highlighter == null) + throw new HighlightingDefinitionInvalidException("The mode defintion " + ruleSet.Reference + " which is refered from the " + this.Name + " mode definition could not be found"); + if (highlighter is IHighlightingStrategyUsingRuleSets) + ruleSet.Highlighter = (IHighlightingStrategyUsingRuleSets)highlighter; + else + throw new HighlightingDefinitionInvalidException("The mode defintion " + ruleSet.Reference + " which is refered from the " + this.Name + " mode definition does not implement IHighlightingStrategyUsingRuleSets"); + } + } + } + +// internal void SetDefaultColor(HighlightBackground color) +// { +// return (HighlightColor)environmentColors[name]; +// defaultColor = color; +// } + + HighlightColor defaultTextColor; + + public HighlightColor DefaultTextColor { + get { + return defaultTextColor; + } + } + + public void SetColorFor(string name, HighlightColor color) + { + if (name == "Default") + defaultTextColor = new HighlightColor(color.Color, color.Bold, color.Italic); + environmentColors[name] = color; + } + + public HighlightColor GetColorFor(string name) + { + HighlightColor color; + if (environmentColors.TryGetValue(name, out color)) + return color; + else + return defaultTextColor; + } + + public HighlightColor GetColor(IDocument document, LineSegment currentSegment, int currentOffset, int currentLength) + { + return GetColor(defaultRuleSet, document, currentSegment, currentOffset, currentLength); + } + + protected virtual HighlightColor GetColor(HighlightRuleSet ruleSet, IDocument document, LineSegment currentSegment, int currentOffset, int currentLength) + { + if (ruleSet != null) { + if (ruleSet.Reference != null) { + return ruleSet.Highlighter.GetColor(document, currentSegment, currentOffset, currentLength); + } else { + return (HighlightColor)ruleSet.KeyWords[document, currentSegment, currentOffset, currentLength]; + } + } + return null; + } + + public HighlightRuleSet GetRuleSet(Span aSpan) + { + if (aSpan == null) { + return this.defaultRuleSet; + } else { + if (aSpan.RuleSet != null) + { + if (aSpan.RuleSet.Reference != null) { + return aSpan.RuleSet.Highlighter.GetRuleSet(null); + } else { + return aSpan.RuleSet; + } + } else { + return null; + } + } + } + + // Line state variable + protected LineSegment currentLine; + protected int currentLineNumber; + + // Span stack state variable + protected SpanStack currentSpanStack; + + public virtual void MarkTokens(IDocument document) + { + if (Rules.Count == 0) { + return; + } + + int lineNumber = 0; + + while (lineNumber < document.TotalNumberOfLines) { + LineSegment previousLine = (lineNumber > 0 ? document.GetLineSegment(lineNumber - 1) : null); + if (lineNumber >= document.LineSegmentCollection.Count) { // may be, if the last line ends with a delimiter + break; // then the last line is not in the collection :) + } + + currentSpanStack = ((previousLine != null && previousLine.HighlightSpanStack != null) ? previousLine.HighlightSpanStack.Clone() : null); + + if (currentSpanStack != null) { + while (!currentSpanStack.IsEmpty && currentSpanStack.Peek().StopEOL) + { + currentSpanStack.Pop(); + } + if (currentSpanStack.IsEmpty) currentSpanStack = null; + } + + currentLine = (LineSegment)document.LineSegmentCollection[lineNumber]; + + if (currentLine.Length == -1) { // happens when buffer is empty ! + return; + } + + currentLineNumber = lineNumber; + List words = ParseLine(document); + // Alex: clear old words + if (currentLine.Words != null) { + currentLine.Words.Clear(); + } + currentLine.Words = words; + currentLine.HighlightSpanStack = (currentSpanStack==null || currentSpanStack.IsEmpty) ? null : currentSpanStack; + + ++lineNumber; + } + document.RequestUpdate(new TextAreaUpdate(TextAreaUpdateType.WholeTextArea)); + document.CommitUpdate(); + currentLine = null; + } + + bool MarkTokensInLine(IDocument document, int lineNumber, ref bool spanChanged) + { + currentLineNumber = lineNumber; + bool processNextLine = false; + LineSegment previousLine = (lineNumber > 0 ? document.GetLineSegment(lineNumber - 1) : null); + + currentSpanStack = ((previousLine != null && previousLine.HighlightSpanStack != null) ? previousLine.HighlightSpanStack.Clone() : null); + if (currentSpanStack != null) { + while (!currentSpanStack.IsEmpty && currentSpanStack.Peek().StopEOL) { + currentSpanStack.Pop(); + } + if (currentSpanStack.IsEmpty) { + currentSpanStack = null; + } + } + + currentLine = (LineSegment)document.LineSegmentCollection[lineNumber]; + + if (currentLine.Length == -1) { // happens when buffer is empty ! + return false; + } + + List words = ParseLine(document); + + if (currentSpanStack != null && currentSpanStack.IsEmpty) { + currentSpanStack = null; + } + + // Check if the span state has changed, if so we must re-render the next line + // This check may seem utterly complicated but I didn't want to introduce any function calls + // or allocations here for perf reasons. + if(currentLine.HighlightSpanStack != currentSpanStack) { + if (currentLine.HighlightSpanStack == null) { + processNextLine = false; + foreach (Span sp in currentSpanStack) { + if (!sp.StopEOL) { + spanChanged = true; + processNextLine = true; + break; + } + } + } else if (currentSpanStack == null) { + processNextLine = false; + foreach (Span sp in currentLine.HighlightSpanStack) { + if (!sp.StopEOL) { + spanChanged = true; + processNextLine = true; + break; + } + } + } else { + SpanStack.Enumerator e1 = currentSpanStack.GetEnumerator(); + SpanStack.Enumerator e2 = currentLine.HighlightSpanStack.GetEnumerator(); + bool done = false; + while (!done) { + bool blockSpanIn1 = false; + while (e1.MoveNext()) { + if (!((Span)e1.Current).StopEOL) { + blockSpanIn1 = true; + break; + } + } + bool blockSpanIn2 = false; + while (e2.MoveNext()) { + if (!((Span)e2.Current).StopEOL) { + blockSpanIn2 = true; + break; + } + } + if (blockSpanIn1 || blockSpanIn2) { + if (blockSpanIn1 && blockSpanIn2) { + if (e1.Current != e2.Current) { + done = true; + processNextLine = true; + spanChanged = true; + } + } else { + spanChanged = true; + done = true; + processNextLine = true; + } + } else { + done = true; + processNextLine = false; + } + } + } + } else { + processNextLine = false; + } + + //// Alex: remove old words + if (currentLine.Words!=null) currentLine.Words.Clear(); + currentLine.Words = words; + currentLine.HighlightSpanStack = (currentSpanStack != null && !currentSpanStack.IsEmpty) ? currentSpanStack : null; + + return processNextLine; + } + + public virtual void MarkTokens(IDocument document, List inputLines) + { + if (Rules.Count == 0) { + return; + } + + Dictionary processedLines = new Dictionary(); + + bool spanChanged = false; + int documentLineSegmentCount = document.LineSegmentCollection.Count; + + foreach (LineSegment lineToProcess in inputLines) { + if (!processedLines.ContainsKey(lineToProcess)) { + int lineNumber = lineToProcess.LineNumber; + bool processNextLine = true; + + if (lineNumber != -1) { + while (processNextLine && lineNumber < documentLineSegmentCount) { + processNextLine = MarkTokensInLine(document, lineNumber, ref spanChanged); + processedLines[currentLine] = true; + ++lineNumber; + } + } + } + } + + if (spanChanged || inputLines.Count > 20) { + // if the span was changed (more than inputLines lines had to be reevaluated) + // or if there are many lines in inputLines, it's faster to update the whole + // text area instead of many small segments + document.RequestUpdate(new TextAreaUpdate(TextAreaUpdateType.WholeTextArea)); + } else { +// document.Caret.ValidateCaretPos(); +// document.RequestUpdate(new TextAreaUpdate(TextAreaUpdateType.SingleLine, document.GetLineNumberForOffset(document.Caret.Offset))); +// + foreach (LineSegment lineToProcess in inputLines) { + document.RequestUpdate(new TextAreaUpdate(TextAreaUpdateType.SingleLine, lineToProcess.LineNumber)); + } + + } + document.CommitUpdate(); + currentLine = null; + } + + // Span state variables + protected bool inSpan; + protected Span activeSpan; + protected HighlightRuleSet activeRuleSet; + + // Line scanning state variables + protected int currentOffset; + protected int currentLength; + protected bool isDelimiter = false; + + void UpdateSpanStateVariables() + { + inSpan = (currentSpanStack != null && !currentSpanStack.IsEmpty); + activeSpan = inSpan ? currentSpanStack.Peek() : null; + activeRuleSet = GetRuleSet(activeSpan); + } + + List ParseLine(IDocument document) + { + List words = new List(); + HighlightColor markNext = null; + + currentOffset = 0; + currentLength = 0; + UpdateSpanStateVariables(); + + int currentLineLength = currentLine.Length; + int currentLineOffset = currentLine.Offset; + + for (int i = 0; i < currentLineLength; ++i) { + char ch = document.GetCharAt(currentLineOffset + i); + switch (ch) { + case '\n': + case '\r': + PushCurWord(document, ref markNext, words); + ++currentOffset; + break; + case ' ': + PushCurWord(document, ref markNext, words); + if (activeSpan != null && activeSpan.Color.HasBackground) { + words.Add(new TextWord.SpaceTextWord(activeSpan.Color)); + } else { + words.Add(TextWord.Space); + } + ++currentOffset; + break; + case '\t': + PushCurWord(document, ref markNext, words); + if (activeSpan != null && activeSpan.Color.HasBackground) { + words.Add(new TextWord.TabTextWord(activeSpan.Color)); + } else { + words.Add(TextWord.Tab); + } + ++currentOffset; + break; + default: + { + // handle escape characters + char escapeCharacter = '\0'; + if (activeSpan != null && activeSpan.EscapeCharacter != '\0') { + escapeCharacter = activeSpan.EscapeCharacter; + } else if (activeRuleSet != null) { + escapeCharacter = activeRuleSet.EscapeCharacter; + } + if (escapeCharacter != '\0' && escapeCharacter == ch) { + // we found the escape character + if (activeSpan != null && activeSpan.End != null && activeSpan.End.Length == 1 + && escapeCharacter == activeSpan.End[0]) + { + // the escape character is a end-doubling escape character + // it may count as escape only when the next character is the escape, too + if (i + 1 < currentLineLength) { + if (document.GetCharAt(currentLineOffset + i + 1) == escapeCharacter) { + currentLength += 2; + PushCurWord(document, ref markNext, words); + ++i; + continue; + } + } + } else { + // this is a normal \-style escape + ++currentLength; + if (i + 1 < currentLineLength) { + ++currentLength; + } + PushCurWord(document, ref markNext, words); + ++i; + continue; + } + } + + // highlight digits + if (!inSpan && (char.IsDigit(ch) || (ch == '.' && i + 1 < currentLineLength && char.IsDigit(document.GetCharAt(currentLineOffset + i + 1)))) && currentLength == 0) { + bool ishex = false; + bool isfloatingpoint = false; + + if (ch == '0' && i + 1 < currentLineLength && char.ToUpper(document.GetCharAt(currentLineOffset + i + 1)) == 'X') { // hex digits + const string hex = "0123456789ABCDEF"; + ++currentLength; + ++i; // skip 'x' + ++currentLength; + ishex = true; + while (i + 1 < currentLineLength && hex.IndexOf(char.ToUpper(document.GetCharAt(currentLineOffset + i + 1))) != -1) { + ++i; + ++currentLength; + } + } else { + ++currentLength; + while (i + 1 < currentLineLength && char.IsDigit(document.GetCharAt(currentLineOffset + i + 1))) { + ++i; + ++currentLength; + } + } + if (!ishex && i + 1 < currentLineLength && document.GetCharAt(currentLineOffset + i + 1) == '.') { + isfloatingpoint = true; + ++i; + ++currentLength; + while (i + 1 < currentLineLength && char.IsDigit(document.GetCharAt(currentLineOffset + i + 1))) { + ++i; + ++currentLength; + } + } + + if (i + 1 < currentLineLength && char.ToUpper(document.GetCharAt(currentLineOffset + i + 1)) == 'E') { + isfloatingpoint = true; + ++i; + ++currentLength; + if (i + 1 < currentLineLength && (document.GetCharAt(currentLineOffset + i + 1) == '+' || document.GetCharAt(currentLine.Offset + i + 1) == '-')) { + ++i; + ++currentLength; + } + while (i + 1 < currentLine.Length && char.IsDigit(document.GetCharAt(currentLineOffset + i + 1))) { + ++i; + ++currentLength; + } + } + + if (i + 1 < currentLine.Length) { + char nextch = char.ToUpper(document.GetCharAt(currentLineOffset + i + 1)); + if (nextch == 'F' || nextch == 'M' || nextch == 'D') { + isfloatingpoint = true; + ++i; + ++currentLength; + } + } + + if (!isfloatingpoint) { + bool isunsigned = false; + if (i + 1 < currentLineLength && char.ToUpper(document.GetCharAt(currentLineOffset + i + 1)) == 'U') { + ++i; + ++currentLength; + isunsigned = true; + } + if (i + 1 < currentLineLength && char.ToUpper(document.GetCharAt(currentLineOffset + i + 1)) == 'L') { + ++i; + ++currentLength; + if (!isunsigned && i + 1 < currentLineLength && char.ToUpper(document.GetCharAt(currentLineOffset + i + 1)) == 'U') { + ++i; + ++currentLength; + } + } + } + + words.Add(new TextWord(document, currentLine, currentOffset, currentLength, DigitColor, false)); + currentOffset += currentLength; + currentLength = 0; + continue; + } + + // Check for SPAN ENDs + if (inSpan) { + if (activeSpan.End != null && activeSpan.End.Length > 0) { + if (MatchExpr(currentLine, activeSpan.End, i, document, activeSpan.IgnoreCase)) { + PushCurWord(document, ref markNext, words); + string regex = GetRegString(currentLine, activeSpan.End, i, document); + currentLength += regex.Length; + words.Add(new TextWord(document, currentLine, currentOffset, currentLength, activeSpan.EndColor, false)); + currentOffset += currentLength; + currentLength = 0; + i += regex.Length - 1; + currentSpanStack.Pop(); + UpdateSpanStateVariables(); + continue; + } + } + } + + // check for SPAN BEGIN + if (activeRuleSet != null) { + foreach (Span span in activeRuleSet.Spans) { + if ((!span.IsBeginSingleWord || currentLength == 0) + && (!span.IsBeginStartOfLine.HasValue || span.IsBeginStartOfLine.Value == (currentLength == 0 && words.TrueForAll(delegate(TextWord textWord) { return textWord.Type != TextWordType.Word; }))) + && MatchExpr(currentLine, span.Begin, i, document, activeRuleSet.IgnoreCase)) { + PushCurWord(document, ref markNext, words); + string regex = GetRegString(currentLine, span.Begin, i, document); + + if (!OverrideSpan(regex, document, words, span, ref i)) { + currentLength += regex.Length; + words.Add(new TextWord(document, currentLine, currentOffset, currentLength, span.BeginColor, false)); + currentOffset += currentLength; + currentLength = 0; + + i += regex.Length - 1; + if (currentSpanStack == null) { + currentSpanStack = new SpanStack(); + } + currentSpanStack.Push(span); + span.IgnoreCase = activeRuleSet.IgnoreCase; + + UpdateSpanStateVariables(); + } + + goto skip; + } + } + } + + // check if the char is a delimiter + if (activeRuleSet != null && (int)ch < 256 && activeRuleSet.Delimiters[(int)ch]) { + PushCurWord(document, ref markNext, words); + isDelimiter = true; + if (currentOffset + currentLength +1 < currentLine.Length) { + ++currentLength; + PushCurWord(document, ref markNext, words); + goto skip; + } + } + + ++currentLength; + skip: continue; + } + } + } + + PushCurWord(document, ref markNext, words); + + OnParsedLine(document, currentLine, words); + + return words; + } + + protected virtual void OnParsedLine(IDocument document, LineSegment currentLine, List words) + { + } + + protected virtual bool OverrideSpan(string spanBegin, IDocument document, List words, Span span, ref int lineOffset) + { + return false; + } + + /// + /// pushes the curWord string on the word list, with the + /// correct color. + /// + void PushCurWord(IDocument document, ref HighlightColor markNext, List words) + { + // Svante Lidman : Need to look through the next prev logic. + if (currentLength > 0) { + if (words.Count > 0 && activeRuleSet != null) { + TextWord prevWord = null; + int pInd = words.Count - 1; + while (pInd >= 0) { + if (!((TextWord)words[pInd]).IsWhiteSpace) { + prevWord = (TextWord)words[pInd]; + if (prevWord.HasDefaultColor) { + PrevMarker marker = (PrevMarker)activeRuleSet.PrevMarkers[document, currentLine, currentOffset, currentLength]; + if (marker != null) { + prevWord.SyntaxColor = marker.Color; +// document.Caret.ValidateCaretPos(); +// document.RequestUpdate(new TextAreaUpdate(TextAreaUpdateType.SingleLine, document.GetLineNumberForOffset(document.Caret.Offset))); + } + } + break; + } + pInd--; + } + } + + if (inSpan) { + HighlightColor c = null; + bool hasDefaultColor = true; + if (activeSpan.Rule == null) { + c = activeSpan.Color; + } else { + c = GetColor(activeRuleSet, document, currentLine, currentOffset, currentLength); + hasDefaultColor = false; + } + + if (c == null) { + c = activeSpan.Color; + if (c.Color == Color.Transparent) { + c = this.DefaultTextColor; + } + hasDefaultColor = true; + } + words.Add(new TextWord(document, currentLine, currentOffset, currentLength, markNext != null ? markNext : c, hasDefaultColor)); + } else { + HighlightColor c = markNext != null ? markNext : GetColor(activeRuleSet, document, currentLine, currentOffset, currentLength); + if (c == null) { + words.Add(new TextWord(document, currentLine, currentOffset, currentLength, this.DefaultTextColor, true) { + IsDelimiter = isDelimiter + }); + } else { + words.Add(new TextWord(document, currentLine, currentOffset, currentLength, c, false) + { + IsDelimiter = isDelimiter + }); + } + isDelimiter = false; + + } + + if (activeRuleSet != null) { + NextMarker nextMarker = (NextMarker)activeRuleSet.NextMarkers[document, currentLine, currentOffset, currentLength]; + if (nextMarker != null) { + if (nextMarker.MarkMarker && words.Count > 0) { + TextWord prevword = ((TextWord)words[words.Count - 1]); + prevword.SyntaxColor = nextMarker.Color; + } + markNext = nextMarker.Color; + } else { + markNext = null; + } + } + currentOffset += currentLength; + currentLength = 0; + } + } + + #region Matching + /// + /// get the string, which matches the regular expression expr, + /// in string s2 at index + /// + static string GetRegString(LineSegment lineSegment, char[] expr, int index, IDocument document) + { + int j = 0; + StringBuilder regexpr = new StringBuilder(); + + for (int i = 0; i < expr.Length; ++i, ++j) { + if (index + j >= lineSegment.Length) + break; + + switch (expr[i]) { + case '@': // "special" meaning + ++i; + if (i == expr.Length) + throw new HighlightingDefinitionInvalidException("Unexpected end of @ sequence, use @@ to look for a single @."); + switch (expr[i]) { + case '!': // don't match the following expression + StringBuilder whatmatch = new StringBuilder(); + ++i; + while (i < expr.Length && expr[i] != '@') { + whatmatch.Append(expr[i++]); + } + break; + case '@': // matches @ + regexpr.Append(document.GetCharAt(lineSegment.Offset + index + j)); + break; + } + break; + default: + if (expr[i] != document.GetCharAt(lineSegment.Offset + index + j)) { + return regexpr.ToString(); + } + regexpr.Append(document.GetCharAt(lineSegment.Offset + index + j)); + break; + } + } + return regexpr.ToString(); + } + + /// + /// returns true, if the get the string s2 at index matches the expression expr + /// + static bool MatchExpr(LineSegment lineSegment, char[] expr, int index, IDocument document, bool ignoreCase) + { + for (int i = 0, j = 0; i < expr.Length; ++i, ++j) { + switch (expr[i]) { + case '@': // "special" meaning + ++i; + if (i == expr.Length) + throw new HighlightingDefinitionInvalidException("Unexpected end of @ sequence, use @@ to look for a single @."); + switch (expr[i]) { + case 'C': // match whitespace or punctuation + if (index + j == lineSegment.Offset || index + j >= lineSegment.Offset + lineSegment.Length) { + // nothing (EOL or SOL) + } else { + char ch = document.GetCharAt(lineSegment.Offset + index + j); + if (!char.IsWhiteSpace(ch) && !char.IsPunctuation(ch)) { + return false; + } + } + break; + case '!': // don't match the following expression + { + StringBuilder whatmatch = new StringBuilder(); + ++i; + while (i < expr.Length && expr[i] != '@') { + whatmatch.Append(expr[i++]); + } + if (lineSegment.Offset + index + j + whatmatch.Length < document.TextLength) { + int k = 0; + for (; k < whatmatch.Length; ++k) { + char docChar = ignoreCase ? char.ToUpperInvariant(document.GetCharAt(lineSegment.Offset + index + j + k)) : document.GetCharAt(lineSegment.Offset + index + j + k); + char spanChar = ignoreCase ? char.ToUpperInvariant(whatmatch[k]) : whatmatch[k]; + if (docChar != spanChar) { + break; + } + } + if (k >= whatmatch.Length) { + return false; + } + } +// --j; + break; + } + case '-': // don't match the expression before + { + StringBuilder whatmatch = new StringBuilder(); + ++i; + while (i < expr.Length && expr[i] != '@') { + whatmatch.Append(expr[i++]); + } + if (index - whatmatch.Length >= 0) { + int k = 0; + for (; k < whatmatch.Length; ++k) { + char docChar = ignoreCase ? char.ToUpperInvariant(document.GetCharAt(lineSegment.Offset + index - whatmatch.Length + k)) : document.GetCharAt(lineSegment.Offset + index - whatmatch.Length + k); + char spanChar = ignoreCase ? char.ToUpperInvariant(whatmatch[k]) : whatmatch[k]; + if (docChar != spanChar) + break; + } + if (k >= whatmatch.Length) { + return false; + } + } +// --j; + break; + } + case '@': // matches @ + if (index + j >= lineSegment.Length || '@' != document.GetCharAt(lineSegment.Offset + index + j)) { + return false; + } + break; + } + break; + default: + { + if (index + j >= lineSegment.Length) { + return false; + } + char docChar = ignoreCase ? char.ToUpperInvariant(document.GetCharAt(lineSegment.Offset + index + j)) : document.GetCharAt(lineSegment.Offset + index + j); + char spanChar = ignoreCase ? char.ToUpperInvariant(expr[i]) : expr[i]; + if (docChar != spanChar) { + return false; + } + break; + } + } + } + return true; + } + #endregion + } +} diff --git a/ICSharpCode.TextEditor/Project/Src/Document/HighlightingStrategy/FontContainer.cs b/ICSharpCode.TextEditor/Project/Src/Document/HighlightingStrategy/FontContainer.cs new file mode 100644 index 0000000..a8662ee --- /dev/null +++ b/ICSharpCode.TextEditor/Project/Src/Document/HighlightingStrategy/FontContainer.cs @@ -0,0 +1,103 @@ +// +// +// +// +// $Revision$ +// + +using System; +using System.Drawing; + +namespace ICSharpCode.TextEditor.Document +{ + /// + /// This class is used to generate bold, italic and bold/italic fonts out + /// of a base font. + /// + public class FontContainer + { + Font defaultFont; + Font regularfont, boldfont, italicfont, bolditalicfont; + + /// + /// The scaled, regular version of the base font + /// + public Font RegularFont { + get { + return regularfont; + } + } + + /// + /// The scaled, bold version of the base font + /// + public Font BoldFont { + get { + return boldfont; + } + } + + /// + /// The scaled, italic version of the base font + /// + public Font ItalicFont { + get { + return italicfont; + } + } + + /// + /// The scaled, bold/italic version of the base font + /// + public Font BoldItalicFont { + get { + return bolditalicfont; + } + } + + static float twipsPerPixelY; + + public static float TwipsPerPixelY { + get { + if (twipsPerPixelY == 0) { + using (Bitmap bmp = new Bitmap(1,1)) { + using (Graphics g = Graphics.FromImage(bmp)) { + twipsPerPixelY = 1440 / g.DpiY; + } + } + } + return twipsPerPixelY; + } + } + + /// + /// The base font + /// + public Font DefaultFont { + get { + return defaultFont; + } + set { + // 1440 twips is one inch + float pixelSize = (float)Math.Round(value.SizeInPoints * 20 / TwipsPerPixelY); + + defaultFont = value; + regularfont = new Font(value.FontFamily, pixelSize * TwipsPerPixelY / 20f, FontStyle.Regular); + boldfont = new Font(regularfont, FontStyle.Bold); + italicfont = new Font(regularfont, FontStyle.Italic); + bolditalicfont = new Font(regularfont, FontStyle.Bold | FontStyle.Italic); + } + } + + public static Font ParseFont(string font) + { + string[] descr = font.Split(new char[]{',', '='}); + return new Font(descr[1], float.Parse(descr[3])); + } + + public FontContainer(Font defaultFont) + { + this.DefaultFont = defaultFont; + } + } +} diff --git a/ICSharpCode.TextEditor/Project/Src/Document/HighlightingStrategy/HighlightBackground.cs b/ICSharpCode.TextEditor/Project/Src/Document/HighlightingStrategy/HighlightBackground.cs new file mode 100644 index 0000000..af2d3cd --- /dev/null +++ b/ICSharpCode.TextEditor/Project/Src/Document/HighlightingStrategy/HighlightBackground.cs @@ -0,0 +1,51 @@ +// +// +// +// +// $Revision$ +// + +using System; +using System.Drawing; +using System.Xml; + +namespace ICSharpCode.TextEditor.Document +{ + /// + /// Extens the highlighting color with a background image. + /// + public class HighlightBackground : HighlightColor + { + Image backgroundImage; + + /// + /// The image used as background + /// + public Image BackgroundImage { + get { + return backgroundImage; + } + } + + /// + /// Creates a new instance of + /// + public HighlightBackground(XmlElement el) : base(el) + { + if (el.Attributes["image"] != null) { + backgroundImage = new Bitmap(el.Attributes["image"].InnerText); + } + } + + /// + /// Creates a new instance of + /// + public HighlightBackground(Color color, Color backgroundcolor, bool bold, bool italic) : base(color, backgroundcolor, bold, italic) + { + } + + public HighlightBackground(string systemColor, string systemBackgroundColor, bool bold, bool italic) : base(systemColor, systemBackgroundColor, bold, italic) + { + } + } +} diff --git a/ICSharpCode.TextEditor/Project/Src/Document/HighlightingStrategy/HighlightColor.cs b/ICSharpCode.TextEditor/Project/Src/Document/HighlightingStrategy/HighlightColor.cs new file mode 100644 index 0000000..6a906e7 --- /dev/null +++ b/ICSharpCode.TextEditor/Project/Src/Document/HighlightingStrategy/HighlightColor.cs @@ -0,0 +1,274 @@ +// +// +// +// +// $Revision$ +// + +using System; +using System.Diagnostics; +using System.Drawing; +using System.Globalization; +using System.Reflection; +using System.Xml; + +namespace ICSharpCode.TextEditor.Document +{ + /// + /// A color used for highlighting + /// + public class HighlightColor + { + Color color; + Color backgroundcolor = System.Drawing.Color.WhiteSmoke; + + bool bold = false; + bool italic = false; + bool hasForeground = false; + bool hasBackground = false; + + public bool HasForeground { + get { + return hasForeground; + } + } + + public bool HasBackground { + get { + return hasBackground; + } + } + + + /// + /// If true the font will be displayed bold style + /// + public bool Bold { + get { + return bold; + } + } + + /// + /// If true the font will be displayed italic style + /// + public bool Italic { + get { + return italic; + } + } + + /// + /// The background color used + /// + public Color BackgroundColor { + get { + return backgroundcolor; + } + } + + /// + /// The foreground color used + /// + public Color Color { + get { + return color; + } + } + + /// + /// The font used + /// + public Font GetFont(FontContainer fontContainer) + { + if (Bold) { + return Italic ? fontContainer.BoldItalicFont : fontContainer.BoldFont; + } + return Italic ? fontContainer.ItalicFont : fontContainer.RegularFont; + } + + Color ParseColorString(string colorName) + { + string[] cNames = colorName.Split('*'); + PropertyInfo myPropInfo = typeof(System.Drawing.SystemColors).GetProperty(cNames[0], BindingFlags.Public | + BindingFlags.Instance | + BindingFlags.Static); + Color c = (Color)myPropInfo.GetValue(null, null); + + if (cNames.Length == 2) { + // hack : can't figure out how to parse doubles with '.' (culture info might set the '.' to ',') + double factor = double.Parse(cNames[1]) / 100; + c = Color.FromArgb((int)((double)c.R * factor), (int)((double)c.G * factor), (int)((double)c.B * factor)); + } + + return c; + } + + /// + /// Creates a new instance of + /// + public HighlightColor(XmlElement el) + { + Debug.Assert(el != null, "ICSharpCode.TextEditor.Document.SyntaxColor(XmlElement el) : el == null"); + if (el.Attributes["bold"] != null) { + bold = bool.Parse(el.Attributes["bold"].InnerText); + } + + if (el.Attributes["italic"] != null) { + italic = bool.Parse(el.Attributes["italic"].InnerText); + } + + if (el.Attributes["color"] != null) { + string c = el.Attributes["color"].InnerText; + if (c[0] == '#') { + color = ParseColor(c); + } else if (c.StartsWith("SystemColors.")) { + color = ParseColorString(c.Substring("SystemColors.".Length)); + } else { + color = (Color)(Color.GetType()).InvokeMember(c, BindingFlags.GetProperty, null, Color, new object[0]); + } + hasForeground = true; + } else { + color = Color.Transparent; // to set it to the default value. + } + + if (el.Attributes["bgcolor"] != null) { + string c = el.Attributes["bgcolor"].InnerText; + if (c[0] == '#') { + backgroundcolor = ParseColor(c); + } else if (c.StartsWith("SystemColors.")) { + backgroundcolor = ParseColorString(c.Substring("SystemColors.".Length)); + } else { + backgroundcolor = (Color)(Color.GetType()).InvokeMember(c, BindingFlags.GetProperty, null, Color, new object[0]); + } + hasBackground = true; + } + } + + /// + /// Creates a new instance of + /// + public HighlightColor(XmlElement el, HighlightColor defaultColor) + { + Debug.Assert(el != null, "ICSharpCode.TextEditor.Document.SyntaxColor(XmlElement el) : el == null"); + if (el.Attributes["bold"] != null) { + bold = bool.Parse(el.Attributes["bold"].InnerText); + } else { + bold = defaultColor.Bold; + } + + if (el.Attributes["italic"] != null) { + italic = bool.Parse(el.Attributes["italic"].InnerText); + } else { + italic = defaultColor.Italic; + } + + if (el.Attributes["color"] != null) { + string c = el.Attributes["color"].InnerText; + if (c[0] == '#') { + color = ParseColor(c); + } else if (c.StartsWith("SystemColors.")) { + color = ParseColorString(c.Substring("SystemColors.".Length)); + } else { + color = (Color)(Color.GetType()).InvokeMember(c, BindingFlags.GetProperty, null, Color, new object[0]); + } + hasForeground = true; + } else { + color = defaultColor.color; + } + + if (el.Attributes["bgcolor"] != null) { + string c = el.Attributes["bgcolor"].InnerText; + if (c[0] == '#') { + backgroundcolor = ParseColor(c); + } else if (c.StartsWith("SystemColors.")) { + backgroundcolor = ParseColorString(c.Substring("SystemColors.".Length)); + } else { + backgroundcolor = (Color)(Color.GetType()).InvokeMember(c, BindingFlags.GetProperty, null, Color, new object[0]); + } + hasBackground = true; + } else { + backgroundcolor = defaultColor.BackgroundColor; + } + } + + /// + /// Creates a new instance of + /// + public HighlightColor(Color color, bool bold, bool italic) + { + hasForeground = true; + this.color = color; + this.bold = bold; + this.italic = italic; + } + + /// + /// Creates a new instance of + /// + public HighlightColor(Color color, Color backgroundcolor, bool bold, bool italic) + { + hasForeground = true; + hasBackground = true; + this.color = color; + this.backgroundcolor = backgroundcolor; + this.bold = bold; + this.italic = italic; + } + + + /// + /// Creates a new instance of + /// + public HighlightColor(string systemColor, string systemBackgroundColor, bool bold, bool italic) + { + hasForeground = true; + hasBackground = true; + + this.color = ParseColorString(systemColor); + this.backgroundcolor = ParseColorString(systemBackgroundColor); + + this.bold = bold; + this.italic = italic; + } + + /// + /// Creates a new instance of + /// + public HighlightColor(string systemColor, bool bold, bool italic) + { + hasForeground = true; + + this.color = ParseColorString(systemColor); + + this.bold = bold; + this.italic = italic; + } + + static Color ParseColor(string c) + { + int a = 255; + int offset = 0; + if (c.Length > 7) { + offset = 2; + a = int.Parse(c.Substring(1,2), NumberStyles.HexNumber); + } + + int r = int.Parse(c.Substring(1 + offset,2), NumberStyles.HexNumber); + int g = int.Parse(c.Substring(3 + offset,2), NumberStyles.HexNumber); + int b = int.Parse(c.Substring(5 + offset,2), NumberStyles.HexNumber); + return Color.FromArgb(a, r, g, b); + } + + /// + /// Converts a instance to string (for debug purposes) + /// + public override string ToString() + { + return "[HighlightColor: Bold = " + Bold + + ", Italic = " + Italic + + ", Color = " + Color + + ", BackgroundColor = " + BackgroundColor + "]"; + } + } +} diff --git a/ICSharpCode.TextEditor/Project/Src/Document/HighlightingStrategy/HighlightInfo.cs b/ICSharpCode.TextEditor/Project/Src/Document/HighlightingStrategy/HighlightInfo.cs new file mode 100644 index 0000000..9d50ca0 --- /dev/null +++ b/ICSharpCode.TextEditor/Project/Src/Document/HighlightingStrategy/HighlightInfo.cs @@ -0,0 +1,25 @@ +// +// +// +// +// $Revision$ +// + +using System; + +namespace ICSharpCode.TextEditor.Document +{ + public class HighlightInfo + { + public bool BlockSpanOn = false; + public bool Span = false; + public Span CurSpan = null; + + public HighlightInfo(Span curSpan, bool span, bool blockSpanOn) + { + this.CurSpan = curSpan; + this.Span = span; + this.BlockSpanOn = blockSpanOn; + } + } +} diff --git a/ICSharpCode.TextEditor/Project/Src/Document/HighlightingStrategy/HighlightRuleSet.cs b/ICSharpCode.TextEditor/Project/Src/Document/HighlightingStrategy/HighlightRuleSet.cs new file mode 100644 index 0000000..1ac1069 --- /dev/null +++ b/ICSharpCode.TextEditor/Project/Src/Document/HighlightingStrategy/HighlightRuleSet.cs @@ -0,0 +1,182 @@ +// +// +// +// +// $Revision$ +// + +using System; +using System.Collections; +using System.Xml; + +using ICSharpCode.TextEditor.Util; + +namespace ICSharpCode.TextEditor.Document +{ + public class HighlightRuleSet + { + LookupTable keyWords; + ArrayList spans = new ArrayList(); + LookupTable prevMarkers; + LookupTable nextMarkers; + char escapeCharacter; + + bool ignoreCase = false; + string name = null; + + bool[] delimiters = new bool[256]; + + string reference = null; + + public ArrayList Spans { + get { + return spans; + } + } + + internal IHighlightingStrategyUsingRuleSets Highlighter; + + public LookupTable KeyWords { + get { + return keyWords; + } + } + + public LookupTable PrevMarkers { + get { + return prevMarkers; + } + } + + public LookupTable NextMarkers { + get { + return nextMarkers; + } + } + + public bool[] Delimiters { + get { + return delimiters; + } + } + + public char EscapeCharacter { + get { + return escapeCharacter; + } + } + + public bool IgnoreCase { + get { + return ignoreCase; + } + } + + public string Name { + get { + return name; + } + set { + name = value; + } + } + + public string Reference { + get { + return reference; + } + } + + public HighlightRuleSet() + { + keyWords = new LookupTable(false); + prevMarkers = new LookupTable(false); + nextMarkers = new LookupTable(false); + } + + public HighlightRuleSet(XmlElement el) + { + XmlNodeList nodes; + + if (el.Attributes["name"] != null) { + Name = el.Attributes["name"].InnerText; + } + + if (el.HasAttribute("escapecharacter")) { + escapeCharacter = el.GetAttribute("escapecharacter")[0]; + } + + if (el.Attributes["reference"] != null) { + reference = el.Attributes["reference"].InnerText; + } + + if (el.Attributes["ignorecase"] != null) { + ignoreCase = bool.Parse(el.Attributes["ignorecase"].InnerText); + } + + for (int i = 0; i < Delimiters.Length; ++i) { + delimiters[i] = false; + } + + if (el["Delimiters"] != null) { + string delimiterString = el["Delimiters"].InnerText; + foreach (char ch in delimiterString) { + delimiters[(int)ch] = true; + } + } + +// Spans = new LookupTable(!IgnoreCase); + + keyWords = new LookupTable(!IgnoreCase); + prevMarkers = new LookupTable(!IgnoreCase); + nextMarkers = new LookupTable(!IgnoreCase); + + nodes = el.GetElementsByTagName("KeyWords"); + foreach (XmlElement el2 in nodes) { + HighlightColor color = new HighlightColor(el2); + + XmlNodeList keys = el2.GetElementsByTagName("Key"); + foreach (XmlElement node in keys) { + keyWords[node.Attributes["word"].InnerText] = color; + } + } + + nodes = el.GetElementsByTagName("Span"); + foreach (XmlElement el2 in nodes) { + Spans.Add(new Span(el2)); + /* + Span span = new Span(el2); + Spans[span.Begin] = span;*/ + } + + nodes = el.GetElementsByTagName("MarkPrevious"); + foreach (XmlElement el2 in nodes) { + PrevMarker prev = new PrevMarker(el2); + prevMarkers[prev.What] = prev; + } + + nodes = el.GetElementsByTagName("MarkFollowing"); + foreach (XmlElement el2 in nodes) { + NextMarker next = new NextMarker(el2); + nextMarkers[next.What] = next; + } + } + + /// + /// Merges spans etc. from the other rule set into this rule set. + /// + public void MergeFrom(HighlightRuleSet ruleSet) + { + for (int i = 0; i < delimiters.Length; i++) { + delimiters[i] |= ruleSet.delimiters[i]; + } + // insert merged spans in front of old spans + ArrayList oldSpans = spans; + spans = (ArrayList)ruleSet.spans.Clone(); + spans.AddRange(oldSpans); + //keyWords.MergeFrom(ruleSet.keyWords); + //prevMarkers.MergeFrom(ruleSet.prevMarkers); + //nextMarkers.MergeFrom(ruleSet.nextMarkers); + } + } +} diff --git a/ICSharpCode.TextEditor/Project/Src/Document/HighlightingStrategy/HighlightingColorNotFoundException.cs b/ICSharpCode.TextEditor/Project/Src/Document/HighlightingStrategy/HighlightingColorNotFoundException.cs new file mode 100644 index 0000000..330b267 --- /dev/null +++ b/ICSharpCode.TextEditor/Project/Src/Document/HighlightingStrategy/HighlightingColorNotFoundException.cs @@ -0,0 +1,32 @@ +// +// +// +// +// $Revision$ +// + +using System; +using System.Runtime.Serialization; + +namespace ICSharpCode.TextEditor.Document +{ + [Serializable()] + public class HighlightingColorNotFoundException : Exception + { + public HighlightingColorNotFoundException() : base() + { + } + + public HighlightingColorNotFoundException(string message) : base(message) + { + } + + public HighlightingColorNotFoundException(string message, Exception innerException) : base(message, innerException) + { + } + + protected HighlightingColorNotFoundException(SerializationInfo info, StreamingContext context) : base(info, context) + { + } + } +} diff --git a/ICSharpCode.TextEditor/Project/Src/Document/HighlightingStrategy/HighlightingDefinitionInvalidException.cs b/ICSharpCode.TextEditor/Project/Src/Document/HighlightingStrategy/HighlightingDefinitionInvalidException.cs new file mode 100644 index 0000000..4fd5216 --- /dev/null +++ b/ICSharpCode.TextEditor/Project/Src/Document/HighlightingStrategy/HighlightingDefinitionInvalidException.cs @@ -0,0 +1,37 @@ +// +// +// +// +// $Revision$ +// + +using System; +using System.Runtime.Serialization; + +namespace ICSharpCode.TextEditor.Document +{ + /// + /// Indicates that the highlighting definition that was tried to load was invalid. + /// You get this exception only once per highlighting definition, after that the definition + /// is replaced with the default highlighter. + /// + [Serializable()] + public class HighlightingDefinitionInvalidException : Exception + { + public HighlightingDefinitionInvalidException() : base() + { + } + + public HighlightingDefinitionInvalidException(string message) : base(message) + { + } + + public HighlightingDefinitionInvalidException(string message, Exception innerException) : base(message, innerException) + { + } + + protected HighlightingDefinitionInvalidException(SerializationInfo info, StreamingContext context) : base(info, context) + { + } + } +} diff --git a/ICSharpCode.TextEditor/Project/Src/Document/HighlightingStrategy/HighlightingDefinitionParser.cs b/ICSharpCode.TextEditor/Project/Src/Document/HighlightingStrategy/HighlightingDefinitionParser.cs new file mode 100644 index 0000000..b83b15c --- /dev/null +++ b/ICSharpCode.TextEditor/Project/Src/Document/HighlightingStrategy/HighlightingDefinitionParser.cs @@ -0,0 +1,110 @@ +// +// +// +// +// $Revision$ +// + +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; +using System.Windows.Forms; +using System.Xml; +using System.Xml.Schema; + +namespace ICSharpCode.TextEditor.Document +{ + public static class HighlightingDefinitionParser + { + public static DefaultHighlightingStrategy Parse(SyntaxMode syntaxMode, XmlReader xmlReader) + { + return Parse(null, syntaxMode, xmlReader); + } + + public static DefaultHighlightingStrategy Parse(DefaultHighlightingStrategy highlighter, SyntaxMode syntaxMode, XmlReader xmlReader) + { + if (syntaxMode == null) + throw new ArgumentNullException("syntaxMode"); + if (xmlReader == null) + throw new ArgumentNullException("xmlTextReader"); + try { + List errors = null; + XmlReaderSettings settings = new XmlReaderSettings(); + Stream shemaStream = typeof(HighlightingDefinitionParser).Assembly.GetManifestResourceStream("ICSharpCode.TextEditor.Resources.Mode.xsd"); + settings.Schemas.Add("", new XmlTextReader(shemaStream)); + settings.Schemas.ValidationEventHandler += delegate(object sender, ValidationEventArgs args) { + if (errors == null) { + errors = new List(); + } + errors.Add(args); + }; + settings.ValidationType = ValidationType.Schema; + XmlReader validatingReader = XmlReader.Create(xmlReader, settings); + + XmlDocument doc = new XmlDocument(); + doc.Load(validatingReader); + + if (highlighter == null) + highlighter = new DefaultHighlightingStrategy(doc.DocumentElement.Attributes["name"].InnerText); + + if (doc.DocumentElement.HasAttribute("extends")) { + KeyValuePair entry = HighlightingManager.Manager.FindHighlighterEntry(doc.DocumentElement.GetAttribute("extends")); + if (entry.Key == null) { + throw new HighlightingDefinitionInvalidException("Cannot find referenced highlighting source " + doc.DocumentElement.GetAttribute("extends")); + } else { + highlighter = Parse(highlighter, entry.Key, entry.Value.GetSyntaxModeFile(entry.Key)); + if (highlighter == null) return null; + } + } + if (doc.DocumentElement.HasAttribute("extensions")) { + highlighter.Extensions = doc.DocumentElement.GetAttribute("extensions").Split(new char[] { ';', '|' }); + } + + XmlElement environment = doc.DocumentElement["Environment"]; + if (environment != null) { + foreach (XmlNode node in environment.ChildNodes) { + if (node is XmlElement) { + XmlElement el = (XmlElement)node; + if (el.Name == "Custom") { + highlighter.SetColorFor(el.GetAttribute("name"), el.HasAttribute("bgcolor") ? new HighlightBackground(el) : new HighlightColor(el)); + } else { + highlighter.SetColorFor(el.Name, el.HasAttribute("bgcolor") ? new HighlightBackground(el) : new HighlightColor(el)); + } + } + } + } + + // parse properties + if (doc.DocumentElement["Properties"]!= null) { + foreach (XmlElement propertyElement in doc.DocumentElement["Properties"].ChildNodes) { + highlighter.Properties[propertyElement.Attributes["name"].InnerText] = propertyElement.Attributes["value"].InnerText; + } + } + + if (doc.DocumentElement["Digits"]!= null) { + highlighter.DigitColor = new HighlightColor(doc.DocumentElement["Digits"]); + } + + XmlNodeList nodes = doc.DocumentElement.GetElementsByTagName("RuleSet"); + foreach (XmlElement element in nodes) { + highlighter.AddRuleSet(new HighlightRuleSet(element)); + } + + xmlReader.Close(); + + if (errors != null) { + StringBuilder msg = new StringBuilder(); + foreach (ValidationEventArgs args in errors) { + msg.AppendLine(args.Message); + } + throw new HighlightingDefinitionInvalidException(msg.ToString()); + } else { + return highlighter; + } + } catch (Exception e) { + throw new HighlightingDefinitionInvalidException("Could not load mode definition file '" + syntaxMode.FileName + "'.\n", e); + } + } + } +} diff --git a/ICSharpCode.TextEditor/Project/Src/Document/HighlightingStrategy/HighlightingManager.cs b/ICSharpCode.TextEditor/Project/Src/Document/HighlightingStrategy/HighlightingManager.cs new file mode 100644 index 0000000..4b4c14d --- /dev/null +++ b/ICSharpCode.TextEditor/Project/Src/Document/HighlightingStrategy/HighlightingManager.cs @@ -0,0 +1,166 @@ +// +// +// +// +// $Revision$ +// + +using System; +using System.Collections; +using System.Collections.Generic; +using System.IO; +using System.Diagnostics; + +namespace ICSharpCode.TextEditor.Document +{ + public class HighlightingManager + { + ArrayList syntaxModeFileProviders = new ArrayList(); + static HighlightingManager highlightingManager; + + // hash table from extension name to highlighting definition, + // OR from extension name to Pair SyntaxMode,ISyntaxModeFileProvider + Hashtable highlightingDefs = new Hashtable(); + + Hashtable extensionsToName = new Hashtable(); + + public Hashtable HighlightingDefinitions { + get { + return highlightingDefs; + } + } + + public static HighlightingManager Manager { + get { + return highlightingManager; + } + } + + static HighlightingManager() + { + highlightingManager = new HighlightingManager(); + highlightingManager.AddSyntaxModeFileProvider(new ResourceSyntaxModeProvider()); + } + + public HighlightingManager() + { + CreateDefaultHighlightingStrategy(); + } + + public void AddSyntaxModeFileProvider(ISyntaxModeFileProvider syntaxModeFileProvider) + { + foreach (SyntaxMode syntaxMode in syntaxModeFileProvider.SyntaxModes) { + highlightingDefs[syntaxMode.Name] = new DictionaryEntry(syntaxMode, syntaxModeFileProvider); + foreach (string extension in syntaxMode.Extensions) { + extensionsToName[extension.ToUpperInvariant()] = syntaxMode.Name; + } + } + if (!syntaxModeFileProviders.Contains(syntaxModeFileProvider)) { + syntaxModeFileProviders.Add(syntaxModeFileProvider); + } + } + + public void AddHighlightingStrategy(IHighlightingStrategy highlightingStrategy) + { + highlightingDefs[highlightingStrategy.Name] = highlightingStrategy; + foreach (string extension in highlightingStrategy.Extensions) + { + extensionsToName[extension.ToUpperInvariant()] = highlightingStrategy.Name; + } + } + + public void ReloadSyntaxModes() + { + highlightingDefs.Clear(); + extensionsToName.Clear(); + CreateDefaultHighlightingStrategy(); + foreach (ISyntaxModeFileProvider provider in syntaxModeFileProviders) { + provider.UpdateSyntaxModeList(); + AddSyntaxModeFileProvider(provider); + } + OnReloadSyntaxHighlighting(EventArgs.Empty); + } + + void CreateDefaultHighlightingStrategy() + { + DefaultHighlightingStrategy defaultHighlightingStrategy = new DefaultHighlightingStrategy(); + defaultHighlightingStrategy.Extensions = new string[] {}; + defaultHighlightingStrategy.Rules.Add(new HighlightRuleSet()); + highlightingDefs["Default"] = defaultHighlightingStrategy; + } + + IHighlightingStrategy LoadDefinition(DictionaryEntry entry) + { + SyntaxMode syntaxMode = (SyntaxMode)entry.Key; + ISyntaxModeFileProvider syntaxModeFileProvider = (ISyntaxModeFileProvider)entry.Value; + + DefaultHighlightingStrategy highlightingStrategy = null; + try { + var reader = syntaxModeFileProvider.GetSyntaxModeFile(syntaxMode); + if (reader == null) + throw new HighlightingDefinitionInvalidException("Could not get syntax mode file for " + syntaxMode.Name); + highlightingStrategy = HighlightingDefinitionParser.Parse(syntaxMode, reader); + if (highlightingStrategy.Name != syntaxMode.Name) { + throw new HighlightingDefinitionInvalidException("The name specified in the .xshd '" + highlightingStrategy.Name + "' must be equal the syntax mode name '" + syntaxMode.Name + "'"); + } + } finally { + if (highlightingStrategy == null) { + highlightingStrategy = DefaultHighlighting; + } + highlightingDefs[syntaxMode.Name] = highlightingStrategy; + highlightingStrategy.ResolveReferences(); + } + return highlightingStrategy; + } + + public DefaultHighlightingStrategy DefaultHighlighting { + get { + return (DefaultHighlightingStrategy)highlightingDefs["Default"]; + } + } + + internal KeyValuePair FindHighlighterEntry(string name) + { + foreach (ISyntaxModeFileProvider provider in syntaxModeFileProviders) { + foreach (SyntaxMode mode in provider.SyntaxModes) { + if (mode.Name == name) { + return new KeyValuePair(mode, provider); + } + } + } + return new KeyValuePair(null, null); + } + + public IHighlightingStrategy FindHighlighter(string name) + { + object def = highlightingDefs[name]; + if (def is DictionaryEntry) { + return LoadDefinition((DictionaryEntry)def); + } + return def == null ? DefaultHighlighting : (IHighlightingStrategy)def; + } + + public IHighlightingStrategy FindHighlighterForFile(string fileName) + { + string highlighterName = (string)extensionsToName[Path.GetExtension(fileName).ToUpperInvariant()]; + if (highlighterName != null) { + object def = highlightingDefs[highlighterName]; + if (def is DictionaryEntry) { + return LoadDefinition((DictionaryEntry)def); + } + return def == null ? DefaultHighlighting : (IHighlightingStrategy)def; + } else { + return DefaultHighlighting; + } + } + + protected virtual void OnReloadSyntaxHighlighting(EventArgs e) + { + if (ReloadSyntaxHighlighting != null) { + ReloadSyntaxHighlighting(this, e); + } + } + + public event EventHandler ReloadSyntaxHighlighting; + } +} diff --git a/ICSharpCode.TextEditor/Project/Src/Document/HighlightingStrategy/HighlightingStrategyFactory.cs b/ICSharpCode.TextEditor/Project/Src/Document/HighlightingStrategy/HighlightingStrategyFactory.cs new file mode 100644 index 0000000..19f7590 --- /dev/null +++ b/ICSharpCode.TextEditor/Project/Src/Document/HighlightingStrategy/HighlightingStrategyFactory.cs @@ -0,0 +1,40 @@ +// +// +// +// +// $Revision$ +// + +using System; + +namespace ICSharpCode.TextEditor.Document +{ + public class HighlightingStrategyFactory + { + public static IHighlightingStrategy CreateHighlightingStrategy() + { + return (IHighlightingStrategy)HighlightingManager.Manager.HighlightingDefinitions["Default"]; + } + + public static IHighlightingStrategy CreateHighlightingStrategy(string name) + { + IHighlightingStrategy highlightingStrategy = HighlightingManager.Manager.FindHighlighter(name); + + if (highlightingStrategy == null) + { + return CreateHighlightingStrategy(); + } + return highlightingStrategy; + } + + public static IHighlightingStrategy CreateHighlightingStrategyForFile(string fileName) + { + IHighlightingStrategy highlightingStrategy = HighlightingManager.Manager.FindHighlighterForFile(fileName); + if (highlightingStrategy == null) + { + return CreateHighlightingStrategy(); + } + return highlightingStrategy; + } + } +} diff --git a/ICSharpCode.TextEditor/Project/Src/Document/HighlightingStrategy/IHighlightingStrategy.cs b/ICSharpCode.TextEditor/Project/Src/Document/HighlightingStrategy/IHighlightingStrategy.cs new file mode 100644 index 0000000..89f7b82 --- /dev/null +++ b/ICSharpCode.TextEditor/Project/Src/Document/HighlightingStrategy/IHighlightingStrategy.cs @@ -0,0 +1,67 @@ +// +// +// +// +// $Revision$ +// + +using System; +using System.Collections.Generic; + +namespace ICSharpCode.TextEditor.Document +{ + /// + /// A highlighting strategy for a buffer. + /// + public interface IHighlightingStrategy + { + /// + /// The name of the highlighting strategy, must be unique + /// + string Name { + get; + } + + /// + /// The file extenstions on which this highlighting strategy gets + /// used + /// + string[] Extensions { + get; + } + + Dictionary Properties { + get; + } + + // returns special color. (BackGround Color, Cursor Color and so on) + + /// + /// Gets the color of an Environment element. + /// + HighlightColor GetColorFor(string name); + + /// + /// Used internally, do not call + /// + void MarkTokens(IDocument document, List lines); + + /// + /// Used internally, do not call + /// + void MarkTokens(IDocument document); + } + + public interface IHighlightingStrategyUsingRuleSets : IHighlightingStrategy + { + /// + /// Used internally, do not call + /// + HighlightRuleSet GetRuleSet(Span span); + + /// + /// Used internally, do not call + /// + HighlightColor GetColor(IDocument document, LineSegment keyWord, int index, int length); + } +} diff --git a/ICSharpCode.TextEditor/Project/Src/Document/HighlightingStrategy/NextMarker.cs b/ICSharpCode.TextEditor/Project/Src/Document/HighlightingStrategy/NextMarker.cs new file mode 100644 index 0000000..b4848b7 --- /dev/null +++ b/ICSharpCode.TextEditor/Project/Src/Document/HighlightingStrategy/NextMarker.cs @@ -0,0 +1,63 @@ +// +// +// +// +// $Revision$ +// + +using System; +using System.Xml; + +namespace ICSharpCode.TextEditor.Document +{ + /// + /// Used for mark next token + /// + public class NextMarker + { + string what; + HighlightColor color; + bool markMarker = false; + + /// + /// String value to indicate to mark next token + /// + public string What { + get { + return what; + } + } + + /// + /// Color for marking next token + /// + public HighlightColor Color { + get { + return color; + } + } + + /// + /// If true the indication text will be marked with the same color + /// too + /// + public bool MarkMarker { + get { + return markMarker; + } + } + + /// + /// Creates a new instance of + /// + public NextMarker(XmlElement mark) + { + color = new HighlightColor(mark); + what = mark.InnerText; + if (mark.Attributes["markmarker"] != null) { + markMarker = bool.Parse(mark.Attributes["markmarker"].InnerText); + } + } + } + +} diff --git a/ICSharpCode.TextEditor/Project/Src/Document/HighlightingStrategy/PrevMarker.cs b/ICSharpCode.TextEditor/Project/Src/Document/HighlightingStrategy/PrevMarker.cs new file mode 100644 index 0000000..9f3393d --- /dev/null +++ b/ICSharpCode.TextEditor/Project/Src/Document/HighlightingStrategy/PrevMarker.cs @@ -0,0 +1,63 @@ +// +// +// +// +// $Revision$ +// + +using System; +using System.Xml; + +namespace ICSharpCode.TextEditor.Document +{ + /// + /// Used for mark previous token + /// + public class PrevMarker + { + string what; + HighlightColor color; + bool markMarker = false; + + /// + /// String value to indicate to mark previous token + /// + public string What { + get { + return what; + } + } + + /// + /// Color for marking previous token + /// + public HighlightColor Color { + get { + return color; + } + } + + /// + /// If true the indication text will be marked with the same color + /// too + /// + public bool MarkMarker { + get { + return markMarker; + } + } + + /// + /// Creates a new instance of + /// + public PrevMarker(XmlElement mark) + { + color = new HighlightColor(mark); + what = mark.InnerText; + if (mark.Attributes["markmarker"] != null) { + markMarker = bool.Parse(mark.Attributes["markmarker"].InnerText); + } + } + } + +} diff --git a/ICSharpCode.TextEditor/Project/Src/Document/HighlightingStrategy/Span.cs b/ICSharpCode.TextEditor/Project/Src/Document/HighlightingStrategy/Span.cs new file mode 100644 index 0000000..337a80d --- /dev/null +++ b/ICSharpCode.TextEditor/Project/Src/Document/HighlightingStrategy/Span.cs @@ -0,0 +1,157 @@ +// +// +// +// +// $Revision$ +// + +using System; +using System.Xml; + +namespace ICSharpCode.TextEditor.Document +{ + public sealed class Span + { + bool stopEOL; + HighlightColor color; + HighlightColor beginColor; + HighlightColor endColor; + char[] begin; + char[] end; + string name; + string rule; + HighlightRuleSet ruleSet; + char escapeCharacter; + bool ignoreCase; + bool isBeginSingleWord; + bool? isBeginStartOfLine; + bool isEndSingleWord; + + internal HighlightRuleSet RuleSet { + get { + return ruleSet; + } + set { + ruleSet = value; + } + } + + public bool IgnoreCase { + get { + return ignoreCase; + } + set { + ignoreCase = value; + } + } + + public bool StopEOL { + get { + return stopEOL; + } + } + + public bool? IsBeginStartOfLine { + get { + return isBeginStartOfLine; + } + } + + public bool IsBeginSingleWord { + get { + return isBeginSingleWord; + } + } + + public bool IsEndSingleWord { + get { + return isEndSingleWord; + } + } + + public HighlightColor Color { + get { + return color; + } + } + + public HighlightColor BeginColor { + get { + if(beginColor != null) { + return beginColor; + } else { + return color; + } + } + } + + public HighlightColor EndColor { + get { + return endColor!=null ? endColor : color; + } + } + + public char[] Begin { + get { return begin; } + } + + public char[] End { + get { return end; } + } + + public string Name { + get { return name; } + } + + public string Rule { + get { return rule; } + } + + /// + /// Gets the escape character of the span. The escape character is a character that can be used in front + /// of the span end to make it not end the span. The escape character followed by another escape character + /// means the escape character was escaped like in @"a "" b" literals in C#. + /// The default value '\0' means no escape character is allowed. + /// + public char EscapeCharacter { + get { return escapeCharacter; } + } + + public Span(XmlElement span) + { + color = new HighlightColor(span); + + if (span.HasAttribute("rule")) { + rule = span.GetAttribute("rule"); + } + + if (span.HasAttribute("escapecharacter")) { + escapeCharacter = span.GetAttribute("escapecharacter")[0]; + } + + name = span.GetAttribute("name"); + if (span.HasAttribute("stopateol")) { + stopEOL = bool.Parse(span.GetAttribute("stopateol")); + } + + begin = span["Begin"].InnerText.ToCharArray(); + beginColor = new HighlightColor(span["Begin"], color); + + if (span["Begin"].HasAttribute("singleword")) { + this.isBeginSingleWord = bool.Parse(span["Begin"].GetAttribute("singleword")); + } + if (span["Begin"].HasAttribute("startofline")) { + this.isBeginStartOfLine = bool.Parse(span["Begin"].GetAttribute("startofline")); + } + + if (span["End"] != null) { + end = span["End"].InnerText.ToCharArray(); + endColor = new HighlightColor(span["End"], color); + if (span["End"].HasAttribute("singleword")) { + this.isEndSingleWord = bool.Parse(span["End"].GetAttribute("singleword")); + } + + } + } + } +} diff --git a/ICSharpCode.TextEditor/Project/Src/Document/HighlightingStrategy/SpanStack.cs b/ICSharpCode.TextEditor/Project/Src/Document/HighlightingStrategy/SpanStack.cs new file mode 100644 index 0000000..6ad89b8 --- /dev/null +++ b/ICSharpCode.TextEditor/Project/Src/Document/HighlightingStrategy/SpanStack.cs @@ -0,0 +1,118 @@ +// +// +// +// +// $Revision$ +// + +using System; +using System.Collections.Generic; + +namespace ICSharpCode.TextEditor.Document +{ + /// + /// A stack of Span instances. Works like Stack<Span>, but can be cloned quickly + /// because it is implemented as linked list. + /// + public sealed class SpanStack : ICloneable, IEnumerable + { + internal sealed class StackNode + { + public readonly StackNode Previous; + public readonly Span Data; + + public StackNode(StackNode previous, Span data) + { + this.Previous = previous; + this.Data = data; + } + } + + StackNode top = null; + + public Span Pop() + { + Span s = top.Data; + top = top.Previous; + return s; + } + + public Span Peek() + { + return top.Data; + } + + public void Push(Span s) + { + top = new StackNode(top, s); + } + + public bool IsEmpty { + get { + return top == null; + } + } + + public SpanStack Clone() + { + SpanStack n = new SpanStack(); + n.top = this.top; + return n; + } + object ICloneable.Clone() + { + return this.Clone(); + } + + public Enumerator GetEnumerator() + { + return new Enumerator(new StackNode(top, null)); + } + IEnumerator IEnumerable.GetEnumerator() + { + return this.GetEnumerator(); + } + System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() + { + return this.GetEnumerator(); + } + + public struct Enumerator : IEnumerator + { + StackNode c; + + internal Enumerator(StackNode node) + { + c = node; + } + + public Span Current { + get { + return c.Data; + } + } + + object System.Collections.IEnumerator.Current { + get { + return c.Data; + } + } + + public void Dispose() + { + c = null; + } + + public bool MoveNext() + { + c = c.Previous; + return c != null; + } + + public void Reset() + { + throw new NotSupportedException(); + } + } + } +} diff --git a/ICSharpCode.TextEditor/Project/Src/Document/HighlightingStrategy/SyntaxModes/FileSyntaxModeProvider.cs b/ICSharpCode.TextEditor/Project/Src/Document/HighlightingStrategy/SyntaxModes/FileSyntaxModeProvider.cs new file mode 100644 index 0000000..9f347da --- /dev/null +++ b/ICSharpCode.TextEditor/Project/Src/Document/HighlightingStrategy/SyntaxModes/FileSyntaxModeProvider.cs @@ -0,0 +1,84 @@ +// +// +// +// +// $Revision$ +// + +using System; +using System.Collections.Generic; +using System.IO; +using System.Windows.Forms; +using System.Xml; + +namespace ICSharpCode.TextEditor.Document +{ + public class FileSyntaxModeProvider : ISyntaxModeFileProvider + { + string directory; + List syntaxModes = null; + + public ICollection SyntaxModes { + get { + return syntaxModes; + } + } + + public FileSyntaxModeProvider(string directory) + { + this.directory = directory; + UpdateSyntaxModeList(); + } + + public void UpdateSyntaxModeList() + { + string syntaxModeFile = Path.Combine(directory, "SyntaxModes.xml"); + if (File.Exists(syntaxModeFile)) { + Stream s = File.OpenRead(syntaxModeFile); + syntaxModes = SyntaxMode.GetSyntaxModes(s); + s.Close(); + } else { + syntaxModes = ScanDirectory(directory); + } + } + + public XmlTextReader GetSyntaxModeFile(SyntaxMode syntaxMode) + { + string syntaxModeFile = Path.Combine(directory, syntaxMode.FileName); + if (!File.Exists(syntaxModeFile)) { + throw new HighlightingDefinitionInvalidException("Can't load highlighting definition " + syntaxModeFile + " (file not found)!"); + } + return new XmlTextReader(File.OpenRead(syntaxModeFile)); + } + + List ScanDirectory(string directory) + { + string[] files = Directory.GetFiles(directory); + List modes = new List(); + foreach (string file in files) { + if (Path.GetExtension(file).Equals(".XSHD", StringComparison.OrdinalIgnoreCase)) { + XmlTextReader reader = new XmlTextReader(file); + while (reader.Read()) { + if (reader.NodeType == XmlNodeType.Element) { + switch (reader.Name) { + case "SyntaxDefinition": + string name = reader.GetAttribute("name"); + string extensions = reader.GetAttribute("extensions"); + modes.Add(new SyntaxMode(Path.GetFileName(file), + name, + extensions)); + goto bailout; + default: + throw new HighlightingDefinitionInvalidException("Unknown root node in syntax highlighting file :" + reader.Name); + } + } + } + bailout: + reader.Close(); + + } + } + return modes; + } + } +} diff --git a/ICSharpCode.TextEditor/Project/Src/Document/HighlightingStrategy/SyntaxModes/ISyntaxModeFileProvider.cs b/ICSharpCode.TextEditor/Project/Src/Document/HighlightingStrategy/SyntaxModes/ISyntaxModeFileProvider.cs new file mode 100644 index 0000000..1df1176 --- /dev/null +++ b/ICSharpCode.TextEditor/Project/Src/Document/HighlightingStrategy/SyntaxModes/ISyntaxModeFileProvider.cs @@ -0,0 +1,23 @@ +// +// +// +// +// $Revision$ +// + +using System; +using System.Collections.Generic; +using System.Xml; + +namespace ICSharpCode.TextEditor.Document +{ + public interface ISyntaxModeFileProvider + { + ICollection SyntaxModes { + get; + } + + XmlTextReader GetSyntaxModeFile(SyntaxMode syntaxMode); + void UpdateSyntaxModeList(); + } +} diff --git a/ICSharpCode.TextEditor/Project/Src/Document/HighlightingStrategy/SyntaxModes/ResourceSyntaxModeProvider.cs b/ICSharpCode.TextEditor/Project/Src/Document/HighlightingStrategy/SyntaxModes/ResourceSyntaxModeProvider.cs new file mode 100644 index 0000000..157caa6 --- /dev/null +++ b/ICSharpCode.TextEditor/Project/Src/Document/HighlightingStrategy/SyntaxModes/ResourceSyntaxModeProvider.cs @@ -0,0 +1,48 @@ +// +// +// +// +// $Revision$ +// + +using System; +using System.Collections.Generic; +using System.IO; +using System.Reflection; +using System.Xml; + +namespace ICSharpCode.TextEditor.Document +{ + public class ResourceSyntaxModeProvider : ISyntaxModeFileProvider + { + List syntaxModes = null; + + public ICollection SyntaxModes { + get { + return syntaxModes; + } + } + + public ResourceSyntaxModeProvider() + { + Assembly assembly = typeof(SyntaxMode).Assembly; + Stream syntaxModeStream = assembly.GetManifestResourceStream("ICSharpCode.TextEditor.Resources.SyntaxModes.xml"); + if (syntaxModeStream != null) { + syntaxModes = SyntaxMode.GetSyntaxModes(syntaxModeStream); + } else { + syntaxModes = new List(); + } + } + + public XmlTextReader GetSyntaxModeFile(SyntaxMode syntaxMode) + { + Assembly assembly = typeof(SyntaxMode).Assembly; + return new XmlTextReader(assembly.GetManifestResourceStream("ICSharpCode.TextEditor.Resources." + syntaxMode.FileName)); + } + + public void UpdateSyntaxModeList() + { + // resources don't change during runtime + } + } +} diff --git a/ICSharpCode.TextEditor/Project/Src/Document/HighlightingStrategy/SyntaxModes/ResourceSyntaxModeProviderEx.cs b/ICSharpCode.TextEditor/Project/Src/Document/HighlightingStrategy/SyntaxModes/ResourceSyntaxModeProviderEx.cs new file mode 100644 index 0000000..c2ca844 --- /dev/null +++ b/ICSharpCode.TextEditor/Project/Src/Document/HighlightingStrategy/SyntaxModes/ResourceSyntaxModeProviderEx.cs @@ -0,0 +1,48 @@ +using System.Collections.Generic; +using System.IO; +using System.Reflection; +using System.Xml; +using ICSharpCode.TextEditor.Document; + +namespace ICSharpCode.TextEditor.Src.Document.HighlightingStrategy.SyntaxModes +{ + public class ResourceSyntaxModeProviderEx : ISyntaxModeFileProvider + { + private const string ResourcesDir = "ICSharpCode.TextEditor.Resources."; + + readonly List _syntaxModes; + + public ICollection SyntaxModes + { + get + { + return _syntaxModes; + } + } + + public ResourceSyntaxModeProviderEx() + { + var syntaxModeStream = GetSyntaxModeStream("SyntaxModesEx.xml"); + + _syntaxModes = syntaxModeStream != null ? SyntaxMode.GetSyntaxModes(syntaxModeStream) : new List(); + } + + public XmlTextReader GetSyntaxModeFile(SyntaxMode syntaxMode) + { + var stream = GetSyntaxModeStream(syntaxMode.FileName); + + return stream != null ? new XmlTextReader(stream) : null; + } + + public void UpdateSyntaxModeList() + { + // resources don't change during runtime + } + + private Stream GetSyntaxModeStream(string filename) + { + Assembly assembly = Assembly.GetExecutingAssembly(); + return assembly.GetManifestResourceStream(string.Format("{0}{1}", ResourcesDir, filename)); + } + } +} diff --git a/ICSharpCode.TextEditor/Project/Src/Document/HighlightingStrategy/SyntaxModes/SyntaxMode.cs b/ICSharpCode.TextEditor/Project/Src/Document/HighlightingStrategy/SyntaxModes/SyntaxMode.cs new file mode 100644 index 0000000..696047f --- /dev/null +++ b/ICSharpCode.TextEditor/Project/Src/Document/HighlightingStrategy/SyntaxModes/SyntaxMode.cs @@ -0,0 +1,96 @@ +// +// +// +// +// $Revision$ +// + +using System; +using System.Collections.Generic; +using System.IO; +using System.Windows.Forms; +using System.Xml; + +namespace ICSharpCode.TextEditor.Document +{ + public class SyntaxMode + { + string fileName; + string name; + string[] extensions; + + public string FileName { + get { + return fileName; + } + set { + fileName = value; + } + } + + public string Name { + get { + return name; + } + set { + name = value; + } + } + + public string[] Extensions { + get { + return extensions; + } + set { + extensions = value; + } + } + + public SyntaxMode(string fileName, string name, string extensions) + { + this.fileName = fileName; + this.name = name; + this.extensions = extensions.Split(';', '|', ','); + } + + public SyntaxMode(string fileName, string name, string[] extensions) + { + this.fileName = fileName; + this.name = name; + this.extensions = extensions; + } + + public static List GetSyntaxModes(Stream xmlSyntaxModeStream) + { + XmlTextReader reader = new XmlTextReader(xmlSyntaxModeStream); + List syntaxModes = new List(); + while (reader.Read()) { + switch (reader.NodeType) { + case XmlNodeType.Element: + switch (reader.Name) { + case "SyntaxModes": + string version = reader.GetAttribute("version"); + if (version != "1.0") { + throw new HighlightingDefinitionInvalidException("Unknown syntax mode file defininition with version " + version); + } + break; + case "Mode": + syntaxModes.Add(new SyntaxMode(reader.GetAttribute("file"), + reader.GetAttribute("name"), + reader.GetAttribute("extensions"))); + break; + default: + throw new HighlightingDefinitionInvalidException("Unknown node in syntax mode file :" + reader.Name); + } + break; + } + } + reader.Close(); + return syntaxModes; + } + public override string ToString() + { + return string.Format("[SyntaxMode: FileName={0}, Name={1}, Extensions=({2})]", fileName, name, string.Join(",", extensions)); + } + } +} diff --git a/ICSharpCode.TextEditor/Project/Src/Document/HighlightingStrategy/TextWord.cs b/ICSharpCode.TextEditor/Project/Src/Document/HighlightingStrategy/TextWord.cs new file mode 100644 index 0000000..c231f97 --- /dev/null +++ b/ICSharpCode.TextEditor/Project/Src/Document/HighlightingStrategy/TextWord.cs @@ -0,0 +1,239 @@ +// +// +// +// +// $Revision$ +// + +using System; +using System.Diagnostics; +using System.Drawing; + +namespace ICSharpCode.TextEditor.Document +{ + public enum TextWordType { + Word, + Space, + Tab + } + + /// + /// This class represents single words with color information, two special versions of a word are + /// spaces and tabs. + /// + public class TextWord + { + HighlightColor color; + LineSegment line; + IDocument document; + + + int offset; + int length; + + public sealed class SpaceTextWord : TextWord + { + public SpaceTextWord() + { + length = 1; + } + + public SpaceTextWord(HighlightColor color) + { + length = 1; + base.SyntaxColor = color; + } + + public override Font GetFont(FontContainer fontContainer) + { + return null; + } + + public override TextWordType Type { + get { + return TextWordType.Space; + } + } + public override bool IsWhiteSpace { + get { + return true; + } + } + } + + public sealed class TabTextWord : TextWord + { + public TabTextWord() + { + length = 1; + } + public TabTextWord(HighlightColor color) + { + length = 1; + base.SyntaxColor = color; + } + + public override Font GetFont(FontContainer fontContainer) + { + return null; + } + + public override TextWordType Type { + get { + return TextWordType.Tab; + } + } + public override bool IsWhiteSpace { + get { + return true; + } + } + } + + static TextWord spaceWord = new SpaceTextWord(); + static TextWord tabWord = new TabTextWord(); + + bool hasDefaultColor; + + public static TextWord Space { + get { + return spaceWord; + } + } + + public static TextWord Tab { + get { + return tabWord; + } + } + + public int Offset { + get { + return offset; + } + } + + public int Length { + get { + return length; + } + } + + /// + /// Splits the into two parts: the part before is assigned to + /// the reference parameter , the part after is returned. + /// + public static TextWord Split(ref TextWord word, int pos) + { + #if DEBUG + if (word.Type != TextWordType.Word) + throw new ArgumentException("word.Type must be Word"); + if (pos <= 0) + throw new ArgumentOutOfRangeException("pos", pos, "pos must be > 0"); + if (pos >= word.Length) + throw new ArgumentOutOfRangeException("pos", pos, "pos must be < word.Length"); + #endif + TextWord after = new TextWord(word.document, word.line, word.offset + pos, word.length - pos, word.color, word.hasDefaultColor); + word = new TextWord(word.document, word.line, word.offset, pos, word.color, word.hasDefaultColor); + return after; + } + + public bool HasDefaultColor { + get { + return hasDefaultColor; + } + } + + public virtual TextWordType Type { + get { + return TextWordType.Word; + } + } + + public string Word { + get { + if (document == null) { + return string.Empty; + } + return document.GetText(line.Offset + offset, length); + } + } + + public virtual Font GetFont(FontContainer fontContainer) + { + return color.GetFont(fontContainer); + } + + public Color Color { + get { + if (color == null) + return Color.Black; + else + return color.Color; + } + } + + public bool Bold { + get { + if (color == null) + return false; + else + return color.Bold; + } + } + + public bool Italic { + get { + if (color == null) + return false; + else + return color.Italic; + } + } + + public HighlightColor SyntaxColor { + get { + return color; + } + set { + Debug.Assert(value != null); + color = value; + } + } + + public virtual bool IsWhiteSpace { + get { + return false; + } + } + + public virtual bool IsDelimiter { get; set; } = false; + + protected TextWord() + { + } + + // TAB + public TextWord(IDocument document, LineSegment line, int offset, int length, HighlightColor color, bool hasDefaultColor) + { + Debug.Assert(document != null); + Debug.Assert(line != null); + Debug.Assert(color != null); + + this.document = document; + this.line = line; + this.offset = offset; + this.length = length; + this.color = color; + this.hasDefaultColor = hasDefaultColor; + } + + /// + /// Converts a instance to string (for debug purposes) + /// + public override string ToString() + { + return "[TextWord: Word = " + Word + ", Color = " + Color + "]"; + } + } +} diff --git a/ICSharpCode.TextEditor/Project/Src/Document/IDocument.cs b/ICSharpCode.TextEditor/Project/Src/Document/IDocument.cs new file mode 100644 index 0000000..45370e6 --- /dev/null +++ b/ICSharpCode.TextEditor/Project/Src/Document/IDocument.cs @@ -0,0 +1,315 @@ +// +// +// +// +// $Revision$ +// + +using System; +using System.Collections.Generic; +using System.Drawing; + +using ICSharpCode.TextEditor.Undo; + +namespace ICSharpCode.TextEditor.Document +{ + /// + /// This interface represents a container which holds a text sequence and + /// all necessary information about it. It is used as the base for a text editor. + /// + public interface IDocument + { + ITextEditorProperties TextEditorProperties { + get; + set; + } + + UndoStack UndoStack { + get; + } + /// + /// If true the document can't be altered + /// + bool ReadOnly { + get; + set; + } + + /// + /// The attached to the instance + /// + IFormattingStrategy FormattingStrategy { + get; + set; + } + + /// + /// The attached to the instance + /// + ITextBufferStrategy TextBufferStrategy { + get; + } + + /// + /// The attached to the instance + /// + FoldingManager FoldingManager { + get; + } + + /// + /// The attached to the instance + /// + IHighlightingStrategy HighlightingStrategy { + get; + set; + } + + /// + /// The attached to the instance + /// + BookmarkManager BookmarkManager { + get; + } + + MarkerStrategy MarkerStrategy { + get; + } + +// /// +// /// The attached to the instance +// /// +// SelectionManager SelectionManager { +// get; +// } + + #region ILineManager interface + /// + /// A collection of all line segments + /// + /// + /// The collection should only be used if you're aware + /// of the 'last line ends with a delimiter problem'. Otherwise + /// the method should be used. + /// + IList LineSegmentCollection { + get; + } + + /// + /// The total number of lines in the document. + /// + int TotalNumberOfLines { + get; + } + + /// + /// Returns a valid line number for the given offset. + /// + /// + /// A offset which points to a character in the line which + /// line number is returned. + /// + /// + /// An int which value is the line number. + /// + /// If offset points not to a valid position + int GetLineNumberForOffset(int offset); + + /// + /// Returns a for the given offset. + /// + /// + /// A offset which points to a character in the line which + /// is returned. + /// + /// + /// A object. + /// + /// If offset points not to a valid position + LineSegment GetLineSegmentForOffset(int offset); + + /// + /// Returns a for the given line number. + /// This function should be used to get a line instead of getting the + /// line using the . + /// + /// + /// The line number which is requested. + /// + /// + /// A object. + /// + /// If offset points not to a valid position + LineSegment GetLineSegment(int lineNumber); + + /// + /// Get the first logical line for a given visible line. + /// example : lineNumber == 100 foldings are in the linetracker + /// between 0..1 (2 folded, invisible lines) this method returns 102 + /// the 'logical' line number + /// + int GetFirstLogicalLine(int lineNumber); + + /// + /// Get the last logical line for a given visible line. + /// example : lineNumber == 100 foldings are in the linetracker + /// between 0..1 (2 folded, invisible lines) this method returns 102 + /// the 'logical' line number + /// + int GetLastLogicalLine(int lineNumber); + + /// + /// Get the visible line for a given logical line. + /// example : lineNumber == 100 foldings are in the linetracker + /// between 0..1 (2 folded, invisible lines) this method returns 98 + /// the 'visible' line number + /// + int GetVisibleLine(int lineNumber); + +// /// +// /// Get the visible column for a given logical line and logical column. +// /// +// int GetVisibleColumn(int logicalLine, int logicalColumn); + + /// + /// Get the next visible line after lineNumber + /// + int GetNextVisibleLineAbove(int lineNumber, int lineCount); + + /// + /// Get the next visible line below lineNumber + /// + int GetNextVisibleLineBelow(int lineNumber, int lineCount); + + event EventHandler LineLengthChanged; + event EventHandler LineCountChanged; + event EventHandler LineDeleted; + #endregion + + #region ITextBufferStrategy interface + /// + /// Get the whole text as string. + /// When setting the text using the TextContent property, the undo stack is cleared. + /// Set TextContent only for actions such as loading a file; if you want to change the current document + /// use the Replace method instead. + /// + string TextContent { + get; + set; + } + + /// + /// The current length of the sequence of characters that can be edited. + /// + int TextLength { + get; + } + + /// + /// Inserts a string of characters into the sequence. + /// + /// + /// offset where to insert the string. + /// + /// + /// text to be inserted. + /// + void Insert(int offset, string text); + + /// + /// Removes some portion of the sequence. + /// + /// + /// offset of the remove. + /// + /// + /// number of characters to remove. + /// + void Remove(int offset, int length); + + /// + /// Replace some portion of the sequence. + /// + /// + /// offset. + /// + /// + /// number of characters to replace. + /// + /// + /// text to be replaced with. + /// + void Replace(int offset, int length, string text); + + /// + /// Returns a specific char of the sequence. + /// + /// + /// Offset of the char to get. + /// + char GetCharAt(int offset); + + /// + /// Fetches a string of characters contained in the sequence. + /// + /// + /// Offset into the sequence to fetch + /// + /// + /// number of characters to copy. + /// + string GetText(int offset, int length); + #endregion + string GetText(ISegment segment); + + #region ITextModel interface + /// + /// returns the logical line/column position from an offset + /// + TextLocation OffsetToPosition(int offset); + + /// + /// returns the offset from a logical line/column position + /// + int PositionToOffset(TextLocation p); + #endregion + /// + /// A container where all TextAreaUpdate objects get stored + /// + List UpdateQueue { + get; + } + + /// + /// Requests an update of the textarea + /// + void RequestUpdate(TextAreaUpdate update); + + /// + /// Commits all updates in the queue to the textarea (the + /// textarea will be painted) + /// + void CommitUpdate(); + + /// + /// Moves, Resizes, Removes a list of segments on insert/remove/replace events. + /// + void UpdateSegmentListOnDocumentChange(List list, DocumentEventArgs e) where T : ISegment; + + /// + /// Is fired when CommitUpdate is called + /// + event EventHandler UpdateCommited; + + /// + /// + event DocumentEventHandler DocumentAboutToBeChanged; + + /// + /// + event DocumentEventHandler DocumentChanged; + + event EventHandler TextContentChanged; + } +} diff --git a/ICSharpCode.TextEditor/Project/Src/Document/ISegment.cs b/ICSharpCode.TextEditor/Project/Src/Document/ISegment.cs new file mode 100644 index 0000000..71375ab --- /dev/null +++ b/ICSharpCode.TextEditor/Project/Src/Document/ISegment.cs @@ -0,0 +1,32 @@ +// +// +// +// +// $Revision$ +// + +namespace ICSharpCode.TextEditor.Document +{ + /// + /// This interface is used to describe a span inside a text sequence + /// + public interface ISegment + { + /// + /// The offset where the span begins + /// + int Offset { + get; + set; + } + + /// + /// The length of the span + /// + int Length { + get; + set; + } + } + +} diff --git a/ICSharpCode.TextEditor/Project/Src/Document/ITextEditorProperties.cs b/ICSharpCode.TextEditor/Project/Src/Document/ITextEditorProperties.cs new file mode 100644 index 0000000..4624143 --- /dev/null +++ b/ICSharpCode.TextEditor/Project/Src/Document/ITextEditorProperties.cs @@ -0,0 +1,177 @@ +// +// +// +// +// $Revision$ +// + +using System; +using System.Drawing; +using System.Text; + +namespace ICSharpCode.TextEditor.Document +{ + public interface ITextEditorProperties + { + bool CaretLine + { + get; + set; + } + + bool AutoInsertCurlyBracket { // is wrapped in text editor control + get; + set; + } + + bool HideMouseCursor { // is wrapped in text editor control + get; + set; + } + + bool IsIconBarVisible { // is wrapped in text editor control + get; + set; + } + + bool AllowCaretBeyondEOL { + get; + set; + } + + bool ShowMatchingBracket { // is wrapped in text editor control + get; + set; + } + + bool CutCopyWholeLine { + get; + set; + } + + System.Drawing.Text.TextRenderingHint TextRenderingHint { // is wrapped in text editor control + get; + set; + } + + bool MouseWheelScrollDown { + get; + set; + } + + bool MouseWheelTextZoom { + get; + set; + } + + string LineTerminator { + get; + set; + } + + LineViewerStyle LineViewerStyle { // is wrapped in text editor control + get; + set; + } + + bool ShowInvalidLines { // is wrapped in text editor control + get; + set; + } + + int VerticalRulerRow { // is wrapped in text editor control + get; + set; + } + + bool ShowSpaces { // is wrapped in text editor control + get; + set; + } + + bool ShowTabs { // is wrapped in text editor control + get; + set; + } + + bool ShowEOLMarker { // is wrapped in text editor control + get; + set; + } + + bool ConvertTabsToSpaces { // is wrapped in text editor control + get; + set; + } + + bool ShowHorizontalRuler { // is wrapped in text editor control + get; + set; + } + + bool ShowVerticalRuler { // is wrapped in text editor control + get; + set; + } + + Encoding Encoding { + get; + set; + } + + bool EnableFolding { // is wrapped in text editor control + get; + set; + } + + bool ShowLineNumbers { // is wrapped in text editor control + get; + set; + } + + /// + /// The width of a tab. + /// + int TabIndent { // is wrapped in text editor control + get; + set; + } + + /// + /// The amount of spaces a tab is converted to if ConvertTabsToSpaces is true. + /// + int IndentationSize { + get; + set; + } + + IndentStyle IndentStyle { // is wrapped in text editor control + get; + set; + } + + DocumentSelectionMode DocumentSelectionMode { + get; + set; + } + + Font Font { // is wrapped in text editor control + get; + set; + } + + FontContainer FontContainer { + get; + } + + BracketMatchingStyle BracketMatchingStyle { // is wrapped in text editor control + get; + set; + } + + bool SupportReadOnlySegments { + get; + set; + } + } +} diff --git a/ICSharpCode.TextEditor/Project/Src/Document/LineManager/DeferredEventList.cs b/ICSharpCode.TextEditor/Project/Src/Document/LineManager/DeferredEventList.cs new file mode 100644 index 0000000..0ab08f1 --- /dev/null +++ b/ICSharpCode.TextEditor/Project/Src/Document/LineManager/DeferredEventList.cs @@ -0,0 +1,44 @@ +// +// +// +// +// $Revision$ +// +using System; +using System.Collections.Generic; + +namespace ICSharpCode.TextEditor.Document +{ + /// + /// A list of events that are fired after the line manager has finished working. + /// + struct DeferredEventList + { + internal List removedLines; + internal List textAnchor; + + public void AddRemovedLine(LineSegment line) + { + if (removedLines == null) + removedLines = new List(); + removedLines.Add(line); + } + + public void AddDeletedAnchor(TextAnchor anchor) + { + if (textAnchor == null) + textAnchor = new List(); + textAnchor.Add(anchor); + } + + public void RaiseEvents() + { + // removedLines is raised by the LineManager + if (textAnchor != null) { + foreach (TextAnchor a in textAnchor) { + a.RaiseDeleted(); + } + } + } + } +} diff --git a/ICSharpCode.TextEditor/Project/Src/Document/LineManager/LineManager.cs b/ICSharpCode.TextEditor/Project/Src/Document/LineManager/LineManager.cs new file mode 100644 index 0000000..829c017 --- /dev/null +++ b/ICSharpCode.TextEditor/Project/Src/Document/LineManager/LineManager.cs @@ -0,0 +1,369 @@ +// +// +// +// +// $Revision$ +// + +using System; +using System.Collections.Generic; +using System.Diagnostics; + +namespace ICSharpCode.TextEditor.Document +{ + internal sealed class LineManager + { + LineSegmentTree lineCollection = new LineSegmentTree(); + + IDocument document; + IHighlightingStrategy highlightingStrategy; + + public IList LineSegmentCollection { + get { + return lineCollection; + } + } + + public int TotalNumberOfLines { + get { + return lineCollection.Count; + } + } + + public IHighlightingStrategy HighlightingStrategy { + get { + return highlightingStrategy; + } + set { + if (highlightingStrategy != value) { + highlightingStrategy = value; + if (highlightingStrategy != null) { + highlightingStrategy.MarkTokens(document); + } + } + } + } + + public LineManager(IDocument document, IHighlightingStrategy highlightingStrategy) + { + this.document = document; + this.highlightingStrategy = highlightingStrategy; + } + + public int GetLineNumberForOffset(int offset) + { + return GetLineSegmentForOffset(offset).LineNumber; + } + + public LineSegment GetLineSegmentForOffset(int offset) + { + return lineCollection.GetByOffset(offset); + } + + public LineSegment GetLineSegment(int lineNr) + { + return lineCollection[lineNr]; + } + + public void Insert(int offset, string text) + { + Replace(offset, 0, text); + } + + public void Remove(int offset, int length) + { + Replace(offset, length, string.Empty); + } + + public void Replace(int offset, int length, string text) + { + Debug.WriteLine("Replace offset="+offset+" length="+length+" text.Length="+text.Length); + int lineStart = GetLineNumberForOffset(offset); + int oldNumberOfLines = this.TotalNumberOfLines; + DeferredEventList deferredEventList = new DeferredEventList(); + RemoveInternal(ref deferredEventList, offset, length); + int numberOfLinesAfterRemoving = this.TotalNumberOfLines; + if (!string.IsNullOrEmpty(text)) { + InsertInternal(offset, text); + } +// #if DEBUG +// Console.WriteLine("New line collection:"); +// Console.WriteLine(lineCollection.GetTreeAsString()); +// Console.WriteLine("New text:"); +// Console.WriteLine("'" + document.TextContent + "'"); +// #endif + // Only fire events after RemoveInternal+InsertInternal finished completely: + // Otherwise we would expose inconsistent state to the event handlers. + RunHighlighter(lineStart, 1 + Math.Max(0, this.TotalNumberOfLines - numberOfLinesAfterRemoving)); + + if (deferredEventList.removedLines != null) { + foreach (LineSegment ls in deferredEventList.removedLines) + OnLineDeleted(new LineEventArgs(document, ls)); + } + deferredEventList.RaiseEvents(); + if (this.TotalNumberOfLines != oldNumberOfLines) { + OnLineCountChanged(new LineCountChangeEventArgs(document, lineStart, this.TotalNumberOfLines - oldNumberOfLines)); + } + } + + void RemoveInternal(ref DeferredEventList deferredEventList, int offset, int length) + { + Debug.Assert(length >= 0); + if (length == 0) return; + LineSegmentTree.Enumerator it = lineCollection.GetEnumeratorForOffset(offset); + LineSegment startSegment = it.Current; + int startSegmentOffset = startSegment.Offset; + if (offset + length < startSegmentOffset + startSegment.TotalLength) { + // just removing a part of this line segment + startSegment.RemovedLinePart(ref deferredEventList, offset - startSegmentOffset, length); + SetSegmentLength(startSegment, startSegment.TotalLength - length); + return; + } + // merge startSegment with another line segment because startSegment's delimiter was deleted + // possibly remove lines in between if multiple delimiters were deleted + int charactersRemovedInStartLine = startSegmentOffset + startSegment.TotalLength - offset; + Debug.Assert(charactersRemovedInStartLine > 0); + startSegment.RemovedLinePart(ref deferredEventList, offset - startSegmentOffset, charactersRemovedInStartLine); + + + LineSegment endSegment = lineCollection.GetByOffset(offset + length); + if (endSegment == startSegment) { + // special case: we are removing a part of the last line up to the + // end of the document + SetSegmentLength(startSegment, startSegment.TotalLength - length); + return; + } + int endSegmentOffset = endSegment.Offset; + int charactersLeftInEndLine = endSegmentOffset + endSegment.TotalLength - (offset + length); + endSegment.RemovedLinePart(ref deferredEventList, 0, endSegment.TotalLength - charactersLeftInEndLine); + startSegment.MergedWith(endSegment, offset - startSegmentOffset); + SetSegmentLength(startSegment, startSegment.TotalLength - charactersRemovedInStartLine + charactersLeftInEndLine); + startSegment.DelimiterLength = endSegment.DelimiterLength; + // remove all segments between startSegment (excl.) and endSegment (incl.) + it.MoveNext(); + LineSegment segmentToRemove; + do { + segmentToRemove = it.Current; + it.MoveNext(); + lineCollection.RemoveSegment(segmentToRemove); + segmentToRemove.Deleted(ref deferredEventList); + } while (segmentToRemove != endSegment); + } + + void InsertInternal(int offset, string text) + { + LineSegment segment = lineCollection.GetByOffset(offset); + DelimiterSegment ds = NextDelimiter(text, 0); + if (ds == null) { + // no newline is being inserted, all text is inserted in a single line + segment.InsertedLinePart(offset - segment.Offset, text.Length); + SetSegmentLength(segment, segment.TotalLength + text.Length); + return; + } + LineSegment firstLine = segment; + firstLine.InsertedLinePart(offset - firstLine.Offset, ds.Offset); + int lastDelimiterEnd = 0; + while (ds != null) { + // split line segment at line delimiter + int lineBreakOffset = offset + ds.Offset + ds.Length; + int segmentOffset = segment.Offset; + int lengthAfterInsertionPos = segmentOffset + segment.TotalLength - (offset + lastDelimiterEnd); + lineCollection.SetSegmentLength(segment, lineBreakOffset - segmentOffset); + LineSegment newSegment = lineCollection.InsertSegmentAfter(segment, lengthAfterInsertionPos); + segment.DelimiterLength = ds.Length; + + segment = newSegment; + lastDelimiterEnd = ds.Offset + ds.Length; + + ds = NextDelimiter(text, lastDelimiterEnd); + } + firstLine.SplitTo(segment); + // insert rest after last delimiter + if (lastDelimiterEnd != text.Length) { + segment.InsertedLinePart(0, text.Length - lastDelimiterEnd); + SetSegmentLength(segment, segment.TotalLength + text.Length - lastDelimiterEnd); + } + } + + void SetSegmentLength(LineSegment segment, int newTotalLength) + { + int delta = newTotalLength - segment.TotalLength; + if (delta != 0) { + lineCollection.SetSegmentLength(segment, newTotalLength); + OnLineLengthChanged(new LineLengthChangeEventArgs(document, segment, delta)); + } + } + + void RunHighlighter(int firstLine, int lineCount) + { + if (highlightingStrategy != null) { + List markLines = new List(); + LineSegmentTree.Enumerator it = lineCollection.GetEnumeratorForIndex(firstLine); + for (int i = 0; i < lineCount && it.IsValid; i++) { + markLines.Add(it.Current); + it.MoveNext(); + } + highlightingStrategy.MarkTokens(document, markLines); + } + } + + public void SetContent(string text) + { + lineCollection.Clear(); + if (text != null) { + Replace(0, 0, text); + } + } + + public int GetVisibleLine(int logicalLineNumber) + { + if (!document.TextEditorProperties.EnableFolding) { + return logicalLineNumber; + } + + int visibleLine = 0; + int foldEnd = 0; + List foldings = document.FoldingManager.GetTopLevelFoldedFoldings(); + foreach (FoldMarker fm in foldings) { + if (fm.StartLine >= logicalLineNumber) { + break; + } + if (fm.StartLine >= foldEnd) { + visibleLine += fm.StartLine - foldEnd; + if (fm.EndLine > logicalLineNumber) { + return visibleLine; + } + foldEnd = fm.EndLine; + } + } +// Debug.Assert(logicalLineNumber >= foldEnd); + visibleLine += logicalLineNumber - foldEnd; + return visibleLine; + } + + public int GetFirstLogicalLine(int visibleLineNumber) + { + if (!document.TextEditorProperties.EnableFolding) { + return visibleLineNumber; + } + int v = 0; + int foldEnd = 0; + List foldings = document.FoldingManager.GetTopLevelFoldedFoldings(); + foreach (FoldMarker fm in foldings) { + if (fm.StartLine >= foldEnd) { + if (v + fm.StartLine - foldEnd >= visibleLineNumber) { + break; + } + v += fm.StartLine - foldEnd; + foldEnd = fm.EndLine; + } + } + // help GC + foldings.Clear(); + foldings = null; + return foldEnd + visibleLineNumber - v; + } + + public int GetLastLogicalLine(int visibleLineNumber) + { + if (!document.TextEditorProperties.EnableFolding) { + return visibleLineNumber; + } + return GetFirstLogicalLine(visibleLineNumber + 1) - 1; + } + + // TODO : speedup the next/prev visible line search + // HOW? : save the foldings in a sorted list and lookup the + // line numbers in this list + public int GetNextVisibleLineAbove(int lineNumber, int lineCount) + { + int curLineNumber = lineNumber; + if (document.TextEditorProperties.EnableFolding) { + for (int i = 0; i < lineCount && curLineNumber < TotalNumberOfLines; ++i) { + ++curLineNumber; + while (curLineNumber < TotalNumberOfLines && (curLineNumber >= lineCollection.Count || !document.FoldingManager.IsLineVisible(curLineNumber))) { + ++curLineNumber; + } + } + } else { + curLineNumber += lineCount; + } + return Math.Min(TotalNumberOfLines - 1, curLineNumber); + } + + public int GetNextVisibleLineBelow(int lineNumber, int lineCount) + { + int curLineNumber = lineNumber; + if (document.TextEditorProperties.EnableFolding) { + for (int i = 0; i < lineCount; ++i) { + --curLineNumber; + while (curLineNumber >= 0 && !document.FoldingManager.IsLineVisible(curLineNumber)) { + --curLineNumber; + } + } + } else { + curLineNumber -= lineCount; + } + return Math.Max(0, curLineNumber); + } + + // use always the same DelimiterSegment object for the NextDelimiter + DelimiterSegment delimiterSegment = new DelimiterSegment(); + + DelimiterSegment NextDelimiter(string text, int offset) + { + for (int i = offset; i < text.Length; i++) { + switch (text[i]) { + case '\r': + if (i + 1 < text.Length) { + if (text[i + 1] == '\n') { + delimiterSegment.Offset = i; + delimiterSegment.Length = 2; + return delimiterSegment; + } + } + #if DATACONSISTENCYTEST + Debug.Assert(false, "Found lone \\r, data consistency problems?"); + #endif + goto case '\n'; + case '\n': + delimiterSegment.Offset = i; + delimiterSegment.Length = 1; + return delimiterSegment; + } + } + return null; + } + + void OnLineCountChanged(LineCountChangeEventArgs e) + { + if (LineCountChanged != null) { + LineCountChanged(this, e); + } + } + + void OnLineLengthChanged(LineLengthChangeEventArgs e) + { + if (LineLengthChanged != null) { + LineLengthChanged(this, e); + } + } + + void OnLineDeleted(LineEventArgs e) + { + if (LineDeleted != null) { + LineDeleted(this, e); + } + } + + public event EventHandler LineLengthChanged; + public event EventHandler LineCountChanged; + public event EventHandler LineDeleted; + + sealed class DelimiterSegment + { + internal int Offset; + internal int Length; + } + } +} diff --git a/ICSharpCode.TextEditor/Project/Src/Document/LineManager/LineManagerEventArgs.cs b/ICSharpCode.TextEditor/Project/Src/Document/LineManager/LineManagerEventArgs.cs new file mode 100644 index 0000000..dbe1909 --- /dev/null +++ b/ICSharpCode.TextEditor/Project/Src/Document/LineManager/LineManagerEventArgs.cs @@ -0,0 +1,97 @@ +// +// +// +// +// $Revision$ +// + +using System; + +namespace ICSharpCode.TextEditor.Document +{ + public class LineCountChangeEventArgs : EventArgs + { + IDocument document; + int start; + int moved; + + /// + /// always a valid Document which is related to the Event. + /// + public IDocument Document { + get { + return document; + } + } + + /// + /// -1 if no offset was specified for this event + /// + public int LineStart { + get { + return start; + } + } + + /// + /// -1 if no length was specified for this event + /// + public int LinesMoved { + get { + return moved; + } + } + + public LineCountChangeEventArgs(IDocument document, int lineStart, int linesMoved) + { + this.document = document; + this.start = lineStart; + this.moved = linesMoved; + } + } + + public class LineEventArgs : EventArgs + { + IDocument document; + LineSegment lineSegment; + + public IDocument Document { + get { return document; } + } + + public LineSegment LineSegment { + get { return lineSegment; } + } + + public LineEventArgs(IDocument document, LineSegment lineSegment) + { + this.document = document; + this.lineSegment = lineSegment; + } + + public override string ToString() + { + return string.Format("[LineEventArgs Document={0} LineSegment={1}]", this.document, this.lineSegment); + } + } + + public class LineLengthChangeEventArgs : LineEventArgs + { + int lengthDelta; + + public int LengthDelta { + get { return lengthDelta; } + } + + public LineLengthChangeEventArgs(IDocument document, LineSegment lineSegment, int moved) + : base(document, lineSegment) + { + this.lengthDelta = moved; + } + + public override string ToString() + { + return string.Format("[LineLengthEventArgs Document={0} LineSegment={1} LengthDelta={2}]", this.Document, this.LineSegment, this.lengthDelta); + } + } +} diff --git a/ICSharpCode.TextEditor/Project/Src/Document/LineManager/LineSegment.cs b/ICSharpCode.TextEditor/Project/Src/Document/LineManager/LineSegment.cs new file mode 100644 index 0000000..2370e74 --- /dev/null +++ b/ICSharpCode.TextEditor/Project/Src/Document/LineManager/LineSegment.cs @@ -0,0 +1,259 @@ +// +// +// +// +// $Revision$ +// + +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Diagnostics; +using System.Text; + +namespace ICSharpCode.TextEditor.Document +{ + public sealed class LineSegment : ISegment + { + internal LineSegmentTree.Enumerator treeEntry; + int totalLength, delimiterLength; + + List words; + SpanStack highlightSpanStack; + + public TextWord GetWord(int column) + { + int curColumn = 0; + foreach (TextWord word in words) { + if (column < curColumn + word.Length) { + return word; + } + curColumn += word.Length; + } + return null; + } + + public bool IsDeleted { + get { return !treeEntry.IsValid; } + } + + public int LineNumber { + get { return treeEntry.CurrentIndex; } + } + + public int Offset { + get { return treeEntry.CurrentOffset; } + } + + public int Length { + get { return totalLength - delimiterLength; } + } + + int ISegment.Offset { + get { return this.Offset; } + set { throw new NotSupportedException(); } + } + int ISegment.Length { + get { return this.Length; } + set { throw new NotSupportedException(); } + } + + public int TotalLength { + get { return totalLength; } + internal set { totalLength = value; } + } + + public int DelimiterLength { + get { return delimiterLength; } + internal set { delimiterLength = value; } + } + + // highlighting information + public List Words { + get { + return words; + } + set { + words = value; + } + } + + public HighlightColor GetColorForPosition(int x) + { + if (Words != null) { + int xPos = 0; + foreach (TextWord word in Words) { + if (x < xPos + word.Length) { + return word.SyntaxColor; + } + xPos += word.Length; + } + } + return new HighlightColor(Color.Black, false, false); + } + + public SpanStack HighlightSpanStack { + get { + return highlightSpanStack; + } + set { + highlightSpanStack = value; + } + } + + /// + /// Converts a instance to string (for debug purposes) + /// + public override string ToString() + { + if (IsDeleted) + return "[LineSegment: (deleted) Length = " + Length + ", TotalLength = " + TotalLength + ", DelimiterLength = " + delimiterLength + "]"; + else + return "[LineSegment: LineNumber=" + LineNumber + ", Offset = "+ Offset +", Length = " + Length + ", TotalLength = " + TotalLength + ", DelimiterLength = " + delimiterLength + "]"; + } + + #region Anchor management + Util.WeakCollection anchors; + + public TextAnchor CreateAnchor(int column) + { + if (column < 0 || column > Length) + throw new ArgumentOutOfRangeException("column"); + TextAnchor anchor = new TextAnchor(this, column); + AddAnchor(anchor); + return anchor; + } + + void AddAnchor(TextAnchor anchor) + { + Debug.Assert(anchor.Line == this); + + if (anchors == null) + anchors = new Util.WeakCollection(); + + anchors.Add(anchor); + } + + /// + /// Is called when the LineSegment is deleted. + /// + internal void Deleted(ref DeferredEventList deferredEventList) + { + //Console.WriteLine("Deleted"); + treeEntry = LineSegmentTree.Enumerator.Invalid; + if (anchors != null) { + foreach (TextAnchor a in anchors) { + a.Delete(ref deferredEventList); + } + anchors = null; + } + } + + /// + /// Is called when a part of the line is removed. + /// + internal void RemovedLinePart(ref DeferredEventList deferredEventList, int startColumn, int length) + { + if (length == 0) + return; + Debug.Assert(length > 0); + + //Console.WriteLine("RemovedLinePart " + startColumn + ", " + length); + if (anchors != null) { + List deletedAnchors = null; + foreach (TextAnchor a in anchors) { + if (a.ColumnNumber > startColumn) { + if (a.ColumnNumber >= startColumn + length) { + a.ColumnNumber -= length; + } else { + if (deletedAnchors == null) + deletedAnchors = new List(); + a.Delete(ref deferredEventList); + deletedAnchors.Add(a); + } + } + } + if (deletedAnchors != null) { + foreach (TextAnchor a in deletedAnchors) { + anchors.Remove(a); + } + } + } + } + + /// + /// Is called when a part of the line is inserted. + /// + internal void InsertedLinePart(int startColumn, int length) + { + if (length == 0) + return; + Debug.Assert(length > 0); + + //Console.WriteLine("InsertedLinePart " + startColumn + ", " + length); + if (anchors != null) { + foreach (TextAnchor a in anchors) { + if (a.MovementType == AnchorMovementType.BeforeInsertion + ? a.ColumnNumber > startColumn + : a.ColumnNumber >= startColumn) + { + a.ColumnNumber += length; + } + } + } + } + + /// + /// Is called after another line's content is appended to this line because the newline in between + /// was deleted. + /// The DefaultLineManager will call Deleted() on the deletedLine after the MergedWith call. + /// + /// firstLineLength: the length of the line before the merge. + /// + internal void MergedWith(LineSegment deletedLine, int firstLineLength) + { + //Console.WriteLine("MergedWith"); + + if (deletedLine.anchors != null) { + foreach (TextAnchor a in deletedLine.anchors) { + a.Line = this; + AddAnchor(a); + a.ColumnNumber += firstLineLength; + } + deletedLine.anchors = null; + } + } + + /// + /// Is called after a newline was inserted into this line, splitting it into this and followingLine. + /// + internal void SplitTo(LineSegment followingLine) + { + //Console.WriteLine("SplitTo"); + + if (anchors != null) { + List movedAnchors = null; + foreach (TextAnchor a in anchors) { + if (a.MovementType == AnchorMovementType.BeforeInsertion + ? a.ColumnNumber > this.Length + : a.ColumnNumber >= this.Length) + { + a.Line = followingLine; + followingLine.AddAnchor(a); + a.ColumnNumber -= this.Length; + + if (movedAnchors == null) + movedAnchors = new List(); + movedAnchors.Add(a); + } + } + if (movedAnchors != null) { + foreach (TextAnchor a in movedAnchors) { + anchors.Remove(a); + } + } + } + } + #endregion + } +} diff --git a/ICSharpCode.TextEditor/Project/Src/Document/LineManager/LineSegmentTree.cs b/ICSharpCode.TextEditor/Project/Src/Document/LineManager/LineSegmentTree.cs new file mode 100644 index 0000000..74d4c12 --- /dev/null +++ b/ICSharpCode.TextEditor/Project/Src/Document/LineManager/LineSegmentTree.cs @@ -0,0 +1,477 @@ +// +// +// +// +// $Revision$ +// + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using ICSharpCode.TextEditor.Util; + +namespace ICSharpCode.TextEditor.Document +{ + /// + /// Data structure for efficient management of the line segments (most operations are O(lg n)). + /// This implements an augmented red-black tree where each node has fields for the number of + /// nodes in its subtree (like an order statistics tree) for access by index(=line number). + /// Additionally, each node knows the total length of all segments in its subtree. + /// This means we can find nodes by offset in O(lg n) time. Since the offset itself is not stored in + /// the line segment but computed from the lengths stored in the tree, we adjusting the offsets when + /// text is inserted in one line means we just have to increment the totalLength of the affected line and + /// its parent nodes - an O(lg n) operation. + /// However this means getting the line number or offset from a LineSegment is not a constant time + /// operation, but takes O(lg n). + /// + /// NOTE: The tree is never empty, Clear() causes it to contain an empty segment. + /// + sealed class LineSegmentTree : IList + { + internal struct RBNode + { + internal LineSegment lineSegment; + internal int count; + internal int totalLength; + + public RBNode(LineSegment lineSegment) + { + this.lineSegment = lineSegment; + this.count = 1; + this.totalLength = lineSegment.TotalLength; + } + + public override string ToString() + { + return "[RBNode count=" + count + " totalLength="+totalLength + + " lineSegment.LineNumber=" + lineSegment.LineNumber + + " lineSegment.Offset=" + lineSegment.Offset + + " lineSegment.TotalLength=" + lineSegment.TotalLength + + " lineSegment.DelimiterLength=" + lineSegment.DelimiterLength + "]"; + } + } + + struct MyHost : IRedBlackTreeHost + { + public int Compare(RBNode x, RBNode y) + { + throw new NotImplementedException(); + } + + public bool Equals(RBNode a, RBNode b) + { + throw new NotImplementedException(); + } + + public void UpdateAfterChildrenChange(RedBlackTreeNode node) + { + int count = 1; + int totalLength = node.val.lineSegment.TotalLength; + if (node.left != null) { + count += node.left.val.count; + totalLength += node.left.val.totalLength; + } + if (node.right != null) { + count += node.right.val.count; + totalLength += node.right.val.totalLength; + } + if (count != node.val.count || totalLength != node.val.totalLength) { + node.val.count = count; + node.val.totalLength = totalLength; + if (node.parent != null) UpdateAfterChildrenChange(node.parent); + } + } + + public void UpdateAfterRotateLeft(RedBlackTreeNode node) + { + UpdateAfterChildrenChange(node); + UpdateAfterChildrenChange(node.parent); + } + + public void UpdateAfterRotateRight(RedBlackTreeNode node) + { + UpdateAfterChildrenChange(node); + UpdateAfterChildrenChange(node.parent); + } + } + + readonly AugmentableRedBlackTree tree = new AugmentableRedBlackTree(new MyHost()); + + RedBlackTreeNode GetNode(int index) + { + if (index < 0 || index >= tree.Count) + throw new ArgumentOutOfRangeException("index", index, "index should be between 0 and " + (tree.Count-1)); + RedBlackTreeNode node = tree.root; + while (true) { + if (node.left != null && index < node.left.val.count) { + node = node.left; + } else { + if (node.left != null) { + index -= node.left.val.count; + } + if (index == 0) + return node; + index--; + node = node.right; + } + } + } + + static int GetIndexFromNode(RedBlackTreeNode node) + { + int index = (node.left != null) ? node.left.val.count : 0; + while (node.parent != null) { + if (node == node.parent.right) { + if (node.parent.left != null) + index += node.parent.left.val.count; + index++; + } + node = node.parent; + } + return index; + } + + RedBlackTreeNode GetNodeByOffset(int offset) + { + if (offset < 0 || offset > this.TotalLength) + throw new ArgumentOutOfRangeException("offset", offset, "offset should be between 0 and " + this.TotalLength); + if (offset == this.TotalLength) { + if (tree.root == null) + throw new InvalidOperationException("Cannot call GetNodeByOffset while tree is empty."); + return tree.root.RightMost; + } + RedBlackTreeNode node = tree.root; + while (true) { + if (node.left != null && offset < node.left.val.totalLength) { + node = node.left; + } else { + if (node.left != null) { + offset -= node.left.val.totalLength; + } + offset -= node.val.lineSegment.TotalLength; + if (offset < 0) + return node; + node = node.right; + } + } + } + + static int GetOffsetFromNode(RedBlackTreeNode node) + { + int offset = (node.left != null) ? node.left.val.totalLength : 0; + while (node.parent != null) { + if (node == node.parent.right) { + if (node.parent.left != null) + offset += node.parent.left.val.totalLength; + offset += node.parent.val.lineSegment.TotalLength; + } + node = node.parent; + } + return offset; + } + + public LineSegment GetByOffset(int offset) + { + return GetNodeByOffset(offset).val.lineSegment; + } + + /// + /// Gets the total length of all line segments. Runs in O(1). + /// + public int TotalLength { + get { + if (tree.root == null) + return 0; + else + return tree.root.val.totalLength; + } + } + + /// + /// Updates the length of a line segment. Runs in O(lg n). + /// + public void SetSegmentLength(LineSegment segment, int newTotalLength) + { + if (segment == null) + throw new ArgumentNullException("segment"); + RedBlackTreeNode node = segment.treeEntry.it.node; + segment.TotalLength = newTotalLength; + default(MyHost).UpdateAfterChildrenChange(node); + #if DEBUG + CheckProperties(); + #endif + } + + public void RemoveSegment(LineSegment segment) + { + tree.RemoveAt(segment.treeEntry.it); + #if DEBUG + CheckProperties(); + #endif + } + + public LineSegment InsertSegmentAfter(LineSegment segment, int length) + { + LineSegment newSegment = new LineSegment(); + newSegment.TotalLength = length; + newSegment.DelimiterLength = segment.DelimiterLength; + + newSegment.treeEntry = InsertAfter(segment.treeEntry.it.node, newSegment); + return newSegment; + } + + Enumerator InsertAfter(RedBlackTreeNode node, LineSegment newSegment) + { + RedBlackTreeNode newNode = new RedBlackTreeNode(new RBNode(newSegment)); + if (node.right == null) { + tree.InsertAsRight(node, newNode); + } else { + tree.InsertAsLeft(node.right.LeftMost, newNode); + } + #if DEBUG + CheckProperties(); + #endif + return new Enumerator(new RedBlackTreeIterator(newNode)); + } + + /// + /// Gets the number of items in the collections. Runs in O(1). + /// + public int Count { + get { return tree.Count; } + } + + /// + /// Gets or sets an item by index. Runs in O(lg n). + /// + public LineSegment this[int index] { + get { + return GetNode(index).val.lineSegment; + } + set { + throw new NotSupportedException(); + } + } + + bool ICollection.IsReadOnly { + get { return true; } + } + + /// + /// Gets the index of an item. Runs in O(lg n). + /// + public int IndexOf(LineSegment item) + { + int index = item.LineNumber; + if (index < 0 || index >= this.Count) + return -1; + if (item != this[index]) + return -1; + return index; + } + + void IList.RemoveAt(int index) + { + throw new NotSupportedException(); + } + + #if DEBUG + [Conditional("DATACONSISTENCYTEST")] + void CheckProperties() + { + if (tree.root == null) { + Debug.Assert(this.Count == 0); + } else { + Debug.Assert(tree.root.val.count == this.Count); + CheckProperties(tree.root); + } + } + + void CheckProperties(RedBlackTreeNode node) + { + int count = 1; + int totalLength = node.val.lineSegment.TotalLength; + if (node.left != null) { + CheckProperties(node.left); + count += node.left.val.count; + totalLength += node.left.val.totalLength; + } + if (node.right != null) { + CheckProperties(node.right); + count += node.right.val.count; + totalLength += node.right.val.totalLength; + } + Debug.Assert(node.val.count == count); + Debug.Assert(node.val.totalLength == totalLength); + } + + public string GetTreeAsString() + { + return tree.GetTreeAsString(); + } + #endif + + public LineSegmentTree() + { + Clear(); + } + + /// + /// Clears the list. Runs in O(1). + /// + public void Clear() + { + tree.Clear(); + LineSegment emptySegment = new LineSegment(); + emptySegment.TotalLength = 0; + emptySegment.DelimiterLength = 0; + tree.Add(new RBNode(emptySegment)); + emptySegment.treeEntry = GetEnumeratorForIndex(0); + #if DEBUG + CheckProperties(); + #endif + } + + /// + /// Tests whether an item is in the list. Runs in O(n). + /// + public bool Contains(LineSegment item) + { + return IndexOf(item) >= 0; + } + + /// + /// Copies all elements from the list to the array. + /// + public void CopyTo(LineSegment[] array, int arrayIndex) + { + if (array == null) throw new ArgumentNullException("array"); + foreach (LineSegment val in this) + array[arrayIndex++] = val; + } + + IEnumerator IEnumerable.GetEnumerator() + { + return this.GetEnumerator(); + } + + System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() + { + return this.GetEnumerator(); + } + + public Enumerator GetEnumerator() + { + return new Enumerator(tree.GetEnumerator()); + } + + public Enumerator GetEnumeratorForIndex(int index) + { + return new Enumerator(new RedBlackTreeIterator(GetNode(index))); + } + + public Enumerator GetEnumeratorForOffset(int offset) + { + return new Enumerator(new RedBlackTreeIterator(GetNodeByOffset(offset))); + } + + public struct Enumerator : IEnumerator + { + /// + /// An invalid enumerator value. Calling MoveNext on the invalid enumerator + /// will always return false, accessing Current will throw an exception. + /// + public static readonly Enumerator Invalid = default(Enumerator); + + internal RedBlackTreeIterator it; + + internal Enumerator(RedBlackTreeIterator it) + { + this.it = it; + } + + /// + /// Gets the current value. Runs in O(1). + /// + public LineSegment Current { + get { + return it.Current.lineSegment; + } + } + + public bool IsValid { + get { + return it.IsValid; + } + } + + /// + /// Gets the index of the current value. Runs in O(lg n). + /// + public int CurrentIndex { + get { + if (it.node == null) + throw new InvalidOperationException(); + return GetIndexFromNode(it.node); + } + } + + /// + /// Gets the offset of the current value. Runs in O(lg n). + /// + public int CurrentOffset { + get { + if (it.node == null) + throw new InvalidOperationException(); + return GetOffsetFromNode(it.node); + } + } + + object System.Collections.IEnumerator.Current { + get { + return it.Current.lineSegment; + } + } + + public void Dispose() + { + } + + /// + /// Moves to the next index. Runs in O(lg n), but for k calls, the combined time is only O(k+lg n). + /// + public bool MoveNext() + { + return it.MoveNext(); + } + + /// + /// Moves to the previous index. Runs in O(lg n), but for k calls, the combined time is only O(k+lg n). + /// + public bool MoveBack() + { + return it.MoveBack(); + } + + void System.Collections.IEnumerator.Reset() + { + throw new NotSupportedException(); + } + } + + void IList.Insert(int index, LineSegment item) + { + throw new NotSupportedException(); + } + + void ICollection.Add(LineSegment item) + { + throw new NotSupportedException(); + } + + bool ICollection.Remove(LineSegment item) + { + throw new NotSupportedException(); + } + } +} diff --git a/ICSharpCode.TextEditor/Project/Src/Document/MarkerStrategy/MarkerStrategy.cs b/ICSharpCode.TextEditor/Project/Src/Document/MarkerStrategy/MarkerStrategy.cs new file mode 100644 index 0000000..9196c30 --- /dev/null +++ b/ICSharpCode.TextEditor/Project/Src/Document/MarkerStrategy/MarkerStrategy.cs @@ -0,0 +1,119 @@ +// +// +// +// +// $Revision$ +// + +using System; +using System.Collections.Generic; +using System.Drawing; + +namespace ICSharpCode.TextEditor.Document +{ + /// + /// Manages the list of markers and provides ways to retrieve markers for specific positions. + /// + public sealed class MarkerStrategy + { + List textMarker = new List(); + IDocument document; + + public IDocument Document { + get { + return document; + } + } + + public IEnumerable TextMarker { + get { + return textMarker.AsReadOnly(); + } + } + + public void AddMarker(TextMarker item) + { + markersTable.Clear(); + textMarker.Add(item); + } + + public void InsertMarker(int index, TextMarker item) + { + markersTable.Clear(); + textMarker.Insert(index, item); + } + + public void RemoveMarker(TextMarker item) + { + markersTable.Clear(); + textMarker.Remove(item); + } + + public void RemoveAll(Predicate match) + { + markersTable.Clear(); + textMarker.RemoveAll(match); + } + + public MarkerStrategy(IDocument document) + { + this.document = document; + document.DocumentChanged += new DocumentEventHandler(DocumentChanged); + } + + Dictionary> markersTable = new Dictionary>(); + + public List GetMarkers(int offset) + { + if (!markersTable.ContainsKey(offset)) { + List markers = new List(); + for (int i = 0; i < textMarker.Count; ++i) { + TextMarker marker = (TextMarker)textMarker[i]; + if (marker.Offset <= offset && offset <= marker.EndOffset) { + markers.Add(marker); + } + } + markersTable[offset] = markers; + } + return markersTable[offset]; + } + + public List GetMarkers(int offset, int length) + { + int endOffset = offset + length - 1; + List markers = new List(); + for (int i = 0; i < textMarker.Count; ++i) { + TextMarker marker = (TextMarker)textMarker[i]; + if (// start in marker region + marker.Offset <= offset && offset <= marker.EndOffset || + // end in marker region + marker.Offset <= endOffset && endOffset <= marker.EndOffset || + // marker start in region + offset <= marker.Offset && marker.Offset <= endOffset || + // marker end in region + offset <= marker.EndOffset && marker.EndOffset <= endOffset + ) + { + markers.Add(marker); + } + } + return markers; + } + + public List GetMarkers(TextLocation position) + { + if (position.Y >= document.TotalNumberOfLines || position.Y < 0) { + return new List(); + } + LineSegment segment = document.GetLineSegment(position.Y); + return GetMarkers(segment.Offset + position.X); + } + + void DocumentChanged(object sender, DocumentEventArgs e) + { + // reset markers table + markersTable.Clear(); + document.UpdateSegmentListOnDocumentChange(textMarker, e); + } + } +} diff --git a/ICSharpCode.TextEditor/Project/Src/Document/MarkerStrategy/TextMarker.cs b/ICSharpCode.TextEditor/Project/Src/Document/MarkerStrategy/TextMarker.cs new file mode 100644 index 0000000..f92f3d9 --- /dev/null +++ b/ICSharpCode.TextEditor/Project/Src/Document/MarkerStrategy/TextMarker.cs @@ -0,0 +1,103 @@ +// +// +// +// +// $Revision$ +// + +using System; +using System.Drawing; + +namespace ICSharpCode.TextEditor.Document +{ + public enum TextMarkerType + { + Invisible, + SolidBlock, + Underlined, + WaveLine + } + + /// + /// Marks a part of a document. + /// + public class TextMarker : AbstractSegment + { + TextMarkerType textMarkerType; + Color color; + Color foreColor; + string toolTip = null; + bool overrideForeColor = false; + + public TextMarkerType TextMarkerType { + get { + return textMarkerType; + } + } + + public Color Color { + get { + return color; + } + } + + public Color ForeColor { + get { + return foreColor; + } + } + + public bool OverrideForeColor { + get { + return overrideForeColor; + } + } + + /// + /// Marks the text segment as read-only. + /// + public bool IsReadOnly { get; set; } + + public string ToolTip { + get { + return toolTip; + } + set { + toolTip = value; + } + } + + /// + /// Gets the last offset that is inside the marker region. + /// + public int EndOffset { + get { + return Offset + Length - 1; + } + } + + public TextMarker(int offset, int length, TextMarkerType textMarkerType) : this(offset, length, textMarkerType, Color.Red) + { + } + + public TextMarker(int offset, int length, TextMarkerType textMarkerType, Color color) + { + if (length < 1) length = 1; + this.offset = offset; + this.length = length; + this.textMarkerType = textMarkerType; + this.color = color; + } + + public TextMarker(int offset, int length, TextMarkerType textMarkerType, Color color, Color foreColor) + { + if (length < 1) length = 1; + this.offset = offset; + this.length = length; + this.textMarkerType = textMarkerType; + this.color = color; + this.foreColor = foreColor; + this.overrideForeColor = true; + } + } +} diff --git a/ICSharpCode.TextEditor/Project/Src/Document/Selection/ColumnRange.cs b/ICSharpCode.TextEditor/Project/Src/Document/Selection/ColumnRange.cs new file mode 100644 index 0000000..594538a --- /dev/null +++ b/ICSharpCode.TextEditor/Project/Src/Document/Selection/ColumnRange.cs @@ -0,0 +1,66 @@ +// +// +// +// +// $Revision$ +// + +using System; + +namespace ICSharpCode.TextEditor.Document +{ + public class ColumnRange + { + public static readonly ColumnRange NoColumn = new ColumnRange(-2, -2); + public static readonly ColumnRange WholeColumn = new ColumnRange(-1, -1); + + int startColumn; + int endColumn; + + public int StartColumn { + get { + return startColumn; + } + set { + startColumn = value; + } + } + + public int EndColumn { + get { + return endColumn; + } + set { + endColumn = value; + } + } + + public ColumnRange(int startColumn, int endColumn) + { + this.startColumn = startColumn; + this.endColumn = endColumn; + + } + + public override int GetHashCode() + { + return startColumn + (endColumn << 16); + } + + public override bool Equals(object obj) + { + if (obj is ColumnRange) { + return ((ColumnRange)obj).startColumn == startColumn && + ((ColumnRange)obj).endColumn == endColumn; + + } + return false; + } + + public override string ToString() + { + return string.Format("[ColumnRange: StartColumn={0}, EndColumn={1}]", startColumn, endColumn); + } + + } +} diff --git a/ICSharpCode.TextEditor/Project/Src/Document/Selection/DefaultSelection.cs b/ICSharpCode.TextEditor/Project/Src/Document/Selection/DefaultSelection.cs new file mode 100644 index 0000000..38c7637 --- /dev/null +++ b/ICSharpCode.TextEditor/Project/Src/Document/Selection/DefaultSelection.cs @@ -0,0 +1,133 @@ +// +// +// +// +// $Revision$ +// + +using System; +using System.Diagnostics; +using System.Drawing; + +namespace ICSharpCode.TextEditor.Document +{ + /// + /// Default implementation of the interface. + /// + public class DefaultSelection : ISelection + { + IDocument document; + bool isRectangularSelection; + TextLocation startPosition; + TextLocation endPosition; + + public TextLocation StartPosition { + get { + return startPosition; + } + set { + DefaultDocument.ValidatePosition(document, value); + startPosition = value; + } + } + + public TextLocation EndPosition { + get { + return endPosition; + } + set { + DefaultDocument.ValidatePosition(document, value); + endPosition = value; + } + } + + public int Offset { + get { + return document.PositionToOffset(startPosition); + } + } + + public int EndOffset { + get { + return document.PositionToOffset(endPosition); + } + } + + public int Length { + get { + return EndOffset - Offset; + } + } + + /// + /// Returns true, if the selection is empty + /// + public bool IsEmpty { + get { + return startPosition == endPosition; + } + } + + /// + /// Returns true, if the selection is rectangular + /// + // TODO : make this unused property used. + public bool IsRectangularSelection { + get { + return isRectangularSelection; + } + set { + isRectangularSelection = value; + } + } + + /// + /// The text which is selected by this selection. + /// + public string SelectedText { + get { + if (document != null) { + if (Length < 0) { + return null; + } + return document.GetText(Offset, Length); + } + return null; + } + } + + /// + /// Creates a new instance of + /// + public DefaultSelection(IDocument document, TextLocation startPosition, TextLocation endPosition) + { + DefaultDocument.ValidatePosition(document, startPosition); + DefaultDocument.ValidatePosition(document, endPosition); + Debug.Assert(startPosition <= endPosition); + this.document = document; + this.startPosition = startPosition; + this.endPosition = endPosition; + } + + /// + /// Converts a instance to string (for debug purposes) + /// + public override string ToString() + { + return string.Format("[DefaultSelection : StartPosition={0}, EndPosition={1}]", startPosition, endPosition); + } + public bool ContainsPosition(TextLocation position) + { + if (this.IsEmpty) + return false; + return startPosition.Y < position.Y && position.Y < endPosition.Y || + startPosition.Y == position.Y && startPosition.X <= position.X && (startPosition.Y != endPosition.Y || position.X <= endPosition.X) || + endPosition.Y == position.Y && startPosition.Y != endPosition.Y && position.X <= endPosition.X; + } + + public bool ContainsOffset(int offset) + { + return Offset <= offset && offset <= EndOffset; + } + } +} diff --git a/ICSharpCode.TextEditor/Project/Src/Document/Selection/ISelection.cs b/ICSharpCode.TextEditor/Project/Src/Document/Selection/ISelection.cs new file mode 100644 index 0000000..702fe5f --- /dev/null +++ b/ICSharpCode.TextEditor/Project/Src/Document/Selection/ISelection.cs @@ -0,0 +1,64 @@ +// +// +// +// +// $Revision$ +// + +using System.Drawing; + +namespace ICSharpCode.TextEditor.Document +{ + /// + /// An interface representing a portion of the current selection. + /// + public interface ISelection + { + TextLocation StartPosition { + get; + set; + } + + TextLocation EndPosition { + get; + set; + } + + int Offset { + get; + } + + int EndOffset { + get; + } + + int Length { + get; + } + + /// + /// Returns true, if the selection is rectangular + /// + bool IsRectangularSelection { + get; + } + + /// + /// Returns true, if the selection is empty + /// + bool IsEmpty { + get; + } + + /// + /// The text which is selected by this selection. + /// + string SelectedText { + get; + } + + bool ContainsOffset(int offset); + + bool ContainsPosition(TextLocation position); + } +} diff --git a/ICSharpCode.TextEditor/Project/Src/Document/Selection/SelectionManager.cs b/ICSharpCode.TextEditor/Project/Src/Document/Selection/SelectionManager.cs new file mode 100644 index 0000000..0783160 --- /dev/null +++ b/ICSharpCode.TextEditor/Project/Src/Document/Selection/SelectionManager.cs @@ -0,0 +1,466 @@ +// +// +// +// +// $Revision$ +// + +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Text; + +namespace ICSharpCode.TextEditor.Document +{ + /// + /// This class manages the selections in a document. + /// + public class SelectionManager : IDisposable + { + TextLocation selectionStart; + + internal TextLocation SelectionStart { + get { return selectionStart; } + set { + DefaultDocument.ValidatePosition(document, value); + selectionStart = value; + } + } + IDocument document; + TextArea textArea; + internal SelectFrom selectFrom = new SelectFrom(); + + internal List selectionCollection = new List(); + + /// + /// A collection containing all selections. + /// + public List SelectionCollection { + get { + return selectionCollection; + } + } + + /// + /// true if the is not empty, false otherwise. + /// + public bool HasSomethingSelected { + get { + return selectionCollection.Count > 0; + } + } + + public bool SelectionIsReadonly { + get { + if (document.ReadOnly) + return true; + foreach (ISelection sel in selectionCollection) { + if (SelectionIsReadOnly(document, sel)) + return true; + } + return false; + } + } + + internal static bool SelectionIsReadOnly(IDocument document, ISelection sel) + { + if (document.TextEditorProperties.SupportReadOnlySegments) + return document.MarkerStrategy.GetMarkers(sel.Offset, sel.Length).Exists(m=>m.IsReadOnly); + else + return false; + } + + /// + /// The text that is currently selected. + /// + public string SelectedText { + get { + StringBuilder builder = new StringBuilder(); + +// PriorityQueue queue = new PriorityQueue(); + + foreach (ISelection s in selectionCollection) { + builder.Append(s.SelectedText); +// queue.Insert(-s.Offset, s); + } + +// while (queue.Count > 0) { +// ISelection s = ((ISelection)queue.Remove()); +// builder.Append(s.SelectedText); +// } + + return builder.ToString(); + } + } + + /// + /// Creates a new instance of + /// + public SelectionManager(IDocument document) + { + this.document = document; + document.DocumentChanged += new DocumentEventHandler(DocumentChanged); + } + + /// + /// Creates a new instance of + /// + public SelectionManager(IDocument document, TextArea textArea) + { + this.document = document; + this.textArea = textArea; + document.DocumentChanged += new DocumentEventHandler(DocumentChanged); + } + + public void Dispose() + { + if (this.document != null) { + document.DocumentChanged -= new DocumentEventHandler(DocumentChanged); + this.document = null; + } + } + + void DocumentChanged(object sender, DocumentEventArgs e) + { + if (e.Text == null) { + Remove(e.Offset, e.Length); + } else { + if (e.Length < 0) { + Insert(e.Offset, e.Text); + } else { + Replace(e.Offset, e.Length, e.Text); + } + } + } + + /// + /// Clears the selection and sets a new selection + /// using the given object. + /// + public void SetSelection(ISelection selection) + { +// autoClearSelection = false; + if (selection != null) { + if (SelectionCollection.Count == 1 && + selection.StartPosition == SelectionCollection[0].StartPosition && + selection.EndPosition == SelectionCollection[0].EndPosition ) { + return; + } + ClearWithoutUpdate(); + selectionCollection.Add(selection); + document.RequestUpdate(new TextAreaUpdate(TextAreaUpdateType.LinesBetween, selection.StartPosition.Y, selection.EndPosition.Y)); + document.CommitUpdate(); + OnSelectionChanged(EventArgs.Empty); + } else { + ClearSelection(); + } + } + + public void SetSelection(TextLocation startPosition, TextLocation endPosition) + { + SetSelection(new DefaultSelection(document, startPosition, endPosition)); + } + + public bool GreaterEqPos(TextLocation p1, TextLocation p2) + { + return p1.Y > p2.Y || p1.Y == p2.Y && p1.X >= p2.X; + } + + public void ExtendSelection(TextLocation oldPosition, TextLocation newPosition) + { + // where oldposition is where the cursor was, + // and newposition is where it has ended up from a click (both zero based) + + if (oldPosition == newPosition) + { + return; + } + + TextLocation min; + TextLocation max; + int oldnewX = newPosition.X; + bool oldIsGreater = GreaterEqPos(oldPosition, newPosition); + if (oldIsGreater) { + min = newPosition; + max = oldPosition; + } else { + min = oldPosition; + max = newPosition; + } + + if (min == max) { + return; + } + + if (!HasSomethingSelected) + { + SetSelection(new DefaultSelection(document, min, max)); + // initialise selectFrom for a cursor selection + if (selectFrom.where == WhereFrom.None) + SelectionStart = oldPosition; //textArea.Caret.Position; + return; + } + + ISelection selection = this.selectionCollection[0]; + + if (min == max) { + //selection.StartPosition = newPosition; + return; + } else { + // changed selection via gutter + if (selectFrom.where == WhereFrom.Gutter) + { + // selection new position is always at the left edge for gutter selections + newPosition.X = 0; + } + + if (GreaterEqPos(newPosition, SelectionStart)) // selecting forward + { + selection.StartPosition = SelectionStart; + // this handles last line selection + if (selectFrom.where == WhereFrom.Gutter ) //&& newPosition.Y != oldPosition.Y) + selection.EndPosition = new TextLocation(textArea.Caret.Column, textArea.Caret.Line); + else { + newPosition.X = oldnewX; + selection.EndPosition = newPosition; + } + } else { // selecting back + if (selectFrom.where == WhereFrom.Gutter && selectFrom.first == WhereFrom.Gutter) + { // gutter selection + selection.EndPosition = NextValidPosition(SelectionStart.Y); + } else { // internal text selection + selection.EndPosition = SelectionStart; //selection.StartPosition; + } + selection.StartPosition = newPosition; + } + } + + document.RequestUpdate(new TextAreaUpdate(TextAreaUpdateType.LinesBetween, min.Y, max.Y)); + document.CommitUpdate(); + OnSelectionChanged(EventArgs.Empty); + } + + // retrieve the next available line + // - checks that there are more lines available after the current one + // - if there are then the next line is returned + // - if there are NOT then the last position on the given line is returned + public TextLocation NextValidPosition(int line) + { + if (line < document.TotalNumberOfLines - 1) + return new TextLocation(0, line + 1); + else + return new TextLocation(document.GetLineSegment(document.TotalNumberOfLines - 1).Length + 1, line); + } + + void ClearWithoutUpdate() + { + while (selectionCollection.Count > 0) { + ISelection selection = selectionCollection[selectionCollection.Count - 1]; + selectionCollection.RemoveAt(selectionCollection.Count - 1); + document.RequestUpdate(new TextAreaUpdate(TextAreaUpdateType.LinesBetween, selection.StartPosition.Y, selection.EndPosition.Y)); + OnSelectionChanged(EventArgs.Empty); + } + } + /// + /// Clears the selection. + /// + public void ClearSelection() + { + Point mousepos; + mousepos = textArea.mousepos; + // this is the most logical place to reset selection starting + // positions because it is always called before a new selection + selectFrom.first = selectFrom.where; + TextLocation newSelectionStart = textArea.TextView.GetLogicalPosition(mousepos.X - textArea.TextView.DrawingPosition.X, mousepos.Y - textArea.TextView.DrawingPosition.Y); + if (selectFrom.where == WhereFrom.Gutter) { + newSelectionStart.X = 0; +// selectionStart.Y = -1; + } + if (newSelectionStart.Line >= document.TotalNumberOfLines) { + newSelectionStart.Line = document.TotalNumberOfLines-1; + newSelectionStart.Column = document.GetLineSegment(document.TotalNumberOfLines-1).Length; + } + this.SelectionStart = newSelectionStart; + + ClearWithoutUpdate(); + document.CommitUpdate(); + } + + /// + /// Removes the selected text from the buffer and clears + /// the selection. + /// + public void RemoveSelectedText() + { + if (SelectionIsReadonly) { + ClearSelection(); + return; + } + List lines = new List(); + int offset = -1; + bool oneLine = true; +// PriorityQueue queue = new PriorityQueue(); + foreach (ISelection s in selectionCollection) { +// ISelection s = ((ISelection)queue.Remove()); + if (oneLine) { + int lineBegin = s.StartPosition.Y; + if (lineBegin != s.EndPosition.Y) { + oneLine = false; + } else { + lines.Add(lineBegin); + } + } + offset = s.Offset; + document.Remove(s.Offset, s.Length); + +// queue.Insert(-s.Offset, s); + } + ClearSelection(); + if (offset >= 0) { + // TODO: +// document.Caret.Offset = offset; + } + if (offset != -1) { + if (oneLine) { + foreach (int i in lines) { + document.RequestUpdate(new TextAreaUpdate(TextAreaUpdateType.SingleLine, i)); + } + } else { + document.RequestUpdate(new TextAreaUpdate(TextAreaUpdateType.WholeTextArea)); + } + document.CommitUpdate(); + } + } + + + bool SelectionsOverlap(ISelection s1, ISelection s2) + { + return (s1.Offset <= s2.Offset && s2.Offset <= s1.Offset + s1.Length) || + (s1.Offset <= s2.Offset + s2.Length && s2.Offset + s2.Length <= s1.Offset + s1.Length) || + (s1.Offset >= s2.Offset && s1.Offset + s1.Length <= s2.Offset + s2.Length); + } + + /// + /// Returns true if the given offset points to a section which is + /// selected. + /// + public bool IsSelected(int offset) + { + return GetSelectionAt(offset) != null; + } + + /// + /// Returns a object giving the selection in which + /// the offset points to. + /// + /// + /// null if the offset doesn't point to a selection + /// + public ISelection GetSelectionAt(int offset) + { + foreach (ISelection s in selectionCollection) { + if (s.ContainsOffset(offset)) { + return s; + } + } + return null; + } + + /// + /// Used internally, do not call. + /// + internal void Insert(int offset, string text) + { +// foreach (ISelection selection in SelectionCollection) { +// if (selection.Offset > offset) { +// selection.Offset += text.Length; +// } else if (selection.Offset + selection.Length > offset) { +// selection.Length += text.Length; +// } +// } + } + + /// + /// Used internally, do not call. + /// + internal void Remove(int offset, int length) + { +// foreach (ISelection selection in selectionCollection) { +// if (selection.Offset > offset) { +// selection.Offset -= length; +// } else if (selection.Offset + selection.Length > offset) { +// selection.Length -= length; +// } +// } + } + + /// + /// Used internally, do not call. + /// + internal void Replace(int offset, int length, string text) + { +// foreach (ISelection selection in selectionCollection) { +// if (selection.Offset > offset) { +// selection.Offset = selection.Offset - length + text.Length; +// } else if (selection.Offset + selection.Length > offset) { +// selection.Length = selection.Length - length + text.Length; +// } +// } + } + + public ColumnRange GetSelectionAtLine(int lineNumber) + { + foreach (ISelection selection in selectionCollection) { + int startLine = selection.StartPosition.Y; + int endLine = selection.EndPosition.Y; + if (startLine < lineNumber && lineNumber < endLine) { + return ColumnRange.WholeColumn; + } + + if (startLine == lineNumber) { + LineSegment line = document.GetLineSegment(startLine); + int startColumn = selection.StartPosition.X; + int endColumn = endLine == lineNumber ? selection.EndPosition.X : line.Length + 1; + return new ColumnRange(startColumn, endColumn); + } + + if (endLine == lineNumber) { + int endColumn = selection.EndPosition.X; + return new ColumnRange(0, endColumn); + } + } + + return ColumnRange.NoColumn; + } + + public void FireSelectionChanged() + { + OnSelectionChanged(EventArgs.Empty); + } + protected virtual void OnSelectionChanged(EventArgs e) + { + if (SelectionChanged != null) { + SelectionChanged(this, e); + } + } + + public event EventHandler SelectionChanged; + } + + // selection initiated from... + internal class SelectFrom { + public int where = WhereFrom.None; // last selection initiator + public int first = WhereFrom.None; // first selection initiator + + public SelectFrom() + { + } + } + + // selection initiated from type... + internal class WhereFrom { + public const int None = 0; + public const int Gutter = 1; + public const int TArea = 2; + } +} diff --git a/ICSharpCode.TextEditor/Project/Src/Document/TextAnchor.cs b/ICSharpCode.TextEditor/Project/Src/Document/TextAnchor.cs new file mode 100644 index 0000000..8747e3c --- /dev/null +++ b/ICSharpCode.TextEditor/Project/Src/Document/TextAnchor.cs @@ -0,0 +1,118 @@ +// +// +// +// +// $Revision$ +// + +using System; + +namespace ICSharpCode.TextEditor.Document +{ + public enum AnchorMovementType + { + /// + /// Behaves like a start marker - when text is inserted at the anchor position, the anchor will stay + /// before the inserted text. + /// + BeforeInsertion, + /// + /// Behave like an end marker - when text is insered at the anchor position, the anchor will move + /// after the inserted text. + /// + AfterInsertion + } + + /// + /// An anchor that can be put into a document and moves around when the document is changed. + /// + public sealed class TextAnchor + { + static Exception AnchorDeletedError() + { + return new InvalidOperationException("The text containing the anchor was deleted"); + } + + LineSegment lineSegment; + int columnNumber; + + public LineSegment Line { + get { + if (lineSegment == null) throw AnchorDeletedError(); + return lineSegment; + } + internal set { + lineSegment = value; + } + } + + public bool IsDeleted { + get { + return lineSegment == null; + } + } + + public int LineNumber { + get { + return this.Line.LineNumber; + } + } + + public int ColumnNumber { + get { + if (lineSegment == null) throw AnchorDeletedError(); + return columnNumber; + } + internal set { + columnNumber = value; + } + } + + public TextLocation Location { + get { + return new TextLocation(this.ColumnNumber, this.LineNumber); + } + } + + public int Offset { + get { + return this.Line.Offset + columnNumber; + } + } + + /// + /// Controls how the anchor moves. + /// + public AnchorMovementType MovementType { get; set; } + + public event EventHandler Deleted; + + internal void Delete(ref DeferredEventList deferredEventList) + { + // we cannot fire an event here because this method is called while the LineManager adjusts the + // lineCollection, so an event handler could see inconsistent state + lineSegment = null; + deferredEventList.AddDeletedAnchor(this); + } + + internal void RaiseDeleted() + { + if (Deleted != null) + Deleted(this, EventArgs.Empty); + } + + internal TextAnchor(LineSegment lineSegment, int columnNumber) + { + this.lineSegment = lineSegment; + this.columnNumber = columnNumber; + } + + public override string ToString() + { + if (this.IsDeleted) + return "[TextAnchor (deleted)]"; + else + return "[TextAnchor " + this.Location.ToString() + "]"; + } + } +} diff --git a/ICSharpCode.TextEditor/Project/Src/Document/TextBufferStrategy/GapTextBufferStrategy.cs b/ICSharpCode.TextEditor/Project/Src/Document/TextBufferStrategy/GapTextBufferStrategy.cs new file mode 100644 index 0000000..7fe1c9d --- /dev/null +++ b/ICSharpCode.TextEditor/Project/Src/Document/TextBufferStrategy/GapTextBufferStrategy.cs @@ -0,0 +1,194 @@ +// +// +// +// +// $Revision$ +// + +using System; +using System.Text; + +namespace ICSharpCode.TextEditor.Document +{ + public class GapTextBufferStrategy : ITextBufferStrategy + { + #if DEBUG + int creatorThread = System.Threading.Thread.CurrentThread.ManagedThreadId; + + void CheckThread() + { + if (System.Threading.Thread.CurrentThread.ManagedThreadId != creatorThread) + throw new InvalidOperationException("GapTextBufferStategy is not thread-safe!"); + } + #endif + + char[] buffer = new char[0]; + string cachedContent; + + int gapBeginOffset = 0; + int gapEndOffset = 0; + int gapLength = 0; // gapLength == gapEndOffset - gapBeginOffset + + const int minGapLength = 128; + const int maxGapLength = 2048; + + public int Length { + get { + return buffer.Length - gapLength; + } + } + + public void SetContent(string text) + { + if (text == null) { + text = string.Empty; + } + cachedContent = text; + buffer = text.ToCharArray(); + gapBeginOffset = gapEndOffset = gapLength = 0; + } + + public char GetCharAt(int offset) + { + #if DEBUG + CheckThread(); + #endif + + if (offset < 0 || offset >= Length) { + throw new ArgumentOutOfRangeException("offset", offset, "0 <= offset < " + Length.ToString()); + } + + return offset < gapBeginOffset ? buffer[offset] : buffer[offset + gapLength]; + } + + public string GetText(int offset, int length) + { + #if DEBUG + CheckThread(); + #endif + + if (offset < 0 || offset > Length) { + throw new ArgumentOutOfRangeException("offset", offset, "0 <= offset <= " + Length.ToString()); + } + if (length < 0 || offset + length > Length) { + throw new ArgumentOutOfRangeException("length", length, "0 <= length, offset(" + offset + ")+length <= " + Length.ToString()); + } + if (offset == 0 && length == Length) { + if (cachedContent != null) + return cachedContent; + else + return cachedContent = GetTextInternal(offset, length); + } else { + return GetTextInternal(offset, length); + } + } + + string GetTextInternal(int offset, int length) + { + int end = offset + length; + + if (end < gapBeginOffset) { + return new string(buffer, offset, length); + } + + if (offset > gapBeginOffset) { + return new string(buffer, offset + gapLength, length); + } + + int block1Size = gapBeginOffset - offset; + int block2Size = end - gapBeginOffset; + + StringBuilder buf = new StringBuilder(block1Size + block2Size); + buf.Append(buffer, offset, block1Size); + buf.Append(buffer, gapEndOffset, block2Size); + return buf.ToString(); + } + + public void Insert(int offset, string text) + { + Replace(offset, 0, text); + } + + public void Remove(int offset, int length) + { + Replace(offset, length, string.Empty); + } + + public void Replace(int offset, int length, string text) + { + if (text == null) { + text = string.Empty; + } + + #if DEBUG + CheckThread(); + #endif + + if (offset < 0 || offset > Length) { + throw new ArgumentOutOfRangeException("offset", offset, "0 <= offset <= " + Length.ToString()); + } + if (length < 0 || offset + length > Length) { + throw new ArgumentOutOfRangeException("length", length, "0 <= length, offset+length <= " + Length.ToString()); + } + + cachedContent = null; + + // Math.Max is used so that if we need to resize the array + // the new array has enough space for all old chars + PlaceGap(offset, text.Length - length); + gapEndOffset += length; // delete removed text + text.CopyTo(0, buffer, gapBeginOffset, text.Length); + gapBeginOffset += text.Length; + gapLength = gapEndOffset - gapBeginOffset; + if (gapLength > maxGapLength) { + MakeNewBuffer(gapBeginOffset, minGapLength); + } + } + + void PlaceGap(int newGapOffset, int minRequiredGapLength) + { + if (gapLength < minRequiredGapLength) { + // enlarge gap + MakeNewBuffer(newGapOffset, minRequiredGapLength); + } else { + while (newGapOffset < gapBeginOffset) { + buffer[--gapEndOffset] = buffer[--gapBeginOffset]; + } + while (newGapOffset > gapBeginOffset) { + buffer[gapBeginOffset++] = buffer[gapEndOffset++]; + } + } + } + + void MakeNewBuffer(int newGapOffset, int newGapLength) + { + if (newGapLength < minGapLength) newGapLength = minGapLength; + + char[] newBuffer = new char[Length + newGapLength]; + if (newGapOffset < gapBeginOffset) { + // gap is moving backwards + + // first part: + Array.Copy(buffer, 0, newBuffer, 0, newGapOffset); + // moving middle part: + Array.Copy(buffer, newGapOffset, newBuffer, newGapOffset + newGapLength, gapBeginOffset - newGapOffset); + // last part: + Array.Copy(buffer, gapEndOffset, newBuffer, newBuffer.Length - (buffer.Length - gapEndOffset), buffer.Length - gapEndOffset); + } else { + // gap is moving forwards + // first part: + Array.Copy(buffer, 0, newBuffer, 0, gapBeginOffset); + // moving middle part: + Array.Copy(buffer, gapEndOffset, newBuffer, gapBeginOffset, newGapOffset - gapBeginOffset); + // last part: + int lastPartLength = newBuffer.Length - (newGapOffset + newGapLength); + Array.Copy(buffer, buffer.Length - lastPartLength, newBuffer, newGapOffset + newGapLength, lastPartLength); + } + + gapBeginOffset = newGapOffset; + gapEndOffset = newGapOffset + newGapLength; + gapLength = newGapLength; + buffer = newBuffer; + } + } +} diff --git a/ICSharpCode.TextEditor/Project/Src/Document/TextBufferStrategy/ITextBufferStrategy.cs b/ICSharpCode.TextEditor/Project/Src/Document/TextBufferStrategy/ITextBufferStrategy.cs new file mode 100644 index 0000000..078836b --- /dev/null +++ b/ICSharpCode.TextEditor/Project/Src/Document/TextBufferStrategy/ITextBufferStrategy.cs @@ -0,0 +1,85 @@ +// +// +// +// +// $Revision$ +// + +namespace ICSharpCode.TextEditor.Document +{ + /// + /// Interface to describe a sequence of characters that can be edited. + /// + public interface ITextBufferStrategy + { + /// + /// The current length of the sequence of characters that can be edited. + /// + int Length { + get; + } + + /// + /// Inserts a string of characters into the sequence. + /// + /// + /// offset where to insert the string. + /// + /// + /// text to be inserted. + /// + void Insert(int offset, string text); + + /// + /// Removes some portion of the sequence. + /// + /// + /// offset of the remove. + /// + /// + /// number of characters to remove. + /// + void Remove(int offset, int length); + + /// + /// Replace some portion of the sequence. + /// + /// + /// offset. + /// + /// + /// number of characters to replace. + /// + /// + /// text to be replaced with. + /// + void Replace(int offset, int length, string text); + + /// + /// Fetches a string of characters contained in the sequence. + /// + /// + /// Offset into the sequence to fetch + /// + /// + /// number of characters to copy. + /// + string GetText(int offset, int length); + + /// + /// Returns a specific char of the sequence. + /// + /// + /// Offset of the char to get. + /// + char GetCharAt(int offset); + + /// + /// This method sets the stored content. + /// + /// + /// The string that represents the character sequence. + /// + void SetContent(string text); + } +} diff --git a/ICSharpCode.TextEditor/Project/Src/Document/TextBufferStrategy/StringTextBufferStrategy.cs b/ICSharpCode.TextEditor/Project/Src/Document/TextBufferStrategy/StringTextBufferStrategy.cs new file mode 100644 index 0000000..249a46e --- /dev/null +++ b/ICSharpCode.TextEditor/Project/Src/Document/TextBufferStrategy/StringTextBufferStrategy.cs @@ -0,0 +1,85 @@ +// +// +// +// +// $Revision$ +// + +using System; +using System.IO; +using System.Text; + +namespace ICSharpCode.TextEditor.Document +{ + /// + /// Simple implementation of the ITextBuffer interface implemented using a + /// string. + /// Only for fall-back purposes. + /// + public class StringTextBufferStrategy : ITextBufferStrategy + { + string storedText = ""; + + public int Length { + get { + return storedText.Length; + } + } + + public void Insert(int offset, string text) + { + if (text != null) { + storedText = storedText.Insert(offset, text); + } + } + + public void Remove(int offset, int length) + { + storedText = storedText.Remove(offset, length); + } + + public void Replace(int offset, int length, string text) + { + Remove(offset, length); + Insert(offset, text); + } + + public string GetText(int offset, int length) + { + if (length == 0) { + return ""; + } + if (offset == 0 && length >= storedText.Length) { + return storedText; + } + return storedText.Substring(offset, Math.Min(length, storedText.Length - offset)); + } + + public char GetCharAt(int offset) + { + if (offset == Length) { + return '\0'; + } + return storedText[offset]; + } + + public void SetContent(string text) + { + storedText = text; + } + + public StringTextBufferStrategy() + { + } + + public static ITextBufferStrategy CreateTextBufferFromFile(string fileName) + { + if (!File.Exists(fileName)) { + throw new System.IO.FileNotFoundException(fileName); + } + StringTextBufferStrategy s = new StringTextBufferStrategy(); + s.SetContent(Util.FileReader.ReadFileContent(fileName, Encoding.Default)); + return s; + } + } +} diff --git a/ICSharpCode.TextEditor/Project/Src/Document/TextLocation.cs b/ICSharpCode.TextEditor/Project/Src/Document/TextLocation.cs new file mode 100644 index 0000000..7403ea9 --- /dev/null +++ b/ICSharpCode.TextEditor/Project/Src/Document/TextLocation.cs @@ -0,0 +1,128 @@ +// +// +// +// +// $Revision: 2658$ +// + +using System; + +namespace ICSharpCode.TextEditor +{ + /// + /// A line/column position. + /// Text editor lines/columns are counting from zero. + /// + public struct TextLocation : IComparable, IEquatable + { + /// + /// Represents no text location (-1, -1). + /// + public static readonly TextLocation Empty = new TextLocation(-1, -1); + + public TextLocation(int column, int line) + { + x = column; + y = line; + } + + int x, y; + + public int X { + get { return x; } + set { x = value; } + } + + public int Y { + get { return y; } + set { y = value; } + } + + public int Line { + get { return y; } + set { y = value; } + } + + public int Column { + get { return x; } + set { x = value; } + } + + public bool IsEmpty { + get { + return x <= 0 && y <= 0; + } + } + + public override string ToString() + { + return string.Format("(Line {1}, Col {0})", this.x, this.y); + } + + public override int GetHashCode() + { + return unchecked (87 * x.GetHashCode() ^ y.GetHashCode()); + } + + public override bool Equals(object obj) + { + if (!(obj is TextLocation)) return false; + return (TextLocation)obj == this; + } + + public bool Equals(TextLocation other) + { + return this == other; + } + + public static bool operator ==(TextLocation a, TextLocation b) + { + return a.x == b.x && a.y == b.y; + } + + public static bool operator !=(TextLocation a, TextLocation b) + { + return a.x != b.x || a.y != b.y; + } + + public static bool operator <(TextLocation a, TextLocation b) + { + if (a.y < b.y) + return true; + else if (a.y == b.y) + return a.x < b.x; + else + return false; + } + + public static bool operator >(TextLocation a, TextLocation b) + { + if (a.y > b.y) + return true; + else if (a.y == b.y) + return a.x > b.x; + else + return false; + } + + public static bool operator <=(TextLocation a, TextLocation b) + { + return !(a > b); + } + + public static bool operator >=(TextLocation a, TextLocation b) + { + return !(a < b); + } + + public int CompareTo(TextLocation other) + { + if (this == other) + return 0; + if (this < other) + return -1; + else + return 1; + } + } +} diff --git a/ICSharpCode.TextEditor/Project/Src/Document/TextUtilities.cs b/ICSharpCode.TextEditor/Project/Src/Document/TextUtilities.cs new file mode 100644 index 0000000..a106c96 --- /dev/null +++ b/ICSharpCode.TextEditor/Project/Src/Document/TextUtilities.cs @@ -0,0 +1,313 @@ +// +// +// +// +// $Revision$ +// + +using System; +using System.Diagnostics; +using System.Text; + +namespace ICSharpCode.TextEditor.Document +{ + + public sealed class TextUtilities + { + /// + /// This function takes a string and converts the whitespace in front of + /// it to tabs. If the length of the whitespace at the start of the string + /// was not a whole number of tabs then there will still be some spaces just + /// before the text starts. + /// the output string will be of the form: + /// 1. zero or more tabs + /// 2. zero or more spaces (less than tabIndent) + /// 3. the rest of the line + /// + public static string LeadingWhiteSpaceToTabs(string line, int tabIndent) { + StringBuilder sb = new StringBuilder(line.Length); + int consecutiveSpaces = 0; + int i = 0; + for(i = 0; i < line.Length; i++) { + if(line[i] == ' ') { + consecutiveSpaces++; + if(consecutiveSpaces == tabIndent) { + sb.Append('\t'); + consecutiveSpaces = 0; + } + } + else if(line[i] == '\t') { + sb.Append('\t'); + // if we had say 3 spaces then a tab and tabIndent was 4 then + // we would want to simply replace all of that with 1 tab + consecutiveSpaces = 0; + } + else { + break; + } + } + + if(i < line.Length) { + sb.Append(line.Substring(i-consecutiveSpaces)); + } + return sb.ToString(); + } + + public static bool IsLetterDigitOrUnderscore(char c) + { + if(!char.IsLetterOrDigit(c)) { + return c == '_'; + } + return true; + } + + public enum CharacterType { + LetterDigitOrUnderscore, + WhiteSpace, + Other + } + + /// + /// This method returns the expression before a specified offset. + /// That method is used in code completion to determine the expression given + /// to the parser for type resolve. + /// + public static string GetExpressionBeforeOffset(TextArea textArea, int initialOffset) + { + IDocument document = textArea.Document; + int offset = initialOffset; + while (offset - 1 > 0) { + switch (document.GetCharAt(offset - 1)) { + case '\n': + case '\r': + case '}': + goto done; +// offset = SearchBracketBackward(document, offset - 2, '{','}'); +// break; + case ']': + offset = SearchBracketBackward(document, offset - 2, '[',']'); + break; + case ')': + offset = SearchBracketBackward(document, offset - 2, '(',')'); + break; + case '.': + --offset; + break; + case '"': + if (offset < initialOffset - 1) { + return null; + } + return "\"\""; + case '\'': + if (offset < initialOffset - 1) { + return null; + } + return "'a'"; + case '>': + if (document.GetCharAt(offset - 2) == '-') { + offset -= 2; + break; + } + goto done; + default: + if (char.IsWhiteSpace(document.GetCharAt(offset - 1))) { + --offset; + break; + } + int start = offset - 1; + if (!IsLetterDigitOrUnderscore(document.GetCharAt(start))) { + goto done; + } + + while (start > 0 && IsLetterDigitOrUnderscore(document.GetCharAt(start - 1))) { + --start; + } + string word = document.GetText(start, offset - start).Trim(); + switch (word) { + case "ref": + case "out": + case "in": + case "return": + case "throw": + case "case": + goto done; + } + + if (word.Length > 0 && !IsLetterDigitOrUnderscore(word[0])) { + goto done; + } + offset = start; + break; + } + } + done: + //// simple exit fails when : is inside comment line or any other character + //// we have to check if we got several ids in resulting line, which usually happens when + //// id. is typed on next line after comment one + //// Would be better if lexer would parse properly such expressions. However this will cause + //// modifications in this area too - to get full comment line and remove it afterwards + if (offset < 0) + return string.Empty; + + string resText=document.GetText(offset, textArea.Caret.Offset - offset ).Trim(); + int pos=resText.LastIndexOf('\n'); + if (pos>=0) { + offset+=pos+1; + //// whitespaces and tabs, which might be inside, will be skipped by trim below + } + string expression = document.GetText(offset, textArea.Caret.Offset - offset ).Trim(); + return expression; + } + + + public static CharacterType GetCharacterType(char c) + { + if(IsLetterDigitOrUnderscore(c)) + return CharacterType.LetterDigitOrUnderscore; + if(char.IsWhiteSpace(c)) + return CharacterType.WhiteSpace; + return CharacterType.Other; + } + + public static int GetFirstNonWSChar(IDocument document, int offset) + { + while (offset < document.TextLength && char.IsWhiteSpace(document.GetCharAt(offset))) { + ++offset; + } + return offset; + } + + public static int FindWordEnd(IDocument document, int offset) + { + LineSegment line = document.GetLineSegmentForOffset(offset); + int endPos = line.Offset + line.Length; + while (offset < endPos && IsLetterDigitOrUnderscore(document.GetCharAt(offset))) { + ++offset; + } + + return offset; + } + + public static int FindWordStart(IDocument document, int offset) + { + LineSegment line = document.GetLineSegmentForOffset(offset); + int lineOffset = line.Offset; + while (offset > lineOffset && IsLetterDigitOrUnderscore(document.GetCharAt(offset - 1))) { + --offset; + } + + return offset; + } + + // go forward to the start of the next word + // if the cursor is at the start or in the middle of a word we move to the end of the word + // and then past any whitespace that follows it + // if the cursor is at the start or in the middle of some whitespace we move to the start of the + // next word + public static int FindNextWordStart(IDocument document, int offset) + { + int originalOffset = offset; + LineSegment line = document.GetLineSegmentForOffset(offset); + int endPos = line.Offset + line.Length; + // lets go to the end of the word, whitespace or operator + CharacterType t = GetCharacterType(document.GetCharAt(offset)); + while (offset < endPos && GetCharacterType(document.GetCharAt(offset)) == t) { + ++offset; + } + + // now we're at the end of the word, lets find the start of the next one by skipping whitespace + while (offset < endPos && GetCharacterType(document.GetCharAt(offset)) == CharacterType.WhiteSpace) { + ++offset; + } + + return offset; + } + + // go back to the start of the word we are on + // if we are already at the start of a word or if we are in whitespace, then go back + // to the start of the previous word + public static int FindPrevWordStart(IDocument document, int offset) + { + int originalOffset = offset; + if (offset > 0) { + LineSegment line = document.GetLineSegmentForOffset(offset); + CharacterType t = GetCharacterType(document.GetCharAt(offset - 1)); + while (offset > line.Offset && GetCharacterType(document.GetCharAt(offset - 1)) == t) { + --offset; + } + + // if we were in whitespace, and now we're at the end of a word or operator, go back to the beginning of it + if(t == CharacterType.WhiteSpace && offset > line.Offset) { + t = GetCharacterType(document.GetCharAt(offset - 1)); + while (offset > line.Offset && GetCharacterType(document.GetCharAt(offset - 1)) == t) { + --offset; + } + } + } + + return offset; + } + + public static string GetLineAsString(IDocument document, int lineNumber) + { + LineSegment line = document.GetLineSegment(lineNumber); + return document.GetText(line.Offset, line.Length); + } + + public static int SearchBracketBackward(IDocument document, int offset, char openBracket, char closingBracket) + { + return document.FormattingStrategy.SearchBracketBackward(document, offset, openBracket, closingBracket); + } + + public static int SearchBracketForward(IDocument document, int offset, char openBracket, char closingBracket) + { + return document.FormattingStrategy.SearchBracketForward(document, offset, openBracket, closingBracket); + } + + /// + /// Returns true, if the line lineNumber is empty or filled with whitespaces. + /// + public static bool IsEmptyLine(IDocument document, int lineNumber) + { + return IsEmptyLine(document, document.GetLineSegment(lineNumber)); + } + + /// + /// Returns true, if the line lineNumber is empty or filled with whitespaces. + /// + public static bool IsEmptyLine(IDocument document, LineSegment line) + { + for (int i = line.Offset; i < line.Offset + line.Length; ++i) { + char ch = document.GetCharAt(i); + if (!char.IsWhiteSpace(ch)) { + return false; + } + } + return true; + } + + static bool IsWordPart(char ch) + { + return IsLetterDigitOrUnderscore(ch) || ch == '.'; + } + + public static string GetWordAt(IDocument document, int offset) + { + if (offset < 0 || offset >= document.TextLength - 1 || !IsWordPart(document.GetCharAt(offset))) { + return string.Empty; + } + int startOffset = offset; + int endOffset = offset; + while (startOffset > 0 && IsWordPart(document.GetCharAt(startOffset - 1))) { + --startOffset; + } + + while (endOffset < document.TextLength - 1 && IsWordPart(document.GetCharAt(endOffset + 1))) { + ++endOffset; + } + + Debug.Assert(endOffset >= startOffset); + return document.GetText(startOffset, endOffset - startOffset + 1); + } + } +} diff --git a/ICSharpCode.TextEditor/Project/Src/Gui/AbstractMargin.cs b/ICSharpCode.TextEditor/Project/Src/Gui/AbstractMargin.cs new file mode 100644 index 0000000..b00c544 --- /dev/null +++ b/ICSharpCode.TextEditor/Project/Src/Gui/AbstractMargin.cs @@ -0,0 +1,115 @@ +// +// +// +// +// $Revision$ +// + +using System; +using System.Drawing; +using System.Windows.Forms; + +using ICSharpCode.TextEditor.Document; + +namespace ICSharpCode.TextEditor +{ + public delegate void MarginMouseEventHandler(AbstractMargin sender, Point mousepos, MouseButtons mouseButtons); + public delegate void MarginPaintEventHandler(AbstractMargin sender, Graphics g, Rectangle rect); + + /// + /// This class views the line numbers and folding markers. + /// + public abstract class AbstractMargin + { + Cursor cursor = Cursors.Default; + + [CLSCompliant(false)] + protected Rectangle drawingPosition = new Rectangle(0, 0, 0, 0); + [CLSCompliant(false)] + protected TextArea textArea; + + public Rectangle DrawingPosition { + get { + return drawingPosition; + } + set { + drawingPosition = value; + } + } + + public TextArea TextArea { + get { + return textArea; + } + } + + public IDocument Document { + get { + return textArea.Document; + } + } + + public ITextEditorProperties TextEditorProperties { + get { + return textArea.Document.TextEditorProperties; + } + } + + public virtual Cursor Cursor { + get { + return cursor; + } + set { + cursor = value; + } + } + + public virtual Size Size { + get { + return new Size(-1, -1); + } + } + + public virtual bool IsVisible { + get { + return true; + } + } + + protected AbstractMargin(TextArea textArea) + { + this.textArea = textArea; + } + + public virtual void HandleMouseDown(Point mousepos, MouseButtons mouseButtons) + { + if (MouseDown != null) { + MouseDown(this, mousepos, mouseButtons); + } + } + public virtual void HandleMouseMove(Point mousepos, MouseButtons mouseButtons) + { + if (MouseMove != null) { + MouseMove(this, mousepos, mouseButtons); + } + } + public virtual void HandleMouseLeave(EventArgs e) + { + if (MouseLeave != null) { + MouseLeave(this, e); + } + } + + public virtual void Paint(Graphics g, Rectangle rect) + { + if (Painted != null) { + Painted(this, g, rect); + } + } + + public event MarginPaintEventHandler Painted; + public event MarginMouseEventHandler MouseDown; + public event MarginMouseEventHandler MouseMove; + public event EventHandler MouseLeave; + } +} diff --git a/ICSharpCode.TextEditor/Project/Src/Gui/BracketHighlighter.cs b/ICSharpCode.TextEditor/Project/Src/Gui/BracketHighlighter.cs new file mode 100644 index 0000000..45d4774 --- /dev/null +++ b/ICSharpCode.TextEditor/Project/Src/Gui/BracketHighlighter.cs @@ -0,0 +1,86 @@ +// +// +// +// +// $Revision$ +// + +using System; +using System.Drawing; +using ICSharpCode.TextEditor.Document; + +namespace ICSharpCode.TextEditor +{ + public class Highlight + { + public TextLocation OpenBrace { get; set; } + public TextLocation CloseBrace { get; set; } + + public Highlight(TextLocation openBrace, TextLocation closeBrace) + { + this.OpenBrace = openBrace; + this.CloseBrace = closeBrace; + } + } + + public class BracketHighlightingSheme + { + char opentag; + char closingtag; + + public char OpenTag { + get { + return opentag; + } + set { + opentag = value; + } + } + + public char ClosingTag { + get { + return closingtag; + } + set { + closingtag = value; + } + } + + public BracketHighlightingSheme(char opentag, char closingtag) + { + this.opentag = opentag; + this.closingtag = closingtag; + } + + public Highlight GetHighlight(IDocument document, int offset) + { + int searchOffset; + if (document.TextEditorProperties.BracketMatchingStyle == BracketMatchingStyle.After) { + searchOffset = offset; + } else { + searchOffset = offset + 1; + } + char word = document.GetCharAt(Math.Max(0, Math.Min(document.TextLength - 1, searchOffset))); + + TextLocation endP = document.OffsetToPosition(searchOffset); + if (word == opentag) { + if (searchOffset < document.TextLength) { + int bracketOffset = TextUtilities.SearchBracketForward(document, searchOffset + 1, opentag, closingtag); + if (bracketOffset >= 0) { + TextLocation p = document.OffsetToPosition(bracketOffset); + return new Highlight(p, endP); + } + } + } else if (word == closingtag) { + if (searchOffset > 0) { + int bracketOffset = TextUtilities.SearchBracketBackward(document, searchOffset - 1, opentag, closingtag); + if (bracketOffset >= 0) { + TextLocation p = document.OffsetToPosition(bracketOffset); + return new Highlight(p, endP); + } + } + } + return null; + } + } +} diff --git a/ICSharpCode.TextEditor/Project/Src/Gui/BrushRegistry.cs b/ICSharpCode.TextEditor/Project/Src/Gui/BrushRegistry.cs new file mode 100644 index 0000000..06c4b95 --- /dev/null +++ b/ICSharpCode.TextEditor/Project/Src/Gui/BrushRegistry.cs @@ -0,0 +1,65 @@ +// +// +// +// +// $Revision$ +// + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Drawing; +using System.Drawing.Drawing2D; + +namespace ICSharpCode.TextEditor +{ + /// + /// Contains brushes/pens for the text editor to speed up drawing. Re-Creation of brushes and pens + /// seems too costly. + /// + public class BrushRegistry + { + static Dictionary brushes = new Dictionary(); + static Dictionary pens = new Dictionary(); + static Dictionary dotPens = new Dictionary(); + + public static Brush GetBrush(Color color) + { + lock (brushes) { + Brush brush; + if (!brushes.TryGetValue(color, out brush)) { + brush = new SolidBrush(color); + brushes.Add(color, brush); + } + return brush; + } + } + + public static Pen GetPen(Color color) + { + lock (pens) { + Pen pen; + if (!pens.TryGetValue(color, out pen)) { + pen = new Pen(color); + pens.Add(color, pen); + } + return pen; + } + } + + static readonly float[] dotPattern = { 1, 1, 1, 1 }; + + public static Pen GetDotPen(Color color) + { + lock (dotPens) { + Pen pen; + if (!dotPens.TryGetValue(color, out pen)) { + pen = new Pen(color); + pen.DashPattern = dotPattern; + dotPens.Add(color, pen); + } + return pen; + } + } + } +} diff --git a/ICSharpCode.TextEditor/Project/Src/Gui/Caret.cs b/ICSharpCode.TextEditor/Project/Src/Gui/Caret.cs new file mode 100644 index 0000000..1129b85 --- /dev/null +++ b/ICSharpCode.TextEditor/Project/Src/Gui/Caret.cs @@ -0,0 +1,510 @@ +// +// +// +// +// $Revision$ +// + +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Diagnostics; +using System.Runtime.InteropServices; + +using ICSharpCode.TextEditor.Document; + +namespace ICSharpCode.TextEditor +{ + /// + /// In this enumeration are all caret modes listed. + /// + public enum CaretMode { + /// + /// If the caret is in insert mode typed characters will be + /// inserted at the caret position + /// + InsertMode, + + /// + /// If the caret is in overwirte mode typed characters will + /// overwrite the character at the caret position + /// + OverwriteMode + } + + + public class Caret : System.IDisposable + { + int line = 0; + int column = 0; + int desiredXPos = 0; + CaretMode caretMode; + + static bool caretCreated = false; + bool hidden = true; + TextArea textArea; + Point currentPos = new Point(-1, -1); + Ime ime = null; + CaretImplementation caretImplementation; + + /// + /// The 'prefered' xPos in which the caret moves, when it is moved + /// up/down. Measured in pixels, not in characters! + /// + public int DesiredColumn { + get { + return desiredXPos; + } + set { + desiredXPos = value; + } + } + + /// + /// The current caret mode. + /// + public CaretMode CaretMode { + get { + return caretMode; + } + set { + caretMode = value; + OnCaretModeChanged(EventArgs.Empty); + } + } + + public int Line { + get { + return line; + } + set { + line = value; + ValidateCaretPos(); + UpdateCaretPosition(); + OnPositionChanged(EventArgs.Empty); + } + } + + public int Column { + get { + return column; + } + set { + column = value; + ValidateCaretPos(); + UpdateCaretPosition(); + OnPositionChanged(EventArgs.Empty); + } + } + + public TextLocation Position { + get { + return new TextLocation(column, line); + } + set { + line = value.Y; + column = value.X; + ValidateCaretPos(); + UpdateCaretPosition(); + OnPositionChanged(EventArgs.Empty); + } + } + + public int Offset { + get { + return textArea.Document.PositionToOffset(Position); + } + } + + public Caret(TextArea textArea) + { + this.textArea = textArea; + textArea.GotFocus += new EventHandler(GotFocus); + textArea.LostFocus += new EventHandler(LostFocus); + if (Environment.OSVersion.Platform == PlatformID.Unix) + caretImplementation = new ManagedCaret(this); + else + caretImplementation = new Win32Caret(this); + } + + public void Dispose() + { + textArea.GotFocus -= new EventHandler(GotFocus); + textArea.LostFocus -= new EventHandler(LostFocus); + textArea = null; + caretImplementation.Dispose(); + } + + public TextLocation ValidatePosition(TextLocation pos) + { + int line = Math.Max(0, Math.Min(textArea.Document.TotalNumberOfLines - 1, pos.Y)); + int column = Math.Max(0, pos.X); + + if (column == int.MaxValue || !textArea.TextEditorProperties.AllowCaretBeyondEOL) { + LineSegment lineSegment = textArea.Document.GetLineSegment(line); + column = Math.Min(column, lineSegment.Length); + } + return new TextLocation(column, line); + } + + /// + /// If the caret position is outside the document text bounds + /// it is set to the correct position by calling ValidateCaretPos. + /// + public void ValidateCaretPos() + { + line = Math.Max(0, Math.Min(textArea.Document.TotalNumberOfLines - 1, line)); + column = Math.Max(0, column); + + if (column == int.MaxValue || !textArea.TextEditorProperties.AllowCaretBeyondEOL) { + LineSegment lineSegment = textArea.Document.GetLineSegment(line); + column = Math.Min(column, lineSegment.Length); + } + } + + void CreateCaret() + { + while (!caretCreated) { + switch (caretMode) { + case CaretMode.InsertMode: + caretCreated = caretImplementation.Create(2, textArea.TextView.FontHeight); + break; + case CaretMode.OverwriteMode: + caretCreated = caretImplementation.Create((int)textArea.TextView.SpaceWidth, textArea.TextView.FontHeight); + break; + } + } + if (currentPos.X < 0) { + ValidateCaretPos(); + currentPos = ScreenPosition; + } + caretImplementation.SetPosition(currentPos.X, currentPos.Y); + caretImplementation.Show(); + } + + public void RecreateCaret() + { + Log("RecreateCaret"); + DisposeCaret(); + if (!hidden) { + CreateCaret(); + } + } + + void DisposeCaret() + { + if (caretCreated) { + caretCreated = false; + caretImplementation.Hide(); + caretImplementation.Destroy(); + } + } + + void GotFocus(object sender, EventArgs e) + { + Log("GotFocus, IsInUpdate=" + textArea.MotherTextEditorControl.IsInUpdate); + hidden = false; + if (!textArea.MotherTextEditorControl.IsInUpdate) { + CreateCaret(); + UpdateCaretPosition(); + } + } + + void LostFocus(object sender, EventArgs e) + { + Log("LostFocus"); + hidden = true; + DisposeCaret(); + } + + public Point ScreenPosition { + get { + int xpos = textArea.TextView.GetDrawingXPos(this.line, this.column); + return new Point(textArea.TextView.DrawingPosition.X + xpos, + textArea.TextView.DrawingPosition.Y + + (textArea.Document.GetVisibleLine(this.line)) * textArea.TextView.FontHeight + - textArea.TextView.TextArea.VirtualTop.Y); + } + } + int oldLine = -1; + bool outstandingUpdate; + + internal void OnEndUpdate() + { + if (outstandingUpdate) + UpdateCaretPosition(); + } + + void PaintCaretLine(Graphics g) + { + if (!textArea.Document.TextEditorProperties.CaretLine) + return; + + HighlightColor caretLineColor = textArea.Document.HighlightingStrategy.GetColorFor("CaretLine"); + + g.DrawLine(BrushRegistry.GetDotPen(caretLineColor.Color), + currentPos.X, + 0, + currentPos.X, + textArea.DisplayRectangle.Height); + } + + public void UpdateCaretPosition() + { + Log("UpdateCaretPosition"); + + if (textArea.TextEditorProperties.CaretLine) { + textArea.Invalidate(); + } else { + if (caretImplementation.RequireRedrawOnPositionChange) { + textArea.UpdateLine(oldLine); + if (line != oldLine) + textArea.UpdateLine(line); + } else { + if (textArea.MotherTextAreaControl.TextEditorProperties.LineViewerStyle == LineViewerStyle.FullRow && oldLine != line) { + textArea.UpdateLine(oldLine); + textArea.UpdateLine(line); + } + } + } + oldLine = line; + + + if (hidden || textArea.MotherTextEditorControl.IsInUpdate) { + outstandingUpdate = true; + return; + } else { + outstandingUpdate = false; + } + ValidateCaretPos(); + int lineNr = this.line; + int xpos = textArea.TextView.GetDrawingXPos(lineNr, this.column); + //LineSegment lineSegment = textArea.Document.GetLineSegment(lineNr); + Point pos = ScreenPosition; + if (xpos >= 0) { + CreateCaret(); + bool success = caretImplementation.SetPosition(pos.X, pos.Y); + if (!success) { + caretImplementation.Destroy(); + caretCreated = false; + UpdateCaretPosition(); + } + } else { + caretImplementation.Destroy(); + } + + // set the input method editor location + if (ime == null) { + ime = new Ime(textArea.Handle, textArea.Document.TextEditorProperties.Font); + } else { + ime.HWnd = textArea.Handle; + ime.Font = textArea.Document.TextEditorProperties.Font; + } + ime.SetIMEWindowLocation(pos.X, pos.Y); + + currentPos = pos; + } + + [Conditional("DEBUG")] + static void Log(string text) + { + //Console.WriteLine(text); + } + + #region Caret implementation + internal void PaintCaret(Graphics g) + { + caretImplementation.PaintCaret(g); + PaintCaretLine(g); + } + + abstract class CaretImplementation : IDisposable + { + public bool RequireRedrawOnPositionChange; + + public abstract bool Create(int width, int height); + public abstract void Hide(); + public abstract void Show(); + public abstract bool SetPosition(int x, int y); + public abstract void PaintCaret(Graphics g); + public abstract void Destroy(); + + public virtual void Dispose() + { + Destroy(); + } + } + + class ManagedCaret : CaretImplementation + { + System.Windows.Forms.Timer timer = new System.Windows.Forms.Timer { Interval = 300 }; + bool visible; + bool blink = true; + int x, y, width, height; + TextArea textArea; + Caret parentCaret; + + public ManagedCaret(Caret caret) + { + base.RequireRedrawOnPositionChange = true; + this.textArea = caret.textArea; + this.parentCaret = caret; + timer.Tick += CaretTimerTick; + } + + void CaretTimerTick(object sender, EventArgs e) + { + blink = !blink; + if (visible) + textArea.UpdateLine(parentCaret.Line); + } + + public override bool Create(int width, int height) + { + this.visible = true; + this.width = width - 2; + this.height = height; + timer.Enabled = true; + return true; + } + public override void Hide() + { + visible = false; + } + public override void Show() + { + visible = true; + } + public override bool SetPosition(int x, int y) + { + this.x = x - 1; + this.y = y; + return true; + } + public override void PaintCaret(Graphics g) + { + if (visible && blink) + g.DrawRectangle(Pens.Gray, x, y, width, height); + } + public override void Destroy() + { + visible = false; + timer.Enabled = false; + } + public override void Dispose() + { + base.Dispose(); + timer.Dispose(); + } + } + + class Win32Caret : CaretImplementation + { + [DllImport("User32.dll")] + static extern bool CreateCaret(IntPtr hWnd, int hBitmap, int nWidth, int nHeight); + + [DllImport("User32.dll")] + static extern bool SetCaretPos(int x, int y); + + [DllImport("User32.dll")] + static extern bool DestroyCaret(); + + [DllImport("User32.dll")] + static extern bool ShowCaret(IntPtr hWnd); + + [DllImport("User32.dll")] + static extern bool HideCaret(IntPtr hWnd); + + TextArea textArea; + + public Win32Caret(Caret caret) + { + this.textArea = caret.textArea; + } + + public override bool Create(int width, int height) + { + return CreateCaret(textArea.Handle, 0, width, height); + } + public override void Hide() + { + HideCaret(textArea.Handle); + } + public override void Show() + { + ShowCaret(textArea.Handle); + } + public override bool SetPosition(int x, int y) + { + return SetCaretPos(x, y); + } + public override void PaintCaret(Graphics g) + { + } + public override void Destroy() + { + DestroyCaret(); + } + } + #endregion + + bool firePositionChangedAfterUpdateEnd; + + void FirePositionChangedAfterUpdateEnd(object sender, EventArgs e) + { + OnPositionChanged(EventArgs.Empty); + } + + protected virtual void OnPositionChanged(EventArgs e) + { + if (this.textArea.MotherTextEditorControl.IsInUpdate) { + if (firePositionChangedAfterUpdateEnd == false) { + firePositionChangedAfterUpdateEnd = true; + this.textArea.Document.UpdateCommited += FirePositionChangedAfterUpdateEnd; + } + return; + } else if (firePositionChangedAfterUpdateEnd) { + this.textArea.Document.UpdateCommited -= FirePositionChangedAfterUpdateEnd; + firePositionChangedAfterUpdateEnd = false; + } + + List foldings = textArea.Document.FoldingManager.GetFoldingsFromPosition(line, column); + bool shouldUpdate = false; + foreach (FoldMarker foldMarker in foldings) { + shouldUpdate |= foldMarker.IsFolded; + foldMarker.IsFolded = false; + } + + if (shouldUpdate) { + textArea.Document.FoldingManager.NotifyFoldingsChanged(EventArgs.Empty); + } + + if (PositionChanged != null) { + PositionChanged(this, e); + } + textArea.ScrollToCaret(); + } + + protected virtual void OnCaretModeChanged(EventArgs e) + { + if (CaretModeChanged != null) { + CaretModeChanged(this, e); + } + caretImplementation.Hide(); + caretImplementation.Destroy(); + caretCreated = false; + CreateCaret(); + caretImplementation.Show(); + } + + /// + /// Is called each time the caret is moved. + /// + public event EventHandler PositionChanged; + + /// + /// Is called each time the CaretMode has changed. + /// + public event EventHandler CaretModeChanged; + } +} diff --git a/ICSharpCode.TextEditor/Project/Src/Gui/CompletionWindow/AbstractCompletionWindow.cs b/ICSharpCode.TextEditor/Project/Src/Gui/CompletionWindow/AbstractCompletionWindow.cs new file mode 100644 index 0000000..8c17c5b --- /dev/null +++ b/ICSharpCode.TextEditor/Project/Src/Gui/CompletionWindow/AbstractCompletionWindow.cs @@ -0,0 +1,214 @@ +// +// +// +// +// $Revision$ +// + +using System; +using System.Drawing; +using System.Windows.Forms; + +namespace ICSharpCode.TextEditor.Gui.CompletionWindow +{ + /// + /// Description of AbstractCompletionWindow. + /// + public abstract class AbstractCompletionWindow : System.Windows.Forms.Form + { + protected TextEditorControl control; + protected Size drawingSize; + Rectangle workingScreen; + Form parentForm; + + protected AbstractCompletionWindow(Form parentForm, TextEditorControl control) + { + workingScreen = Screen.GetWorkingArea(parentForm); +// SetStyle(ControlStyles.Selectable, false); + this.parentForm = parentForm; + this.control = control; + + SetLocation(); + StartPosition = FormStartPosition.Manual; + FormBorderStyle = FormBorderStyle.None; + ShowInTaskbar = false; + MinimumSize = new Size(1, 1); + Size = new Size(1, 1); + } + + protected virtual void SetLocation() + { + TextArea textArea = control.ActiveTextAreaControl.TextArea; + TextLocation caretPos = textArea.Caret.Position; + + int xpos = textArea.TextView.GetDrawingXPos(caretPos.Y, caretPos.X); + int rulerHeight = textArea.TextEditorProperties.ShowHorizontalRuler ? textArea.TextView.FontHeight : 0; + Point pos = new Point(textArea.TextView.DrawingPosition.X + xpos, + textArea.TextView.DrawingPosition.Y + (textArea.Document.GetVisibleLine(caretPos.Y)) * textArea.TextView.FontHeight + - textArea.TextView.TextArea.VirtualTop.Y + textArea.TextView.FontHeight + rulerHeight); + + Point location = control.ActiveTextAreaControl.PointToScreen(pos); + + // set bounds + Rectangle bounds = new Rectangle(location, drawingSize); + + if (!workingScreen.Contains(bounds)) { + if (bounds.Right > workingScreen.Right) { + bounds.X = workingScreen.Right - bounds.Width; + } + if (bounds.Left < workingScreen.Left) { + bounds.X = workingScreen.Left; + } + if (bounds.Top < workingScreen.Top) { + bounds.Y = workingScreen.Top; + } + if (bounds.Bottom > workingScreen.Bottom) { + bounds.Y = bounds.Y - bounds.Height - control.ActiveTextAreaControl.TextArea.TextView.FontHeight; + if (bounds.Bottom > workingScreen.Bottom) { + bounds.Y = workingScreen.Bottom - bounds.Height; + } + } + } + Bounds = bounds; + } + + protected override CreateParams CreateParams { + get { + CreateParams p = base.CreateParams; + AddShadowToWindow(p); + return p; + } + } + + static int shadowStatus; + + /// + /// Adds a shadow to the create params if it is supported by the operating system. + /// + public static void AddShadowToWindow(CreateParams createParams) + { + if (shadowStatus == 0) { + // Test OS version + shadowStatus = -1; // shadow not supported + if (Environment.OSVersion.Platform == PlatformID.Win32NT) { + Version ver = Environment.OSVersion.Version; + if (ver.Major > 5 || ver.Major == 5 && ver.Minor >= 1) { + shadowStatus = 1; + } + } + } + if (shadowStatus == 1) { + createParams.ClassStyle |= 0x00020000; // set CS_DROPSHADOW + } + } + + protected override bool ShowWithoutActivation { + get { + return true; + } + } + + protected void ShowCompletionWindow() + { + Owner = parentForm; + Enabled = true; + this.Show(); + + control.Focus(); + + if (parentForm != null) { + parentForm.LocationChanged += new EventHandler(this.ParentFormLocationChanged); + } + + control.ActiveTextAreaControl.VScrollBar.ValueChanged += new EventHandler(ParentFormLocationChanged); + control.ActiveTextAreaControl.HScrollBar.ValueChanged += new EventHandler(ParentFormLocationChanged); + control.ActiveTextAreaControl.TextArea.DoProcessDialogKey += new DialogKeyProcessor(ProcessTextAreaKey); + control.ActiveTextAreaControl.Caret.PositionChanged += new EventHandler(CaretOffsetChanged); + control.ActiveTextAreaControl.TextArea.LostFocus += new EventHandler(this.TextEditorLostFocus); + control.Resize += new EventHandler(ParentFormLocationChanged); + + foreach (Control c in Controls) { + c.MouseMove += ControlMouseMove; + } + } + + void ParentFormLocationChanged(object sender, EventArgs e) + { + SetLocation(); + } + + public virtual bool ProcessKeyEvent(char ch) + { + return false; + } + + protected virtual bool ProcessTextAreaKey(Keys keyData) + { + if (!Visible) { + return false; + } + switch (keyData) { + case Keys.Escape: + Close(); + return true; + } + return false; + } + + protected virtual void CaretOffsetChanged(object sender, EventArgs e) + { + } + + protected void TextEditorLostFocus(object sender, EventArgs e) + { + if (!control.ActiveTextAreaControl.TextArea.Focused && !this.ContainsFocus) { + Close(); + } + } + + protected override void OnClosed(EventArgs e) + { + base.OnClosed(e); + + // take out the inserted methods + parentForm.LocationChanged -= new EventHandler(ParentFormLocationChanged); + + foreach (Control c in Controls) { + c.MouseMove -= ControlMouseMove; + } + + if (control.ActiveTextAreaControl.VScrollBar != null) { + control.ActiveTextAreaControl.VScrollBar.ValueChanged -= new EventHandler(ParentFormLocationChanged); + } + if (control.ActiveTextAreaControl.HScrollBar != null) { + control.ActiveTextAreaControl.HScrollBar.ValueChanged -= new EventHandler(ParentFormLocationChanged); + } + + control.ActiveTextAreaControl.TextArea.LostFocus -= new EventHandler(this.TextEditorLostFocus); + control.ActiveTextAreaControl.Caret.PositionChanged -= new EventHandler(CaretOffsetChanged); + control.ActiveTextAreaControl.TextArea.DoProcessDialogKey -= new DialogKeyProcessor(ProcessTextAreaKey); + control.Resize -= new EventHandler(ParentFormLocationChanged); + Dispose(); + } + + protected override void OnMouseMove(MouseEventArgs e) + { + base.OnMouseMove(e); + ControlMouseMove(this, e); + } + + /// + /// Invoked when the mouse moves over this form or any child control. + /// Shows the mouse cursor on the text area if it has been hidden. + /// + /// + /// Derived classes should attach this handler to the MouseMove event + /// of all created controls which are not added to the Controls + /// collection. + /// + protected void ControlMouseMove(object sender, MouseEventArgs e) + { + control.ActiveTextAreaControl.TextArea.ShowHiddenCursor(false); + } + } +} diff --git a/ICSharpCode.TextEditor/Project/Src/Gui/CompletionWindow/CodeCompletionListView.cs b/ICSharpCode.TextEditor/Project/Src/Gui/CompletionWindow/CodeCompletionListView.cs new file mode 100644 index 0000000..e7bfb11 --- /dev/null +++ b/ICSharpCode.TextEditor/Project/Src/Gui/CompletionWindow/CodeCompletionListView.cs @@ -0,0 +1,293 @@ +// +// +// +// +// $Revision$ +// + +using System; +using System.Drawing; +using System.Windows.Forms; + +namespace ICSharpCode.TextEditor.Gui.CompletionWindow +{ + /// + /// Description of CodeCompletionListView. + /// + public class CodeCompletionListView : System.Windows.Forms.UserControl + { + ICompletionData[] completionData; + int firstItem = 0; + int selectedItem = -1; + ImageList imageList; + + public ImageList ImageList { + get { + return imageList; + } + set { + imageList = value; + } + } + + public int FirstItem { + get { + return firstItem; + } + set { + if (firstItem != value) { + firstItem = value; + OnFirstItemChanged(EventArgs.Empty); + } + } + } + + public ICompletionData SelectedCompletionData { + get { + if (selectedItem < 0) { + return null; + } + return completionData[selectedItem]; + } + } + + public int ItemHeight { + get { + return Math.Max(imageList.ImageSize.Height, (int)(Font.Height * 1.25)); + } + } + + public int MaxVisibleItem { + get { + return Height / ItemHeight; + } + } + + public CodeCompletionListView(ICompletionData[] completionData) + { + Array.Sort(completionData, DefaultCompletionData.Compare); + this.completionData = completionData; + +// this.KeyDown += new System.Windows.Forms.KeyEventHandler(OnKey); +// SetStyle(ControlStyles.Selectable, false); +// SetStyle(ControlStyles.UserPaint, true); +// SetStyle(ControlStyles.DoubleBuffer, false); + } + + public void Close() + { + if (completionData != null) { + Array.Clear(completionData, 0, completionData.Length); + } + base.Dispose(); + } + + public void SelectIndex(int index) + { + int oldSelectedItem = selectedItem; + int oldFirstItem = firstItem; + + index = Math.Max(0, index); + selectedItem = Math.Max(0, Math.Min(completionData.Length - 1, index)); + if (selectedItem < firstItem) { + FirstItem = selectedItem; + } + if (firstItem + MaxVisibleItem <= selectedItem) { + FirstItem = selectedItem - MaxVisibleItem + 1; + } + if (oldSelectedItem != selectedItem) { + if (firstItem != oldFirstItem) { + Invalidate(); + } else { + int min = Math.Min(selectedItem, oldSelectedItem) - firstItem; + int max = Math.Max(selectedItem, oldSelectedItem) - firstItem; + Invalidate(new Rectangle(0, 1 + min * ItemHeight, Width, (max - min + 1) * ItemHeight)); + } + OnSelectedItemChanged(EventArgs.Empty); + } + } + + public void CenterViewOn(int index) + { + int oldFirstItem = this.FirstItem; + int firstItem = index - MaxVisibleItem / 2; + if (firstItem < 0) + this.FirstItem = 0; + else if (firstItem >= completionData.Length - MaxVisibleItem) + this.FirstItem = completionData.Length - MaxVisibleItem; + else + this.FirstItem = firstItem; + if (this.FirstItem != oldFirstItem) { + Invalidate(); + } + } + + public void ClearSelection() + { + if (selectedItem < 0) + return; + int itemNum = selectedItem - firstItem; + selectedItem = -1; + Invalidate(new Rectangle(0, itemNum * ItemHeight, Width, (itemNum + 1) * ItemHeight + 1)); + Update(); + OnSelectedItemChanged(EventArgs.Empty); + } + + public void PageDown() + { + SelectIndex(selectedItem + MaxVisibleItem); + } + + public void PageUp() + { + SelectIndex(selectedItem - MaxVisibleItem); + } + + public void SelectNextItem() + { + SelectIndex(selectedItem + 1); + } + + public void SelectPrevItem() + { + SelectIndex(selectedItem - 1); + } + + public void SelectItemWithStart(string startText) + { + if (startText == null || startText.Length == 0) return; + string originalStartText = startText; + startText = startText.ToLower(); + int bestIndex = -1; + int bestQuality = -1; + // Qualities: 0 = match start + // 1 = match start case sensitive + // 2 = full match + // 3 = full match case sensitive + double bestPriority = 0; + for (int i = 0; i < completionData.Length; ++i) { + string itemText = completionData[i].Text; + string lowerText = itemText.ToLower(); + if (lowerText.StartsWith(startText)) { + double priority = completionData[i].Priority; + int quality; + if (lowerText == startText) { + if (itemText == originalStartText) + quality = 3; + else + quality = 2; + } else if (itemText.StartsWith(originalStartText)) { + quality = 1; + } else { + quality = 0; + } + bool useThisItem; + if (bestQuality < quality) { + useThisItem = true; + } else { + if (bestIndex == selectedItem) { + useThisItem = false; + } else if (i == selectedItem) { + useThisItem = bestQuality == quality; + } else { + useThisItem = bestQuality == quality && bestPriority < priority; + } + } + if (useThisItem) { + bestIndex = i; + bestPriority = priority; + bestQuality = quality; + } + } + } + if (bestIndex < 0) { + ClearSelection(); + } else { + if (bestIndex < firstItem || firstItem + MaxVisibleItem <= bestIndex) { + SelectIndex(bestIndex); + CenterViewOn(bestIndex); + } else { + SelectIndex(bestIndex); + } + } + } + + protected override void OnPaint(PaintEventArgs pe) + { + float yPos = 1; + float itemHeight = ItemHeight; + // Maintain aspect ratio + int imageWidth = (int)(itemHeight * imageList.ImageSize.Width / imageList.ImageSize.Height); + + int curItem = firstItem; + Graphics g = pe.Graphics; + while (curItem < completionData.Length && yPos < Height) { + RectangleF drawingBackground = new RectangleF(1, yPos, Width - 2, itemHeight); + if (drawingBackground.IntersectsWith(pe.ClipRectangle)) { + // draw Background + if (curItem == selectedItem) { + g.FillRectangle(SystemBrushes.Highlight, drawingBackground); + } else { + g.FillRectangle(SystemBrushes.Window, drawingBackground); + } + + // draw Icon + int xPos = 0; + if (imageList != null && completionData[curItem].ImageIndex < imageList.Images.Count) { + g.DrawImage(imageList.Images[completionData[curItem].ImageIndex], new RectangleF(1, yPos, imageWidth, itemHeight)); + xPos = imageWidth; + } + + // draw text + if (curItem == selectedItem) { + g.DrawString(completionData[curItem].Text, Font, SystemBrushes.HighlightText, xPos, yPos); + } else { + g.DrawString(completionData[curItem].Text, Font, SystemBrushes.WindowText, xPos, yPos); + } + } + + yPos += itemHeight; + ++curItem; + } + g.DrawRectangle(SystemPens.Control, new Rectangle(0, 0, Width - 1, Height - 1)); + } + + protected override void OnMouseDown(System.Windows.Forms.MouseEventArgs e) + { + float yPos = 1; + int curItem = firstItem; + float itemHeight = ItemHeight; + + while (curItem < completionData.Length && yPos < Height) { + RectangleF drawingBackground = new RectangleF(1, yPos, Width - 2, itemHeight); + if (drawingBackground.Contains(e.X, e.Y)) { + SelectIndex(curItem); + break; + } + yPos += itemHeight; + ++curItem; + } + } + + protected override void OnPaintBackground(PaintEventArgs pe) + { + } + + protected virtual void OnSelectedItemChanged(EventArgs e) + { + if (SelectedItemChanged != null) { + SelectedItemChanged(this, e); + } + } + + protected virtual void OnFirstItemChanged(EventArgs e) + { + if (FirstItemChanged != null) { + FirstItemChanged(this, e); + } + } + + public event EventHandler SelectedItemChanged; + public event EventHandler FirstItemChanged; + } +} diff --git a/ICSharpCode.TextEditor/Project/Src/Gui/CompletionWindow/CodeCompletionWindow.cs b/ICSharpCode.TextEditor/Project/Src/Gui/CompletionWindow/CodeCompletionWindow.cs new file mode 100644 index 0000000..f53a5ff --- /dev/null +++ b/ICSharpCode.TextEditor/Project/Src/Gui/CompletionWindow/CodeCompletionWindow.cs @@ -0,0 +1,364 @@ +// +// +// +// +// $Revision$ +// + +using System; +using System.Drawing; +using System.Diagnostics; +using System.Windows.Forms; +using ICSharpCode.TextEditor.Document; + +namespace ICSharpCode.TextEditor.Gui.CompletionWindow +{ + public class CodeCompletionWindow : AbstractCompletionWindow + { + ICompletionData[] completionData; + CodeCompletionListView codeCompletionListView; + VScrollBar vScrollBar = new VScrollBar(); + ICompletionDataProvider dataProvider; + IDocument document; + bool showDeclarationWindow = true; + bool fixedListViewWidth = true; + const int ScrollbarWidth = 16; + const int MaxListLength = 10; + + int startOffset; + int endOffset; + DeclarationViewWindow declarationViewWindow = null; + Rectangle workingScreen; + + public static CodeCompletionWindow ShowCompletionWindow(Form parent, TextEditorControl control, string fileName, ICompletionDataProvider completionDataProvider, char firstChar) + { + return ShowCompletionWindow(parent, control, fileName, completionDataProvider, firstChar, true, true); + } + + public static CodeCompletionWindow ShowCompletionWindow(Form parent, TextEditorControl control, string fileName, ICompletionDataProvider completionDataProvider, char firstChar, bool showDeclarationWindow, bool fixedListViewWidth) + { + ICompletionData[] completionData = completionDataProvider.GenerateCompletionData(fileName, control.ActiveTextAreaControl.TextArea, firstChar); + if (completionData == null || completionData.Length == 0) { + return null; + } + CodeCompletionWindow codeCompletionWindow = new CodeCompletionWindow(completionDataProvider, completionData, parent, control, showDeclarationWindow, fixedListViewWidth); + codeCompletionWindow.CloseWhenCaretAtBeginning = firstChar == '\0'; + codeCompletionWindow.ShowCompletionWindow(); + return codeCompletionWindow; + } + + CodeCompletionWindow(ICompletionDataProvider completionDataProvider, ICompletionData[] completionData, Form parentForm, TextEditorControl control, bool showDeclarationWindow, bool fixedListViewWidth) : base(parentForm, control) + { + this.dataProvider = completionDataProvider; + this.completionData = completionData; + this.document = control.Document; + this.showDeclarationWindow = showDeclarationWindow; + this.fixedListViewWidth = fixedListViewWidth; + + workingScreen = Screen.GetWorkingArea(Location); + startOffset = control.ActiveTextAreaControl.Caret.Offset + 1; + endOffset = startOffset; + if (completionDataProvider.PreSelection != null) { + startOffset -= completionDataProvider.PreSelection.Length + 1; + endOffset--; + } + + codeCompletionListView = new CodeCompletionListView(completionData); + codeCompletionListView.ImageList = completionDataProvider.ImageList; + codeCompletionListView.Dock = DockStyle.Fill; + codeCompletionListView.SelectedItemChanged += new EventHandler(CodeCompletionListViewSelectedItemChanged); + codeCompletionListView.DoubleClick += new EventHandler(CodeCompletionListViewDoubleClick); + codeCompletionListView.Click += new EventHandler(CodeCompletionListViewClick); + Controls.Add(codeCompletionListView); + + if (completionData.Length > MaxListLength) { + vScrollBar.Dock = DockStyle.Right; + vScrollBar.Minimum = 0; + vScrollBar.Maximum = completionData.Length - 1; + vScrollBar.SmallChange = 1; + vScrollBar.LargeChange = MaxListLength; + codeCompletionListView.FirstItemChanged += new EventHandler(CodeCompletionListViewFirstItemChanged); + Controls.Add(vScrollBar); + } + + this.drawingSize = GetListViewSize(); + SetLocation(); + + if (declarationViewWindow == null) { + declarationViewWindow = new DeclarationViewWindow(parentForm); + } + SetDeclarationViewLocation(); + declarationViewWindow.ShowDeclarationViewWindow(); + declarationViewWindow.MouseMove += ControlMouseMove; + control.Focus(); + CodeCompletionListViewSelectedItemChanged(this, EventArgs.Empty); + + if (completionDataProvider.DefaultIndex >= 0) { + codeCompletionListView.SelectIndex(completionDataProvider.DefaultIndex); + } + + if (completionDataProvider.PreSelection != null) { + CaretOffsetChanged(this, EventArgs.Empty); + } + + vScrollBar.ValueChanged += VScrollBarValueChanged; + document.DocumentAboutToBeChanged += DocumentAboutToBeChanged; + } + + bool inScrollUpdate; + + void CodeCompletionListViewFirstItemChanged(object sender, EventArgs e) + { + if (inScrollUpdate) return; + inScrollUpdate = true; + vScrollBar.Value = Math.Min(vScrollBar.Maximum, codeCompletionListView.FirstItem); + inScrollUpdate = false; + } + + void VScrollBarValueChanged(object sender, EventArgs e) + { + if (inScrollUpdate) return; + inScrollUpdate = true; + codeCompletionListView.FirstItem = vScrollBar.Value; + codeCompletionListView.Refresh(); + control.ActiveTextAreaControl.TextArea.Focus(); + inScrollUpdate = false; + } + + void SetDeclarationViewLocation() + { + // This method uses the side with more free space + int leftSpace = Bounds.Left - workingScreen.Left; + int rightSpace = workingScreen.Right - Bounds.Right; + Point pos; + // The declaration view window has better line break when used on + // the right side, so prefer the right side to the left. + if (rightSpace * 2 > leftSpace) { + declarationViewWindow.FixedWidth = false; + pos = new Point(Bounds.Right, Bounds.Top); + if (declarationViewWindow.Location != pos) { + declarationViewWindow.Location = pos; + } + } else { + declarationViewWindow.Width = declarationViewWindow.GetRequiredLeftHandSideWidth(new Point(Bounds.Left, Bounds.Top)); + declarationViewWindow.FixedWidth = true; + if (Bounds.Left < declarationViewWindow.Width) { + pos = new Point(0, Bounds.Top); + } else { + pos = new Point(Bounds.Left - declarationViewWindow.Width, Bounds.Top); + } + if (declarationViewWindow.Location != pos) { + declarationViewWindow.Location = pos; + } + declarationViewWindow.Refresh(); + } + } + + protected override void SetLocation() + { + base.SetLocation(); + if (declarationViewWindow != null) { + SetDeclarationViewLocation(); + } + } + + Util.MouseWheelHandler mouseWheelHandler = new Util.MouseWheelHandler(); + + public void HandleMouseWheel(MouseEventArgs e) + { + int scrollDistance = mouseWheelHandler.GetScrollAmount(e); + if (scrollDistance == 0) + return; + if (control.TextEditorProperties.MouseWheelScrollDown) + scrollDistance = -scrollDistance; + int newValue = vScrollBar.Value + vScrollBar.SmallChange * scrollDistance; + vScrollBar.Value = Math.Max(vScrollBar.Minimum, Math.Min(vScrollBar.Maximum - vScrollBar.LargeChange + 1, newValue)); + } + + void CodeCompletionListViewSelectedItemChanged(object sender, EventArgs e) + { + ICompletionData data = codeCompletionListView.SelectedCompletionData; + if (showDeclarationWindow && data != null && data.Description != null && data.Description.Length > 0) { + declarationViewWindow.Description = data.Description; + SetDeclarationViewLocation(); + } else { + declarationViewWindow.Description = null; + } + } + + public override bool ProcessKeyEvent(char ch) + { + switch (dataProvider.ProcessKey(ch)) { + case CompletionDataProviderKeyResult.BeforeStartKey: + // increment start+end, then process as normal char + ++startOffset; + ++endOffset; + return base.ProcessKeyEvent(ch); + case CompletionDataProviderKeyResult.NormalKey: + // just process normally + return base.ProcessKeyEvent(ch); + case CompletionDataProviderKeyResult.InsertionKey: + return InsertSelectedItem(ch); + default: + throw new InvalidOperationException("Invalid return value of dataProvider.ProcessKey"); + } + } + + void DocumentAboutToBeChanged(object sender, DocumentEventArgs e) + { + // => startOffset test required so that this startOffset/endOffset are not incremented again + // for BeforeStartKey characters + if (e.Offset >= startOffset && e.Offset <= endOffset) { + if (e.Length > 0) { // length of removed region + endOffset -= e.Length; + } + if (!string.IsNullOrEmpty(e.Text)) { + endOffset += e.Text.Length; + } + } + } + + /// + /// When this flag is set, code completion closes if the caret moves to the + /// beginning of the allowed range. This is useful in Ctrl+Space and "complete when typing", + /// but not in dot-completion. + /// + public bool CloseWhenCaretAtBeginning { get; set; } + + protected override void CaretOffsetChanged(object sender, EventArgs e) + { + int offset = control.ActiveTextAreaControl.Caret.Offset; + if (offset == startOffset) { + if (CloseWhenCaretAtBeginning) + Close(); + return; + } + if (offset < startOffset || offset > endOffset) { + Close(); + } else { + codeCompletionListView.SelectItemWithStart(control.Document.GetText(startOffset, offset - startOffset)); + } + } + + protected override bool ProcessTextAreaKey(Keys keyData) + { + if (!Visible) { + return false; + } + + switch (keyData) { + case Keys.Home: + codeCompletionListView.SelectIndex(0); + return true; + case Keys.End: + codeCompletionListView.SelectIndex(completionData.Length-1); + return true; + case Keys.PageDown: + codeCompletionListView.PageDown(); + return true; + case Keys.PageUp: + codeCompletionListView.PageUp(); + return true; + case Keys.Down: + codeCompletionListView.SelectNextItem(); + return true; + case Keys.Up: + codeCompletionListView.SelectPrevItem(); + return true; + case Keys.Tab: + InsertSelectedItem('\t'); + return true; + case Keys.Return: + InsertSelectedItem('\n'); + return true; + } + return base.ProcessTextAreaKey(keyData); + } + + void CodeCompletionListViewDoubleClick(object sender, EventArgs e) + { + InsertSelectedItem('\0'); + } + + void CodeCompletionListViewClick(object sender, EventArgs e) + { + control.ActiveTextAreaControl.TextArea.Focus(); + } + + protected override void Dispose(bool disposing) + { + if (disposing) { + document.DocumentAboutToBeChanged -= DocumentAboutToBeChanged; + if (codeCompletionListView != null) { + codeCompletionListView.Dispose(); + codeCompletionListView = null; + } + if (declarationViewWindow != null) { + declarationViewWindow.Dispose(); + declarationViewWindow = null; + } + } + base.Dispose(disposing); + } + + bool InsertSelectedItem(char ch) + { + document.DocumentAboutToBeChanged -= DocumentAboutToBeChanged; + ICompletionData data = codeCompletionListView.SelectedCompletionData; + bool result = false; + if (data != null) { + control.BeginUpdate(); + + try { + if (endOffset - startOffset > 0) { + control.Document.Remove(startOffset, endOffset - startOffset); + } + Debug.Assert(startOffset <= document.TextLength); + result = dataProvider.InsertAction(data, control.ActiveTextAreaControl.TextArea, startOffset, ch); + } finally { + control.EndUpdate(); + } + } + Close(); + return result; + } + + Size GetListViewSize() + { + int height = codeCompletionListView.ItemHeight * Math.Min(MaxListLength, completionData.Length); + int width = codeCompletionListView.ItemHeight * 10; + if (!fixedListViewWidth) { + width = GetListViewWidth(width, height); + } + return new Size(width, height); + } + + /// + /// Gets the list view width large enough to handle the longest completion data + /// text string. + /// + /// The default width of the list view. + /// The height of the list view. This is + /// used to determine if the scrollbar is visible. + /// The list view width to accommodate the longest completion + /// data text string; otherwise the default width. + int GetListViewWidth(int defaultWidth, int height) + { + float width = defaultWidth; + using (Graphics graphics = codeCompletionListView.CreateGraphics()) { + for (int i = 0; i < completionData.Length; ++i) { + float itemWidth = graphics.MeasureString(completionData[i].Text.ToString(), codeCompletionListView.Font).Width; + if(itemWidth > width) { + width = itemWidth; + } + } + } + + float totalItemsHeight = codeCompletionListView.ItemHeight * completionData.Length; + if (totalItemsHeight > height) { + width += ScrollbarWidth; // Compensate for scroll bar. + } + return (int)width; + } + } +} diff --git a/ICSharpCode.TextEditor/Project/Src/Gui/CompletionWindow/DeclarationViewWindow.cs b/ICSharpCode.TextEditor/Project/Src/Gui/CompletionWindow/DeclarationViewWindow.cs new file mode 100644 index 0000000..080eae5 --- /dev/null +++ b/ICSharpCode.TextEditor/Project/Src/Gui/CompletionWindow/DeclarationViewWindow.cs @@ -0,0 +1,125 @@ +// +// +// +// +// $Revision$ +// + +using System; +using System.Drawing; +using System.Windows.Forms; + +using ICSharpCode.TextEditor.Util; + +namespace ICSharpCode.TextEditor.Gui.CompletionWindow +{ + public interface IDeclarationViewWindow + { + string Description { + get; + set; + } + void ShowDeclarationViewWindow(); + void CloseDeclarationViewWindow(); + } + + public class DeclarationViewWindow : Form, IDeclarationViewWindow + { + string description = string.Empty; + bool fixedWidth; + + public string Description { + get { + return description; + } + set { + description = value; + if (value == null && Visible) { + Visible = false; + } else if (value != null) { + if (!Visible) ShowDeclarationViewWindow(); + Refresh(); + } + } + } + + public bool FixedWidth { + get { + return fixedWidth; + } + set { + fixedWidth = value; + } + } + + public int GetRequiredLeftHandSideWidth(Point p) { + if (description != null && description.Length > 0) { + using (Graphics g = CreateGraphics()) { + Size s = TipPainterTools.GetLeftHandSideDrawingSizeHelpTipFromCombinedDescription(this, g, Font, null, description, p); + return s.Width; + } + } + return 0; + } + + public bool HideOnClick; + + public DeclarationViewWindow(Form parent) + { + SetStyle(ControlStyles.Selectable, false); + StartPosition = FormStartPosition.Manual; + FormBorderStyle = FormBorderStyle.None; + Owner = parent; + ShowInTaskbar = false; + Size = new Size(0, 0); + base.CreateHandle(); + } + + protected override CreateParams CreateParams { + get { + CreateParams p = base.CreateParams; + AbstractCompletionWindow.AddShadowToWindow(p); + return p; + } + } + + protected override bool ShowWithoutActivation { + get { + return true; + } + } + + protected override void OnClick(EventArgs e) + { + base.OnClick(e); + if (HideOnClick) Hide(); + } + + public void ShowDeclarationViewWindow() + { + Show(); + } + + public void CloseDeclarationViewWindow() + { + Close(); + Dispose(); + } + + protected override void OnPaint(PaintEventArgs pe) + { + if (description != null && description.Length > 0) { + if (fixedWidth) { + TipPainterTools.DrawFixedWidthHelpTipFromCombinedDescription(this, pe.Graphics, Font, null, description); + } else { + TipPainterTools.DrawHelpTipFromCombinedDescription(this, pe.Graphics, Font, null, description); + } + } + } + + protected override void OnPaintBackground(PaintEventArgs pe) + { + pe.Graphics.FillRectangle(SystemBrushes.Info, pe.ClipRectangle); + } + } +} diff --git a/ICSharpCode.TextEditor/Project/Src/Gui/CompletionWindow/ICompletionData.cs b/ICSharpCode.TextEditor/Project/Src/Gui/CompletionWindow/ICompletionData.cs new file mode 100644 index 0000000..ba1c114 --- /dev/null +++ b/ICSharpCode.TextEditor/Project/Src/Gui/CompletionWindow/ICompletionData.cs @@ -0,0 +1,114 @@ +// +// +// +// +// $Revision$ +// + +using System; + +namespace ICSharpCode.TextEditor.Gui.CompletionWindow +{ + public interface ICompletionData + { + int ImageIndex { + get; + } + + string Text { + get; + set; + } + + string Description { + get; + } + + /// + /// Gets a priority value for the completion data item. + /// When selecting items by their start characters, the item with the highest + /// priority is selected first. + /// + double Priority { + get; + } + + /// + /// Insert the element represented by the completion data into the text + /// editor. + /// + /// TextArea to insert the completion data in. + /// Character that should be inserted after the completion data. + /// \0 when no character should be inserted. + /// Returns true when the insert action has processed the character + /// ; false when the character was not processed. + bool InsertAction(TextArea textArea, char ch); + } + + public class DefaultCompletionData : ICompletionData + { + string text; + string description; + int imageIndex; + + public int ImageIndex { + get { + return imageIndex; + } + } + + public string Text { + get { + return text; + } + set { + text = value; + } + } + + public virtual string Description { + get { + return description; + } + } + + double priority; + + public double Priority { + get { + return priority; + } + set { + priority = value; + } + } + + public virtual bool InsertAction(TextArea textArea, char ch) + { + textArea.InsertString(text); + return false; + } + + public DefaultCompletionData(string text, int imageIndex) + { + this.text = text; + this.imageIndex = imageIndex; + } + + public DefaultCompletionData(string text, string description, int imageIndex) + { + this.text = text; + this.description = description; + this.imageIndex = imageIndex; + } + + public static int Compare(ICompletionData a, ICompletionData b) + { + if (a == null) + throw new ArgumentNullException("a"); + if (b == null) + throw new ArgumentNullException("b"); + return string.Compare(a.Text, b.Text, StringComparison.InvariantCultureIgnoreCase); + } + } +} diff --git a/ICSharpCode.TextEditor/Project/Src/Gui/CompletionWindow/ICompletionDataProvider.cs b/ICSharpCode.TextEditor/Project/Src/Gui/CompletionWindow/ICompletionDataProvider.cs new file mode 100644 index 0000000..4096fea --- /dev/null +++ b/ICSharpCode.TextEditor/Project/Src/Gui/CompletionWindow/ICompletionDataProvider.cs @@ -0,0 +1,62 @@ +// +// +// +// +// $Revision$ +// + +using System; +using System.Windows.Forms; + +namespace ICSharpCode.TextEditor.Gui.CompletionWindow +{ + public interface ICompletionDataProvider + { + ImageList ImageList { + get; + } + string PreSelection { + get; + } + /// + /// Gets the index of the element in the list that is chosen by default. + /// + int DefaultIndex { + get; + } + + /// + /// Processes a keypress. Returns the action to be run with the key. + /// + CompletionDataProviderKeyResult ProcessKey(char key); + + /// + /// Executes the insertion. The provider should set the caret position and then + /// call data.InsertAction. + /// + bool InsertAction(ICompletionData data, TextArea textArea, int insertionOffset, char key); + + /// + /// Generates the completion data. This method is called by the text editor control. + /// + ICompletionData[] GenerateCompletionData(string fileName, TextArea textArea, char charTyped); + } + + public enum CompletionDataProviderKeyResult + { + /// + /// Normal key, used to choose an entry from the completion list + /// + NormalKey, + /// + /// This key triggers insertion of the completed expression + /// + InsertionKey, + /// + /// Increment both start and end offset of completion region when inserting this + /// key. Can be used to insert whitespace (or other characters) in front of the expression + /// while the completion window is open. + /// + BeforeStartKey + } +} diff --git a/ICSharpCode.TextEditor/Project/Src/Gui/DrawableLine.cs b/ICSharpCode.TextEditor/Project/Src/Gui/DrawableLine.cs new file mode 100644 index 0000000..3c8d780 --- /dev/null +++ b/ICSharpCode.TextEditor/Project/Src/Gui/DrawableLine.cs @@ -0,0 +1,190 @@ +// +// +// +// +// $Revision$ +// + +using System; +using System.Collections.Generic; +using System.Drawing; + +using ICSharpCode.TextEditor.Document; + +namespace ICSharpCode.TextEditor +{ + /// + /// A class that is able to draw a line on any control (outside the text editor) + /// + public class DrawableLine + { + static StringFormat sf = (StringFormat)System.Drawing.StringFormat.GenericTypographic.Clone(); + + List words = new List(); + SizeF spaceSize; + Font monospacedFont; + Font boldMonospacedFont; + + private class SimpleTextWord { + internal TextWordType Type; + internal string Word; + internal bool Bold; + internal Color Color; + + public SimpleTextWord(TextWordType Type, string Word, bool Bold, Color Color) + { + this.Type = Type; + this.Word = Word; + this.Bold = Bold; + this.Color = Color; + } + + internal readonly static SimpleTextWord Space = new SimpleTextWord(TextWordType.Space, " ", false, Color.Black); + internal readonly static SimpleTextWord Tab = new SimpleTextWord(TextWordType.Tab, "\t", false, Color.Black); + } + + public DrawableLine(IDocument document, LineSegment line, Font monospacedFont, Font boldMonospacedFont) + { + this.monospacedFont = monospacedFont; + this.boldMonospacedFont = boldMonospacedFont; + if (line.Words != null) { + foreach (TextWord word in line.Words) { + if (word.Type == TextWordType.Space) { + words.Add(SimpleTextWord.Space); + } else if (word.Type == TextWordType.Tab) { + words.Add(SimpleTextWord.Tab); + } else { + words.Add(new SimpleTextWord(TextWordType.Word, word.Word, word.Bold, word.Color)); + } + } + } else { + words.Add(new SimpleTextWord(TextWordType.Word, document.GetText(line), false, Color.Black)); + } + } + + public int LineLength { + get { + int length = 0; + foreach (SimpleTextWord word in words) { + length += word.Word.Length; + } + return length; + } + } + + public void SetBold(int startIndex, int endIndex, bool bold) + { + if (startIndex < 0) + throw new ArgumentException("startIndex must be >= 0"); + if (startIndex > endIndex) + throw new ArgumentException("startIndex must be <= endIndex"); + if (startIndex == endIndex) return; + int pos = 0; + for (int i = 0; i < words.Count; i++) { + SimpleTextWord word = words[i]; + if (pos >= endIndex) + break; + int wordEnd = pos + word.Word.Length; + // 3 possibilities: + if (startIndex <= pos && endIndex >= wordEnd) { + // word is fully in region: + word.Bold = bold; + } else if (startIndex <= pos) { + // beginning of word is in region + int inRegionLength = endIndex - pos; + SimpleTextWord newWord = new SimpleTextWord(word.Type, word.Word.Substring(inRegionLength), word.Bold, word.Color); + words.Insert(i + 1, newWord); + + word.Bold = bold; + word.Word = word.Word.Substring(0, inRegionLength); + } else if (startIndex < wordEnd) { + // end of word is in region (or middle of word is in region) + int notInRegionLength = startIndex - pos; + + SimpleTextWord newWord = new SimpleTextWord(word.Type, word.Word.Substring(notInRegionLength), word.Bold, word.Color); + // newWord.Bold will be set in the next iteration + words.Insert(i + 1, newWord); + + word.Word = word.Word.Substring(0, notInRegionLength); + } + pos = wordEnd; + } + } + + public static float DrawDocumentWord(Graphics g, string word, PointF position, Font font, Color foreColor) + { + if (word == null || word.Length == 0) { + return 0f; + } + SizeF wordSize = g.MeasureString(word, font, 32768, sf); + + g.DrawString(word, + font, + BrushRegistry.GetBrush(foreColor), + position, + sf); + return wordSize.Width; + } + + public SizeF GetSpaceSize(Graphics g) + { + if (spaceSize.IsEmpty) { + spaceSize = g.MeasureString("-", boldMonospacedFont, new PointF(0, 0), sf); + } + return spaceSize; + } + + public void DrawLine(Graphics g, ref float xPos, float xOffset, float yPos, Color c) + { + SizeF spaceSize = GetSpaceSize(g); + foreach (SimpleTextWord word in words) { + switch (word.Type) { + case TextWordType.Space: + xPos += spaceSize.Width; + break; + case TextWordType.Tab: + float tabWidth = spaceSize.Width * 4; + xPos += tabWidth; + xPos = (int)((xPos + 2) / tabWidth) * tabWidth; + break; + case TextWordType.Word: + xPos += DrawDocumentWord(g, + word.Word, + new PointF(xPos + xOffset, yPos), + word.Bold ? boldMonospacedFont : monospacedFont, + c == Color.Empty ? word.Color : c + ); + break; + } + } + } + + public void DrawLine(Graphics g, ref float xPos, float xOffset, float yPos) + { + DrawLine(g, ref xPos, xOffset, yPos, Color.Empty); + } + + public float MeasureWidth(Graphics g, float xPos) + { + SizeF spaceSize = GetSpaceSize(g); + foreach (SimpleTextWord word in words) { + switch (word.Type) { + case TextWordType.Space: + xPos += spaceSize.Width; + break; + case TextWordType.Tab: + float tabWidth = spaceSize.Width * 4; + xPos += tabWidth; + xPos = (int)((xPos + 2) / tabWidth) * tabWidth; + break; + case TextWordType.Word: + if (word.Word != null && word.Word.Length > 0) { + xPos += g.MeasureString(word.Word, word.Bold ? boldMonospacedFont : monospacedFont, 32768, sf).Width; + } + break; + } + } + return xPos; + } + } +} diff --git a/ICSharpCode.TextEditor/Project/Src/Gui/FoldMargin.cs b/ICSharpCode.TextEditor/Project/Src/Gui/FoldMargin.cs new file mode 100644 index 0000000..2ebd32c --- /dev/null +++ b/ICSharpCode.TextEditor/Project/Src/Gui/FoldMargin.cs @@ -0,0 +1,276 @@ +// +// +// +// +// $Revision$ +// + +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Windows.Forms; + +using ICSharpCode.TextEditor.Document; + +namespace ICSharpCode.TextEditor +{ + /// + /// This class views the line numbers and folding markers. + /// + public class FoldMargin : AbstractMargin + { + int selectedFoldLine = -1; + + public override Size Size { + get { + return new Size((int)(textArea.TextView.FontHeight), + -1); + } + } + + public override bool IsVisible { + get { + return textArea.TextEditorProperties.EnableFolding; + } + } + + public FoldMargin(TextArea textArea) : base(textArea) + { + } + + public override void Paint(Graphics g, Rectangle rect) + { + if (rect.Width <= 0 || rect.Height <= 0) { + return; + } + HighlightColor lineNumberPainterColor = textArea.Document.HighlightingStrategy.GetColorFor("LineNumbers"); + + + for (int y = 0; y < (DrawingPosition.Height + textArea.TextView.VisibleLineDrawingRemainder) / textArea.TextView.FontHeight + 1; ++y) { + Rectangle markerRectangle = new Rectangle(DrawingPosition.X, + DrawingPosition.Top + y * textArea.TextView.FontHeight - textArea.TextView.VisibleLineDrawingRemainder, + DrawingPosition.Width, + textArea.TextView.FontHeight); + + if (rect.IntersectsWith(markerRectangle)) { + // draw dotted separator line + if (textArea.Document.TextEditorProperties.ShowLineNumbers) { + g.FillRectangle(BrushRegistry.GetBrush(textArea.Enabled ? lineNumberPainterColor.BackgroundColor : SystemColors.InactiveBorder), + markerRectangle); + + g.DrawLine(BrushRegistry.GetDotPen(lineNumberPainterColor.Color), + base.drawingPosition.X, + markerRectangle.Y, + base.drawingPosition.X, + markerRectangle.Bottom); + } else { + g.FillRectangle(BrushRegistry.GetBrush(textArea.Enabled ? lineNumberPainterColor.BackgroundColor : SystemColors.InactiveBorder), markerRectangle); + } + + int currentLine = textArea.Document.GetFirstLogicalLine(textArea.TextView.FirstPhysicalLine + y); + if (currentLine < textArea.Document.TotalNumberOfLines) { + PaintFoldMarker(g, currentLine, markerRectangle); + } + } + } + } + + bool SelectedFoldingFrom(List list) + { + if (list != null) { + for (int i = 0; i < list.Count; ++i) { + if (this.selectedFoldLine == list[i].StartLine) { + return true; + } + } + } + return false; + } + + void PaintFoldMarker(Graphics g, int lineNumber, Rectangle drawingRectangle) + { + HighlightColor foldLineColor = textArea.Document.HighlightingStrategy.GetColorFor("FoldLine"); + HighlightColor selectedFoldLine = textArea.Document.HighlightingStrategy.GetColorFor("SelectedFoldLine"); + + List foldingsWithStart = textArea.Document.FoldingManager.GetFoldingsWithStart(lineNumber); + List foldingsBetween = textArea.Document.FoldingManager.GetFoldingsContainsLineNumber(lineNumber); + List foldingsWithEnd = textArea.Document.FoldingManager.GetFoldingsWithEnd(lineNumber); + + bool isFoldStart = foldingsWithStart.Count > 0; + bool isBetween = foldingsBetween.Count > 0; + bool isFoldEnd = foldingsWithEnd.Count > 0; + + bool isStartSelected = SelectedFoldingFrom(foldingsWithStart); + bool isBetweenSelected = SelectedFoldingFrom(foldingsBetween); + bool isEndSelected = SelectedFoldingFrom(foldingsWithEnd); + + int foldMarkerSize = (int)Math.Round(textArea.TextView.FontHeight * 0.57f); + foldMarkerSize -= (foldMarkerSize) % 2; + int foldMarkerYPos = drawingRectangle.Y + (int)((drawingRectangle.Height - foldMarkerSize) / 2); + int xPos = drawingRectangle.X + (drawingRectangle.Width - foldMarkerSize) / 2 + foldMarkerSize / 2; + + + if (isFoldStart) { + bool isVisible = true; + bool moreLinedOpenFold = false; + foreach (FoldMarker foldMarker in foldingsWithStart) { + if (foldMarker.IsFolded) { + isVisible = false; + } else { + moreLinedOpenFold = foldMarker.EndLine > foldMarker.StartLine; + } + } + + bool isFoldEndFromUpperFold = false; + foreach (FoldMarker foldMarker in foldingsWithEnd) { + if (foldMarker.EndLine > foldMarker.StartLine && !foldMarker.IsFolded) { + isFoldEndFromUpperFold = true; + } + } + + DrawFoldMarker(g, new RectangleF(drawingRectangle.X + (drawingRectangle.Width - foldMarkerSize) / 2, + foldMarkerYPos, + foldMarkerSize, + foldMarkerSize), + isVisible, + isStartSelected + ); + + // draw line above fold marker + if (isBetween || isFoldEndFromUpperFold) { + g.DrawLine(BrushRegistry.GetPen(isBetweenSelected ? selectedFoldLine.Color : foldLineColor.Color), + xPos, + drawingRectangle.Top, + xPos, + foldMarkerYPos - 1); + } + + // draw line below fold marker + if (isBetween || moreLinedOpenFold) { + g.DrawLine(BrushRegistry.GetPen(isEndSelected || (isStartSelected && isVisible) || isBetweenSelected ? selectedFoldLine.Color : foldLineColor.Color), + xPos, + foldMarkerYPos + foldMarkerSize + 1, + xPos, + drawingRectangle.Bottom); + } + } else { + if (isFoldEnd) { + int midy = drawingRectangle.Top + drawingRectangle.Height / 2; + + // draw fold end marker + g.DrawLine(BrushRegistry.GetPen(isEndSelected ? selectedFoldLine.Color : foldLineColor.Color), + xPos, + midy, + xPos + foldMarkerSize / 2, + midy); + + // draw line above fold end marker + // must be drawn after fold marker because it might have a different color than the fold marker + g.DrawLine(BrushRegistry.GetPen(isBetweenSelected || isEndSelected ? selectedFoldLine.Color : foldLineColor.Color), + xPos, + drawingRectangle.Top, + xPos, + midy); + + // draw line below fold end marker + if (isBetween) { + g.DrawLine(BrushRegistry.GetPen(isBetweenSelected ? selectedFoldLine.Color : foldLineColor.Color), + xPos, + midy + 1, + xPos, + drawingRectangle.Bottom); + } + } else if (isBetween) { + // just draw the line :) + g.DrawLine(BrushRegistry.GetPen(isBetweenSelected ? selectedFoldLine.Color : foldLineColor.Color), + xPos, + drawingRectangle.Top, + xPos, + drawingRectangle.Bottom); + } + } + } + + public override void HandleMouseMove(Point mousepos, MouseButtons mouseButtons) + { + bool showFolding = textArea.Document.TextEditorProperties.EnableFolding; + int physicalLine = + (int)((mousepos.Y + textArea.VirtualTop.Y) / textArea.TextView.FontHeight); + int realline = textArea.Document.GetFirstLogicalLine(physicalLine); + + if (!showFolding || realline < 0 || realline + 1 >= textArea.Document.TotalNumberOfLines) { + return; + } + + List foldMarkers = textArea.Document.FoldingManager.GetFoldingsWithStart(realline); + int oldSelection = selectedFoldLine; + if (foldMarkers.Count > 0) { + selectedFoldLine = realline; + } else { + selectedFoldLine = -1; + } + if (oldSelection != selectedFoldLine) { + textArea.Refresh(this); + } + } + + public override void HandleMouseDown(Point mousepos, MouseButtons mouseButtons) + { + bool showFolding = textArea.Document.TextEditorProperties.EnableFolding; + int physicalLine = + (int)((mousepos.Y + textArea.VirtualTop.Y) / textArea.TextView.FontHeight); + int realline = textArea.Document.GetFirstLogicalLine(physicalLine); + + // focus the textarea if the user clicks on the line number view + textArea.Focus(); + + if (!showFolding || realline < 0 || realline + 1 >= textArea.Document.TotalNumberOfLines) { + return; + } + + List foldMarkers = textArea.Document.FoldingManager.GetFoldingsWithStart(realline); + foreach (FoldMarker fm in foldMarkers) { + fm.IsFolded = !fm.IsFolded; + } + textArea.Document.FoldingManager.NotifyFoldingsChanged(EventArgs.Empty); + } + + public override void HandleMouseLeave(EventArgs e) + { + if (selectedFoldLine != -1) { + selectedFoldLine = -1; + textArea.Refresh(this); + } + } + + #region Drawing functions + void DrawFoldMarker(Graphics g, RectangleF rectangle, bool isOpened, bool isSelected) + { + HighlightColor foldMarkerColor = textArea.Document.HighlightingStrategy.GetColorFor("FoldMarker"); + HighlightColor foldLineColor = textArea.Document.HighlightingStrategy.GetColorFor("FoldLine"); + HighlightColor selectedFoldLine = textArea.Document.HighlightingStrategy.GetColorFor("SelectedFoldLine"); + + Rectangle intRect = new Rectangle((int)rectangle.X, (int)rectangle.Y, (int)rectangle.Width, (int)rectangle.Height); + g.FillRectangle(BrushRegistry.GetBrush(foldMarkerColor.BackgroundColor), intRect); + g.DrawRectangle(BrushRegistry.GetPen(isSelected ? selectedFoldLine.Color : foldLineColor.Color), intRect); + + int space = (int)Math.Round(((double)rectangle.Height) / 8d) + 1; + int mid = intRect.Height / 2 + intRect.Height % 2; + + // draw minus + g.DrawLine(BrushRegistry.GetPen(foldMarkerColor.Color), + rectangle.X + space, + rectangle.Y + mid, + rectangle.X + rectangle.Width - space, + rectangle.Y + mid); + + // draw plus + if (!isOpened) { + g.DrawLine(BrushRegistry.GetPen(foldMarkerColor.Color), + rectangle.X + mid, + rectangle.Y + space, + rectangle.X + mid, + rectangle.Y + rectangle.Height - space); + } + } + #endregion + } +} diff --git a/ICSharpCode.TextEditor/Project/Src/Gui/GutterMargin.cs b/ICSharpCode.TextEditor/Project/Src/Gui/GutterMargin.cs new file mode 100644 index 0000000..caa5303 --- /dev/null +++ b/ICSharpCode.TextEditor/Project/Src/Gui/GutterMargin.cs @@ -0,0 +1,159 @@ +// +// +// +// +// $Revision$ +// + +using System; +using System.Drawing; +using System.IO; +using System.Reflection; +using System.Windows.Forms; + +using ICSharpCode.TextEditor.Document; + +namespace ICSharpCode.TextEditor +{ + /// + /// This class views the line numbers and folding markers. + /// + public class GutterMargin : AbstractMargin, IDisposable + { + StringFormat numberStringFormat = (StringFormat)StringFormat.GenericTypographic.Clone(); + + public static Cursor RightLeftCursor; + + static GutterMargin() + { + using (var ms = new MemoryStream(Properties.Resources.RightArrow)) + { + RightLeftCursor = new Cursor(ms); + } + } + + public void Dispose() + { + numberStringFormat.Dispose(); + } + + public override Cursor Cursor { + get { + return RightLeftCursor; + } + } + + public override Size Size { + get { + return new Size((int)(textArea.TextView.WideSpaceWidth + * Math.Max(3, (int)Math.Log10(textArea.Document.TotalNumberOfLines) + 1)), + -1); + } + } + + public override bool IsVisible { + get { + return textArea.TextEditorProperties.ShowLineNumbers; + } + } + + public GutterMargin(TextArea textArea) : base(textArea) + { + numberStringFormat.LineAlignment = StringAlignment.Far; + numberStringFormat.FormatFlags = StringFormatFlags.MeasureTrailingSpaces | StringFormatFlags.FitBlackBox | + StringFormatFlags.NoWrap | StringFormatFlags.NoClip; + } + + public override void Paint(Graphics g, Rectangle rect) + { + if (rect.Width <= 0 || rect.Height <= 0) { + return; + } + HighlightColor lineNumberPainterColor = textArea.Document.HighlightingStrategy.GetColorFor("LineNumbers"); + int fontHeight = textArea.TextView.FontHeight; + Brush fillBrush = textArea.Enabled ? BrushRegistry.GetBrush(lineNumberPainterColor.BackgroundColor) : SystemBrushes.InactiveBorder; + Brush drawBrush = BrushRegistry.GetBrush(lineNumberPainterColor.Color); + for (int y = 0; y < (DrawingPosition.Height + textArea.TextView.VisibleLineDrawingRemainder) / fontHeight + 1; ++y) { + int ypos = drawingPosition.Y + fontHeight * y - textArea.TextView.VisibleLineDrawingRemainder; + Rectangle backgroundRectangle = new Rectangle(drawingPosition.X, ypos, drawingPosition.Width, fontHeight); + if (rect.IntersectsWith(backgroundRectangle)) { + g.FillRectangle(fillBrush, backgroundRectangle); + int curLine = textArea.Document.GetFirstLogicalLine(textArea.Document.GetVisibleLine(textArea.TextView.FirstVisibleLine) + y); + + if (curLine < textArea.Document.TotalNumberOfLines) { + g.DrawString((curLine + 1).ToString(), + lineNumberPainterColor.GetFont(TextEditorProperties.FontContainer), + drawBrush, + backgroundRectangle, + numberStringFormat); + } + } + } + } + + public override void HandleMouseDown(Point mousepos, MouseButtons mouseButtons) + { + TextLocation selectionStartPos; + + textArea.SelectionManager.selectFrom.where = WhereFrom.Gutter; + int realline = textArea.TextView.GetLogicalLine(mousepos.Y); + if (realline >= 0 && realline < textArea.Document.TotalNumberOfLines) { + // shift-select + if((Control.ModifierKeys & Keys.Shift) != 0) { + if(!textArea.SelectionManager.HasSomethingSelected && realline != textArea.Caret.Position.Y) { + if (realline >= textArea.Caret.Position.Y) + { // at or below starting selection, place the cursor on the next line + // nothing is selected so make a new selection from cursor + selectionStartPos = textArea.Caret.Position; + // whole line selection - start of line to start of next line + if (realline < textArea.Document.TotalNumberOfLines - 1) + { + textArea.SelectionManager.SetSelection(new DefaultSelection(textArea.Document, selectionStartPos, new TextLocation(0, realline + 1))); + textArea.Caret.Position = new TextLocation(0, realline + 1); + } + else + { + textArea.SelectionManager.SetSelection(new DefaultSelection(textArea.Document, selectionStartPos, new TextLocation(textArea.Document.GetLineSegment(realline).Length + 1, realline))); + textArea.Caret.Position = new TextLocation(textArea.Document.GetLineSegment(realline).Length + 1, realline); + } + } + else + { // prior lines to starting selection, place the cursor on the same line as the new selection + // nothing is selected so make a new selection from cursor + selectionStartPos = textArea.Caret.Position; + // whole line selection - start of line to start of next line + textArea.SelectionManager.SetSelection(new DefaultSelection(textArea.Document, selectionStartPos, new TextLocation(selectionStartPos.X, selectionStartPos.Y))); + textArea.SelectionManager.ExtendSelection(new TextLocation(selectionStartPos.X, selectionStartPos.Y), new TextLocation(0, realline)); + textArea.Caret.Position = new TextLocation(0, realline); + } + } + else + { + // let MouseMove handle a shift-click in a gutter + MouseEventArgs e = new MouseEventArgs(mouseButtons, 1, mousepos.X, mousepos.Y, 0); + textArea.RaiseMouseMove(e); + } + } else { // this is a new selection with no shift-key + // sync the textareamousehandler mouse location + // (fixes problem with clicking out into a menu then back to the gutter whilst + // there is a selection) + textArea.mousepos = mousepos; + + selectionStartPos = new TextLocation(0, realline); + textArea.SelectionManager.ClearSelection(); + // whole line selection - start of line to start of next line + if (realline < textArea.Document.TotalNumberOfLines - 1) + { + textArea.SelectionManager.SetSelection(new DefaultSelection(textArea.Document, selectionStartPos, new TextLocation(selectionStartPos.X, selectionStartPos.Y + 1))); + textArea.Caret.Position = new TextLocation(selectionStartPos.X, selectionStartPos.Y + 1); + } + else + { + textArea.SelectionManager.SetSelection(new DefaultSelection(textArea.Document, new TextLocation(0, realline), new TextLocation(textArea.Document.GetLineSegment(realline).Length + 1, selectionStartPos.Y))); + textArea.Caret.Position = new TextLocation(textArea.Document.GetLineSegment(realline).Length + 1, selectionStartPos.Y); + } + } + } + } + } +} diff --git a/ICSharpCode.TextEditor/Project/Src/Gui/HRuler.cs b/ICSharpCode.TextEditor/Project/Src/Gui/HRuler.cs new file mode 100644 index 0000000..020a908 --- /dev/null +++ b/ICSharpCode.TextEditor/Project/Src/Gui/HRuler.cs @@ -0,0 +1,54 @@ +// +// +// +// +// $Revision$ +// + +using System; +using System.Drawing; +using System.Windows.Forms; + +namespace ICSharpCode.TextEditor +{ + /// + /// Horizontal ruler - text column measuring ruler at the top of the text area. + /// + public class HRuler : Control + { + TextArea textArea; + + public HRuler(TextArea textArea) + { + this.textArea = textArea; + } + + protected override void OnPaint(System.Windows.Forms.PaintEventArgs e) + { + Graphics g = e.Graphics; + int num = 0; + for (float x = textArea.TextView.DrawingPosition.Left; x < textArea.TextView.DrawingPosition.Right; x += textArea.TextView.WideSpaceWidth) { + int offset = (Height * 2) / 3; + if (num % 5 == 0) { + offset = (Height * 4) / 5; + } + + if (num % 10 == 0) { + offset = 1; + } + ++num; + g.DrawLine(Pens.Black, + (int)x, offset, (int)x, Height - offset); + } + } + + protected override void OnPaintBackground(System.Windows.Forms.PaintEventArgs e) + { + e.Graphics.FillRectangle(Brushes.White, + new Rectangle(0, + 0, + Width, + Height)); + } + } +} diff --git a/ICSharpCode.TextEditor/Project/Src/Gui/IconBarMargin.cs b/ICSharpCode.TextEditor/Project/Src/Gui/IconBarMargin.cs new file mode 100644 index 0000000..38b2704 --- /dev/null +++ b/ICSharpCode.TextEditor/Project/Src/Gui/IconBarMargin.cs @@ -0,0 +1,254 @@ +// +// +// +// +// $Revision$ +// + +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Drawing.Drawing2D; +using System.Windows.Forms; + +using ICSharpCode.TextEditor.Document; + +namespace ICSharpCode.TextEditor +{ + /// + /// This class views the line numbers and folding markers. + /// + public class IconBarMargin : AbstractMargin + { + const int iconBarWidth = 18; + + static readonly Size iconBarSize = new Size(iconBarWidth, -1); + + public override Size Size { + get { + return iconBarSize; + } + } + + public override bool IsVisible { + get { + return textArea.TextEditorProperties.IsIconBarVisible; + } + } + + + public IconBarMargin(TextArea textArea) : base(textArea) + { + } + + public override void Paint(Graphics g, Rectangle rect) + { + if (rect.Width <= 0 || rect.Height <= 0) { + return; + } + // paint background + g.FillRectangle(SystemBrushes.Control, new Rectangle(drawingPosition.X, rect.Top, drawingPosition.Width - 1, rect.Height)); + g.DrawLine(SystemPens.ControlDark, base.drawingPosition.Right - 1, rect.Top, base.drawingPosition.Right - 1, rect.Bottom); + + // paint icons + foreach (Bookmark mark in textArea.Document.BookmarkManager.Marks) { + int lineNumber = textArea.Document.GetVisibleLine(mark.LineNumber); + int lineHeight = textArea.TextView.FontHeight; + int yPos = (int)(lineNumber * lineHeight) - textArea.VirtualTop.Y; + if (IsLineInsideRegion(yPos, yPos + lineHeight, rect.Y, rect.Bottom)) { + if (lineNumber == textArea.Document.GetVisibleLine(mark.LineNumber - 1)) { + // marker is inside folded region, do not draw it + continue; + } + mark.Draw(this, g, new Point(0, yPos)); + } + } + base.Paint(g, rect); + } + + public override void HandleMouseDown(Point mousePos, MouseButtons mouseButtons) + { + int clickedVisibleLine = (mousePos.Y + textArea.VirtualTop.Y) / textArea.TextView.FontHeight; + int lineNumber = textArea.Document.GetFirstLogicalLine(clickedVisibleLine); + + if ((mouseButtons & MouseButtons.Right) == MouseButtons.Right) { + if (textArea.Caret.Line != lineNumber) { + textArea.Caret.Line = lineNumber; + } + } + + IList marks = textArea.Document.BookmarkManager.Marks; + List marksInLine = new List(); + int oldCount = marks.Count; + foreach (Bookmark mark in marks) { + if (mark.LineNumber == lineNumber) { + marksInLine.Add(mark); + } + } + for (int i = marksInLine.Count - 1; i >= 0; i--) { + Bookmark mark = marksInLine[i]; + if (mark.Click(textArea, new MouseEventArgs(mouseButtons, 1, mousePos.X, mousePos.Y, 0))) { + if (oldCount != marks.Count) { + textArea.UpdateLine(lineNumber); + } + return; + } + } + base.HandleMouseDown(mousePos, mouseButtons); + } + + #region Drawing functions + public void DrawBreakpoint(Graphics g, int y, bool isEnabled, bool isHealthy) + { + int diameter = Math.Min(iconBarWidth - 2, textArea.TextView.FontHeight); + Rectangle rect = new Rectangle(1, + y + (textArea.TextView.FontHeight - diameter) / 2, + diameter, + diameter); + + + using (GraphicsPath path = new GraphicsPath()) { + path.AddEllipse(rect); + using (PathGradientBrush pthGrBrush = new PathGradientBrush(path)) { + pthGrBrush.CenterPoint = new PointF(rect.Left + rect.Width / 3 , rect.Top + rect.Height / 3); + pthGrBrush.CenterColor = Color.MistyRose; + Color[] colors = {isHealthy ? Color.Firebrick : Color.Olive}; + pthGrBrush.SurroundColors = colors; + + if (isEnabled) { + g.FillEllipse(pthGrBrush, rect); + } else { + g.FillEllipse(SystemBrushes.Control, rect); + using (Pen pen = new Pen(pthGrBrush)) { + g.DrawEllipse(pen, new Rectangle(rect.X + 1, rect.Y + 1, rect.Width - 2, rect.Height - 2)); + } + } + } + } + } + + public void DrawBookmark(Graphics g, int y, bool isEnabled) + { + int delta = textArea.TextView.FontHeight / 8; + Rectangle rect = new Rectangle(1, y + delta, base.drawingPosition.Width - 4, textArea.TextView.FontHeight - delta * 2); + + if (isEnabled) { + using (Brush brush = new LinearGradientBrush(new Point(rect.Left, rect.Top), + new Point(rect.Right, rect.Bottom), + Color.SkyBlue, + Color.White)) { + FillRoundRect(g, brush, rect); + } + } else { + FillRoundRect(g, Brushes.White, rect); + } + using (Brush brush = new LinearGradientBrush(new Point(rect.Left, rect.Top), + new Point(rect.Right, rect.Bottom), + Color.SkyBlue, + Color.Blue)) { + using (Pen pen = new Pen(brush)) { + DrawRoundRect(g, pen, rect); + } + } + } + + public void DrawArrow(Graphics g, int y) + { + int delta = textArea.TextView.FontHeight / 8; + Rectangle rect = new Rectangle(1, y + delta, base.drawingPosition.Width - 4, textArea.TextView.FontHeight - delta * 2); + using (Brush brush = new LinearGradientBrush(new Point(rect.Left, rect.Top), + new Point(rect.Right, rect.Bottom), + Color.LightYellow, + Color.Yellow)) { + FillArrow(g, brush, rect); + } + + using (Brush brush = new LinearGradientBrush(new Point(rect.Left, rect.Top), + new Point(rect.Right, rect.Bottom), + Color.Yellow, + Color.Brown)) { + using (Pen pen = new Pen(brush)) { + DrawArrow(g, pen, rect); + } + } + } + + GraphicsPath CreateArrowGraphicsPath(Rectangle r) + { + GraphicsPath gp = new GraphicsPath(); + int halfX = r.Width / 2; + int halfY = r.Height/ 2; + gp.AddLine(r.X, r.Y + halfY/2, r.X + halfX, r.Y + halfY/2); + gp.AddLine(r.X + halfX, r.Y + halfY/2, r.X + halfX, r.Y); + gp.AddLine(r.X + halfX, r.Y, r.Right, r.Y + halfY); + gp.AddLine(r.Right, r.Y + halfY, r.X + halfX, r.Bottom); + gp.AddLine(r.X + halfX, r.Bottom, r.X + halfX, r.Bottom - halfY/2); + gp.AddLine(r.X + halfX, r.Bottom - halfY/2, r.X, r.Bottom - halfY/2); + gp.AddLine(r.X, r.Bottom - halfY/2, r.X, r.Y + halfY/2); + gp.CloseFigure(); + return gp; + } + + GraphicsPath CreateRoundRectGraphicsPath(Rectangle r) + { + GraphicsPath gp = new GraphicsPath(); + int radius = r.Width / 2; + gp.AddLine(r.X + radius, r.Y, r.Right - radius, r.Y); + gp.AddArc(r.Right - radius, r.Y, radius, radius, 270, 90); + + gp.AddLine(r.Right, r.Y + radius, r.Right, r.Bottom - radius); + gp.AddArc(r.Right - radius, r.Bottom - radius, radius, radius, 0, 90); + + gp.AddLine(r.Right - radius, r.Bottom, r.X + radius, r.Bottom); + gp.AddArc(r.X, r.Bottom - radius, radius, radius, 90, 90); + + gp.AddLine(r.X, r.Bottom - radius, r.X, r.Y + radius); + gp.AddArc(r.X, r.Y, radius, radius, 180, 90); + + gp.CloseFigure(); + return gp; + } + + void DrawRoundRect(Graphics g, Pen p , Rectangle r) + { + using (GraphicsPath gp = CreateRoundRectGraphicsPath(r)) { + g.DrawPath(p, gp); + } + } + + void FillRoundRect(Graphics g, Brush b , Rectangle r) + { + using (GraphicsPath gp = CreateRoundRectGraphicsPath(r)) { + g.FillPath(b, gp); + } + } + + void DrawArrow(Graphics g, Pen p , Rectangle r) + { + using (GraphicsPath gp = CreateArrowGraphicsPath(r)) { + g.DrawPath(p, gp); + } + } + + void FillArrow(Graphics g, Brush b , Rectangle r) + { + using (GraphicsPath gp = CreateArrowGraphicsPath(r)) { + g.FillPath(b, gp); + } + } + + #endregion + + static bool IsLineInsideRegion(int top, int bottom, int regionTop, int regionBottom) + { + if (top >= regionTop && top <= regionBottom) { + // Region overlaps the line's top edge. + return true; + } else if(regionTop > top && regionTop < bottom) { + // Region's top edge inside line. + return true; + } + return false; + } + } +} diff --git a/ICSharpCode.TextEditor/Project/Src/Gui/Ime.cs b/ICSharpCode.TextEditor/Project/Src/Gui/Ime.cs new file mode 100644 index 0000000..f5d6519 --- /dev/null +++ b/ICSharpCode.TextEditor/Project/Src/Gui/Ime.cs @@ -0,0 +1,179 @@ +// +// +// +// +// $Revision$ +// + +using System; +using System.Drawing; +using System.Runtime.InteropServices; +using System.Windows.Forms; + +namespace ICSharpCode.TextEditor +{ + /// + /// Used internally, not for own use. + /// + internal class Ime + { + public Ime(IntPtr hWnd, Font font) + { + // For unknown reasons, the IME support is causing crashes when used in a WOW64 process + // or when used in .NET 4.0. We'll disable IME support in those cases. + string PROCESSOR_ARCHITEW6432 = Environment.GetEnvironmentVariable("PROCESSOR_ARCHITEW6432"); + if (PROCESSOR_ARCHITEW6432 == "IA64" || PROCESSOR_ARCHITEW6432 == "AMD64" || Environment.OSVersion.Platform == PlatformID.Unix || Environment.Version >= new Version(4,0)) { + disableIME = true; + } else { + this.hIMEWnd = ImmGetDefaultIMEWnd(hWnd); + } + this.hWnd = hWnd; + this.font = font; + SetIMEWindowFont(font); + } + + private Font font = null; + public Font Font + { + get { + return font; + } + set { + if (!value.Equals(font)) { + font = value; + lf = null; + SetIMEWindowFont(value); + } + } + } + + public IntPtr HWnd + { + set { + if (this.hWnd != value) { + this.hWnd = value; + if (!disableIME) + this.hIMEWnd = ImmGetDefaultIMEWnd(value); + SetIMEWindowFont(font); + } + } + } + + [ DllImport("imm32.dll") ] + private static extern IntPtr ImmGetDefaultIMEWnd(IntPtr hWnd); + + [ DllImport("user32.dll") ] + private static extern IntPtr SendMessage(IntPtr hWnd, int msg, IntPtr wParam, COMPOSITIONFORM lParam); + [ DllImport("user32.dll") ] + private static extern IntPtr SendMessage(IntPtr hWnd, int msg, IntPtr wParam, [In, MarshalAs(UnmanagedType.LPStruct)] LOGFONT lParam); + + [ StructLayout(LayoutKind.Sequential) ] + private class COMPOSITIONFORM + { + public int dwStyle = 0; + public POINT ptCurrentPos = null; + public RECT rcArea = null; + } + + [ StructLayout(LayoutKind.Sequential) ] + private class POINT + { + public int x = 0; + public int y = 0; + } + + [ StructLayout(LayoutKind.Sequential) ] + private class RECT + { + public int left = 0; + public int top = 0; + public int right = 0; + public int bottom = 0; + } + + private const int WM_IME_CONTROL = 0x0283; + + private const int IMC_SETCOMPOSITIONWINDOW = 0x000c; + private IntPtr hIMEWnd; + private IntPtr hWnd; + private const int CFS_POINT = 0x0002; + + [ StructLayout(LayoutKind.Sequential) ] + private class LOGFONT + { + public int lfHeight = 0; + public int lfWidth = 0; + public int lfEscapement = 0; + public int lfOrientation = 0; + public int lfWeight = 0; + public byte lfItalic = 0; + public byte lfUnderline = 0; + public byte lfStrikeOut = 0; + public byte lfCharSet = 0; + public byte lfOutPrecision = 0; + public byte lfClipPrecision = 0; + public byte lfQuality = 0; + public byte lfPitchAndFamily = 0; + [ MarshalAs(UnmanagedType.ByValTStr, SizeConst=32) ] public string lfFaceName = null; + } + private const int IMC_SETCOMPOSITIONFONT = 0x000a; + LOGFONT lf = null; + static bool disableIME; + + private void SetIMEWindowFont(Font f) + { + if (disableIME || hIMEWnd == IntPtr.Zero) return; + + if (lf == null) { + lf = new LOGFONT(); + f.ToLogFont(lf); + lf.lfFaceName = f.Name; // This is very important! "Font.ToLogFont" Method sets invalid value to LOGFONT.lfFaceName + } + + try { + SendMessage( + hIMEWnd, + WM_IME_CONTROL, + new IntPtr(IMC_SETCOMPOSITIONFONT), + lf + ); + } catch (AccessViolationException ex) { + Handle(ex); + } + } + + public void SetIMEWindowLocation(int x, int y) + { + if (disableIME || hIMEWnd == IntPtr.Zero) return; + + POINT p = new POINT(); + p.x = x; + p.y = y; + + COMPOSITIONFORM lParam = new COMPOSITIONFORM(); + lParam.dwStyle = CFS_POINT; + lParam.ptCurrentPos = p; + lParam.rcArea = new RECT(); + + try { + SendMessage( + hIMEWnd, + WM_IME_CONTROL, + new IntPtr(IMC_SETCOMPOSITIONWINDOW), + lParam + ); + } catch (AccessViolationException ex) { + Handle(ex); + } + } + + void Handle(Exception ex) + { + Console.WriteLine(ex); + if (!disableIME) { + disableIME = true; + MessageBox.Show("Error calling IME: " + ex.Message + "\nIME is disabled.", "IME error"); + } + } + } +} diff --git a/ICSharpCode.TextEditor/Project/Src/Gui/InsightWindow/IInsightDataProvider.cs b/ICSharpCode.TextEditor/Project/Src/Gui/InsightWindow/IInsightDataProvider.cs new file mode 100644 index 0000000..03b7441 --- /dev/null +++ b/ICSharpCode.TextEditor/Project/Src/Gui/InsightWindow/IInsightDataProvider.cs @@ -0,0 +1,53 @@ +// +// +// +// +// $Revision$ +// + +using System; + +namespace ICSharpCode.TextEditor.Gui.InsightWindow +{ + public interface IInsightDataProvider + { + /// + /// Tells the insight provider to prepare its data. + /// + /// The name of the edited file + /// The text area in which the file is being edited + void SetupDataProvider(string fileName, TextArea textArea); + + /// + /// Notifies the insight provider that the caret offset has changed. + /// + /// Return true to close the insight window (e.g. when the + /// caret was moved outside the region where insight is displayed for). + /// Return false to keep the window open. + bool CaretOffsetChanged(); + + /// + /// Gets the text to display in the insight window. + /// + /// The number of the active insight entry. + /// Multiple insight entries might be multiple overloads of the same method. + /// The text to display, e.g. a multi-line string where + /// the first line is the method definition, followed by a description. + string GetInsightData(int number); + + /// + /// Gets the number of available insight entries, e.g. the number of available + /// overloads to call. + /// + int InsightDataCount { + get; + } + + /// + /// Gets the index of the entry to initially select. + /// + int DefaultIndex { + get; + } + } +} diff --git a/ICSharpCode.TextEditor/Project/Src/Gui/InsightWindow/InsightWindow.cs b/ICSharpCode.TextEditor/Project/Src/Gui/InsightWindow/InsightWindow.cs new file mode 100644 index 0000000..b6f19ae --- /dev/null +++ b/ICSharpCode.TextEditor/Project/Src/Gui/InsightWindow/InsightWindow.cs @@ -0,0 +1,199 @@ +// +// +// +// +// $Revision$ +// + +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Windows.Forms; + +using ICSharpCode.TextEditor.Gui.CompletionWindow; +using ICSharpCode.TextEditor.Util; + +namespace ICSharpCode.TextEditor.Gui.InsightWindow +{ + public class InsightWindow : AbstractCompletionWindow + { + public InsightWindow(Form parentForm, TextEditorControl control) : base(parentForm, control) + { + SetStyle(ControlStyles.UserPaint, true); + SetStyle(ControlStyles.OptimizedDoubleBuffer, true); + } + + public void ShowInsightWindow() + { + if (!Visible) { + if (insightDataProviderStack.Count > 0) { + ShowCompletionWindow(); + } + } else { + Refresh(); + } + } + + #region Event handling routines + protected override bool ProcessTextAreaKey(Keys keyData) + { + if (!Visible) { + return false; + } + switch (keyData) { + case Keys.Down: + if (DataProvider != null && DataProvider.InsightDataCount > 0) { + CurrentData = (CurrentData + 1) % DataProvider.InsightDataCount; + Refresh(); + } + return true; + case Keys.Up: + if (DataProvider != null && DataProvider.InsightDataCount > 0) { + CurrentData = (CurrentData + DataProvider.InsightDataCount - 1) % DataProvider.InsightDataCount; + Refresh(); + } + return true; + } + return base.ProcessTextAreaKey(keyData); + } + + protected override void CaretOffsetChanged(object sender, EventArgs e) + { + // move the window under the caret (don't change the x position) + TextLocation caretPos = control.ActiveTextAreaControl.Caret.Position; + int y = (int)((1 + caretPos.Y) * control.ActiveTextAreaControl.TextArea.TextView.FontHeight) + - control.ActiveTextAreaControl.TextArea.VirtualTop.Y - 1 + + control.ActiveTextAreaControl.TextArea.TextView.DrawingPosition.Y; + + int xpos = control.ActiveTextAreaControl.TextArea.TextView.GetDrawingXPos(caretPos.Y, caretPos.X); + int ypos = (control.ActiveTextAreaControl.Document.GetVisibleLine(caretPos.Y) + 1) * control.ActiveTextAreaControl.TextArea.TextView.FontHeight + - control.ActiveTextAreaControl.TextArea.VirtualTop.Y; + int rulerHeight = control.TextEditorProperties.ShowHorizontalRuler ? control.ActiveTextAreaControl.TextArea.TextView.FontHeight : 0; + + Point p = control.ActiveTextAreaControl.PointToScreen(new Point(xpos, ypos + rulerHeight)); + if (p.Y != Location.Y) { + Location = p; + } + + while (DataProvider != null && DataProvider.CaretOffsetChanged()) { + CloseCurrentDataProvider(); + } + } + + protected override void OnMouseDown(MouseEventArgs e) + { + base.OnMouseDown(e); + control.ActiveTextAreaControl.TextArea.Focus(); + if (TipPainterTools.DrawingRectangle1.Contains(e.X, e.Y)) { + CurrentData = (CurrentData + DataProvider.InsightDataCount - 1) % DataProvider.InsightDataCount; + Refresh(); + } + if (TipPainterTools.DrawingRectangle2.Contains(e.X, e.Y)) { + CurrentData = (CurrentData + 1) % DataProvider.InsightDataCount; + Refresh(); + } + } + + #endregion + + MouseWheelHandler mouseWheelHandler = new MouseWheelHandler(); + + public void HandleMouseWheel(MouseEventArgs e) + { + if (DataProvider != null && DataProvider.InsightDataCount > 0) { + int distance = mouseWheelHandler.GetScrollAmount(e); + if (control.TextEditorProperties.MouseWheelScrollDown) + distance = -distance; + if (distance > 0) { + CurrentData = (CurrentData + 1) % DataProvider.InsightDataCount; + } else if (distance < 0) { + CurrentData = (CurrentData + DataProvider.InsightDataCount - 1) % DataProvider.InsightDataCount; + } + Refresh(); + } + } + + #region Insight Window Drawing routines + protected override void OnPaint(PaintEventArgs pe) + { + string methodCountMessage = null, description; + if (DataProvider == null || DataProvider.InsightDataCount < 1) { + description = "Unknown Method"; + } else { + if (DataProvider.InsightDataCount > 1) { + methodCountMessage = control.GetRangeDescription(CurrentData + 1, DataProvider.InsightDataCount); + } + description = DataProvider.GetInsightData(CurrentData); + } + + drawingSize = TipPainterTools.GetDrawingSizeHelpTipFromCombinedDescription(this, + pe.Graphics, + Font, + methodCountMessage, + description); + if (drawingSize != Size) { + SetLocation(); + } else { + TipPainterTools.DrawHelpTipFromCombinedDescription(this, pe.Graphics, Font, methodCountMessage, description); + } + } + + protected override void OnPaintBackground(PaintEventArgs pe) + { + pe.Graphics.FillRectangle(SystemBrushes.Info, pe.ClipRectangle); + } + #endregion + + #region InsightDataProvider handling + Stack insightDataProviderStack = new Stack(); + + int CurrentData { + get { + return insightDataProviderStack.Peek().currentData; + } + set { + insightDataProviderStack.Peek().currentData = value; + } + } + + IInsightDataProvider DataProvider { + get { + if (insightDataProviderStack.Count == 0) { + return null; + } + return insightDataProviderStack.Peek().dataProvider; + } + } + + public void AddInsightDataProvider(IInsightDataProvider provider, string fileName) + { + provider.SetupDataProvider(fileName, control.ActiveTextAreaControl.TextArea); + if (provider.InsightDataCount > 0) { + insightDataProviderStack.Push(new InsightDataProviderStackElement(provider)); + } + } + + void CloseCurrentDataProvider() + { + insightDataProviderStack.Pop(); + if (insightDataProviderStack.Count == 0) { + Close(); + } else { + Refresh(); + } + } + + class InsightDataProviderStackElement + { + public int currentData; + public IInsightDataProvider dataProvider; + + public InsightDataProviderStackElement(IInsightDataProvider dataProvider) + { + this.currentData = Math.Max(dataProvider.DefaultIndex, 0); + this.dataProvider = dataProvider; + } + } + #endregion + } +} diff --git a/ICSharpCode.TextEditor/Project/Src/Gui/TextArea.cs b/ICSharpCode.TextEditor/Project/Src/Gui/TextArea.cs new file mode 100644 index 0000000..c3cee17 --- /dev/null +++ b/ICSharpCode.TextEditor/Project/Src/Gui/TextArea.cs @@ -0,0 +1,947 @@ +// +// +// +// +// $Revision$ +// + +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Diagnostics; +using System.Drawing; +using System.Drawing.Text; +using System.Text; +using System.Windows.Forms; + +using ICSharpCode.TextEditor.Actions; +using ICSharpCode.TextEditor.Document; +using ICSharpCode.TextEditor.Gui.CompletionWindow; + +namespace ICSharpCode.TextEditor +{ + public delegate bool KeyEventHandler(char ch); + public delegate bool DialogKeyProcessor(Keys keyData); + + /// + /// This class paints the textarea. + /// + [ToolboxItem(false)] + public class TextArea : Control + { + bool hiddenMouseCursor = false; + /// + /// The position where the mouse cursor was when it was hidden. Sometimes the text editor gets MouseMove + /// events when typing text even if the mouse is not moved. + /// + Point mouseCursorHidePosition; + + Point virtualTop = new Point(0, 0); + TextAreaControl motherTextAreaControl; + TextEditorControl motherTextEditorControl; + + List bracketshemes = new List(); + TextAreaClipboardHandler textAreaClipboardHandler; + bool autoClearSelection = false; + + List leftMargins = new List(); + + TextView textView; + GutterMargin gutterMargin; + FoldMargin foldMargin; + IconBarMargin iconBarMargin; + + SelectionManager selectionManager; + Caret caret; + + internal Point mousepos = new Point(0, 0); + //public Point selectionStartPos = new Point(0,0); + + bool disposed; + + [Browsable(false)] + public IList LeftMargins { + get { + return leftMargins.AsReadOnly(); + } + } + + public void InsertLeftMargin(int index, AbstractMargin margin) + { + leftMargins.Insert(index, margin); + Refresh(); + } + + public TextEditorControl MotherTextEditorControl { + get { + return motherTextEditorControl; + } + } + + public TextAreaControl MotherTextAreaControl { + get { + return motherTextAreaControl; + } + } + + public SelectionManager SelectionManager { + get { + return selectionManager; + } + } + + public Caret Caret { + get { + return caret; + } + } + + public TextView TextView { + get { + return textView; + } + } + + public GutterMargin GutterMargin { + get { + return gutterMargin; + } + } + + public FoldMargin FoldMargin { + get { + return foldMargin; + } + } + + public IconBarMargin IconBarMargin { + get { + return iconBarMargin; + } + } + + public Encoding Encoding { + get { + return motherTextEditorControl.Encoding; + } + } + public int MaxVScrollValue { + get { + return (Document.GetVisibleLine(Document.TotalNumberOfLines - 1) + 1 + TextView.VisibleLineCount * 2 / 3) * TextView.FontHeight; + } + } + + public Point VirtualTop { + get { + return virtualTop; + } + set { + Point newVirtualTop = new Point(value.X, Math.Min(MaxVScrollValue, Math.Max(0, value.Y))); + if (virtualTop != newVirtualTop) { + virtualTop = newVirtualTop; + motherTextAreaControl.VScrollBar.Value = virtualTop.Y; + Invalidate(); + } + caret.UpdateCaretPosition(); + } + } + + public bool AutoClearSelection { + get { + return autoClearSelection; + } + set { + autoClearSelection = value; + } + } + + [Browsable(false)] + public IDocument Document { + get { + return motherTextEditorControl.Document; + } + } + + public TextAreaClipboardHandler ClipboardHandler { + get { + return textAreaClipboardHandler; + } + } + + + public ITextEditorProperties TextEditorProperties { + get { + return motherTextEditorControl.TextEditorProperties; + } + } + + public TextArea(TextEditorControl motherTextEditorControl, TextAreaControl motherTextAreaControl) + { + this.motherTextAreaControl = motherTextAreaControl; + this.motherTextEditorControl = motherTextEditorControl; + + caret = new Caret(this); + selectionManager = new SelectionManager(Document, this); + + this.textAreaClipboardHandler = new TextAreaClipboardHandler(this); + + ResizeRedraw = true; + + SetStyle(ControlStyles.OptimizedDoubleBuffer, true); +// SetStyle(ControlStyles.AllPaintingInWmPaint, true); +// SetStyle(ControlStyles.UserPaint, true); + SetStyle(ControlStyles.Opaque, false); + SetStyle(ControlStyles.ResizeRedraw, true); + SetStyle(ControlStyles.Selectable, true); + + textView = new TextView(this); + + gutterMargin = new GutterMargin(this); + foldMargin = new FoldMargin(this); + iconBarMargin = new IconBarMargin(this); + leftMargins.AddRange(new AbstractMargin[] { iconBarMargin, gutterMargin, foldMargin }); + OptionsChanged(); + + + new TextAreaMouseHandler(this).Attach(); + new TextAreaDragDropHandler().Attach(this); + + bracketshemes.Add(new BracketHighlightingSheme('{', '}')); + bracketshemes.Add(new BracketHighlightingSheme('(', ')')); + bracketshemes.Add(new BracketHighlightingSheme('[', ']')); + + caret.PositionChanged += new EventHandler(SearchMatchingBracket); + Document.TextContentChanged += new EventHandler(TextContentChanged); + Document.FoldingManager.FoldingsChanged += new EventHandler(DocumentFoldingsChanged); + } + + public void UpdateMatchingBracket() + { + SearchMatchingBracket(null, null); + } + + void TextContentChanged(object sender, EventArgs e) + { + Caret.Position = new TextLocation(0, 0); + SelectionManager.SelectionCollection.Clear(); + } + void SearchMatchingBracket(object sender, EventArgs e) + { + if (!TextEditorProperties.ShowMatchingBracket) { + textView.Highlight = null; + return; + } + int oldLine1 = -1, oldLine2 = -1; + if (textView.Highlight != null && textView.Highlight.OpenBrace.Y >=0 && textView.Highlight.OpenBrace.Y < Document.TotalNumberOfLines) { + oldLine1 = textView.Highlight.OpenBrace.Y; + } + if (textView.Highlight != null && textView.Highlight.CloseBrace.Y >=0 && textView.Highlight.CloseBrace.Y < Document.TotalNumberOfLines) { + oldLine2 = textView.Highlight.CloseBrace.Y; + } + textView.Highlight = FindMatchingBracketHighlight(); + if (oldLine1 >= 0) + UpdateLine(oldLine1); + if (oldLine2 >= 0 && oldLine2 != oldLine1) + UpdateLine(oldLine2); + if (textView.Highlight != null) { + int newLine1 = textView.Highlight.OpenBrace.Y; + int newLine2 = textView.Highlight.CloseBrace.Y; + if (newLine1 != oldLine1 && newLine1 != oldLine2) + UpdateLine(newLine1); + if (newLine2 != oldLine1 && newLine2 != oldLine2 && newLine2 != newLine1) + UpdateLine(newLine2); + } + } + + public Highlight FindMatchingBracketHighlight() + { + if (Caret.Offset == 0) + return null; + foreach (BracketHighlightingSheme bracketsheme in bracketshemes) { + Highlight highlight = bracketsheme.GetHighlight(Document, Caret.Offset - 1); + if (highlight != null) { + return highlight; + } + } + return null; + } + + public void SetDesiredColumn() + { + Caret.DesiredColumn = TextView.GetDrawingXPos(Caret.Line, Caret.Column) + VirtualTop.X; + } + + public void SetCaretToDesiredColumn() + { + FoldMarker dummy; + Caret.Position = textView.GetLogicalColumn(Caret.Line, Caret.DesiredColumn + VirtualTop.X, out dummy); + } + + public void OptionsChanged() + { + UpdateMatchingBracket(); + textView.OptionsChanged(); + caret.RecreateCaret(); + caret.UpdateCaretPosition(); + Refresh(); + } + + AbstractMargin lastMouseInMargin; + + protected override void OnMouseLeave(System.EventArgs e) + { + base.OnMouseLeave(e); + this.Cursor = Cursors.Default; + if (lastMouseInMargin != null) { + lastMouseInMargin.HandleMouseLeave(EventArgs.Empty); + lastMouseInMargin = null; + } + CloseToolTip(); + } + + protected override void OnMouseDown(System.Windows.Forms.MouseEventArgs e) + { + // this corrects weird problems when text is selected, + // then a menu item is selected, then the text is + // clicked again - it correctly synchronises the + // click position + mousepos = new Point(e.X, e.Y); + + base.OnMouseDown(e); + CloseToolTip(); + + foreach (AbstractMargin margin in leftMargins) { + if (margin.DrawingPosition.Contains(e.X, e.Y)) { + margin.HandleMouseDown(new Point(e.X, e.Y), e.Button); + } + } + } + + /// + /// Shows the mouse cursor if it has been hidden. + /// + /// true to always show the cursor or false to show it only if it has been moved since it was hidden. + internal void ShowHiddenCursor(bool forceShow) + { + if (hiddenMouseCursor) { + if (mouseCursorHidePosition != Cursor.Position || forceShow) { + Cursor.Show(); + hiddenMouseCursor = false; + } + } + } + + + // static because the mouse can only be in one text area and we don't want to have + // tooltips of text areas from inactive tabs floating around. + static DeclarationViewWindow toolTip; + static string oldToolTip; + + void SetToolTip(string text, int lineNumber) + { + if (toolTip == null || toolTip.IsDisposed) + toolTip = new DeclarationViewWindow(this.FindForm()); + if (oldToolTip == text) + return; + if (text == null) { + toolTip.Hide(); + } else { + Point p = Control.MousePosition; + Point cp = PointToClient(p); + if (lineNumber >= 0) { + lineNumber = this.Document.GetVisibleLine(lineNumber); + p.Y = (p.Y - cp.Y) + (lineNumber * this.TextView.FontHeight) - this.virtualTop.Y; + } + p.Offset(3, 3); + toolTip.Owner = this.FindForm(); + toolTip.Location = p; + toolTip.Description = text; + toolTip.HideOnClick = true; + toolTip.Show(); + } + oldToolTip = text; + } + + public event ToolTipRequestEventHandler ToolTipRequest; + + protected virtual void OnToolTipRequest(ToolTipRequestEventArgs e) + { + if (ToolTipRequest != null) { + ToolTipRequest(this, e); + } + } + + bool toolTipActive; + /// + /// Rectangle in text area that caused the current tool tip. + /// Prevents tooltip from re-showing when it was closed because of a click or keyboard + /// input and the mouse was not used. + /// + Rectangle toolTipRectangle; + + void CloseToolTip() + { + if (toolTipActive) { + //Console.WriteLine("Closing tooltip"); + toolTipActive = false; + SetToolTip(null, -1); + } + ResetMouseEventArgs(); + } + + protected override void OnMouseHover(EventArgs e) + { + base.OnMouseHover(e); + //Console.WriteLine("Hover raised at " + PointToClient(Control.MousePosition)); + if (MouseButtons == MouseButtons.None) { + RequestToolTip(PointToClient(Control.MousePosition)); + } else { + CloseToolTip(); + } + } + + protected void RequestToolTip(Point mousePos) + { + if (toolTipRectangle.Contains(mousePos)) { + if (!toolTipActive) + ResetMouseEventArgs(); + return; + } + + //Console.WriteLine("Request tooltip for " + mousePos); + + toolTipRectangle = new Rectangle(mousePos.X - 4, mousePos.Y - 4, 8, 8); + + TextLocation logicPos = textView.GetLogicalPosition(mousePos.X - textView.DrawingPosition.Left, + mousePos.Y - textView.DrawingPosition.Top); + bool inDocument = textView.DrawingPosition.Contains(mousePos) + && logicPos.Y >= 0 && logicPos.Y < Document.TotalNumberOfLines; + ToolTipRequestEventArgs args = new ToolTipRequestEventArgs(mousePos, logicPos, inDocument); + OnToolTipRequest(args); + if (args.ToolTipShown) { + //Console.WriteLine("Set tooltip to " + args.toolTipText); + toolTipActive = true; + SetToolTip(args.toolTipText, inDocument ? logicPos.Y + 1 : -1); + } else { + CloseToolTip(); + } + } + + // external interface to the attached event + internal void RaiseMouseMove(MouseEventArgs e) + { + OnMouseMove(e); + } + + protected override void OnMouseMove(MouseEventArgs e) + { + base.OnMouseMove(e); + if (!toolTipRectangle.Contains(e.Location)) { + toolTipRectangle = Rectangle.Empty; + if (toolTipActive) + RequestToolTip(e.Location); + } + foreach (AbstractMargin margin in leftMargins) { + if (margin.DrawingPosition.Contains(e.X, e.Y)) { + this.Cursor = margin.Cursor; + margin.HandleMouseMove(new Point(e.X, e.Y), e.Button); + if (lastMouseInMargin != margin) { + if (lastMouseInMargin != null) { + lastMouseInMargin.HandleMouseLeave(EventArgs.Empty); + } + lastMouseInMargin = margin; + } + return; + } + } + if (lastMouseInMargin != null) { + lastMouseInMargin.HandleMouseLeave(EventArgs.Empty); + lastMouseInMargin = null; + } + if (textView.DrawingPosition.Contains(e.X, e.Y)) { + TextLocation realmousepos = TextView.GetLogicalPosition(e.X - TextView.DrawingPosition.X, e.Y - TextView.DrawingPosition.Y); + if(SelectionManager.IsSelected(Document.PositionToOffset(realmousepos)) && MouseButtons == MouseButtons.None) { + // mouse is hovering over a selection, so show default mouse + this.Cursor = Cursors.Default; + } else { + // mouse is hovering over text area, not a selection, so show the textView cursor + this.Cursor = textView.Cursor; + } + return; + } + this.Cursor = Cursors.Default; + } + AbstractMargin updateMargin = null; + + public void Refresh(AbstractMargin margin) + { + updateMargin = margin; + Invalidate(updateMargin.DrawingPosition); + Update(); + updateMargin = null; + } + + protected override void OnPaintBackground(System.Windows.Forms.PaintEventArgs pevent) + { + } + + protected override void OnPaint(System.Windows.Forms.PaintEventArgs e) + { + int currentXPos = 0; + int currentYPos = 0; + bool adjustScrollBars = false; + Graphics g = e.Graphics; + Rectangle clipRectangle = e.ClipRectangle; + + bool isFullRepaint = clipRectangle.X == 0 && clipRectangle.Y == 0 + && clipRectangle.Width == this.Width && clipRectangle.Height == this.Height; + + g.TextRenderingHint = this.TextEditorProperties.TextRenderingHint; + + if (updateMargin != null) { + updateMargin.Paint(g, updateMargin.DrawingPosition); +// clipRectangle.Intersect(updateMargin.DrawingPosition); + } + + if (clipRectangle.Width <= 0 || clipRectangle.Height <= 0) { + return; + } + + foreach (AbstractMargin margin in leftMargins) { + if (margin.IsVisible) { + Rectangle marginRectangle = new Rectangle(currentXPos , currentYPos, margin.Size.Width, Height - currentYPos); + if (marginRectangle != margin.DrawingPosition) { + // margin changed size + if (!isFullRepaint && !clipRectangle.Contains(marginRectangle)) { + Invalidate(); // do a full repaint + } + adjustScrollBars = true; + margin.DrawingPosition = marginRectangle; + } + currentXPos += margin.DrawingPosition.Width; + if (clipRectangle.IntersectsWith(marginRectangle)) { + marginRectangle.Intersect(clipRectangle); + if (!marginRectangle.IsEmpty) { + margin.Paint(g, marginRectangle); + } + } + } + } + + Rectangle textViewArea = new Rectangle(currentXPos, currentYPos, Width - currentXPos, Height - currentYPos); + if (textViewArea != textView.DrawingPosition) { + adjustScrollBars = true; + textView.DrawingPosition = textViewArea; + // update caret position (but outside of WM_PAINT!) + BeginInvoke((MethodInvoker)caret.UpdateCaretPosition); + } + if (clipRectangle.IntersectsWith(textViewArea)) { + textViewArea.Intersect(clipRectangle); + if (!textViewArea.IsEmpty) { + textView.Paint(g, textViewArea); + } + } + + if (adjustScrollBars) { + this.motherTextAreaControl.AdjustScrollBars(); + } + + // we cannot update the caret position here, it's not allowed to call the caret API inside WM_PAINT + //Caret.UpdateCaretPosition(); + + base.OnPaint(e); + } + void DocumentFoldingsChanged(object sender, EventArgs e) + { + Caret.UpdateCaretPosition(); + Invalidate(); + this.motherTextAreaControl.AdjustScrollBars(); + } + + #region keyboard handling methods + + /// + /// This method is called on each Keypress + /// + /// + /// True, if the key is handled by this method and should NOT be + /// inserted in the textarea. + /// + protected internal virtual bool HandleKeyPress(char ch) + { + if (KeyEventHandler != null) { + return KeyEventHandler(ch); + } + return false; + } + + // Fixes SD2-747: Form containing the text editor and a button with a shortcut + protected override bool IsInputChar(char charCode) + { + return true; + } + + internal bool IsReadOnly(int offset) + { + if (Document.ReadOnly) { + return true; + } + if (TextEditorProperties.SupportReadOnlySegments) { + return Document.MarkerStrategy.GetMarkers(offset).Exists(m=>m.IsReadOnly); + } else { + return false; + } + } + + internal bool IsReadOnly(int offset, int length) + { + if (Document.ReadOnly) { + return true; + } + if (TextEditorProperties.SupportReadOnlySegments) { + return Document.MarkerStrategy.GetMarkers(offset, length).Exists(m=>m.IsReadOnly); + } else { + return false; + } + } + + public void SimulateKeyPress(char ch) + { + if (SelectionManager.HasSomethingSelected) { + if (SelectionManager.SelectionIsReadonly) + return; + } else if (IsReadOnly(Caret.Offset)) { + return; + } + + if (ch < ' ') { + return; + } + + if (!hiddenMouseCursor && TextEditorProperties.HideMouseCursor) { + if (this.ClientRectangle.Contains(PointToClient(Cursor.Position))) { + mouseCursorHidePosition = Cursor.Position; + hiddenMouseCursor = true; + Cursor.Hide(); + } + } + CloseToolTip(); + + BeginUpdate(); + Document.UndoStack.StartUndoGroup(); + try { + // INSERT char + if (!HandleKeyPress(ch)) { + switch (Caret.CaretMode) { + case CaretMode.InsertMode: + InsertChar(ch); + break; + case CaretMode.OverwriteMode: + ReplaceChar(ch); + break; + default: + Debug.Assert(false, "Unknown caret mode " + Caret.CaretMode); + break; + } + } + + int currentLineNr = Caret.Line; + Document.FormattingStrategy.FormatLine(this, currentLineNr, Document.PositionToOffset(Caret.Position), ch); + + EndUpdate(); + } finally { + Document.UndoStack.EndUndoGroup(); + } + } + + protected override void OnKeyPress(KeyPressEventArgs e) + { + base.OnKeyPress(e); + SimulateKeyPress(e.KeyChar); + e.Handled = true; + } + + /// + /// This method executes a dialog key + /// + public bool ExecuteDialogKey(Keys keyData) + { + // try, if a dialog key processor was set to use this + if (DoProcessDialogKey != null && DoProcessDialogKey(keyData)) { + return true; + } + + // if not (or the process was 'silent', use the standard edit actions + IEditAction action = motherTextEditorControl.GetEditAction(keyData); + AutoClearSelection = true; + if (action != null) { + BeginUpdate(); + try { + lock (Document) { + action.Execute(this); + if (SelectionManager.HasSomethingSelected && AutoClearSelection /*&& caretchanged*/) { + if (Document.TextEditorProperties.DocumentSelectionMode == DocumentSelectionMode.Normal) { + SelectionManager.ClearSelection(); + } + } + } + } finally { + EndUpdate(); + Caret.UpdateCaretPosition(); + } + return true; + } + return false; + } + + protected override bool ProcessDialogKey(Keys keyData) + { + return ExecuteDialogKey(keyData) || base.ProcessDialogKey(keyData); + } + #endregion + + public void ScrollToCaret() + { + motherTextAreaControl.ScrollToCaret(); + } + + public void ScrollTo(int line) + { + motherTextAreaControl.ScrollTo(line); + } + + public void BeginUpdate() + { + motherTextEditorControl.BeginUpdate(); + } + + public void EndUpdate() + { + motherTextEditorControl.EndUpdate(); + } + + public bool EnableCutOrPaste { + get { + if (motherTextAreaControl == null) + return false; + if (SelectionManager.HasSomethingSelected) + return !SelectionManager.SelectionIsReadonly; + else + return !IsReadOnly(Caret.Offset); + } + } + + string GenerateWhitespaceString(int length) + { + return new string(' ', length); + } + /// + /// Inserts a single character at the caret position + /// + public void InsertChar(char ch) + { + bool updating = motherTextEditorControl.IsInUpdate; + if (!updating) { + BeginUpdate(); + } + + // filter out forgein whitespace chars and replace them with standard space (ASCII 32) + if (char.IsWhiteSpace(ch) && ch != '\t' && ch != '\n') { + ch = ' '; + } + + Document.UndoStack.StartUndoGroup(); + if (Document.TextEditorProperties.DocumentSelectionMode == DocumentSelectionMode.Normal && + SelectionManager.SelectionCollection.Count > 0) { + Caret.Position = SelectionManager.SelectionCollection[0].StartPosition; + SelectionManager.RemoveSelectedText(); + } + LineSegment caretLine = Document.GetLineSegment(Caret.Line); + int offset = Caret.Offset; + // use desired column for generated whitespaces + int dc = Caret.Column; + if (caretLine.Length < dc && ch != '\n') { + Document.Insert(offset, GenerateWhitespaceString(dc - caretLine.Length) + ch); + } else { + Document.Insert(offset, ch.ToString()); + } + Document.UndoStack.EndUndoGroup(); + ++Caret.Column; + + if (!updating) { + EndUpdate(); + UpdateLineToEnd(Caret.Line, Caret.Column); + } + + // I prefer to set NOT the standard column, if you type something +// ++Caret.DesiredColumn; + } + + /// + /// Inserts a whole string at the caret position + /// + public void InsertString(string str) + { + bool updating = motherTextEditorControl.IsInUpdate; + if (!updating) { + BeginUpdate(); + } + try { + Document.UndoStack.StartUndoGroup(); + if (Document.TextEditorProperties.DocumentSelectionMode == DocumentSelectionMode.Normal && + SelectionManager.SelectionCollection.Count > 0) { + Caret.Position = SelectionManager.SelectionCollection[0].StartPosition; + SelectionManager.RemoveSelectedText(); + } + + int oldOffset = Document.PositionToOffset(Caret.Position); + int oldLine = Caret.Line; + LineSegment caretLine = Document.GetLineSegment(Caret.Line); + if (caretLine.Length < Caret.Column) { + int whiteSpaceLength = Caret.Column - caretLine.Length; + Document.Insert(oldOffset, GenerateWhitespaceString(whiteSpaceLength) + str); + Caret.Position = Document.OffsetToPosition(oldOffset + str.Length + whiteSpaceLength); + } else { + Document.Insert(oldOffset, str); + Caret.Position = Document.OffsetToPosition(oldOffset + str.Length); + } + Document.UndoStack.EndUndoGroup(); + if (oldLine != Caret.Line) { + UpdateToEnd(oldLine); + } else { + UpdateLineToEnd(Caret.Line, Caret.Column); + } + } finally { + if (!updating) { + EndUpdate(); + } + } + } + + /// + /// Replaces a char at the caret position + /// + public void ReplaceChar(char ch) + { + bool updating = motherTextEditorControl.IsInUpdate; + if (!updating) { + BeginUpdate(); + } + if (Document.TextEditorProperties.DocumentSelectionMode == DocumentSelectionMode.Normal && SelectionManager.SelectionCollection.Count > 0) { + Caret.Position = SelectionManager.SelectionCollection[0].StartPosition; + SelectionManager.RemoveSelectedText(); + } + + int lineNr = Caret.Line; + LineSegment line = Document.GetLineSegment(lineNr); + int offset = Document.PositionToOffset(Caret.Position); + if (offset < line.Offset + line.Length) { + Document.Replace(offset, 1, ch.ToString()); + } else { + Document.Insert(offset, ch.ToString()); + } + if (!updating) { + EndUpdate(); + UpdateLineToEnd(lineNr, Caret.Column); + } + ++Caret.Column; +// ++Caret.DesiredColumn; + } + + protected override void Dispose(bool disposing) + { + base.Dispose(disposing); + if (disposing) { + if (!disposed) { + disposed = true; + if (caret != null) { + caret.PositionChanged -= new EventHandler(SearchMatchingBracket); + caret.Dispose(); + } + if (selectionManager != null) { + selectionManager.Dispose(); + } + Document.TextContentChanged -= new EventHandler(TextContentChanged); + Document.FoldingManager.FoldingsChanged -= new EventHandler(DocumentFoldingsChanged); + motherTextAreaControl = null; + motherTextEditorControl = null; + foreach (AbstractMargin margin in leftMargins) { + if (margin is IDisposable) + (margin as IDisposable).Dispose(); + } + textView.Dispose(); + } + } + } + + #region UPDATE Commands + internal void UpdateLine(int line) + { + UpdateLines(0, line, line); + } + + internal void UpdateLines(int lineBegin, int lineEnd) + { + UpdateLines(0, lineBegin, lineEnd); + } + + internal void UpdateToEnd(int lineBegin) + { +// if (lineBegin > FirstPhysicalLine + textView.VisibleLineCount) { +// return; +// } + + lineBegin = Document.GetVisibleLine(lineBegin); + int y = Math.Max( 0, (int)(lineBegin * textView.FontHeight)); + y = Math.Max(0, y - this.virtualTop.Y); + Rectangle r = new Rectangle(0, + y, + Width, + Height - y); + Invalidate(r); + } + + internal void UpdateLineToEnd(int lineNr, int xStart) + { + UpdateLines(xStart, lineNr, lineNr); + } + + internal void UpdateLine(int line, int begin, int end) + { + UpdateLines(line, line); + } + int FirstPhysicalLine { + get { + return VirtualTop.Y / textView.FontHeight; + } + } + internal void UpdateLines(int xPos, int lineBegin, int lineEnd) + { +// if (lineEnd < FirstPhysicalLine || lineBegin > FirstPhysicalLine + textView.VisibleLineCount) { +// return; +// } + + InvalidateLines((int)(xPos * this.TextView.WideSpaceWidth), lineBegin, lineEnd); + } + + void InvalidateLines(int xPos, int lineBegin, int lineEnd) + { + lineBegin = Math.Max(Document.GetVisibleLine(lineBegin), FirstPhysicalLine); + lineEnd = Math.Min(Document.GetVisibleLine(lineEnd), FirstPhysicalLine + textView.VisibleLineCount); + int y = Math.Max( 0, (int)(lineBegin * textView.FontHeight)); + int height = Math.Min(textView.DrawingPosition.Height, (int)((1 + lineEnd - lineBegin) * (textView.FontHeight + 1))); + + Rectangle r = new Rectangle(0, + y - 1 - this.virtualTop.Y, + Width, + height + 3); + + Invalidate(r); + } + #endregion + public event KeyEventHandler KeyEventHandler; + public event DialogKeyProcessor DoProcessDialogKey; + + //internal void + } +} diff --git a/ICSharpCode.TextEditor/Project/Src/Gui/TextAreaClipboardHandler.cs b/ICSharpCode.TextEditor/Project/Src/Gui/TextAreaClipboardHandler.cs new file mode 100644 index 0000000..2cc8a4c --- /dev/null +++ b/ICSharpCode.TextEditor/Project/Src/Gui/TextAreaClipboardHandler.cs @@ -0,0 +1,268 @@ +// +// +// +// +// $Revision$ +// + +using System; +using System.Drawing; +using System.IO; +using System.Runtime.InteropServices; +using System.Windows.Forms; + +using ICSharpCode.TextEditor.Document; +using ICSharpCode.TextEditor.Util; + +namespace ICSharpCode.TextEditor +{ + public class TextAreaClipboardHandler + { + TextArea textArea; + + public bool EnableCut { + get { + return textArea.EnableCutOrPaste; //textArea.SelectionManager.HasSomethingSelected; + } + } + + public bool EnableCopy { + get { + return true; //textArea.SelectionManager.HasSomethingSelected; + } + } + + public delegate bool ClipboardContainsTextDelegate(); + + /// + /// Is called when CachedClipboardContainsText should be updated. + /// If this property is null (the default value), the text editor uses + /// System.Windows.Forms.Clipboard.ContainsText. + /// + /// + /// This property is useful if you want to prevent the default Clipboard.ContainsText + /// behaviour that waits for the clipboard to be available - the clipboard might + /// never become available if it is owned by a process that is paused by the debugger. + /// + public static ClipboardContainsTextDelegate GetClipboardContainsText; + + public bool EnablePaste { + get { + if (!textArea.EnableCutOrPaste) + return false; + ClipboardContainsTextDelegate d = GetClipboardContainsText; + if (d != null) { + return d(); + } else { + try { + return Clipboard.ContainsText(); + } catch (ExternalException) { + return false; + } + } + } + } + + public bool EnableDelete { + get { + return textArea.SelectionManager.HasSomethingSelected && !textArea.SelectionManager.SelectionIsReadonly; + } + } + + public bool EnableSelectAll { + get { + return true; + } + } + + public TextAreaClipboardHandler(TextArea textArea) + { + this.textArea = textArea; + textArea.SelectionManager.SelectionChanged += new EventHandler(DocumentSelectionChanged); + } + + void DocumentSelectionChanged(object sender, EventArgs e) + { +// ((DefaultWorkbench)WorkbenchSingleton.Workbench).UpdateToolbars(); + } + + const string LineSelectedType = "MSDEVLineSelect"; // This is the type VS 2003 and 2005 use for flagging a whole line copy + + bool CopyTextToClipboard(string stringToCopy, bool asLine) + { + if (stringToCopy.Length > 0) { + DataObject dataObject = new DataObject(); + dataObject.SetData(DataFormats.UnicodeText, true, stringToCopy); + if (asLine) { + MemoryStream lineSelected = new MemoryStream(1); + lineSelected.WriteByte(1); + dataObject.SetData(LineSelectedType, false, lineSelected); + } + // Default has no highlighting, therefore we don't need RTF output + if (textArea.Document.HighlightingStrategy.Name != "Default") { + dataObject.SetData(DataFormats.Rtf, RtfWriter.GenerateRtf(textArea)); + } + OnCopyText(new CopyTextEventArgs(stringToCopy)); + + SafeSetClipboard(dataObject); + return true; + } else { + return false; + } + } + + // Code duplication: TextAreaClipboardHandler.cs also has SafeSetClipboard + [ThreadStatic] static int SafeSetClipboardDataVersion; + + static void SafeSetClipboard(object dataObject) + { + // Work around ExternalException bug. (SD2-426) + // Best reproducable inside Virtual PC. + int version = unchecked(++SafeSetClipboardDataVersion); + try { + Clipboard.SetDataObject(dataObject, true); + } catch (ExternalException) { + Timer timer = new Timer(); + timer.Interval = 100; + timer.Tick += delegate { + timer.Stop(); + timer.Dispose(); + if (SafeSetClipboardDataVersion == version) { + try { + Clipboard.SetDataObject(dataObject, true, 10, 50); + } catch (ExternalException) { } + } + }; + timer.Start(); + } + } + + bool CopyTextToClipboard(string stringToCopy) + { + return CopyTextToClipboard(stringToCopy, false); + } + + public void Cut(object sender, EventArgs e) + { + if (textArea.SelectionManager.HasSomethingSelected) { + if (CopyTextToClipboard(textArea.SelectionManager.SelectedText)) { + if (textArea.SelectionManager.SelectionIsReadonly) + return; + // Remove text + textArea.BeginUpdate(); + textArea.Caret.Position = textArea.SelectionManager.SelectionCollection[0].StartPosition; + textArea.SelectionManager.RemoveSelectedText(); + textArea.EndUpdate(); + } + } else if (textArea.Document.TextEditorProperties.CutCopyWholeLine) { + // No text was selected, select and cut the entire line + int curLineNr = textArea.Document.GetLineNumberForOffset(textArea.Caret.Offset); + LineSegment lineWhereCaretIs = textArea.Document.GetLineSegment(curLineNr); + string caretLineText = textArea.Document.GetText(lineWhereCaretIs.Offset, lineWhereCaretIs.TotalLength); + textArea.SelectionManager.SetSelection(textArea.Document.OffsetToPosition(lineWhereCaretIs.Offset), textArea.Document.OffsetToPosition(lineWhereCaretIs.Offset + lineWhereCaretIs.TotalLength)); + if (CopyTextToClipboard(caretLineText, true)) { + if (textArea.SelectionManager.SelectionIsReadonly) + return; + // remove line + textArea.BeginUpdate(); + textArea.Caret.Position = textArea.Document.OffsetToPosition(lineWhereCaretIs.Offset); + textArea.SelectionManager.RemoveSelectedText(); + textArea.Document.RequestUpdate(new TextAreaUpdate(TextAreaUpdateType.PositionToEnd, new TextLocation(0, curLineNr))); + textArea.EndUpdate(); + } + } + } + + public void Copy(object sender, EventArgs e) + { + if (!CopyTextToClipboard(textArea.SelectionManager.SelectedText) && textArea.Document.TextEditorProperties.CutCopyWholeLine) { + // No text was selected, select the entire line, copy it, and then deselect + int curLineNr = textArea.Document.GetLineNumberForOffset(textArea.Caret.Offset); + LineSegment lineWhereCaretIs = textArea.Document.GetLineSegment(curLineNr); + string caretLineText = textArea.Document.GetText(lineWhereCaretIs.Offset, lineWhereCaretIs.TotalLength); + CopyTextToClipboard(caretLineText, true); + } + } + + public void Paste(object sender, EventArgs e) + { + if (!textArea.EnableCutOrPaste) { + return; + } + // Clipboard.GetDataObject may throw an exception... + for (int i = 0;; i++) { + try { + IDataObject data = Clipboard.GetDataObject(); + if (data == null) + return; + bool fullLine = data.GetDataPresent(LineSelectedType); + if (data.GetDataPresent(DataFormats.UnicodeText)) { + string text = (string)data.GetData(DataFormats.UnicodeText); + // we got NullReferenceExceptions here, apparently the clipboard can contain null strings + if (!string.IsNullOrEmpty(text)) { + textArea.Document.UndoStack.StartUndoGroup(); + try { + if (textArea.SelectionManager.HasSomethingSelected) { + textArea.Caret.Position = textArea.SelectionManager.SelectionCollection[0].StartPosition; + textArea.SelectionManager.RemoveSelectedText(); + } + if (fullLine) { + int col = textArea.Caret.Column; + textArea.Caret.Column = 0; + if (!textArea.IsReadOnly(textArea.Caret.Offset)) + textArea.InsertString(text); + textArea.Caret.Column = col; + } else { + // textArea.EnableCutOrPaste already checked readonly for this case + textArea.InsertString(text); + } + } finally { + textArea.Document.UndoStack.EndUndoGroup(); + } + } + } + return; + } catch (ExternalException) { + // GetDataObject does not provide RetryTimes parameter + if (i > 5) throw; + } + } + } + + public void Delete(object sender, EventArgs e) + { + new ICSharpCode.TextEditor.Actions.Delete().Execute(textArea); + } + + public void SelectAll(object sender, EventArgs e) + { + new ICSharpCode.TextEditor.Actions.SelectWholeDocument().Execute(textArea); + } + + protected virtual void OnCopyText(CopyTextEventArgs e) + { + if (CopyText != null) { + CopyText(this, e); + } + } + + public event CopyTextEventHandler CopyText; + } + + public delegate void CopyTextEventHandler(object sender, CopyTextEventArgs e); + public class CopyTextEventArgs : EventArgs + { + string text; + + public string Text { + get { + return text; + } + } + + public CopyTextEventArgs(string text) + { + this.text = text; + } + } +} diff --git a/ICSharpCode.TextEditor/Project/Src/Gui/TextAreaControl.cs b/ICSharpCode.TextEditor/Project/Src/Gui/TextAreaControl.cs new file mode 100644 index 0000000..3b03b01 --- /dev/null +++ b/ICSharpCode.TextEditor/Project/Src/Gui/TextAreaControl.cs @@ -0,0 +1,463 @@ +// +// +// +// +// $Revision$ +// + +using System; +using System.ComponentModel; +using System.Drawing; +using System.Windows.Forms; + +using ICSharpCode.TextEditor.Document; + +namespace ICSharpCode.TextEditor +{ + /// + /// This class paints the textarea. + /// + [ToolboxItem(false)] + public class TextAreaControl : Panel + { + TextEditorControl motherTextEditorControl; + + HRuler hRuler = null; + + VScrollBar vScrollBar = new VScrollBar(); + HScrollBar hScrollBar = new HScrollBar(); + TextArea textArea; + bool doHandleMousewheel = true; + bool disposed; + + public TextArea TextArea { + get { + return textArea; + } + } + + public SelectionManager SelectionManager { + get { + return textArea.SelectionManager; + } + } + + public Caret Caret { + get { + return textArea.Caret; + } + } + + [Browsable(false)] + public IDocument Document { + get { + if (motherTextEditorControl != null) + return motherTextEditorControl.Document; + return null; + } + } + + public ITextEditorProperties TextEditorProperties { + get { + if (motherTextEditorControl != null) + return motherTextEditorControl.TextEditorProperties; + return null; + } + } + + public VScrollBar VScrollBar { + get { + return vScrollBar; + } + } + + public HScrollBar HScrollBar { + get { + return hScrollBar; + } + } + + public bool DoHandleMousewheel { + get { + return doHandleMousewheel; + } + set { + doHandleMousewheel = value; + } + } + + public TextAreaControl(TextEditorControl motherTextEditorControl) + { + this.motherTextEditorControl = motherTextEditorControl; + + this.textArea = new TextArea(motherTextEditorControl, this); + Controls.Add(textArea); + + vScrollBar.ValueChanged += new EventHandler(VScrollBarValueChanged); + Controls.Add(this.vScrollBar); + + hScrollBar.ValueChanged += new EventHandler(HScrollBarValueChanged); + Controls.Add(this.hScrollBar); + ResizeRedraw = true; + + Document.TextContentChanged += DocumentTextContentChanged; + Document.DocumentChanged += AdjustScrollBarsOnDocumentChange; + Document.UpdateCommited += DocumentUpdateCommitted; + } + + protected override void Dispose(bool disposing) + { + if (disposing) { + if (!disposed) { + disposed = true; + Document.TextContentChanged -= DocumentTextContentChanged; + Document.DocumentChanged -= AdjustScrollBarsOnDocumentChange; + Document.UpdateCommited -= DocumentUpdateCommitted; + motherTextEditorControl = null; + if (vScrollBar != null) { + vScrollBar.Dispose(); + vScrollBar = null; + } + if (hScrollBar != null) { + hScrollBar.Dispose(); + hScrollBar = null; + } + if (hRuler != null) { + hRuler.Dispose(); + hRuler = null; + } + } + } + base.Dispose(disposing); + } + + void DocumentTextContentChanged(object sender, EventArgs e) + { + // after the text content is changed abruptly, we need to validate the + // caret position - otherwise the caret position is invalid for a short amount + // of time, which can break client code that expects that the caret position is always valid + Caret.ValidateCaretPos(); + } + + protected override void OnResize(System.EventArgs e) + { + base.OnResize(e); + ResizeTextArea(); + } + + public void ResizeTextArea() + { + int y = 0; + int h = 0; + if (hRuler != null) { + hRuler.Bounds = new Rectangle(0, + 0, + Width - SystemInformation.HorizontalScrollBarArrowWidth, + textArea.TextView.FontHeight); + + y = hRuler.Bounds.Bottom; + h = hRuler.Bounds.Height; + } + + textArea.Bounds = new Rectangle(0, y, + Width - SystemInformation.HorizontalScrollBarArrowWidth, + Height - SystemInformation.VerticalScrollBarArrowHeight - h); + SetScrollBarBounds(); + } + + public void SetScrollBarBounds() + { + vScrollBar.Bounds = new Rectangle(textArea.Bounds.Right, 0, SystemInformation.HorizontalScrollBarArrowWidth, Height - SystemInformation.VerticalScrollBarArrowHeight); + hScrollBar.Bounds = new Rectangle(0, + textArea.Bounds.Bottom, + Width - SystemInformation.HorizontalScrollBarArrowWidth, + SystemInformation.VerticalScrollBarArrowHeight); + } + + bool adjustScrollBarsOnNextUpdate; + Point scrollToPosOnNextUpdate; + + void AdjustScrollBarsOnDocumentChange(object sender, DocumentEventArgs e) + { + if (motherTextEditorControl.IsInUpdate == false) { + AdjustScrollBarsClearCache(); + AdjustScrollBars(); + } else { + adjustScrollBarsOnNextUpdate = true; + } + } + + void DocumentUpdateCommitted(object sender, EventArgs e) + { + if (motherTextEditorControl.IsInUpdate == false) { + Caret.ValidateCaretPos(); + + // AdjustScrollBarsOnCommittedUpdate + if (!scrollToPosOnNextUpdate.IsEmpty) { + ScrollTo(scrollToPosOnNextUpdate.Y, scrollToPosOnNextUpdate.X); + } + if (adjustScrollBarsOnNextUpdate) { + AdjustScrollBarsClearCache(); + AdjustScrollBars(); + } + } + } + + int[] lineLengthCache; + const int LineLengthCacheAdditionalSize = 100; + + void AdjustScrollBarsClearCache() + { + if (lineLengthCache != null) { + if (lineLengthCache.Length < this.Document.TotalNumberOfLines + 2 * LineLengthCacheAdditionalSize) { + lineLengthCache = null; + } else { + Array.Clear(lineLengthCache, 0, lineLengthCache.Length); + } + } + } + + public void AdjustScrollBars() + { + adjustScrollBarsOnNextUpdate = false; + vScrollBar.Minimum = 0; + // number of visible lines in document (folding!) + vScrollBar.Maximum = textArea.MaxVScrollValue; + int max = 0; + + int firstLine = textArea.TextView.FirstVisibleLine; + int lastLine = this.Document.GetFirstLogicalLine(textArea.TextView.FirstPhysicalLine + textArea.TextView.VisibleLineCount); + if (lastLine >= this.Document.TotalNumberOfLines) + lastLine = this.Document.TotalNumberOfLines - 1; + + if (lineLengthCache == null || lineLengthCache.Length <= lastLine) { + lineLengthCache = new int[lastLine + LineLengthCacheAdditionalSize]; + } + + for (int lineNumber = firstLine; lineNumber <= lastLine; lineNumber++) { + LineSegment lineSegment = this.Document.GetLineSegment(lineNumber); + if (Document.FoldingManager.IsLineVisible(lineNumber)) { + if (lineLengthCache[lineNumber] > 0) { + max = Math.Max(max, lineLengthCache[lineNumber]); + } else { + int visualLength = textArea.TextView.GetVisualColumnFast(lineSegment, lineSegment.Length); + lineLengthCache[lineNumber] = Math.Max(1, visualLength); + max = Math.Max(max, visualLength); + } + } + } + hScrollBar.Minimum = 0; + hScrollBar.Maximum = (Math.Max(max + 20, textArea.TextView.VisibleColumnCount - 1)); + + vScrollBar.LargeChange = Math.Max(0, textArea.TextView.DrawingPosition.Height); + vScrollBar.SmallChange = Math.Max(0, textArea.TextView.FontHeight); + + hScrollBar.LargeChange = Math.Max(0, textArea.TextView.VisibleColumnCount - 1); + hScrollBar.SmallChange = Math.Max(0, (int)textArea.TextView.SpaceWidth); + } + + public void OptionsChanged() + { + textArea.OptionsChanged(); + + if (textArea.TextEditorProperties.ShowHorizontalRuler) { + if (hRuler == null) { + hRuler = new HRuler(textArea); + Controls.Add(hRuler); + ResizeTextArea(); + } else { + hRuler.Invalidate(); + } + } else { + if (hRuler != null) { + Controls.Remove(hRuler); + hRuler.Dispose(); + hRuler = null; + ResizeTextArea(); + } + } + + AdjustScrollBars(); + } + + void VScrollBarValueChanged(object sender, EventArgs e) + { + textArea.VirtualTop = new Point(textArea.VirtualTop.X, vScrollBar.Value); + textArea.Invalidate(); + AdjustScrollBars(); + } + + void HScrollBarValueChanged(object sender, EventArgs e) + { + textArea.VirtualTop = new Point(hScrollBar.Value * textArea.TextView.WideSpaceWidth, textArea.VirtualTop.Y); + textArea.Invalidate(); + } + + Util.MouseWheelHandler mouseWheelHandler = new Util.MouseWheelHandler(); + + public void HandleMouseWheel(MouseEventArgs e) + { + int scrollDistance = mouseWheelHandler.GetScrollAmount(e); + if (scrollDistance == 0) + return; + if ((Control.ModifierKeys & Keys.Control) != 0 && TextEditorProperties.MouseWheelTextZoom) { + if (scrollDistance > 0) { + motherTextEditorControl.Font = new Font(motherTextEditorControl.Font.Name, + motherTextEditorControl.Font.Size + 1); + } else { + motherTextEditorControl.Font = new Font(motherTextEditorControl.Font.Name, + Math.Max(6, motherTextEditorControl.Font.Size - 1)); + } + } else { + if (TextEditorProperties.MouseWheelScrollDown) + scrollDistance = -scrollDistance; + int newValue = vScrollBar.Value + vScrollBar.SmallChange * scrollDistance; + vScrollBar.Value = Math.Max(vScrollBar.Minimum, Math.Min(vScrollBar.Maximum - vScrollBar.LargeChange + 1, newValue)); + } + } + + protected override void OnMouseWheel(MouseEventArgs e) + { + base.OnMouseWheel(e); + if (DoHandleMousewheel) { + HandleMouseWheel(e); + } + } + + public void ScrollToCaret() + { + ScrollTo(textArea.Caret.Line, textArea.Caret.Column); + } + + public void ScrollTo(int line, int column) + { + if (motherTextEditorControl.IsInUpdate) { + scrollToPosOnNextUpdate = new Point(column, line); + return; + } else { + scrollToPosOnNextUpdate = Point.Empty; + } + + ScrollTo(line); + + int curCharMin = (int)(this.hScrollBar.Value - this.hScrollBar.Minimum); + int curCharMax = curCharMin + textArea.TextView.VisibleColumnCount; + + int pos = textArea.TextView.GetVisualColumn(line, column); + + if (textArea.TextView.VisibleColumnCount < 0) { + hScrollBar.Value = 0; + } else { + if (pos < curCharMin) { + hScrollBar.Value = (int)(Math.Max(0, pos - scrollMarginHeight)); + } else { + if (pos > curCharMax) { + hScrollBar.Value = (int)Math.Max(0, Math.Min(hScrollBar.Maximum, (pos - textArea.TextView.VisibleColumnCount + scrollMarginHeight))); + } + } + } + } + + int scrollMarginHeight = 3; + + /// + /// Ensure that is visible. + /// + public void ScrollTo(int line) + { + line = Math.Max(0, Math.Min(Document.TotalNumberOfLines - 1, line)); + line = Document.GetVisibleLine(line); + int curLineMin = textArea.TextView.FirstPhysicalLine; + if (textArea.TextView.LineHeightRemainder > 0) { + curLineMin ++; + } + + if (line - scrollMarginHeight + 3 < curLineMin) { + this.vScrollBar.Value = Math.Max(0, Math.Min(this.vScrollBar.Maximum, (line - scrollMarginHeight + 3) * textArea.TextView.FontHeight)) ; + VScrollBarValueChanged(this, EventArgs.Empty); + } else { + int curLineMax = curLineMin + this.textArea.TextView.VisibleLineCount; + if (line + scrollMarginHeight - 1 > curLineMax) { + if (this.textArea.TextView.VisibleLineCount == 1) { + this.vScrollBar.Value = Math.Max(0, Math.Min(this.vScrollBar.Maximum, (line - scrollMarginHeight - 1) * textArea.TextView.FontHeight)) ; + } else { + this.vScrollBar.Value = Math.Min(this.vScrollBar.Maximum, + (line - this.textArea.TextView.VisibleLineCount + scrollMarginHeight - 1)* textArea.TextView.FontHeight) ; + } + VScrollBarValueChanged(this, EventArgs.Empty); + } + } + } + + /// + /// Scroll so that the specified line is centered. + /// + /// Line to center view on + /// If this action would cause scrolling by less than or equal to + /// lines in any direction, don't scroll. + /// Use -1 to always center the view. + public void CenterViewOn(int line, int treshold) + { + line = Math.Max(0, Math.Min(Document.TotalNumberOfLines - 1, line)); + // convert line to visible line: + line = Document.GetVisibleLine(line); + // subtract half the visible line count + line -= textArea.TextView.VisibleLineCount / 2; + + int curLineMin = textArea.TextView.FirstPhysicalLine; + if (textArea.TextView.LineHeightRemainder > 0) { + curLineMin ++; + } + if (Math.Abs(curLineMin - line) > treshold) { + // scroll: + this.vScrollBar.Value = Math.Max(0, Math.Min(this.vScrollBar.Maximum, (line - scrollMarginHeight + 3) * textArea.TextView.FontHeight)) ; + VScrollBarValueChanged(this, EventArgs.Empty); + } + } + + public void JumpTo(int line) + { + line = Math.Max(0, Math.Min(line, Document.TotalNumberOfLines - 1)); + string text = Document.GetText(Document.GetLineSegment(line)); + JumpTo(line, text.Length - text.TrimStart().Length); + } + + public void JumpTo(int line, int column) + { + textArea.Focus(); + textArea.SelectionManager.ClearSelection(); + textArea.Caret.Position = new TextLocation(column, line); + textArea.SetDesiredColumn(); + ScrollToCaret(); + } + + public event MouseEventHandler ShowContextMenu; + + protected override void WndProc(ref Message m) + { + if (m.Msg == 0x007B) { // handle WM_CONTEXTMENU + if (ShowContextMenu != null) { + long lParam = m.LParam.ToInt64(); + int x = unchecked((short)(lParam & 0xffff)); + int y = unchecked((short)((lParam & 0xffff0000) >> 16)); + if (x == -1 && y == -1) { + Point pos = Caret.ScreenPosition; + ShowContextMenu(this, new MouseEventArgs(MouseButtons.None, 0, pos.X, pos.Y + textArea.TextView.FontHeight, 0)); + } else { + Point pos = PointToClient(new Point(x, y)); + ShowContextMenu(this, new MouseEventArgs(MouseButtons.Right, 1, pos.X, pos.Y, 0)); + } + } + } + base.WndProc(ref m); + } + + protected override void OnEnter(EventArgs e) + { + // SD2-1072 - Make sure the caret line is valid if anyone + // has handlers for the Enter event. + Caret.ValidateCaretPos(); + base.OnEnter(e); + } + } +} diff --git a/ICSharpCode.TextEditor/Project/Src/Gui/TextAreaDragDropHandler.cs b/ICSharpCode.TextEditor/Project/Src/Gui/TextAreaDragDropHandler.cs new file mode 100644 index 0000000..48719db --- /dev/null +++ b/ICSharpCode.TextEditor/Project/Src/Gui/TextAreaDragDropHandler.cs @@ -0,0 +1,144 @@ +// +// +// +// +// $Revision$ +// + +using System; +using System.Drawing; +using System.Windows.Forms; + +using ICSharpCode.TextEditor.Document; + +namespace ICSharpCode.TextEditor +{ + public class TextAreaDragDropHandler + { + public static Action OnDragDropException = ex => MessageBox.Show(ex.ToString()); + + TextArea textArea; + + public void Attach(TextArea textArea) + { + this.textArea = textArea; + textArea.AllowDrop = true; + + textArea.DragEnter += MakeDragEventHandler(OnDragEnter); + textArea.DragDrop += MakeDragEventHandler(OnDragDrop); + textArea.DragOver += MakeDragEventHandler(OnDragOver); + } + + /// + /// Create a drag'n'drop event handler. + /// Windows Forms swallows unhandled exceptions during drag'n'drop, so we report them here. + /// + static DragEventHandler MakeDragEventHandler(DragEventHandler h) + { + return (sender, e) => { + try { + h(sender, e); + } catch (Exception ex) { + OnDragDropException(ex); + } + }; + } + + static DragDropEffects GetDragDropEffect(DragEventArgs e) + { + if ((e.AllowedEffect & DragDropEffects.Move) > 0 && + (e.AllowedEffect & DragDropEffects.Copy) > 0) { + return (e.KeyState & 8) > 0 ? DragDropEffects.Copy : DragDropEffects.Move; + } else if ((e.AllowedEffect & DragDropEffects.Move) > 0) { + return DragDropEffects.Move; + } else if ((e.AllowedEffect & DragDropEffects.Copy) > 0) { + return DragDropEffects.Copy; + } + return DragDropEffects.None; + } + + protected void OnDragEnter(object sender, DragEventArgs e) + { + if (e.Data.GetDataPresent(typeof(string))) { + e.Effect = GetDragDropEffect(e); + } + } + + + void InsertString(int offset, string str) + { + textArea.Document.Insert(offset, str); + + textArea.SelectionManager.SetSelection(new DefaultSelection(textArea.Document, + textArea.Document.OffsetToPosition(offset), + textArea.Document.OffsetToPosition(offset + str.Length))); + textArea.Caret.Position = textArea.Document.OffsetToPosition(offset + str.Length); + textArea.Refresh(); + } + + protected void OnDragDrop(object sender, DragEventArgs e) + { + Point p = textArea.PointToClient(new Point(e.X, e.Y)); + + if (e.Data.GetDataPresent(typeof(string))) { + textArea.BeginUpdate(); + textArea.Document.UndoStack.StartUndoGroup(); + try { + int offset = textArea.Caret.Offset; + if (textArea.IsReadOnly(offset)) { + // prevent dragging text into readonly section + return; + } + if (e.Data.GetDataPresent(typeof(DefaultSelection))) { + ISelection sel = (ISelection)e.Data.GetData(typeof(DefaultSelection)); + if (sel.ContainsPosition(textArea.Caret.Position)) { + return; + } + if (GetDragDropEffect(e) == DragDropEffects.Move) { + if (SelectionManager.SelectionIsReadOnly(textArea.Document, sel)) { + // prevent dragging text out of readonly section + return; + } + int len = sel.Length; + textArea.Document.Remove(sel.Offset, len); + if (sel.Offset < offset) { + offset -= len; + } + } + } + textArea.SelectionManager.ClearSelection(); + InsertString(offset, (string)e.Data.GetData(typeof(string))); + textArea.Document.RequestUpdate(new TextAreaUpdate(TextAreaUpdateType.WholeTextArea)); + } finally { + textArea.Document.UndoStack.EndUndoGroup(); + textArea.EndUpdate(); + } + } + } + + protected void OnDragOver(object sender, DragEventArgs e) + { + if (!textArea.Focused) { + textArea.Focus(); + } + + Point p = textArea.PointToClient(new Point(e.X, e.Y)); + + if (textArea.TextView.DrawingPosition.Contains(p.X, p.Y)) { + TextLocation realmousepos= textArea.TextView.GetLogicalPosition(p.X - textArea.TextView.DrawingPosition.X, + p.Y - textArea.TextView.DrawingPosition.Y); + int lineNr = Math.Min(textArea.Document.TotalNumberOfLines - 1, Math.Max(0, realmousepos.Y)); + + textArea.Caret.Position = new TextLocation(realmousepos.X, lineNr); + textArea.SetDesiredColumn(); + if (e.Data.GetDataPresent(typeof(string)) && !textArea.IsReadOnly(textArea.Caret.Offset)) { + e.Effect = GetDragDropEffect(e); + } else { + e.Effect = DragDropEffects.None; + } + } else { + e.Effect = DragDropEffects.None; + } + } + } +} diff --git a/ICSharpCode.TextEditor/Project/Src/Gui/TextAreaMouseHandler.cs b/ICSharpCode.TextEditor/Project/Src/Gui/TextAreaMouseHandler.cs new file mode 100644 index 0000000..8acc431 --- /dev/null +++ b/ICSharpCode.TextEditor/Project/Src/Gui/TextAreaMouseHandler.cs @@ -0,0 +1,492 @@ +// +// +// +// +// $Revision$ +// + +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Text; +using System.Windows.Forms; + +using ICSharpCode.TextEditor.Document; + +namespace ICSharpCode.TextEditor +{ + /// + /// This class handles all mouse stuff for a textArea. + /// + public class TextAreaMouseHandler + { + TextArea textArea; + bool doubleclick = false; + bool clickedOnSelectedText = false; + + MouseButtons button; + + static readonly Point nilPoint = new Point(-1, -1); + Point mousedownpos = nilPoint; + Point lastmousedownpos = nilPoint; + + bool gotmousedown = false; + bool dodragdrop = false; + + public TextAreaMouseHandler(TextArea ttextArea) + { + textArea = ttextArea; + } + + public void Attach() + { + textArea.Click += new EventHandler(TextAreaClick); + textArea.MouseMove += new MouseEventHandler(TextAreaMouseMove); + + textArea.MouseDown += new MouseEventHandler(OnMouseDown); + textArea.DoubleClick += new EventHandler(OnDoubleClick); + textArea.MouseLeave += new EventHandler(OnMouseLeave); + textArea.MouseUp += new MouseEventHandler(OnMouseUp); + textArea.LostFocus += new EventHandler(TextAreaLostFocus); + textArea.ToolTipRequest += new ToolTipRequestEventHandler(OnToolTipRequest); + } + + void OnToolTipRequest(object sender, ToolTipRequestEventArgs e) + { + if (e.ToolTipShown) + return; + Point mousepos = e.MousePosition; + FoldMarker marker = textArea.TextView.GetFoldMarkerFromPosition(mousepos.X - textArea.TextView.DrawingPosition.X, + mousepos.Y - textArea.TextView.DrawingPosition.Y); + if (marker != null && marker.IsFolded) { + StringBuilder sb = new StringBuilder(marker.InnerText); + + // max 10 lines + int endLines = 0; + for (int i = 0; i < sb.Length; ++i) { + if (sb[i] == '\n') { + ++endLines; + if (endLines >= 10) { + sb.Remove(i + 1, sb.Length - i - 1); + sb.Append(Environment.NewLine); + sb.Append("..."); + break; + + } + } + } + sb.Replace("\t", " "); + e.ShowToolTip(sb.ToString()); + return; + } + + List markers = textArea.Document.MarkerStrategy.GetMarkers(e.LogicalPosition); + foreach (TextMarker tm in markers) { + if (tm.ToolTip != null) { + e.ShowToolTip(tm.ToolTip.Replace("\t", " ")); + return; + } + } + } + + void ShowHiddenCursorIfMovedOrLeft() + { + textArea.ShowHiddenCursor(!textArea.Focused || + !textArea.ClientRectangle.Contains(textArea.PointToClient(Cursor.Position))); + } + + void TextAreaLostFocus(object sender, EventArgs e) + { + // The call to ShowHiddenCursorIfMovedOrLeft is delayed + // until pending messages have been processed + // so that it can properly detect whether the TextArea + // has really lost focus. + // For example, the CodeCompletionWindow gets focus when it is shown, + // but immediately gives back focus to the TextArea. + textArea.BeginInvoke(new MethodInvoker(ShowHiddenCursorIfMovedOrLeft)); + } + + void OnMouseLeave(object sender, EventArgs e) + { + ShowHiddenCursorIfMovedOrLeft(); + gotmousedown = false; + mousedownpos = nilPoint; + } + + void OnMouseUp(object sender, MouseEventArgs e) + { + textArea.SelectionManager.selectFrom.where = WhereFrom.None; + gotmousedown = false; + mousedownpos = nilPoint; + } + + void TextAreaClick(object sender, EventArgs e) + { + Point mousepos; + mousepos = textArea.mousepos; + + if (dodragdrop) + { + return; + } + + if (clickedOnSelectedText && textArea.TextView.DrawingPosition.Contains(mousepos.X, mousepos.Y)) + { + textArea.SelectionManager.ClearSelection(); + + TextLocation clickPosition = textArea.TextView.GetLogicalPosition( + mousepos.X - textArea.TextView.DrawingPosition.X, + mousepos.Y - textArea.TextView.DrawingPosition.Y); + textArea.Caret.Position = clickPosition; + textArea.SetDesiredColumn(); + } + } + + + void TextAreaMouseMove(object sender, MouseEventArgs e) + { + textArea.mousepos = e.Location; + + // honour the starting selection strategy + switch (textArea.SelectionManager.selectFrom.where) + { + case WhereFrom.Gutter: + ExtendSelectionToMouse(); + return; + + case WhereFrom.TArea: + break; + + } + textArea.ShowHiddenCursor(false); + if (dodragdrop) { + dodragdrop = false; + return; + } + + doubleclick = false; + textArea.mousepos = new Point(e.X, e.Y); + + if (clickedOnSelectedText) { + if (Math.Abs(mousedownpos.X - e.X) >= SystemInformation.DragSize.Width / 2 || + Math.Abs(mousedownpos.Y - e.Y) >= SystemInformation.DragSize.Height / 2) + { + clickedOnSelectedText = false; + ISelection selection = textArea.SelectionManager.GetSelectionAt(textArea.Caret.Offset); + if (selection != null) { + string text = selection.SelectedText; + bool isReadOnly = SelectionManager.SelectionIsReadOnly(textArea.Document, selection); + if (text != null && text.Length > 0) { + DataObject dataObject = new DataObject (); + dataObject.SetData(DataFormats.UnicodeText, true, text); + dataObject.SetData(selection); + dodragdrop = true; + textArea.DoDragDrop(dataObject, isReadOnly ? DragDropEffects.All & ~DragDropEffects.Move : DragDropEffects.All); + } + } + } + + return; + } + + if (e.Button == MouseButtons.Left) { + if (gotmousedown && textArea.SelectionManager.selectFrom.where == WhereFrom.TArea) + { + ExtendSelectionToMouse(); + } + } + } + + void ExtendSelectionToMouse() + { + Point mousepos; + mousepos = textArea.mousepos; + TextLocation realmousepos = textArea.TextView.GetLogicalPosition( + Math.Max(0, mousepos.X - textArea.TextView.DrawingPosition.X), + mousepos.Y - textArea.TextView.DrawingPosition.Y); + int y = realmousepos.Y; + realmousepos = textArea.Caret.ValidatePosition(realmousepos); + TextLocation oldPos = textArea.Caret.Position; + if (oldPos == realmousepos && textArea.SelectionManager.selectFrom.where != WhereFrom.Gutter) + { + return; + } + + // the selection is from the gutter + if (textArea.SelectionManager.selectFrom.where == WhereFrom.Gutter) { + if(realmousepos.Y < textArea.SelectionManager.SelectionStart.Y) { + // the selection has moved above the startpoint + textArea.Caret.Position = new TextLocation(0, realmousepos.Y); + } else { + // the selection has moved below the startpoint + textArea.Caret.Position = textArea.SelectionManager.NextValidPosition(realmousepos.Y); + } + } else { + textArea.Caret.Position = realmousepos; + } + + // moves selection across whole words for double-click initiated selection + if (!minSelection.IsEmpty && textArea.SelectionManager.SelectionCollection.Count > 0 && textArea.SelectionManager.selectFrom.where == WhereFrom.TArea) { + // Extend selection when selection was started with double-click + ISelection selection = textArea.SelectionManager.SelectionCollection[0]; + TextLocation min = textArea.SelectionManager.GreaterEqPos(minSelection, maxSelection) ? maxSelection : minSelection; + TextLocation max = textArea.SelectionManager.GreaterEqPos(minSelection, maxSelection) ? minSelection : maxSelection; + if (textArea.SelectionManager.GreaterEqPos(max, realmousepos) && textArea.SelectionManager.GreaterEqPos(realmousepos, min)) { + textArea.SelectionManager.SetSelection(min, max); + } else if (textArea.SelectionManager.GreaterEqPos(max, realmousepos)) { + int moff = textArea.Document.PositionToOffset(realmousepos); + min = textArea.Document.OffsetToPosition(FindWordStart(textArea.Document, moff)); + textArea.SelectionManager.SetSelection(min, max); + } else { + int moff = textArea.Document.PositionToOffset(realmousepos); + max = textArea.Document.OffsetToPosition(FindWordEnd(textArea.Document, moff)); + textArea.SelectionManager.SetSelection(min, max); + } + } else { + textArea.SelectionManager.ExtendSelection(oldPos, textArea.Caret.Position); + } + textArea.SetDesiredColumn(); + } + + void DoubleClickSelectionExtend() + { + Point mousepos; + mousepos = textArea.mousepos; + + textArea.SelectionManager.ClearSelection(); + if (textArea.TextView.DrawingPosition.Contains(mousepos.X, mousepos.Y)) + { + FoldMarker marker = textArea.TextView.GetFoldMarkerFromPosition(mousepos.X - textArea.TextView.DrawingPosition.X, + mousepos.Y - textArea.TextView.DrawingPosition.Y); + if (marker != null && marker.IsFolded) { + marker.IsFolded = false; + textArea.MotherTextAreaControl.AdjustScrollBars(); + } + if (textArea.Caret.Offset < textArea.Document.TextLength) { + switch (textArea.Document.GetCharAt(textArea.Caret.Offset)) { + case '"': + if (textArea.Caret.Offset < textArea.Document.TextLength) { + int next = FindNext(textArea.Document, textArea.Caret.Offset + 1, '"'); + minSelection = textArea.Caret.Position; + if (next > textArea.Caret.Offset && next < textArea.Document.TextLength) + next += 1; + maxSelection = textArea.Document.OffsetToPosition(next); + } + break; + default: + minSelection = textArea.Document.OffsetToPosition(FindWordStart(textArea.Document, textArea.Caret.Offset)); + maxSelection = textArea.Document.OffsetToPosition(FindWordEnd(textArea.Document, textArea.Caret.Offset)); + break; + + } + textArea.Caret.Position = maxSelection; + textArea.SelectionManager.ExtendSelection(minSelection, maxSelection); + } + + if (textArea.SelectionManager.selectionCollection.Count > 0) { + ISelection selection = textArea.SelectionManager.selectionCollection[0]; + + selection.StartPosition = minSelection; + selection.EndPosition = maxSelection; + textArea.SelectionManager.SelectionStart = minSelection; + } + + // after a double-click selection, the caret is placed correctly, + // but it is not positioned internally. The effect is when the cursor + // is moved up or down a line, the caret will take on the column first + // clicked on for the double-click + textArea.SetDesiredColumn(); + + // HACK WARNING !!! + // must refresh here, because when a error tooltip is showed and the underlined + // code is double clicked the textArea don't update corrctly, updateline doesn't + // work ... but the refresh does. + // Mike + textArea.Refresh(); + } + } + + void OnMouseDown(object sender, MouseEventArgs e) + { + Point mousepos; + textArea.mousepos = e.Location; + mousepos = e.Location; + + if (dodragdrop) + { + return; + } + + if (doubleclick) { + doubleclick = false; + return; + } + + if (textArea.TextView.DrawingPosition.Contains(mousepos.X, mousepos.Y)) { + gotmousedown = true; + textArea.SelectionManager.selectFrom.where = WhereFrom.TArea; + button = e.Button; + + // double-click + if (button == MouseButtons.Left && e.Clicks == 2) { + int deltaX = Math.Abs(lastmousedownpos.X - e.X); + int deltaY = Math.Abs(lastmousedownpos.Y - e.Y); + if (deltaX <= SystemInformation.DoubleClickSize.Width && + deltaY <= SystemInformation.DoubleClickSize.Height) { + DoubleClickSelectionExtend(); + lastmousedownpos = new Point(e.X, e.Y); + + if (textArea.SelectionManager.selectFrom.where == WhereFrom.Gutter) { + if (!minSelection.IsEmpty && !maxSelection.IsEmpty && textArea.SelectionManager.SelectionCollection.Count > 0) { + textArea.SelectionManager.SelectionCollection[0].StartPosition = minSelection; + textArea.SelectionManager.SelectionCollection[0].EndPosition = maxSelection; + textArea.SelectionManager.SelectionStart = minSelection; + + minSelection = TextLocation.Empty; + maxSelection = TextLocation.Empty; + } + } + return; + } + } + minSelection = TextLocation.Empty; + maxSelection = TextLocation.Empty; + + lastmousedownpos = mousedownpos = new Point(e.X, e.Y); + + if (button == MouseButtons.Left) { + FoldMarker marker = textArea.TextView.GetFoldMarkerFromPosition(mousepos.X - textArea.TextView.DrawingPosition.X, + mousepos.Y - textArea.TextView.DrawingPosition.Y); + if (marker != null && marker.IsFolded) { + if (textArea.SelectionManager.HasSomethingSelected) { + clickedOnSelectedText = true; + } + + TextLocation startLocation = new TextLocation(marker.StartColumn, marker.StartLine); + TextLocation endLocation = new TextLocation(marker.EndColumn, marker.EndLine); + textArea.SelectionManager.SetSelection(new DefaultSelection(textArea.TextView.Document, startLocation, endLocation)); + textArea.Caret.Position = startLocation; + textArea.SetDesiredColumn(); + textArea.Focus(); + return; + } + + if ((Control.ModifierKeys & Keys.Shift) == Keys.Shift) { + ExtendSelectionToMouse(); + } else { + TextLocation realmousepos = textArea.TextView.GetLogicalPosition(mousepos.X - textArea.TextView.DrawingPosition.X, mousepos.Y - textArea.TextView.DrawingPosition.Y); + clickedOnSelectedText = false; + + int offset = textArea.Document.PositionToOffset(realmousepos); + + if (textArea.SelectionManager.HasSomethingSelected && + textArea.SelectionManager.IsSelected(offset)) { + clickedOnSelectedText = true; + } else { + textArea.SelectionManager.ClearSelection(); + if (mousepos.Y > 0 && mousepos.Y < textArea.TextView.DrawingPosition.Height) { + TextLocation pos = new TextLocation(); + pos.Y = Math.Min(textArea.Document.TotalNumberOfLines - 1, realmousepos.Y); + pos.X = realmousepos.X; + textArea.Caret.Position = pos; + textArea.SetDesiredColumn(); + } + } + } + } else if (button == MouseButtons.Right) { + // Rightclick sets the cursor to the click position unless + // the previous selection was clicked + TextLocation realmousepos = textArea.TextView.GetLogicalPosition(mousepos.X - textArea.TextView.DrawingPosition.X, mousepos.Y - textArea.TextView.DrawingPosition.Y); + int offset = textArea.Document.PositionToOffset(realmousepos); + if (!textArea.SelectionManager.HasSomethingSelected || + !textArea.SelectionManager.IsSelected(offset)) + { + textArea.SelectionManager.ClearSelection(); + if (mousepos.Y > 0 && mousepos.Y < textArea.TextView.DrawingPosition.Height) { + TextLocation pos = new TextLocation(); + pos.Y = Math.Min(textArea.Document.TotalNumberOfLines - 1, realmousepos.Y); + pos.X = realmousepos.X; + textArea.Caret.Position = pos; + textArea.SetDesiredColumn(); + } + } + } + } + textArea.Focus(); + } + + int FindNext(IDocument document, int offset, char ch) + { + LineSegment line = document.GetLineSegmentForOffset(offset); + int endPos = line.Offset + line.Length; + + while (offset < endPos && document.GetCharAt(offset) != ch) { + ++offset; + } + return offset; + } + + bool IsSelectableChar(char ch) + { + return char.IsLetterOrDigit(ch) || ch=='_'; + } + + int FindWordStart(IDocument document, int offset) + { + LineSegment line = document.GetLineSegmentForOffset(offset); + + if (offset > 0 && char.IsWhiteSpace(document.GetCharAt(offset - 1)) && char.IsWhiteSpace(document.GetCharAt(offset))) { + while (offset > line.Offset && char.IsWhiteSpace(document.GetCharAt(offset - 1))) { + --offset; + } + } else if (IsSelectableChar(document.GetCharAt(offset)) || (offset > 0 && char.IsWhiteSpace(document.GetCharAt(offset)) && IsSelectableChar(document.GetCharAt(offset - 1)))) { + while (offset > line.Offset && IsSelectableChar(document.GetCharAt(offset - 1))) { + --offset; + } + } else { + if (offset > 0 && !char.IsWhiteSpace(document.GetCharAt(offset - 1)) && !IsSelectableChar(document.GetCharAt(offset - 1)) ) { + return Math.Max(0, offset - 1); + } + } + return offset; + } + + int FindWordEnd(IDocument document, int offset) + { + LineSegment line = document.GetLineSegmentForOffset(offset); + if (line.Length == 0) + return offset; + int endPos = line.Offset + line.Length; + offset = Math.Min(offset, endPos - 1); + + if (IsSelectableChar(document.GetCharAt(offset))) { + while (offset < endPos && IsSelectableChar(document.GetCharAt(offset))) { + ++offset; + } + } else if (char.IsWhiteSpace(document.GetCharAt(offset))) { + if (offset > 0 && char.IsWhiteSpace(document.GetCharAt(offset - 1))) { + while (offset < endPos && char.IsWhiteSpace(document.GetCharAt(offset))) { + ++offset; + } + } + } else { + return Math.Max(0, offset + 1); + } + + return offset; + } + TextLocation minSelection = TextLocation.Empty; + TextLocation maxSelection = TextLocation.Empty; + + void OnDoubleClick(object sender, System.EventArgs e) + { + if (dodragdrop) { + return; + } + + textArea.SelectionManager.selectFrom.where = WhereFrom.TArea; + doubleclick = true; + + } + } +} diff --git a/ICSharpCode.TextEditor/Project/Src/Gui/TextAreaUpdate.cs b/ICSharpCode.TextEditor/Project/Src/Gui/TextAreaUpdate.cs new file mode 100644 index 0000000..ef2160e --- /dev/null +++ b/ICSharpCode.TextEditor/Project/Src/Gui/TextAreaUpdate.cs @@ -0,0 +1,85 @@ +// +// +// +// +// $Revision$ +// + +using System; +using System.Drawing; + +namespace ICSharpCode.TextEditor +{ + /// + /// This enum describes all implemented request types + /// + public enum TextAreaUpdateType { + WholeTextArea, + SingleLine, + SinglePosition, + PositionToLineEnd, + PositionToEnd, + LinesBetween + } + + /// + /// This class is used to request an update of the textarea + /// + public class TextAreaUpdate + { + TextLocation position; + TextAreaUpdateType type; + + public TextAreaUpdateType TextAreaUpdateType { + get { + return type; + } + } + + public TextLocation Position { + get { + return position; + } + } + + /// + /// Creates a new instance of + /// + public TextAreaUpdate(TextAreaUpdateType type) + { + this.type = type; + } + + /// + /// Creates a new instance of + /// + public TextAreaUpdate(TextAreaUpdateType type, TextLocation position) + { + this.type = type; + this.position = position; + } + + /// + /// Creates a new instance of + /// + public TextAreaUpdate(TextAreaUpdateType type, int startLine, int endLine) + { + this.type = type; + this.position = new TextLocation(startLine, endLine); + } + + /// + /// Creates a new instance of + /// + public TextAreaUpdate(TextAreaUpdateType type, int singleLine) + { + this.type = type; + this.position = new TextLocation(0, singleLine); + } + + public override string ToString() + { + return string.Format("[TextAreaUpdate: Type={0}, Position={1}]", type, position); + } + } +} diff --git a/ICSharpCode.TextEditor/Project/Src/Gui/TextEditorControl.cs b/ICSharpCode.TextEditor/Project/Src/Gui/TextEditorControl.cs new file mode 100644 index 0000000..373cd68 --- /dev/null +++ b/ICSharpCode.TextEditor/Project/Src/Gui/TextEditorControl.cs @@ -0,0 +1,396 @@ +// +// +// +// +// $Revision$ +// + +using System; +using System.ComponentModel; +using System.Drawing; +using System.Drawing.Printing; +using System.Windows.Forms; + +using ICSharpCode.TextEditor.Document; + +namespace ICSharpCode.TextEditor +{ + /// + /// This class is used for a basic text area control + /// + [ToolboxBitmap("ICSharpCode.TextEditor.Resources.TextEditorControl.bmp")] + [ToolboxItem(true)] + public class TextEditorControl : TextEditorControlBase + { + protected Panel textAreaPanel = new Panel(); + TextAreaControl primaryTextArea; + Splitter textAreaSplitter = null; + TextAreaControl secondaryTextArea = null; + + PrintDocument printDocument = null; + + [Browsable(false)] + public PrintDocument PrintDocument { + get { + if (printDocument == null) { + printDocument = new PrintDocument(); + printDocument.BeginPrint += new PrintEventHandler(this.BeginPrint); + printDocument.PrintPage += new PrintPageEventHandler(this.PrintPage); + } + return printDocument; + } + } + + TextAreaControl activeTextAreaControl; + + public override TextAreaControl ActiveTextAreaControl { + get { + return activeTextAreaControl; + } + } + + protected void SetActiveTextAreaControl(TextAreaControl value) + { + if (activeTextAreaControl != value) { + activeTextAreaControl = value; + + if (ActiveTextAreaControlChanged != null) { + ActiveTextAreaControlChanged(this, EventArgs.Empty); + } + } + } + + public event EventHandler ActiveTextAreaControlChanged; + + public TextEditorControl() + { + SetStyle(ControlStyles.ContainerControl, true); + + textAreaPanel.Dock = DockStyle.Fill; + + Document = (new DocumentFactory()).CreateDocument(); + Document.HighlightingStrategy = HighlightingStrategyFactory.CreateHighlightingStrategy(); + + primaryTextArea = new TextAreaControl(this); + activeTextAreaControl = primaryTextArea; + primaryTextArea.TextArea.GotFocus += delegate { + SetActiveTextAreaControl(primaryTextArea); + }; + primaryTextArea.Dock = DockStyle.Fill; + textAreaPanel.Controls.Add(primaryTextArea); + InitializeTextAreaControl(primaryTextArea); + Controls.Add(textAreaPanel); + ResizeRedraw = true; + Document.UpdateCommited += new EventHandler(CommitUpdateRequested); + OptionsChanged(); + } + + protected virtual void InitializeTextAreaControl(TextAreaControl newControl) + { + } + + public override void OptionsChanged() + { + primaryTextArea.OptionsChanged(); + if (secondaryTextArea != null) { + secondaryTextArea.OptionsChanged(); + } + } + + public void Split() + { + if (secondaryTextArea == null) { + secondaryTextArea = new TextAreaControl(this); + secondaryTextArea.Dock = DockStyle.Bottom; + secondaryTextArea.Height = Height / 2; + + secondaryTextArea.TextArea.GotFocus += delegate { + SetActiveTextAreaControl(secondaryTextArea); + }; + + textAreaSplitter = new Splitter(); + textAreaSplitter.BorderStyle = BorderStyle.FixedSingle ; + textAreaSplitter.Height = 8; + textAreaSplitter.Dock = DockStyle.Bottom; + textAreaPanel.Controls.Add(textAreaSplitter); + textAreaPanel.Controls.Add(secondaryTextArea); + InitializeTextAreaControl(secondaryTextArea); + secondaryTextArea.OptionsChanged(); + } else { + SetActiveTextAreaControl(primaryTextArea); + + textAreaPanel.Controls.Remove(secondaryTextArea); + textAreaPanel.Controls.Remove(textAreaSplitter); + + secondaryTextArea.Dispose(); + textAreaSplitter.Dispose(); + secondaryTextArea = null; + textAreaSplitter = null; + } + } + + [Browsable(false)] + public bool EnableUndo { + get { + return Document.UndoStack.CanUndo; + } + } + + [Browsable(false)] + public bool EnableRedo { + get { + return Document.UndoStack.CanRedo; + } + } + + public void Undo() + { + if (Document.ReadOnly) { + return; + } + if (Document.UndoStack.CanUndo) { + BeginUpdate(); + Document.UndoStack.Undo(); + + Document.RequestUpdate(new TextAreaUpdate(TextAreaUpdateType.WholeTextArea)); + this.primaryTextArea.TextArea.UpdateMatchingBracket(); + if (secondaryTextArea != null) { + this.secondaryTextArea.TextArea.UpdateMatchingBracket(); + } + EndUpdate(); + } + } + + public void Redo() + { + if (Document.ReadOnly) { + return; + } + if (Document.UndoStack.CanRedo) { + BeginUpdate(); + Document.UndoStack.Redo(); + + Document.RequestUpdate(new TextAreaUpdate(TextAreaUpdateType.WholeTextArea)); + this.primaryTextArea.TextArea.UpdateMatchingBracket(); + if (secondaryTextArea != null) { + this.secondaryTextArea.TextArea.UpdateMatchingBracket(); + } + EndUpdate(); + } + } + + public virtual void SetHighlighting(string name) + { + Document.HighlightingStrategy = HighlightingStrategyFactory.CreateHighlightingStrategy(name); + } + + protected override void Dispose(bool disposing) + { + if (disposing) { + if (printDocument != null) { + printDocument.BeginPrint -= new PrintEventHandler(this.BeginPrint); + printDocument.PrintPage -= new PrintPageEventHandler(this.PrintPage); + printDocument = null; + } + Document.UndoStack.ClearAll(); + Document.UpdateCommited -= new EventHandler(CommitUpdateRequested); + if (textAreaPanel != null) { + if (secondaryTextArea != null) { + secondaryTextArea.Dispose(); + textAreaSplitter.Dispose(); + secondaryTextArea = null; + textAreaSplitter = null; + } + if (primaryTextArea != null) { + primaryTextArea.Dispose(); + } + textAreaPanel.Dispose(); + textAreaPanel = null; + } + } + base.Dispose(disposing); + } + + #region Update Methods + public override void EndUpdate() + { + base.EndUpdate(); + Document.CommitUpdate(); + if (!IsInUpdate) { + ActiveTextAreaControl.Caret.OnEndUpdate(); + } + } + + void CommitUpdateRequested(object sender, EventArgs e) + { + if (IsInUpdate) { + return; + } + foreach (TextAreaUpdate update in Document.UpdateQueue) { + switch (update.TextAreaUpdateType) { + case TextAreaUpdateType.PositionToEnd: + this.primaryTextArea.TextArea.UpdateToEnd(update.Position.Y); + if (this.secondaryTextArea != null) { + this.secondaryTextArea.TextArea.UpdateToEnd(update.Position.Y); + } + break; + case TextAreaUpdateType.PositionToLineEnd: + case TextAreaUpdateType.SingleLine: + this.primaryTextArea.TextArea.UpdateLine(update.Position.Y); + if (this.secondaryTextArea != null) { + this.secondaryTextArea.TextArea.UpdateLine(update.Position.Y); + } + break; + case TextAreaUpdateType.SinglePosition: + this.primaryTextArea.TextArea.UpdateLine(update.Position.Y, update.Position.X, update.Position.X); + if (this.secondaryTextArea != null) { + this.secondaryTextArea.TextArea.UpdateLine(update.Position.Y, update.Position.X, update.Position.X); + } + break; + case TextAreaUpdateType.LinesBetween: + this.primaryTextArea.TextArea.UpdateLines(update.Position.X, update.Position.Y); + if (this.secondaryTextArea != null) { + this.secondaryTextArea.TextArea.UpdateLines(update.Position.X, update.Position.Y); + } + break; + case TextAreaUpdateType.WholeTextArea: + this.primaryTextArea.TextArea.Invalidate(); + if (this.secondaryTextArea != null) { + this.secondaryTextArea.TextArea.Invalidate(); + } + break; + } + } + Document.UpdateQueue.Clear(); +// this.primaryTextArea.TextArea.Update(); +// if (this.secondaryTextArea != null) { +// this.secondaryTextArea.TextArea.Update(); +// } + } + #endregion + + #region Printing routines + int curLineNr = 0; + float curTabIndent = 0; + StringFormat printingStringFormat; + + void BeginPrint(object sender, PrintEventArgs ev) + { + curLineNr = 0; + printingStringFormat = (StringFormat)System.Drawing.StringFormat.GenericTypographic.Clone(); + + // 100 should be enough for everyone ...err ? + float[] tabStops = new float[100]; + for (int i = 0; i < tabStops.Length; ++i) { + tabStops[i] = TabIndent * primaryTextArea.TextArea.TextView.WideSpaceWidth; + } + + printingStringFormat.SetTabStops(0, tabStops); + } + + void Advance(ref float x, ref float y, float maxWidth, float size, float fontHeight) + { + if (x + size < maxWidth) { + x += size; + } else { + x = curTabIndent; + y += fontHeight; + } + } + + // btw. I hate source code duplication ... but this time I don't care !!!! + float MeasurePrintingHeight(Graphics g, LineSegment line, float maxWidth) + { + float xPos = 0; + float yPos = 0; + float fontHeight = Font.GetHeight(g); +// bool gotNonWhitespace = false; + curTabIndent = 0; + FontContainer fontContainer = TextEditorProperties.FontContainer; + foreach (TextWord word in line.Words) { + switch (word.Type) { + case TextWordType.Space: + Advance(ref xPos, ref yPos, maxWidth, primaryTextArea.TextArea.TextView.SpaceWidth, fontHeight); +// if (!gotNonWhitespace) { +// curTabIndent = xPos; +// } + break; + case TextWordType.Tab: + Advance(ref xPos, ref yPos, maxWidth, TabIndent * primaryTextArea.TextArea.TextView.WideSpaceWidth, fontHeight); +// if (!gotNonWhitespace) { +// curTabIndent = xPos; +// } + break; + case TextWordType.Word: +// if (!gotNonWhitespace) { +// gotNonWhitespace = true; +// curTabIndent += TabIndent * primaryTextArea.TextArea.TextView.GetWidth(' '); +// } + SizeF drawingSize = g.MeasureString(word.Word, word.GetFont(fontContainer), new SizeF(maxWidth, fontHeight * 100), printingStringFormat); + Advance(ref xPos, ref yPos, maxWidth, drawingSize.Width, fontHeight); + break; + } + } + return yPos + fontHeight; + } + + void DrawLine(Graphics g, LineSegment line, float yPos, RectangleF margin) + { + float xPos = 0; + float fontHeight = Font.GetHeight(g); +// bool gotNonWhitespace = false; + curTabIndent = 0 ; + + FontContainer fontContainer = TextEditorProperties.FontContainer; + foreach (TextWord word in line.Words) { + switch (word.Type) { + case TextWordType.Space: + Advance(ref xPos, ref yPos, margin.Width, primaryTextArea.TextArea.TextView.SpaceWidth, fontHeight); +// if (!gotNonWhitespace) { +// curTabIndent = xPos; +// } + break; + case TextWordType.Tab: + Advance(ref xPos, ref yPos, margin.Width, TabIndent * primaryTextArea.TextArea.TextView.WideSpaceWidth, fontHeight); +// if (!gotNonWhitespace) { +// curTabIndent = xPos; +// } + break; + case TextWordType.Word: +// if (!gotNonWhitespace) { +// gotNonWhitespace = true; +// curTabIndent += TabIndent * primaryTextArea.TextArea.TextView.GetWidth(' '); +// } + g.DrawString(word.Word, word.GetFont(fontContainer), BrushRegistry.GetBrush(word.Color), xPos + margin.X, yPos); + SizeF drawingSize = g.MeasureString(word.Word, word.GetFont(fontContainer), new SizeF(margin.Width, fontHeight * 100), printingStringFormat); + Advance(ref xPos, ref yPos, margin.Width, drawingSize.Width, fontHeight); + break; + } + } + } + + void PrintPage(object sender, PrintPageEventArgs ev) + { + Graphics g = ev.Graphics; + float yPos = ev.MarginBounds.Top; + + while (curLineNr < Document.TotalNumberOfLines) { + LineSegment curLine = Document.GetLineSegment(curLineNr); + if (curLine.Words != null) { + float drawingHeight = MeasurePrintingHeight(g, curLine, ev.MarginBounds.Width); + if (drawingHeight + yPos > ev.MarginBounds.Bottom) { + break; + } + + DrawLine(g, curLine, yPos, ev.MarginBounds); + yPos += drawingHeight; + } + ++curLineNr; + } + + // If more lines exist, print another page. + ev.HasMorePages = curLineNr < Document.TotalNumberOfLines; + } + #endregion + } +} diff --git a/ICSharpCode.TextEditor/Project/Src/Gui/TextEditorControlBase.cs b/ICSharpCode.TextEditor/Project/Src/Gui/TextEditorControlBase.cs new file mode 100644 index 0000000..ba98071 --- /dev/null +++ b/ICSharpCode.TextEditor/Project/Src/Gui/TextEditorControlBase.cs @@ -0,0 +1,759 @@ +// +// +// +// +// $Revision$ +// + +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Diagnostics; +using System.Drawing; +using System.Drawing.Text; +using System.IO; +using System.Text; +using System.Windows.Forms; + +using ICSharpCode.TextEditor.Actions; +using ICSharpCode.TextEditor.Document; + +namespace ICSharpCode.TextEditor +{ + /// + /// This class is used for a basic text area control + /// + [ToolboxItem(false)] + public abstract class TextEditorControlBase : UserControl + { + string currentFileName = null; + int updateLevel = 0; + protected IDocument document; // Stef Heyenrath : Changed to protected + + /// + /// This hashtable contains all editor keys, where + /// the key is the key combination and the value the + /// action. + /// + protected Dictionary editactions = new Dictionary(); + + [Browsable(false)] + [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public ITextEditorProperties TextEditorProperties { + get { + return document.TextEditorProperties; + } + set { + document.TextEditorProperties = value; + OptionsChanged(); + } + } + + Encoding encoding; + + /// + /// Current file's character encoding + /// + [Browsable(false)] + [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public Encoding Encoding { + get { + if (encoding == null) + return TextEditorProperties.Encoding; + return encoding; + } + set { + encoding = value; + } + } + + /// + /// The current file name + /// + [Browsable(false)] + [ReadOnly(true)] + public string FileName { + get { + return currentFileName; + } + set { + if (currentFileName != value) { + currentFileName = value; + OnFileNameChanged(EventArgs.Empty); + } + } + } + + /// + /// The current document + /// + [Browsable(false)] + [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public IDocument Document { + get { + return document; + } + set { + if (value == null) + throw new ArgumentNullException("value"); + if (document != null) { + document.DocumentChanged -= OnDocumentChanged; + } + document = value; + document.UndoStack.TextEditorControl = this; + document.DocumentChanged += OnDocumentChanged; + } + } + + protected void OnDocumentChanged(object sender, EventArgs e) // Stef Heyenrath : changed to protected + { + OnTextChanged(e); + } + + [EditorBrowsable(EditorBrowsableState.Always), Browsable(true)] + [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)] + [Editor("System.ComponentModel.Design.MultilineStringEditor, System.Design, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a", typeof(System.Drawing.Design.UITypeEditor))] + public override string Text { + get { + return Document.TextContent; + } + set { + Document.TextContent = value; + } + } + + [EditorBrowsable(EditorBrowsableState.Always), Browsable(true)] + public new event EventHandler TextChanged + { + add { base.TextChanged += value; } + remove { base.TextChanged -= value; } + } + + static Font ParseFont(string font) + { + string[] descr = font.Split(new char[]{',', '='}); + return new Font(descr[1], float.Parse(descr[3])); + } + + /// + /// If set to true the contents can't be altered. + /// + [Browsable(false)] + public bool IsReadOnly { + get { + return Document.ReadOnly; + } + set { + Document.ReadOnly = value; + } + } + + /// + /// true, if the textarea is updating it's status, while + /// it updates it status no redraw operation occurs. + /// + [Browsable(false)] + public bool IsInUpdate { + get { + return updateLevel > 0; + } + } + + /// + /// supposedly this is the way to do it according to .NET docs, + /// as opposed to setting the size in the constructor + /// + protected override Size DefaultSize { + get { + return new Size(100, 100); + } + } + + #region Document Properties + /// + /// If true spaces are shown in the textarea + /// + [Category("Appearance")] + [DefaultValue(false)] + [Description("If true spaces are shown in the textarea")] + public bool ShowSpaces { + get { + return document.TextEditorProperties.ShowSpaces; + } + set { + document.TextEditorProperties.ShowSpaces = value; + OptionsChanged(); + } + } + + /// + /// Specifies the quality of text rendering (whether to use hinting and/or anti-aliasing). + /// + [Category("Appearance")] + [DefaultValue(TextRenderingHint.SystemDefault)] + [Description("Specifies the quality of text rendering (whether to use hinting and/or anti-aliasing).")] + public TextRenderingHint TextRenderingHint { + get { + return document.TextEditorProperties.TextRenderingHint; + } + set { + document.TextEditorProperties.TextRenderingHint = value; + OptionsChanged(); + } + } + + /// + /// If true tabs are shown in the textarea + /// + [Category("Appearance")] + [DefaultValue(false)] + [Description("If true tabs are shown in the textarea")] + public bool ShowTabs { + get { + return document.TextEditorProperties.ShowTabs; + } + set { + document.TextEditorProperties.ShowTabs = value; + OptionsChanged(); + } + } + + /// + /// If true EOL markers are shown in the textarea + /// + [Category("Appearance")] + [DefaultValue(false)] + [Description("If true EOL markers are shown in the textarea")] + public bool ShowEOLMarkers { + get { + return document.TextEditorProperties.ShowEOLMarker; + } + set { + document.TextEditorProperties.ShowEOLMarker = value; + OptionsChanged(); + } + } + + /// + /// If true the horizontal ruler is shown in the textarea + /// + [Category("Appearance")] + [DefaultValue(false)] + [Description("If true the horizontal ruler is shown in the textarea")] + public bool ShowHRuler { + get { + return document.TextEditorProperties.ShowHorizontalRuler; + } + set { + document.TextEditorProperties.ShowHorizontalRuler = value; + OptionsChanged(); + } + } + + /// + /// If true the vertical ruler is shown in the textarea + /// + [Category("Appearance")] + [DefaultValue(true)] + [Description("If true the vertical ruler is shown in the textarea")] + public bool ShowVRuler { + get { + return document.TextEditorProperties.ShowVerticalRuler; + } + set { + document.TextEditorProperties.ShowVerticalRuler = value; + OptionsChanged(); + } + } + + /// + /// The row in which the vertical ruler is displayed + /// + [Category("Appearance")] + [DefaultValue(80)] + [Description("The row in which the vertical ruler is displayed")] + public int VRulerRow { + get { + return document.TextEditorProperties.VerticalRulerRow; + } + set { + document.TextEditorProperties.VerticalRulerRow = value; + OptionsChanged(); + } + } + + /// + /// If true line numbers are shown in the textarea + /// + [Category("Appearance")] + [DefaultValue(true)] + [Description("If true line numbers are shown in the textarea")] + public bool ShowLineNumbers { + get { + return document.TextEditorProperties.ShowLineNumbers; + } + set { + document.TextEditorProperties.ShowLineNumbers = value; + OptionsChanged(); + } + } + + /// + /// If true invalid lines are marked in the textarea + /// + [Category("Appearance")] + [DefaultValue(false)] + [Description("If true invalid lines are marked in the textarea")] + public bool ShowInvalidLines { + get { + return document.TextEditorProperties.ShowInvalidLines; + } + set { + document.TextEditorProperties.ShowInvalidLines = value; + OptionsChanged(); + } + } + + /// + /// If true folding is enabled in the textarea + /// + [Category("Appearance")] + [DefaultValue(true)] + [Description("If true folding is enabled in the textarea")] + public bool EnableFolding { + get { + return document.TextEditorProperties.EnableFolding; + } + set { + document.TextEditorProperties.EnableFolding = value; + OptionsChanged(); + } + } + + [Category("Appearance")] + [DefaultValue(true)] + [Description("If true matching brackets are highlighted")] + public bool ShowMatchingBracket { + get { + return document.TextEditorProperties.ShowMatchingBracket; + } + set { + document.TextEditorProperties.ShowMatchingBracket = value; + OptionsChanged(); + } + } + + [Category("Appearance")] + [DefaultValue(false)] + [Description("If true the icon bar is displayed")] + public bool IsIconBarVisible { + get { + return document.TextEditorProperties.IsIconBarVisible; + } + set { + document.TextEditorProperties.IsIconBarVisible = value; + OptionsChanged(); + } + } + + /// + /// The width in spaces of a tab character + /// + [Category("Appearance")] + [DefaultValue(4)] + [Description("The width in spaces of a tab character")] + public int TabIndent { + get { + return document.TextEditorProperties.TabIndent; + } + set { + document.TextEditorProperties.TabIndent = value; + OptionsChanged(); + } + } + + /// + /// The line viewer style + /// + [Category("Appearance")] + [DefaultValue(LineViewerStyle.None)] + [Description("The line viewer style")] + public LineViewerStyle LineViewerStyle { + get { + return document.TextEditorProperties.LineViewerStyle; + } + set { + document.TextEditorProperties.LineViewerStyle = value; + OptionsChanged(); + } + } + + /// + /// The indent style + /// + [Category("Behavior")] + [DefaultValue(IndentStyle.Smart)] + [Description("The indent style")] + public IndentStyle IndentStyle { + get { + return document.TextEditorProperties.IndentStyle; + } + set { + document.TextEditorProperties.IndentStyle = value; + OptionsChanged(); + } + } + + /// + /// if true spaces are converted to tabs + /// + [Category("Behavior")] + [DefaultValue(false)] + [Description("Converts tabs to spaces while typing")] + public bool ConvertTabsToSpaces { + get { + return document.TextEditorProperties.ConvertTabsToSpaces; + } + set { + document.TextEditorProperties.ConvertTabsToSpaces = value; + OptionsChanged(); + } + } + + /// + /// if true spaces are converted to tabs + /// + [Category("Behavior")] + [DefaultValue(false)] + [Description("Hide the mouse cursor while typing")] + public bool HideMouseCursor { + get { + return document.TextEditorProperties.HideMouseCursor; + } + set { + document.TextEditorProperties.HideMouseCursor = value; + OptionsChanged(); + } + } + + /// + /// if true spaces are converted to tabs + /// + [Category("Behavior")] + [DefaultValue(false)] + [Description("Allows the caret to be placed beyond the end of line")] + public bool AllowCaretBeyondEOL { + get { + return document.TextEditorProperties.AllowCaretBeyondEOL; + } + set { + document.TextEditorProperties.AllowCaretBeyondEOL = value; + OptionsChanged(); + } + } + /// + /// if true spaces are converted to tabs + /// + [Category("Behavior")] + [DefaultValue(BracketMatchingStyle.After)] + [Description("Specifies if the bracket matching should match the bracket before or after the caret.")] + public BracketMatchingStyle BracketMatchingStyle { + get { + return document.TextEditorProperties.BracketMatchingStyle; + } + set { + document.TextEditorProperties.BracketMatchingStyle = value; + OptionsChanged(); + } + } + + /// + /// The base font of the text area. No bold or italic fonts + /// can be used because bold/italic is reserved for highlighting + /// purposes. + /// + [Browsable(true)] + [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)] + [DefaultValue(typeof(Font), null)] // Stef_H + [Description("The base font of the text area. No bold or italic fonts can be used because bold/italic is reserved for highlighting purposes.")] + public override Font Font { + get { + return document.TextEditorProperties.Font; + } + set { + document.TextEditorProperties.Font = value; + OptionsChanged(); + } + } + + #endregion + public abstract TextAreaControl ActiveTextAreaControl { + get; + } + + protected TextEditorControlBase() + { + GenerateDefaultActions(); + HighlightingManager.Manager.ReloadSyntaxHighlighting += new EventHandler(OnReloadHighlighting); + } + + protected virtual void OnReloadHighlighting(object sender, EventArgs e) + { + if (Document.HighlightingStrategy != null) { + try { + Document.HighlightingStrategy = HighlightingStrategyFactory.CreateHighlightingStrategy(Document.HighlightingStrategy.Name); + } catch (HighlightingDefinitionInvalidException ex) { + MessageBox.Show(ex.ToString(), "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); + } + OptionsChanged(); + } + } + + public bool IsEditAction(Keys keyData) + { + return editactions.ContainsKey(keyData); + } + + internal IEditAction GetEditAction(Keys keyData) + { + if (!IsEditAction(keyData)) { + return null; + } + return (IEditAction)editactions[keyData]; + } + + void GenerateDefaultActions() + { + editactions[Keys.Left] = new CaretLeft(); + editactions[Keys.Left | Keys.Shift] = new ShiftCaretLeft(); + editactions[Keys.Left | Keys.Control] = new WordLeft(); + editactions[Keys.Left | Keys.Control | Keys.Shift] = new ShiftWordLeft(); + editactions[Keys.Right] = new CaretRight(); + editactions[Keys.Right | Keys.Shift] = new ShiftCaretRight(); + editactions[Keys.Right | Keys.Control] = new WordRight(); + editactions[Keys.Right | Keys.Control | Keys.Shift] = new ShiftWordRight(); + editactions[Keys.Up] = new CaretUp(); + editactions[Keys.Up | Keys.Shift] = new ShiftCaretUp(); + editactions[Keys.Up | Keys.Control] = new ScrollLineUp(); + editactions[Keys.Down] = new CaretDown(); + editactions[Keys.Down | Keys.Shift] = new ShiftCaretDown(); + editactions[Keys.Down | Keys.Control] = new ScrollLineDown(); + + editactions[Keys.Insert] = new ToggleEditMode(); + editactions[Keys.Insert | Keys.Control] = new Copy(); + editactions[Keys.Insert | Keys.Shift] = new Paste(); + editactions[Keys.Delete] = new Delete(); + editactions[Keys.Delete | Keys.Shift] = new Cut(); + editactions[Keys.Home] = new Home(); + editactions[Keys.Home | Keys.Shift] = new ShiftHome(); + editactions[Keys.Home | Keys.Control] = new MoveToStart(); + editactions[Keys.Home | Keys.Control | Keys.Shift] = new ShiftMoveToStart(); + editactions[Keys.End] = new End(); + editactions[Keys.End | Keys.Shift] = new ShiftEnd(); + editactions[Keys.End | Keys.Control] = new MoveToEnd(); + editactions[Keys.End | Keys.Control | Keys.Shift] = new ShiftMoveToEnd(); + editactions[Keys.PageUp] = new MovePageUp(); + editactions[Keys.PageUp | Keys.Shift] = new ShiftMovePageUp(); + editactions[Keys.PageDown] = new MovePageDown(); + editactions[Keys.PageDown | Keys.Shift] = new ShiftMovePageDown(); + + editactions[Keys.Return] = new Return(); + editactions[Keys.Tab] = new Tab(); + editactions[Keys.Tab | Keys.Shift] = new ShiftTab(); + editactions[Keys.Back] = new Backspace(); + editactions[Keys.Back | Keys.Shift] = new Backspace(); + + editactions[Keys.X | Keys.Control] = new Cut(); + editactions[Keys.C | Keys.Control] = new Copy(); + editactions[Keys.V | Keys.Control] = new Paste(); + + editactions[Keys.A | Keys.Control] = new SelectWholeDocument(); + editactions[Keys.Escape] = new ClearAllSelections(); + + editactions[Keys.Divide | Keys.Control] = new ToggleComment(); + editactions[Keys.OemQuestion | Keys.Control] = new ToggleComment(); + + editactions[Keys.Back | Keys.Alt] = new Actions.Undo(); + editactions[Keys.Z | Keys.Control] = new Actions.Undo(); + editactions[Keys.Y | Keys.Control] = new Redo(); + + editactions[Keys.Delete | Keys.Control] = new DeleteWord(); + editactions[Keys.Back | Keys.Control] = new WordBackspace(); + editactions[Keys.D | Keys.Control] = new DeleteLine(); + editactions[Keys.D | Keys.Shift | Keys.Control] = new DeleteToLineEnd(); + + editactions[Keys.B | Keys.Control] = new GotoMatchingBrace(); + } + + /// + /// Call this method before a long update operation this + /// 'locks' the text area so that no screen update occurs. + /// + public virtual void BeginUpdate() + { + ++updateLevel; + } + + /// + /// Call this method to 'unlock' the text area. After this call + /// screen update can occur. But no automatical refresh occurs you + /// have to commit the updates in the queue. + /// + public virtual void EndUpdate() + { + Debug.Assert(updateLevel > 0); + updateLevel = Math.Max(0, updateLevel - 1); + } + + public void LoadFile(string fileName) + { + LoadFile(fileName, true, true); + } + + /// + /// Loads a file given by fileName + /// + /// The name of the file to open + /// Automatically load the highlighting for the file + /// Automatically detect file encoding and set Encoding property to the detected encoding. + public void LoadFile(string fileName, bool autoLoadHighlighting, bool autodetectEncoding) + { + using (FileStream fs = new FileStream(fileName, FileMode.Open, FileAccess.Read)) { + LoadFile(fileName, fs, autoLoadHighlighting, autodetectEncoding); + } + } + + /// + /// Loads a file from the specified stream. + /// + /// The name of the file to open. Used to find the correct highlighting strategy + /// if autoLoadHighlighting is active, and sets the filename property to this value. + /// The stream to actually load the file content from. + /// Automatically load the highlighting for the file + /// Automatically detect file encoding and set Encoding property to the detected encoding. + public void LoadFile(string fileName, Stream stream, bool autoLoadHighlighting, bool autodetectEncoding) + { + if (stream == null) + throw new ArgumentNullException("stream"); + + BeginUpdate(); + document.TextContent = string.Empty; + document.UndoStack.ClearAll(); + document.BookmarkManager.Clear(); + if (autoLoadHighlighting) { + try { + document.HighlightingStrategy = HighlightingStrategyFactory.CreateHighlightingStrategyForFile(fileName); + } catch (HighlightingDefinitionInvalidException ex) { + MessageBox.Show(ex.ToString(), "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); + } + } + + if (autodetectEncoding) { + Encoding encoding = this.Encoding; + Document.TextContent = Util.FileReader.ReadFileContent(stream, ref encoding); + this.Encoding = encoding; + } else { + using (StreamReader reader = new StreamReader(fileName, this.Encoding)) { + Document.TextContent = reader.ReadToEnd(); + } + } + + this.FileName = fileName; + Document.UpdateQueue.Clear(); + EndUpdate(); + + OptionsChanged(); + Refresh(); + } + + /// + /// Gets if the document can be saved with the current encoding without losing data. + /// + public bool CanSaveWithCurrentEncoding() + { + if (encoding == null || Util.FileReader.IsUnicode(encoding)) + return true; + // not a unicode codepage + string text = document.TextContent; + return encoding.GetString(encoding.GetBytes(text)) == text; + } + + /// + /// Saves the text editor content into the file. + /// + public void SaveFile(string fileName) + { + using (FileStream fs = new FileStream(fileName, FileMode.Create, FileAccess.Write)) { + SaveFile(fs); + } + this.FileName = fileName; + } + + /// + /// Saves the text editor content into the specified stream. + /// Does not close the stream. + /// + public void SaveFile(Stream stream) + { + StreamWriter streamWriter = new StreamWriter(stream, this.Encoding ?? Encoding.UTF8); + + // save line per line to apply the LineTerminator to all lines + // (otherwise we might save files with mixed-up line endings) + foreach (LineSegment line in Document.LineSegmentCollection) { + streamWriter.Write(Document.GetText(line.Offset, line.Length)); + if (line.DelimiterLength > 0) { + char charAfterLine = Document.GetCharAt(line.Offset + line.Length); + if (charAfterLine != '\n' && charAfterLine != '\r') + throw new InvalidOperationException("The document cannot be saved because it is corrupted."); + // only save line terminator if the line has one + streamWriter.Write(document.TextEditorProperties.LineTerminator); + } + } + streamWriter.Flush(); + } + + public abstract void OptionsChanged(); + + // Localization ISSUES + + // used in insight window + public virtual string GetRangeDescription(int selectedItem, int itemCount) + { + StringBuilder sb=new StringBuilder(selectedItem.ToString()); + sb.Append(" from "); + sb.Append(itemCount.ToString()); + return sb.ToString(); + } + + /// + /// Overwritten refresh method that does nothing if the control is in + /// an update cycle. + /// + public override void Refresh() + { + if (IsInUpdate) { + return; + } + base.Refresh(); + } + + protected override void Dispose(bool disposing) + { + if (disposing) { + HighlightingManager.Manager.ReloadSyntaxHighlighting -= new EventHandler(OnReloadHighlighting); + document.HighlightingStrategy = null; + document.UndoStack.TextEditorControl = null; + } + base.Dispose(disposing); + } + + protected virtual void OnFileNameChanged(EventArgs e) + { + if (FileNameChanged != null) { + FileNameChanged(this, e); + } + } + + public event EventHandler FileNameChanged; + } +} diff --git a/ICSharpCode.TextEditor/Project/Src/Gui/TextEditorControlEx.cs b/ICSharpCode.TextEditor/Project/Src/Gui/TextEditorControlEx.cs new file mode 100644 index 0000000..f0e131f --- /dev/null +++ b/ICSharpCode.TextEditor/Project/Src/Gui/TextEditorControlEx.cs @@ -0,0 +1,541 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Drawing; +using System.Linq; +using System.Windows.Forms; +using ICSharpCode.TextEditor.Actions; +using ICSharpCode.TextEditor.Document; +using ICSharpCode.TextEditor.Properties; +using ICSharpCode.TextEditor.Src.Actions; +using ICSharpCode.TextEditor.Src.Document.FoldingStrategy; +using ICSharpCode.TextEditor.Src.Document.HighlightingStrategy.SyntaxModes; +using ICSharpCode.TextEditor.UserControls; + +// ReSharper disable once CheckNamespace +namespace ICSharpCode.TextEditor +{ + [ToolboxBitmap("ICSharpCode.TextEditor.Resources.TextEditorControl.bmp")] + [ToolboxItem(true)] + public class TextEditorControlEx : TextEditorControl + { + private bool _contextMenuEnabled; + private bool _contextMenuShowDefaultIcons; + private bool _contextMenuShowShortCutKeys; + private readonly FindAndReplaceForm _findForm = new FindAndReplaceForm(); + + public TextEditorControlEx() + { + editactions[Keys.Control | Keys.F] = new EditFindAction(_findForm, this); + editactions[Keys.Control | Keys.H] = new EditReplaceAction(_findForm, this); + editactions[Keys.F3] = new FindAgainAction(_findForm, this); + editactions[Keys.F3 | Keys.Shift] = new FindAgainReverseAction(_findForm, this); + editactions[Keys.Control | Keys.G] = new GoToLineNumberAction(); + + // Add additional Syntax highlighting providers + HighlightingManager.Manager.AddSyntaxModeFileProvider(new ResourceSyntaxModeProviderEx()); + + TextChanged += TextChangedEventHandler; + } + + protected override void OnLoad(EventArgs e) + { + if (ContextMenuEnabled) + { + AssignContextMenu(CreateNewContextMenu(ContextMenuShowDefaultIcons, ContextMenuShowShortCutKeys)); + } + + base.OnLoad(e); + } + + /// + /// Releases unmanaged and - optionally - managed resources. + /// + /// true to release both managed and unmanaged resources; false to release only unmanaged resources. + protected override void Dispose(bool disposing) + { + base.Dispose(disposing); + + TextChanged -= TextChangedEventHandler; + } + + private void TextChangedEventHandler(object sender, EventArgs e) + { + var editor = sender as TextEditorControlEx; + if (editor != null) + { + bool vScrollBarIsNeeded = editor.Document.TotalNumberOfLines > ActiveTextAreaControl.TextArea.TextView.VisibleLineCount; + if (ActiveTextAreaControl.VScrollBar.Visible && HideVScrollBarIfPossible && !vScrollBarIsNeeded) + { + ActiveTextAreaControl.ShowScrollBars(Orientation.Vertical, false); + } + } + } + + #region Extended properties + public string SelectedText + { + get + { + return ActiveTextAreaControl.SelectionManager.SelectedText; + } + } + + public string[] Lines + { + get + { + return base.Text.Split(new[] { "\r\n" }, StringSplitOptions.None); + } + } + #endregion + + #region Designer Properties + /// + /// The current document + /// + [Browsable(false)] + [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public new IDocument Document + { + get + { + return document; + } + set + { + if (value == null) + throw new ArgumentNullException("value"); + + if (document != null) + { + document.DocumentChanged -= OnDocumentChanged; + document.DocumentChanged -= OnDocumentChangedDoUpdateContextMenu; + } + + document = value; + document.UndoStack.TextEditorControl = this; + document.DocumentChanged += OnDocumentChanged; + document.DocumentChanged += OnDocumentChangedDoUpdateContextMenu; + } + } + + /// + /// The base font of the text area. No bold or italic fonts + /// can be used because bold/italic is reserved for highlighting + /// purposes. + /// + [Browsable(true)] + [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)] + [DefaultValue(typeof(Font), null)] + [Description("The base font of the text area. No bold or italic fonts can be used because bold/italic is reserved for highlighting purposes.")] + public override Font Font + { + get + { + return Document.TextEditorProperties.Font; + } + set + { + Document.TextEditorProperties.Font = value; + OptionsChanged(); + } + } + + [Category("Appearance")] + [DefaultValue(false)] + [Description("Hide the vertical ScrollBar if it's not needed. ")] + public bool HideVScrollBarIfPossible { get; set; } + + private string _foldingStrategy; + [Category("Appearance")] + [Description("Set the Folding Strategy. Supported : XML and CSharp.")] + public string FoldingStrategy + { + get + { + return _foldingStrategy; + } + set + { + SetFoldingStrategy(value); + OptionsChanged(); + } + } + + private string _syntaxHighlighting; + [Category("Appearance")] + [Description("Sets the Syntax Highlighting.")] + public string SyntaxHighlighting + { + get + { + return _syntaxHighlighting; + } + set + { + _syntaxHighlighting = value; + SetHighlighting(_syntaxHighlighting); + OptionsChanged(); + } + } + + [Category("Behavior")] + [DefaultValue(false)] + [Description("If true document is readonly.")] + [Browsable(true)] + public new bool IsReadOnly + { + get + { + return Document.ReadOnly; + } + set + { + Document.ReadOnly = value; + OptionsChanged(); + } + } + + [DefaultValue(false)] + [Category("Appearance")] + [Description("Show default Icons in ContextMenu")] + [Browsable(true)] + public bool ContextMenuShowDefaultIcons + { + get + { + return _contextMenuShowDefaultIcons & _contextMenuEnabled; + } + + set + { + _contextMenuShowDefaultIcons = _contextMenuEnabled & value; + } + } + + [DefaultValue(false)] + [Category("Appearance")] + [Description("Show shortcut keys in ContextMenu")] + [Browsable(true)] + public bool ContextMenuShowShortCutKeys + { + get + { + return _contextMenuShowShortCutKeys & _contextMenuEnabled; + } + + set + { + _contextMenuShowShortCutKeys = _contextMenuEnabled & value; + } + } + + [DefaultValue(false)] + [Category("Appearance")] + [Description("Enable a ContextMenu")] + [Browsable(true)] + public bool ContextMenuEnabled + { + get + { + return _contextMenuEnabled; + } + + set + { + _contextMenuEnabled = value; + } + } + #endregion + + /// + /// Sets the text and refreshes the control. + /// + /// The text. + /// if set to true [update foldings]. + public void SetTextAndRefresh(string text, bool updateFoldings = false) + { + ResetText(); + Text = text; + + if (updateFoldings && Document.TextEditorProperties.EnableFolding) + { + Document.FoldingManager.UpdateFoldings(null, null); + } + + Refresh(); + } + + /// + /// Sets the folding strategy. Currently only XML is supported. + /// + /// The foldingStrategy. + public void SetFoldingStrategy(string foldingStrategy) + { + if (foldingStrategy == null) + { + throw new ArgumentNullException("foldingStrategy"); + } + + if (!Document.TextEditorProperties.EnableFolding) + { + return; + } + + switch (foldingStrategy) + { + case "XML": + _foldingStrategy = foldingStrategy; + Document.FoldingManager.FoldingStrategy = new XmlFoldingStrategy(); + break; + + case "C#": + _foldingStrategy = foldingStrategy; + Document.FoldingManager.FoldingStrategy = new CSharpFoldingStrategy(); + break; + + case "JSON": + _foldingStrategy = foldingStrategy; + Document.FoldingManager.FoldingStrategy = new JSONFoldingStrategy(); + break; + + default: + Document.FoldingManager.FoldingStrategy = null; + _foldingStrategy = null; + break; + } + + Document.FoldingManager.UpdateFoldings(null, null); + } + + /// + /// Gets the folding errors. Currently only XML is supported. + /// + /// List of errors, else empty list + public List GetFoldingErrors() + { + if (_foldingStrategy == "XML") + { + var foldingStrategy = Document.FoldingManager.FoldingStrategy as IFoldingStrategyEx; + if (foldingStrategy != null) + { + return foldingStrategy.GetFoldingErrors(); + } + } + + return new List(); + } + + #region ContextMenu Commands implementations + private bool CanUndo() + { + return Document.UndoStack.CanUndo; + } + + private bool CanRedo() + { + return Document.UndoStack.CanRedo; + } + + private bool CanCopy() + { + return ActiveTextAreaControl.SelectionManager.HasSomethingSelected; + } + + private bool CanCut() + { + return ActiveTextAreaControl.SelectionManager.HasSomethingSelected; + } + + private bool CanDelete() + { + return ActiveTextAreaControl.SelectionManager.HasSomethingSelected; + } + + private bool CanPaste() + { + return ActiveTextAreaControl.TextArea.ClipboardHandler.EnablePaste; + } + + private bool CanSelectAll() + { + if (Document.TextContent == null) + return false; + + return !Document.TextContent.Trim().Equals(string.Empty); + } + + private bool CanFind() + { + if (Document.TextContent == null) + return false; + + return Document.TextContent.Trim().Any(); + } + + private void DoCut() + { + new Cut().Execute(ActiveTextAreaControl.TextArea); + ActiveTextAreaControl.Focus(); + } + + private void DoDelete() + { + new Delete().Execute(ActiveTextAreaControl.TextArea); + ActiveTextAreaControl.Focus(); + } + + private void DoCopy() + { + new Copy().Execute(ActiveTextAreaControl.TextArea); + ActiveTextAreaControl.Focus(); + } + + private void DoPaste() + { + new Paste().Execute(ActiveTextAreaControl.TextArea); + ActiveTextAreaControl.Focus(); + } + + private void DoSelectAll() + { + new SelectWholeDocument().Execute(ActiveTextAreaControl.TextArea); + ActiveTextAreaControl.Focus(); + } + + public void DoToggleFoldings() + { + new ToggleAllFoldings().Execute(ActiveTextAreaControl.TextArea); + } + + private void DoFind() + { + new EditFindAction(_findForm, this).Execute(ActiveTextAreaControl.TextArea); + } + #endregion + + #region ContextMenu Initialization + + + private ContextMenuStrip CreateNewContextMenu(bool showImages, bool showKeys) + { + var mnu = new ContextMenuStripEx(); + mnu.AddToolStripMenuItem("&Undo", + showImages ? Resources.sc_undo : null, + (sender, e) => Undo(), + showKeys ? Keys.Control | Keys.Z : Keys.None, + CanUndo + ); + + mnu.AddToolStripMenuItem("&Redo", + showImages ? Resources.sc_redo : null, + (sender, e) => Redo(), + showKeys ? Keys.Control | Keys.Y : Keys.None, + CanRedo + ); + + mnu.AddToolStripSeparator(); + + mnu.AddToolStripMenuItem("&Cut", + showImages ? Resources.cut : null, + (sender, e) => DoCut(), + showKeys ? Keys.Control | Keys.X : Keys.None, + CanCut + ); + + mnu.AddToolStripMenuItem("Cop&y", + showImages ? Resources.sc_copy : null, + (sender, e) => DoCopy(), + showKeys ? Keys.Control | Keys.C : Keys.None, + CanCopy + ); + + mnu.AddToolStripMenuItem("&Paste", + showImages ? Resources.sc_paste : null, + (sender, e) => DoPaste(), + showKeys ? Keys.Control | Keys.V : Keys.None, + CanPaste + ); + + mnu.AddToolStripSeparator(); + + mnu.AddToolStripMenuItem("&Delete", + showImages ? Resources.sc_cancel : null, + (sender, e) => DoDelete(), + showKeys ? Keys.Delete : Keys.None, + CanDelete + ); + + mnu.AddToolStripMenuItem("&Select All", + showImages ? Resources.sc_selectall : null, + (sender, e) => DoSelectAll(), + showKeys ? Keys.Control | Keys.A : Keys.None, + CanSelectAll + ); + + mnu.AddToolStripMenuItem("&Find", + showImages ? Resources.sc_searchdialog : null, + (sender, e) => DoFind(), + showKeys ? Keys.Control | Keys.F : Keys.None, + CanFind + ); + + return mnu; + } + + private void AssignContextMenu(ContextMenuStrip mnu) + { + if (ActiveTextAreaControl.ContextMenuStrip != null) + { + ActiveTextAreaControl.ContextMenuStrip.Dispose(); + ActiveTextAreaControl.ContextMenuStrip = null; + } + + ActiveTextAreaControl.ContextMenuStrip = mnu; + } + #endregion + + #region ContextMenu Methods + public void SelectText(int start, int length) + { + var textLength = Document.TextLength; + if (textLength < (start + length)) + { + length = (textLength - 1) - start; + } + ActiveTextAreaControl.Caret.Position = Document.OffsetToPosition(start + length); + ActiveTextAreaControl.SelectionManager.ClearSelection(); + ActiveTextAreaControl.SelectionManager.SetSelection(new DefaultSelection(Document, Document.OffsetToPosition(start), Document.OffsetToPosition(start + length))); + Refresh(); + } + + private void OnDocumentChangedDoUpdateContextMenu(object sender, DocumentEventArgs e) + { + bool isVisible = (Document.TotalNumberOfLines > ActiveTextAreaControl.TextArea.TextView.VisibleLineCount); + ActiveTextAreaControl.VScrollBar.Visible = isVisible; + } + #endregion + } + + public static class TextAreaControlExtensions + { + /// + /// Extension method to show a scrollbar. + /// + /// The text area control. + /// The orientation. + /// if set to true [is visible]. + public static void ShowScrollBars(this TextAreaControl textAreaControl, Orientation orientation, bool isVisible) + { + if (orientation == Orientation.Vertical) + { + textAreaControl.VScrollBar.Visible = isVisible; + } + else + { + textAreaControl.HScrollBar.Visible = isVisible; + } + } + } +} \ No newline at end of file diff --git a/ICSharpCode.TextEditor/Project/Src/Gui/TextView.cs b/ICSharpCode.TextEditor/Project/Src/Gui/TextView.cs new file mode 100644 index 0000000..f42c2d0 --- /dev/null +++ b/ICSharpCode.TextEditor/Project/Src/Gui/TextView.cs @@ -0,0 +1,1096 @@ +// +// +// +// +// $Revision$ +// + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Drawing; +using System.Windows.Forms; + +using ICSharpCode.TextEditor.Document; + +namespace ICSharpCode.TextEditor +{ + /// + /// This class paints the textarea. + /// + public class TextView : AbstractMargin, IDisposable + { + int fontHeight; + //Hashtable charWitdh = new Hashtable(); + //StringFormat measureStringFormat = (StringFormat)StringFormat.GenericTypographic.Clone(); + Highlight highlight; + int physicalColumn = 0; // used for calculating physical column during paint + + public void Dispose() + { + measureCache.Clear(); + //measureStringFormat.Dispose(); + } + + public Highlight Highlight { + get { + return highlight; + } + set { + highlight = value; + } + } + + public int FirstPhysicalLine { + get { + return textArea.VirtualTop.Y / fontHeight; + } + } + public int LineHeightRemainder { + get { + return textArea.VirtualTop.Y % fontHeight; + } + } + /// Gets the first visible logical line. + public int FirstVisibleLine { + get { + return textArea.Document.GetFirstLogicalLine(textArea.VirtualTop.Y / fontHeight); + } + set { + if (FirstVisibleLine != value) { + textArea.VirtualTop = new Point(textArea.VirtualTop.X, textArea.Document.GetVisibleLine(value) * fontHeight); + + } + } + } + + public int VisibleLineDrawingRemainder { + get { + return textArea.VirtualTop.Y % fontHeight; + } + } + + public int FontHeight { + get { + return fontHeight; + } + } + + public int VisibleLineCount { + get { + return 1 + DrawingPosition.Height / fontHeight; + } + } + + public int VisibleColumnCount { + get { + return (int)(DrawingPosition.Width / WideSpaceWidth) - 1; + } + } + + public TextView(TextArea textArea) : base(textArea) + { + base.Cursor = Cursors.IBeam; + OptionsChanged(); + } + + static int GetFontHeight(Font font) + { + int height1 = TextRenderer.MeasureText("_", font).Height; + int height2 = (int)Math.Ceiling(font.GetHeight()); + return Math.Max(height1, height2) + 1; + } + + int spaceWidth; + + /// + /// Gets the width of a space character. + /// This value can be quite small in some fonts - consider using WideSpaceWidth instead. + /// + public int SpaceWidth { + get { + return spaceWidth; + } + } + + int wideSpaceWidth; + + /// + /// Gets the width of a 'wide space' (=one quarter of a tab, if tab is set to 4 spaces). + /// On monospaced fonts, this is the same value as spaceWidth. + /// + public int WideSpaceWidth { + get { + return wideSpaceWidth; + } + } + + Font lastFont; + + public void OptionsChanged() + { + this.lastFont = TextEditorProperties.FontContainer.RegularFont; + this.fontHeight = GetFontHeight(lastFont); + // use minimum width - in some fonts, space has no width but kerning is used instead + // -> DivideByZeroException + this.spaceWidth = Math.Max(GetWidth(' ', lastFont), 1); + // tab should have the width of 4*'x' + this.wideSpaceWidth = Math.Max(spaceWidth, GetWidth('x', lastFont)); + } + + #region Paint functions + public override void Paint(Graphics g, Rectangle rect) + { + if (rect.Width <= 0 || rect.Height <= 0) { + return; + } + + // Just to ensure that fontHeight and char widths are always correct... + if (lastFont != TextEditorProperties.FontContainer.RegularFont) { + OptionsChanged(); + textArea.Invalidate(); + } + + int horizontalDelta = textArea.VirtualTop.X; + if (horizontalDelta > 0) { + g.SetClip(this.DrawingPosition); + } + + for (int y = 0; y < (DrawingPosition.Height + VisibleLineDrawingRemainder) / fontHeight + 1; ++y) { + Rectangle lineRectangle = new Rectangle(DrawingPosition.X - horizontalDelta, + DrawingPosition.Top + y * fontHeight - VisibleLineDrawingRemainder, + DrawingPosition.Width + horizontalDelta, + fontHeight); + + if (rect.IntersectsWith(lineRectangle)) { + int fvl = textArea.Document.GetVisibleLine(FirstVisibleLine); + int currentLine = textArea.Document.GetFirstLogicalLine(textArea.Document.GetVisibleLine(FirstVisibleLine) + y); + PaintDocumentLine(g, currentLine, lineRectangle); + } + } + + DrawMarkerDraw(g); + + if (horizontalDelta > 0) { + g.ResetClip(); + } + textArea.Caret.PaintCaret(g); + } + + void PaintDocumentLine(Graphics g, int lineNumber, Rectangle lineRectangle) + { + Debug.Assert(lineNumber >= 0); + Brush bgColorBrush = GetBgColorBrush(lineNumber); + Brush backgroundBrush = textArea.Enabled ? bgColorBrush : SystemBrushes.InactiveBorder; + + if (lineNumber >= textArea.Document.TotalNumberOfLines) { + g.FillRectangle(backgroundBrush, lineRectangle); + if (TextEditorProperties.ShowInvalidLines) { + DrawInvalidLineMarker(g, lineRectangle.Left, lineRectangle.Top); + } + if (TextEditorProperties.ShowVerticalRuler) { + DrawVerticalRuler(g, lineRectangle); + } +// bgColorBrush.Dispose(); + return; + } + + int physicalXPos = lineRectangle.X; + // there can't be a folding wich starts in an above line and ends here, because the line is a new one, + // there must be a return before this line. + int column = 0; + physicalColumn = 0; + if (TextEditorProperties.EnableFolding) { + while (true) { + List starts = textArea.Document.FoldingManager.GetFoldedFoldingsWithStartAfterColumn(lineNumber, column - 1); + if (starts == null || starts.Count <= 0) { + if (lineNumber < textArea.Document.TotalNumberOfLines) { + physicalXPos = PaintLinePart(g, lineNumber, column, textArea.Document.GetLineSegment(lineNumber).Length, lineRectangle, physicalXPos); + } + break; + } + // search the first starting folding + FoldMarker firstFolding = (FoldMarker)starts[0]; + foreach (FoldMarker fm in starts) { + if (fm.StartColumn < firstFolding.StartColumn) { + firstFolding = fm; + } + } + starts.Clear(); + + physicalXPos = PaintLinePart(g, lineNumber, column, firstFolding.StartColumn, lineRectangle, physicalXPos); + column = firstFolding.EndColumn; + lineNumber = firstFolding.EndLine; + if (lineNumber >= textArea.Document.TotalNumberOfLines) { + Debug.Assert(false, "Folding ends after document end"); + break; + } + + ColumnRange selectionRange2 = textArea.SelectionManager.GetSelectionAtLine(lineNumber); + bool drawSelected = ColumnRange.WholeColumn.Equals(selectionRange2) || firstFolding.StartColumn >= selectionRange2.StartColumn && firstFolding.EndColumn <= selectionRange2.EndColumn; + + physicalXPos = PaintFoldingText(g, lineNumber, physicalXPos, lineRectangle, firstFolding.FoldText, drawSelected); + } + } else { + physicalXPos = PaintLinePart(g, lineNumber, 0, textArea.Document.GetLineSegment(lineNumber).Length, lineRectangle, physicalXPos); + } + + if (lineNumber < textArea.Document.TotalNumberOfLines) { + // Paint things after end of line + ColumnRange selectionRange = textArea.SelectionManager.GetSelectionAtLine(lineNumber); + LineSegment currentLine = textArea.Document.GetLineSegment(lineNumber); + HighlightColor selectionColor = textArea.Document.HighlightingStrategy.GetColorFor("Selection"); + + bool selectionBeyondEOL = selectionRange.EndColumn > currentLine.Length || ColumnRange.WholeColumn.Equals(selectionRange); + + if (TextEditorProperties.ShowEOLMarker) { + HighlightColor eolMarkerColor = textArea.Document.HighlightingStrategy.GetColorFor("EOLMarkers"); + physicalXPos += DrawEOLMarker(g, eolMarkerColor.Color, selectionBeyondEOL ? bgColorBrush : backgroundBrush, physicalXPos, lineRectangle.Y); + } else { + if (selectionBeyondEOL) { + g.FillRectangle(BrushRegistry.GetBrush(selectionColor.BackgroundColor), new RectangleF(physicalXPos, lineRectangle.Y, WideSpaceWidth, lineRectangle.Height)); + physicalXPos += WideSpaceWidth; + } + } + + Brush fillBrush = selectionBeyondEOL && TextEditorProperties.AllowCaretBeyondEOL ? bgColorBrush : backgroundBrush; + g.FillRectangle(fillBrush, + new RectangleF(physicalXPos, lineRectangle.Y, lineRectangle.Width - physicalXPos + lineRectangle.X, lineRectangle.Height)); + } + if (TextEditorProperties.ShowVerticalRuler) { + DrawVerticalRuler(g, lineRectangle); + } +// bgColorBrush.Dispose(); + } + + bool DrawLineMarkerAtLine(int lineNumber) + { + return lineNumber == base.textArea.Caret.Line && textArea.MotherTextAreaControl.TextEditorProperties.LineViewerStyle == LineViewerStyle.FullRow; + } + + Brush GetBgColorBrush(int lineNumber) + { + if (DrawLineMarkerAtLine(lineNumber)) { + HighlightColor caretLine = textArea.Document.HighlightingStrategy.GetColorFor("CaretMarker"); + return BrushRegistry.GetBrush(caretLine.Color); + } + HighlightColor background = textArea.Document.HighlightingStrategy.GetColorFor("Default"); + Color bgColor = background.BackgroundColor; + return BrushRegistry.GetBrush(bgColor); + } + + const int additionalFoldTextSize = 1; + + int PaintFoldingText(Graphics g, int lineNumber, int physicalXPos, Rectangle lineRectangle, string text, bool drawSelected) + { + // TODO: get font and color from the highlighting file + HighlightColor selectionColor = textArea.Document.HighlightingStrategy.GetColorFor("Selection"); + Brush bgColorBrush = drawSelected ? BrushRegistry.GetBrush(selectionColor.BackgroundColor) : GetBgColorBrush(lineNumber); + Brush backgroundBrush = textArea.Enabled ? bgColorBrush : SystemBrushes.InactiveBorder; + + Font font = textArea.TextEditorProperties.FontContainer.RegularFont; + + int wordWidth = MeasureStringWidth(g, text, font) + additionalFoldTextSize; + Rectangle rect = new Rectangle(physicalXPos, lineRectangle.Y, wordWidth, lineRectangle.Height - 1); + + g.FillRectangle(backgroundBrush, rect); + + physicalColumn += text.Length; + DrawString(g, + text, + font, + drawSelected ? selectionColor.Color : Color.Gray, + rect.X + 1, rect.Y); + g.DrawRectangle(BrushRegistry.GetPen(drawSelected ? Color.DarkGray : Color.Gray), rect.X, rect.Y, rect.Width, rect.Height); + + return physicalXPos + wordWidth + 1; + } + + struct MarkerToDraw { + internal TextMarker marker; + internal RectangleF drawingRect; + + public MarkerToDraw(TextMarker marker, RectangleF drawingRect) + { + this.marker = marker; + this.drawingRect = drawingRect; + } + } + + List markersToDraw = new List(); + + void DrawMarker(Graphics g, TextMarker marker, RectangleF drawingRect) + { + // draw markers later so they can overdraw the following text + markersToDraw.Add(new MarkerToDraw(marker, drawingRect)); + } + + void DrawMarkerDraw(Graphics g) + { + foreach (MarkerToDraw m in markersToDraw) { + TextMarker marker = m.marker; + RectangleF drawingRect = m.drawingRect; + float drawYPos = drawingRect.Bottom - 1; + switch (marker.TextMarkerType) { + case TextMarkerType.Underlined: + g.DrawLine(BrushRegistry.GetPen(marker.Color), drawingRect.X, drawYPos, drawingRect.Right, drawYPos); + break; + case TextMarkerType.WaveLine: + int reminder = ((int)drawingRect.X) % 6; + for (float i = (int)drawingRect.X - reminder; i < drawingRect.Right; i += 6) { + g.DrawLine(BrushRegistry.GetPen(marker.Color), i, drawYPos + 3 - 4, i + 3, drawYPos + 1 - 4); + if (i + 3 < drawingRect.Right) { + g.DrawLine(BrushRegistry.GetPen(marker.Color), i + 3, drawYPos + 1 - 4, i + 6, drawYPos + 3 - 4); + } + } + break; + case TextMarkerType.SolidBlock: + g.FillRectangle(BrushRegistry.GetBrush(marker.Color), drawingRect); + break; + } + } + markersToDraw.Clear(); + } + + /// + /// Get the marker brush (for solid block markers) at a given position. + /// + /// The offset. + /// The length. + /// All markers that have been found. + /// The Brush or null when no marker was found. + Brush GetMarkerBrushAt(int offset, int length, ref Color foreColor, out IList markers) + { + markers = Document.MarkerStrategy.GetMarkers(offset, length); + foreach (TextMarker marker in markers) { + if (marker.TextMarkerType == TextMarkerType.SolidBlock) { + if (marker.OverrideForeColor) { + foreColor = marker.ForeColor; + } + return BrushRegistry.GetBrush(marker.Color); + } + } + return null; + } + + int PaintLinePart(Graphics g, int lineNumber, int startColumn, int endColumn, Rectangle lineRectangle, int physicalXPos) + { + bool drawLineMarker = DrawLineMarkerAtLine(lineNumber); + Brush backgroundBrush = textArea.Enabled ? GetBgColorBrush(lineNumber) : SystemBrushes.InactiveBorder; + + HighlightColor selectionColor = textArea.Document.HighlightingStrategy.GetColorFor("Selection"); + ColumnRange selectionRange = textArea.SelectionManager.GetSelectionAtLine(lineNumber); + HighlightColor tabMarkerColor = textArea.Document.HighlightingStrategy.GetColorFor("TabMarkers"); + HighlightColor spaceMarkerColor = textArea.Document.HighlightingStrategy.GetColorFor("SpaceMarkers"); + + LineSegment currentLine = textArea.Document.GetLineSegment(lineNumber); + + Brush selectionBackgroundBrush = BrushRegistry.GetBrush(selectionColor.BackgroundColor); + + if (currentLine.Words == null) { + return physicalXPos; + } + + int currentWordOffset = 0; // we cannot use currentWord.Offset because it is not set on space words + + TextWord currentWord; + TextWord nextCurrentWord = null; + FontContainer fontContainer = TextEditorProperties.FontContainer; + for (int wordIdx = 0; wordIdx < currentLine.Words.Count; wordIdx++) { + currentWord = currentLine.Words[wordIdx]; + if (currentWordOffset < startColumn) { + // TODO: maybe we need to split at startColumn when we support fold markers + // inside words + currentWordOffset += currentWord.Length; + continue; + } + repeatDrawCurrentWord: + //physicalXPos += 10; // leave room between drawn words - useful for debugging the drawing code + if (currentWordOffset >= endColumn || physicalXPos >= lineRectangle.Right) { + break; + } + int currentWordEndOffset = currentWordOffset + currentWord.Length - 1; + TextWordType currentWordType = currentWord.Type; + + IList markers; + Color wordForeColor; + if (currentWordType == TextWordType.Space) + wordForeColor = spaceMarkerColor.Color; + else if (currentWordType == TextWordType.Tab) + wordForeColor = tabMarkerColor.Color; + else + wordForeColor = currentWord.Color; + Brush wordBackBrush = GetMarkerBrushAt(currentLine.Offset + currentWordOffset, currentWord.Length, ref wordForeColor, out markers); + + // It is possible that we have to split the current word because a marker/the selection begins/ends inside it + if (currentWord.Length > 1) { + int splitPos = int.MaxValue; + if (highlight != null) { + // split both before and after highlight + if (highlight.OpenBrace.Y == lineNumber) { + if (highlight.OpenBrace.X >= currentWordOffset && highlight.OpenBrace.X <= currentWordEndOffset) { + splitPos = Math.Min(splitPos, highlight.OpenBrace.X - currentWordOffset); + } + } + if (highlight.CloseBrace.Y == lineNumber) { + if (highlight.CloseBrace.X >= currentWordOffset && highlight.CloseBrace.X <= currentWordEndOffset) { + splitPos = Math.Min(splitPos, highlight.CloseBrace.X - currentWordOffset); + } + } + if (splitPos == 0) { + splitPos = 1; // split after highlight + } + } + if (endColumn < currentWordEndOffset) { // split when endColumn is reached + splitPos = Math.Min(splitPos, endColumn - currentWordOffset); + } + if (selectionRange.StartColumn > currentWordOffset && selectionRange.StartColumn <= currentWordEndOffset) { + splitPos = Math.Min(splitPos, selectionRange.StartColumn - currentWordOffset); + } else if (selectionRange.EndColumn > currentWordOffset && selectionRange.EndColumn <= currentWordEndOffset) { + splitPos = Math.Min(splitPos, selectionRange.EndColumn - currentWordOffset); + } + foreach (TextMarker marker in markers) { + int markerColumn = marker.Offset - currentLine.Offset; + int markerEndColumn = marker.EndOffset - currentLine.Offset + 1; // make end offset exclusive + if (markerColumn > currentWordOffset && markerColumn <= currentWordEndOffset) { + splitPos = Math.Min(splitPos, markerColumn - currentWordOffset); + } else if (markerEndColumn > currentWordOffset && markerEndColumn <= currentWordEndOffset) { + splitPos = Math.Min(splitPos, markerEndColumn - currentWordOffset); + } + } + if (splitPos != int.MaxValue) { + if (nextCurrentWord != null) + throw new ApplicationException("split part invalid: first part cannot be splitted further"); + nextCurrentWord = TextWord.Split(ref currentWord, splitPos); + goto repeatDrawCurrentWord; // get markers for first word part + } + } + + // get colors from selection status: + if (ColumnRange.WholeColumn.Equals(selectionRange) || (selectionRange.StartColumn <= currentWordOffset + && selectionRange.EndColumn > currentWordEndOffset)) + { + // word is completely selected + wordBackBrush = selectionBackgroundBrush; + if (selectionColor.HasForeground) { + wordForeColor = selectionColor.Color; + } + } else if (drawLineMarker) { + wordBackBrush = backgroundBrush; + } + + if (wordBackBrush == null) { // use default background if no other background is set + if (currentWord.SyntaxColor != null && currentWord.SyntaxColor.HasBackground) + wordBackBrush = BrushRegistry.GetBrush(currentWord.SyntaxColor.BackgroundColor); + else + wordBackBrush = backgroundBrush; + } + + RectangleF wordRectangle; + + if (currentWord.Type == TextWordType.Space) { + ++physicalColumn; + + wordRectangle = new RectangleF(physicalXPos, lineRectangle.Y, SpaceWidth, lineRectangle.Height); + g.FillRectangle(wordBackBrush, wordRectangle); + + if (TextEditorProperties.ShowSpaces) { + DrawSpaceMarker(g, wordForeColor, physicalXPos, lineRectangle.Y); + } + physicalXPos += SpaceWidth; + } else if (currentWord.Type == TextWordType.Tab) { + + physicalColumn += TextEditorProperties.TabIndent; + physicalColumn = (physicalColumn / TextEditorProperties.TabIndent) * TextEditorProperties.TabIndent; + // go to next tabstop + int physicalTabEnd = ((physicalXPos + MinTabWidth - lineRectangle.X) + / WideSpaceWidth / TextEditorProperties.TabIndent) + * WideSpaceWidth * TextEditorProperties.TabIndent + lineRectangle.X; + physicalTabEnd += WideSpaceWidth * TextEditorProperties.TabIndent; + + wordRectangle = new RectangleF(physicalXPos, lineRectangle.Y, physicalTabEnd - physicalXPos, lineRectangle.Height); + g.FillRectangle(wordBackBrush, wordRectangle); + + if (TextEditorProperties.ShowTabs) { + DrawTabMarker(g, wordForeColor, physicalXPos, lineRectangle.Y); + } + physicalXPos = physicalTabEnd; + } else { + int wordWidth = DrawDocumentWord(g, + currentWord.Word, + new Point(physicalXPos, lineRectangle.Y), + currentWord.GetFont(fontContainer), + wordForeColor, + wordBackBrush); + wordRectangle = new RectangleF(physicalXPos, lineRectangle.Y, wordWidth, lineRectangle.Height); + physicalXPos += wordWidth; + } + foreach (TextMarker marker in markers) { + if (marker.TextMarkerType != TextMarkerType.SolidBlock) { + DrawMarker(g, marker, wordRectangle); + } + } + + // draw bracket highlight + if (highlight != null) { + if (highlight.OpenBrace.Y == lineNumber && highlight.OpenBrace.X == currentWordOffset || + highlight.CloseBrace.Y == lineNumber && highlight.CloseBrace.X == currentWordOffset) { + DrawBracketHighlight(g, new Rectangle((int)wordRectangle.X, lineRectangle.Y, (int)wordRectangle.Width - 1, lineRectangle.Height - 1)); + } + } + + currentWordOffset += currentWord.Length; + if (nextCurrentWord != null) { + currentWord = nextCurrentWord; + nextCurrentWord = null; + goto repeatDrawCurrentWord; + } + } + if (physicalXPos < lineRectangle.Right && endColumn >= currentLine.Length) { + // draw markers at line end + IList markers = Document.MarkerStrategy.GetMarkers(currentLine.Offset + currentLine.Length); + foreach (TextMarker marker in markers) { + if (marker.TextMarkerType != TextMarkerType.SolidBlock) { + DrawMarker(g, marker, new RectangleF(physicalXPos, lineRectangle.Y, WideSpaceWidth, lineRectangle.Height)); + } + } + } + return physicalXPos; + } + + int DrawDocumentWord(Graphics g, string word, Point position, Font font, Color foreColor, Brush backBrush) + { + if (word == null || word.Length == 0) { + return 0; + } + + if (word.Length > MaximumWordLength) { + int width = 0; + for (int i = 0; i < word.Length; i += MaximumWordLength) { + Point pos = position; + pos.X += width; + if (i + MaximumWordLength < word.Length) + width += DrawDocumentWord(g, word.Substring(i, MaximumWordLength), pos, font, foreColor, backBrush); + else + width += DrawDocumentWord(g, word.Substring(i, word.Length - i), pos, font, foreColor, backBrush); + } + return width; + } + + int wordWidth = MeasureStringWidth(g, word, font); + + //num = ++num % 3; + g.FillRectangle(backBrush, //num == 0 ? Brushes.LightBlue : num == 1 ? Brushes.LightGreen : Brushes.Yellow, + new RectangleF(position.X, position.Y, wordWidth + 1, FontHeight)); + + DrawString(g, + word, + font, + foreColor, + position.X, + position.Y); + return wordWidth; + } + + struct WordFontPair { + string word; + Font font; + public WordFontPair(string word, Font font) { + this.word = word; + this.font = font; + } + public override bool Equals(object obj) { + WordFontPair myWordFontPair = (WordFontPair)obj; + if (!word.Equals(myWordFontPair.word)) return false; + return font.Equals(myWordFontPair.font); + } + + public override int GetHashCode() { + return word.GetHashCode() ^ font.GetHashCode(); + } + } + + Dictionary measureCache = new Dictionary(); + + // split words after 1000 characters. Fixes GDI+ crash on very longs words, for example + // a 100 KB Base64-file without any line breaks. + const int MaximumWordLength = 1000; + const int MaximumCacheSize = 2000; + + int MeasureStringWidth(Graphics g, string word, Font font) + { + int width; + + if (word == null || word.Length == 0) + return 0; + if (word.Length > MaximumWordLength) { + width = 0; + for (int i = 0; i < word.Length; i += MaximumWordLength) { + if (i + MaximumWordLength < word.Length) + width += MeasureStringWidth(g, word.Substring(i, MaximumWordLength), font); + else + width += MeasureStringWidth(g, word.Substring(i, word.Length - i), font); + } + return width; + } + if (measureCache.TryGetValue(new WordFontPair(word, font), out width)) { + return width; + } + if (measureCache.Count > MaximumCacheSize) { + measureCache.Clear(); + } + + // This code here provides better results than MeasureString! + // Example line that is measured wrong: + // txt.GetPositionFromCharIndex(txt.SelectionStart) + // (Verdana 10, highlighting makes GetP... bold) -> note the space between 'x' and '(' + // this also fixes "jumping" characters when selecting in non-monospace fonts + // [...] + // Replaced GDI+ measurement with GDI measurement: faster and even more exact + width = TextRenderer.MeasureText(g, word, font, new Size(short.MaxValue, short.MaxValue), textFormatFlags).Width; + measureCache.Add(new WordFontPair(word, font), width); + return width; + } + + // Important: Some flags combinations work on WinXP, but not on Win2000. + // Make sure to test changes here on all operating systems. + const TextFormatFlags textFormatFlags = + TextFormatFlags.NoPadding | TextFormatFlags.NoPrefix | TextFormatFlags.PreserveGraphicsClipping; + #endregion + + #region Conversion Functions + Dictionary> fontBoundCharWidth = new Dictionary>(); + + public int GetWidth(char ch, Font font) + { + if (!fontBoundCharWidth.ContainsKey(font)) { + fontBoundCharWidth.Add(font, new Dictionary()); + } + if (!fontBoundCharWidth[font].ContainsKey(ch)) { + using (Graphics g = textArea.CreateGraphics()) { + return GetWidth(g, ch, font); + } + } + return fontBoundCharWidth[font][ch]; + } + + public int GetWidth(Graphics g, char ch, Font font) + { + if (!fontBoundCharWidth.ContainsKey(font)) { + fontBoundCharWidth.Add(font, new Dictionary()); + } + if (!fontBoundCharWidth[font].ContainsKey(ch)) { + //Console.WriteLine("Calculate character width: " + ch); + fontBoundCharWidth[font].Add(ch, MeasureStringWidth(g, ch.ToString(), font)); + } + return fontBoundCharWidth[font][ch]; + } + + public int GetVisualColumn(int logicalLine, int logicalColumn) + { + int column = 0; + using (Graphics g = textArea.CreateGraphics()) { + CountColumns(ref column, 0, logicalColumn, logicalLine, g); + } + return column; + } + + public int GetVisualColumnFast(LineSegment line, int logicalColumn) + { + int lineOffset = line.Offset; + int tabIndent = Document.TextEditorProperties.TabIndent; + int guessedColumn = 0; + for (int i = 0; i < logicalColumn; ++i) { + char ch; + if (i >= line.Length) { + ch = ' '; + } else { + ch = Document.GetCharAt(lineOffset + i); + } + switch (ch) { + case '\t': + guessedColumn += tabIndent; + guessedColumn = (guessedColumn / tabIndent) * tabIndent; + break; + default: + ++guessedColumn; + break; + } + } + return guessedColumn; + } + + /// + /// returns line/column for a visual point position + /// + public TextLocation GetLogicalPosition(Point mousePosition) + { + FoldMarker dummy; + return GetLogicalColumn(GetLogicalLine(mousePosition.Y), mousePosition.X, out dummy); + } + + /// + /// returns line/column for a visual point position + /// + public TextLocation GetLogicalPosition(int visualPosX, int visualPosY) + { + FoldMarker dummy; + return GetLogicalColumn(GetLogicalLine(visualPosY), visualPosX, out dummy); + } + + /// + /// returns line/column for a visual point position + /// + public FoldMarker GetFoldMarkerFromPosition(int visualPosX, int visualPosY) + { + FoldMarker foldMarker; + GetLogicalColumn(GetLogicalLine(visualPosY), visualPosX, out foldMarker); + return foldMarker; + } + + /// + /// returns logical line number for a visual point + /// + public int GetLogicalLine(int visualPosY) + { + int clickedVisualLine = Math.Max(0, (visualPosY + this.textArea.VirtualTop.Y) / fontHeight); + return Document.GetFirstLogicalLine(clickedVisualLine); + } + + internal TextLocation GetLogicalColumn(int lineNumber, int visualPosX, out FoldMarker inFoldMarker) + { + visualPosX += textArea.VirtualTop.X; + + inFoldMarker = null; + if (lineNumber >= Document.TotalNumberOfLines) { + return new TextLocation((int)(visualPosX / WideSpaceWidth), lineNumber); + } + if (visualPosX <= 0) { + return new TextLocation(0, lineNumber); + } + + int start = 0; // column + int posX = 0; // visual position + + int result; + using (Graphics g = textArea.CreateGraphics()) { + // call GetLogicalColumnInternal to skip over text, + // then skip over fold markers + // and repeat as necessary. + // The loop terminates once the correct logical column is reached in + // GetLogicalColumnInternal or inside a fold marker. + while (true) { + + LineSegment line = Document.GetLineSegment(lineNumber); + FoldMarker nextFolding = FindNextFoldedFoldingOnLineAfterColumn(lineNumber, start-1); + int end = nextFolding != null ? nextFolding.StartColumn : int.MaxValue; + result = GetLogicalColumnInternal(g, line, start, end, ref posX, visualPosX); + + // break when GetLogicalColumnInternal found the result column + if (result < end) + break; + + // reached fold marker + lineNumber = nextFolding.EndLine; + start = nextFolding.EndColumn; + int newPosX = posX + 1 + MeasureStringWidth(g, nextFolding.FoldText, TextEditorProperties.FontContainer.RegularFont); + if (newPosX >= visualPosX) { + inFoldMarker = nextFolding; + if (IsNearerToAThanB(visualPosX, posX, newPosX)) + return new TextLocation(nextFolding.StartColumn, nextFolding.StartLine); + else + return new TextLocation(nextFolding.EndColumn, nextFolding.EndLine); + } + posX = newPosX; + } + } + return new TextLocation(result, lineNumber); + } + + int GetLogicalColumnInternal(Graphics g, LineSegment line, int start, int end, ref int drawingPos, int targetVisualPosX) + { + if (start == end) + return end; + Debug.Assert(start < end); + Debug.Assert(drawingPos < targetVisualPosX); + + int tabIndent = Document.TextEditorProperties.TabIndent; + + /*float spaceWidth = SpaceWidth; + float drawingPos = 0; + LineSegment currentLine = Document.GetLineSegment(logicalLine); + List words = currentLine.Words; + if (words == null) return 0; + int wordCount = words.Count; + int wordOffset = 0; + FontContainer fontContainer = TextEditorProperties.FontContainer; + */ + FontContainer fontContainer = TextEditorProperties.FontContainer; + + List words = line.Words; + if (words == null) return 0; + int wordOffset = 0; + for (int i = 0; i < words.Count; i++) { + TextWord word = words[i]; + if (wordOffset >= end) { + return wordOffset; + } + if (wordOffset + word.Length >= start) { + int newDrawingPos; + switch (word.Type) { + case TextWordType.Space: + newDrawingPos = drawingPos + spaceWidth; + if (newDrawingPos >= targetVisualPosX) + return IsNearerToAThanB(targetVisualPosX, drawingPos, newDrawingPos) ? wordOffset : wordOffset+1; + break; + case TextWordType.Tab: + // go to next tab position + drawingPos = (int)((drawingPos + MinTabWidth) / tabIndent / WideSpaceWidth) * tabIndent * WideSpaceWidth; + newDrawingPos = drawingPos + tabIndent * WideSpaceWidth; + if (newDrawingPos >= targetVisualPosX) + return IsNearerToAThanB(targetVisualPosX, drawingPos, newDrawingPos) ? wordOffset : wordOffset+1; + break; + case TextWordType.Word: + default: + int wordStart = Math.Max(wordOffset, start); + int wordLength = Math.Min(wordOffset + word.Length, end) - wordStart; + string text = Document.GetText(line.Offset + wordStart, wordLength); + Font font = word.GetFont(fontContainer) ?? fontContainer.RegularFont; + newDrawingPos = drawingPos + MeasureStringWidth(g, text, font); + if (newDrawingPos >= targetVisualPosX) { + for (int j = 0; j < text.Length; j++) { + newDrawingPos = drawingPos + MeasureStringWidth(g, text[j].ToString(), font); + if (newDrawingPos >= targetVisualPosX) { + if (IsNearerToAThanB(targetVisualPosX, drawingPos, newDrawingPos)) + return wordStart + j; + else + return wordStart + j + 1; + } + drawingPos = newDrawingPos; + } + return wordStart + text.Length; + } + break; + } + drawingPos = newDrawingPos; + } + wordOffset += word.Length; + } + return wordOffset; + } + + static bool IsNearerToAThanB(int num, int a, int b) + { + return Math.Abs(a - num) < Math.Abs(b - num); + } + + FoldMarker FindNextFoldedFoldingOnLineAfterColumn(int lineNumber, int column) + { + List list = Document.FoldingManager.GetFoldedFoldingsWithStartAfterColumn(lineNumber, column); + if (list.Count != 0) + return list[0]; + else + return null; + } + + const int MinTabWidth = 4; + + float CountColumns(ref int column, int start, int end, int logicalLine, Graphics g) + { + if (start > end) throw new ArgumentException("start > end"); + if (start == end) return 0; + float spaceWidth = SpaceWidth; + float drawingPos = 0; + int tabIndent = Document.TextEditorProperties.TabIndent; + LineSegment currentLine = Document.GetLineSegment(logicalLine); + List words = currentLine.Words; + if (words == null) return 0; + int wordCount = words.Count; + int wordOffset = 0; + FontContainer fontContainer = TextEditorProperties.FontContainer; + for (int i = 0; i < wordCount; i++) { + TextWord word = words[i]; + if (wordOffset >= end) + break; + if (wordOffset + word.Length >= start) { + switch (word.Type) { + case TextWordType.Space: + drawingPos += spaceWidth; + break; + case TextWordType.Tab: + // go to next tab position + drawingPos = (int)((drawingPos + MinTabWidth) / tabIndent / WideSpaceWidth) * tabIndent * WideSpaceWidth; + drawingPos += tabIndent * WideSpaceWidth; + break; + case TextWordType.Word: + int wordStart = Math.Max(wordOffset, start); + int wordLength = Math.Min(wordOffset + word.Length, end) - wordStart; + string text = Document.GetText(currentLine.Offset + wordStart, wordLength); + drawingPos += MeasureStringWidth(g, text, word.GetFont(fontContainer) ?? fontContainer.RegularFont); + break; + } + } + wordOffset += word.Length; + } + for (int j = currentLine.Length; j < end; j++) { + drawingPos += WideSpaceWidth; + } + // add one pixel in column calculation to account for floating point calculation errors + column += (int)((drawingPos + 1) / WideSpaceWidth); + + /* OLD Code (does not work for fonts like Verdana) + for (int j = start; j < end; ++j) { + char ch; + if (j >= line.Length) { + ch = ' '; + } else { + ch = Document.GetCharAt(line.Offset + j); + } + + switch (ch) { + case '\t': + int oldColumn = column; + column += tabIndent; + column = (column / tabIndent) * tabIndent; + drawingPos += (column - oldColumn) * spaceWidth; + break; + default: + ++column; + TextWord word = line.GetWord(j); + if (word == null || word.Font == null) { + drawingPos += GetWidth(ch, TextEditorProperties.Font); + } else { + drawingPos += GetWidth(ch, word.Font); + } + break; + } + } + //*/ + return drawingPos; + } + + public int GetDrawingXPos(int logicalLine, int logicalColumn) + { + List foldings = Document.FoldingManager.GetTopLevelFoldedFoldings(); + int i; + FoldMarker f = null; + // search the last folding that's interresting + for (i = foldings.Count - 1; i >= 0; --i) { + f = foldings[i]; + if (f.StartLine < logicalLine || f.StartLine == logicalLine && f.StartColumn < logicalColumn) { + break; + } + FoldMarker f2 = foldings[i / 2]; + if (f2.StartLine > logicalLine || f2.StartLine == logicalLine && f2.StartColumn >= logicalColumn) { + i /= 2; + } + } + int lastFolding = 0; + int firstFolding = 0; + int column = 0; + int tabIndent = Document.TextEditorProperties.TabIndent; + float drawingPos; + Graphics g = textArea.CreateGraphics(); + // if no folding is interresting + if (f == null || !(f.StartLine < logicalLine || f.StartLine == logicalLine && f.StartColumn < logicalColumn)) { + drawingPos = CountColumns(ref column, 0, logicalColumn, logicalLine, g); + return (int)(drawingPos - textArea.VirtualTop.X); + } + + // if logicalLine/logicalColumn is in folding + if (f.EndLine > logicalLine || f.EndLine == logicalLine && f.EndColumn > logicalColumn) { + logicalColumn = f.StartColumn; + logicalLine = f.StartLine; + --i; + } + lastFolding = i; + + // search backwards until a new visible line is reched + for (; i >= 0; --i) { + f = (FoldMarker)foldings[i]; + if (f.EndLine < logicalLine) { // reached the begin of a new visible line + break; + } + } + firstFolding = i + 1; + + if (lastFolding < firstFolding) { + drawingPos = CountColumns(ref column, 0, logicalColumn, logicalLine, g); + return (int)(drawingPos - textArea.VirtualTop.X); + } + + int foldEnd = 0; + drawingPos = 0; + for (i = firstFolding; i <= lastFolding; ++i) { + f = foldings[i]; + drawingPos += CountColumns(ref column, foldEnd, f.StartColumn, f.StartLine, g); + foldEnd = f.EndColumn; + column += f.FoldText.Length; + drawingPos += additionalFoldTextSize; + drawingPos += MeasureStringWidth(g, f.FoldText, TextEditorProperties.FontContainer.RegularFont); + } + drawingPos += CountColumns(ref column, foldEnd, logicalColumn, logicalLine, g); + g.Dispose(); + return (int)(drawingPos - textArea.VirtualTop.X); + } + #endregion + + #region DrawHelper functions + void DrawBracketHighlight(Graphics g, Rectangle rect) + { + g.FillRectangle(BrushRegistry.GetBrush(Color.FromArgb(50, 0, 0, 255)), rect); + g.DrawRectangle(Pens.Blue, rect); + } + + void DrawString(Graphics g, string text, Font font, Color color, int x, int y) + { + TextRenderer.DrawText(g, text, font, new Point(x, y), color, textFormatFlags); + } + + void DrawInvalidLineMarker(Graphics g, int x, int y) + { + HighlightColor invalidLinesColor = textArea.Document.HighlightingStrategy.GetColorFor("InvalidLines"); + DrawString(g, "~", invalidLinesColor.GetFont(TextEditorProperties.FontContainer), invalidLinesColor.Color, x, y); + } + + void DrawSpaceMarker(Graphics g, Color color, int x, int y) + { + HighlightColor spaceMarkerColor = textArea.Document.HighlightingStrategy.GetColorFor("SpaceMarkers"); + DrawString(g, "\u00B7", spaceMarkerColor.GetFont(TextEditorProperties.FontContainer), color, x, y); + } + + void DrawTabMarker(Graphics g, Color color, int x, int y) + { + HighlightColor tabMarkerColor = textArea.Document.HighlightingStrategy.GetColorFor("TabMarkers"); + DrawString(g, "\u00BB", tabMarkerColor.GetFont(TextEditorProperties.FontContainer), color, x, y); + } + + int DrawEOLMarker(Graphics g, Color color, Brush backBrush, int x, int y) + { + HighlightColor eolMarkerColor = textArea.Document.HighlightingStrategy.GetColorFor("EOLMarkers"); + + int width = GetWidth('\u00B6', eolMarkerColor.GetFont(TextEditorProperties.FontContainer)); + g.FillRectangle(backBrush, + new RectangleF(x, y, width, fontHeight)); + + DrawString(g, "\u00B6", eolMarkerColor.GetFont(TextEditorProperties.FontContainer), color, x, y); + return width; + } + + void DrawVerticalRuler(Graphics g, Rectangle lineRectangle) + { + int xpos = WideSpaceWidth * TextEditorProperties.VerticalRulerRow - textArea.VirtualTop.X; + if (xpos <= 0) { + return; + } + HighlightColor vRulerColor = textArea.Document.HighlightingStrategy.GetColorFor("VRuler"); + + g.DrawLine(BrushRegistry.GetPen(vRulerColor.Color), + drawingPosition.Left + xpos, + lineRectangle.Top, + drawingPosition.Left + xpos, + lineRectangle.Bottom); + } + #endregion + } +} diff --git a/ICSharpCode.TextEditor/Project/Src/Gui/ToolTipRequestEventArgs.cs b/ICSharpCode.TextEditor/Project/Src/Gui/ToolTipRequestEventArgs.cs new file mode 100644 index 0000000..dc2207b --- /dev/null +++ b/ICSharpCode.TextEditor/Project/Src/Gui/ToolTipRequestEventArgs.cs @@ -0,0 +1,62 @@ +// +// +// +// +// $Revision$ +// + +using System; +using System.Drawing; + +namespace ICSharpCode.TextEditor +{ + public delegate void ToolTipRequestEventHandler(object sender, ToolTipRequestEventArgs e); + + public class ToolTipRequestEventArgs + { + Point mousePosition; + TextLocation logicalPosition; + bool inDocument; + + public Point MousePosition { + get { + return mousePosition; + } + } + + public TextLocation LogicalPosition { + get { + return logicalPosition; + } + } + + public bool InDocument { + get { + return inDocument; + } + } + + /// + /// Gets if some client handling the event has already shown a tool tip. + /// + public bool ToolTipShown { + get { + return toolTipText != null; + } + } + + internal string toolTipText; + + public void ShowToolTip(string text) + { + toolTipText = text; + } + + public ToolTipRequestEventArgs(Point mousePosition, TextLocation logicalPosition, bool inDocument) + { + this.mousePosition = mousePosition; + this.logicalPosition = logicalPosition; + this.inDocument = inDocument; + } + } +} diff --git a/ICSharpCode.TextEditor/Project/Src/Undo/IUndoableOperation.cs b/ICSharpCode.TextEditor/Project/Src/Undo/IUndoableOperation.cs new file mode 100644 index 0000000..25d6b55 --- /dev/null +++ b/ICSharpCode.TextEditor/Project/Src/Undo/IUndoableOperation.cs @@ -0,0 +1,26 @@ +// +// +// +// +// $Revision$ +// + +namespace ICSharpCode.TextEditor.Undo +{ + /// + /// This Interface describes a the basic Undo/Redo operation + /// all Undo Operations must implement this interface. + /// + public interface IUndoableOperation + { + /// + /// Undo the last operation + /// + void Undo(); + + /// + /// Redo the last operation + /// + void Redo(); + } +} diff --git a/ICSharpCode.TextEditor/Project/Src/Undo/UndoQueue.cs b/ICSharpCode.TextEditor/Project/Src/Undo/UndoQueue.cs new file mode 100644 index 0000000..d5638b5 --- /dev/null +++ b/ICSharpCode.TextEditor/Project/Src/Undo/UndoQueue.cs @@ -0,0 +1,53 @@ +// +// +// +// +// $Revision$ +// + +using System; +using System.Collections.Generic; +using System.Diagnostics; + +namespace ICSharpCode.TextEditor.Undo +{ + /// + /// This class stacks the last x operations from the undostack and makes + /// one undo/redo operation from it. + /// + internal sealed class UndoQueue : IUndoableOperation + { + List undolist = new List(); + + /// + /// + public UndoQueue(Stack stack, int numops) + { + if (stack == null) { + throw new ArgumentNullException("stack"); + } + + Debug.Assert(numops > 0 , "ICSharpCode.TextEditor.Undo.UndoQueue : numops should be > 0"); + if (numops > stack.Count) { + numops = stack.Count; + } + + for (int i = 0; i < numops; ++i) { + undolist.Add(stack.Pop()); + } + } + public void Undo() + { + for (int i = 0; i < undolist.Count; ++i) { + undolist[i].Undo(); + } + } + + public void Redo() + { + for (int i = undolist.Count - 1 ; i >= 0 ; --i) { + undolist[i].Redo(); + } + } + } +} diff --git a/ICSharpCode.TextEditor/Project/Src/Undo/UndoStack.cs b/ICSharpCode.TextEditor/Project/Src/Undo/UndoStack.cs new file mode 100644 index 0000000..ee720a3 --- /dev/null +++ b/ICSharpCode.TextEditor/Project/Src/Undo/UndoStack.cs @@ -0,0 +1,242 @@ +// +// +// +// +// $Revision$ +// + +using System; +using System.Collections.Generic; +using System.Drawing; + +namespace ICSharpCode.TextEditor.Undo +{ + /// + /// This class implements an undo stack + /// + public class UndoStack + { + Stack undostack = new Stack(); + Stack redostack = new Stack(); + + public TextEditorControlBase TextEditorControl = null; + + /// + /// + public event EventHandler ActionUndone; + /// + /// + public event EventHandler ActionRedone; + + public event OperationEventHandler OperationPushed; + + /// + /// Gets/Sets if changes to the document are protocolled by the undo stack. + /// Used internally to disable the undo stack temporarily while undoing an action. + /// + internal bool AcceptChanges = true; + + /// + /// Gets if there are actions on the undo stack. + /// + public bool CanUndo { + get { + return undostack.Count > 0; + } + } + + /// + /// Gets if there are actions on the redo stack. + /// + public bool CanRedo { + get { + return redostack.Count > 0; + } + } + + /// + /// Gets the number of actions on the undo stack. + /// + public int UndoItemCount { + get { + return undostack.Count; + } + } + + /// + /// Gets the number of actions on the redo stack. + /// + public int RedoItemCount { + get { + return redostack.Count; + } + } + + int undoGroupDepth; + int actionCountInUndoGroup; + + public void StartUndoGroup() + { + if (undoGroupDepth == 0) { + actionCountInUndoGroup = 0; + } + undoGroupDepth++; + //Util.LoggingService.Debug("Open undo group (new depth=" + undoGroupDepth + ")"); + } + + public void EndUndoGroup() + { + if (undoGroupDepth == 0) + throw new InvalidOperationException("There are no open undo groups"); + undoGroupDepth--; + //Util.LoggingService.Debug("Close undo group (new depth=" + undoGroupDepth + ")"); + if (undoGroupDepth == 0 && actionCountInUndoGroup > 1) { + UndoQueue op = new UndoQueue(undostack, actionCountInUndoGroup); + undostack.Push(op); + if (OperationPushed != null) { + OperationPushed(this, new OperationEventArgs(op)); + } + } + } + + public void AssertNoUndoGroupOpen() + { + if (undoGroupDepth != 0) { + undoGroupDepth = 0; + throw new InvalidOperationException("No undo group should be open at this point"); + } + } + + /// + /// Call this method to undo the last operation on the stack + /// + public void Undo() + { + AssertNoUndoGroupOpen(); + if (undostack.Count > 0) { + IUndoableOperation uedit = (IUndoableOperation)undostack.Pop(); + redostack.Push(uedit); + uedit.Undo(); + OnActionUndone(); + } + } + + /// + /// Call this method to redo the last undone operation + /// + public void Redo() + { + AssertNoUndoGroupOpen(); + if (redostack.Count > 0) { + IUndoableOperation uedit = (IUndoableOperation)redostack.Pop(); + undostack.Push(uedit); + uedit.Redo(); + OnActionRedone(); + } + } + + /// + /// Call this method to push an UndoableOperation on the undostack, the redostack + /// will be cleared, if you use this method. + /// + public void Push(IUndoableOperation operation) + { + if (operation == null) { + throw new ArgumentNullException("operation"); + } + + if (AcceptChanges) { + StartUndoGroup(); + undostack.Push(operation); + actionCountInUndoGroup++; + if (TextEditorControl != null) { + undostack.Push(new UndoableSetCaretPosition(this, TextEditorControl.ActiveTextAreaControl.Caret.Position)); + actionCountInUndoGroup++; + } + EndUndoGroup(); + ClearRedoStack(); + } + } + + /// + /// Call this method, if you want to clear the redo stack + /// + public void ClearRedoStack() + { + redostack.Clear(); + } + + /// + /// Clears both the undo and redo stack. + /// + public void ClearAll() + { + AssertNoUndoGroupOpen(); + undostack.Clear(); + redostack.Clear(); + actionCountInUndoGroup = 0; + } + + /// + /// + protected void OnActionUndone() + { + if (ActionUndone != null) { + ActionUndone(null, null); + } + } + + /// + /// + protected void OnActionRedone() + { + if (ActionRedone != null) { + ActionRedone(null, null); + } + } + + class UndoableSetCaretPosition : IUndoableOperation + { + UndoStack stack; + TextLocation pos; + TextLocation redoPos; + + public UndoableSetCaretPosition(UndoStack stack, TextLocation pos) + { + this.stack = stack; + this.pos = pos; + } + + public void Undo() + { + redoPos = stack.TextEditorControl.ActiveTextAreaControl.Caret.Position; + stack.TextEditorControl.ActiveTextAreaControl.Caret.Position = pos; + stack.TextEditorControl.ActiveTextAreaControl.SelectionManager.ClearSelection(); + } + + public void Redo() + { + stack.TextEditorControl.ActiveTextAreaControl.Caret.Position = redoPos; + stack.TextEditorControl.ActiveTextAreaControl.SelectionManager.ClearSelection(); + } + } + } + + public class OperationEventArgs : EventArgs + { + public OperationEventArgs(IUndoableOperation op) + { + this.op = op; + } + + IUndoableOperation op; + + public IUndoableOperation Operation { + get { + return op; + } + } + } + + public delegate void OperationEventHandler(object sender, OperationEventArgs e); +} diff --git a/ICSharpCode.TextEditor/Project/Src/Undo/UndoableDelete.cs b/ICSharpCode.TextEditor/Project/Src/Undo/UndoableDelete.cs new file mode 100644 index 0000000..87ca5c0 --- /dev/null +++ b/ICSharpCode.TextEditor/Project/Src/Undo/UndoableDelete.cs @@ -0,0 +1,72 @@ +// +// +// +// +// $Revision$ +// + +using System; +using System.Diagnostics; +using ICSharpCode.TextEditor.Document; + +namespace ICSharpCode.TextEditor.Undo +{ + /// + /// This class is for the undo of Document insert operations + /// + public class UndoableDelete : IUndoableOperation + { + IDocument document; +// int oldCaretPos; + int offset; + string text; + + /// + /// Creates a new instance of + /// + public UndoableDelete(IDocument document, int offset, string text) + { + if (document == null) { + throw new ArgumentNullException("document"); + } + if (offset < 0 || offset > document.TextLength) { + throw new ArgumentOutOfRangeException("offset"); + } + + Debug.Assert(text != null, "text can't be null"); +// oldCaretPos = document.Caret.Offset; + this.document = document; + this.offset = offset; + this.text = text; + } + + /// + /// Undo last operation + /// + public void Undo() + { + // we clear all selection direct, because the redraw + // is done per refresh at the end of the action +// textArea.SelectionManager.SelectionCollection.Clear(); + document.UndoStack.AcceptChanges = false; + document.Insert(offset, text); +// document.Caret.Offset = Math.Min(document.TextLength, Math.Max(0, oldCaretPos)); + document.UndoStack.AcceptChanges = true; + } + + /// + /// Redo last undone operation + /// + public void Redo() + { + // we clear all selection direct, because the redraw + // is done per refresh at the end of the action +// textArea.SelectionManager.SelectionCollection.Clear(); + + document.UndoStack.AcceptChanges = false; + document.Remove(offset, text.Length); +// document.Caret.Offset = Math.Min(document.TextLength, Math.Max(0, document.Caret.Offset)); + document.UndoStack.AcceptChanges = true; + } + } +} diff --git a/ICSharpCode.TextEditor/Project/Src/Undo/UndoableInsert.cs b/ICSharpCode.TextEditor/Project/Src/Undo/UndoableInsert.cs new file mode 100644 index 0000000..5e8c718 --- /dev/null +++ b/ICSharpCode.TextEditor/Project/Src/Undo/UndoableInsert.cs @@ -0,0 +1,73 @@ +// +// +// +// +// $Revision$ +// + +using System; +using System.Diagnostics; +using ICSharpCode.TextEditor.Document; + +namespace ICSharpCode.TextEditor.Undo +{ + /// + /// This class is for the undo of Document insert operations + /// + public class UndoableInsert : IUndoableOperation + { + IDocument document; +// int oldCaretPos; + int offset; + string text; + + /// + /// Creates a new instance of + /// + public UndoableInsert(IDocument document, int offset, string text) + { + if (document == null) { + throw new ArgumentNullException("document"); + } + if (offset < 0 || offset > document.TextLength) { + throw new ArgumentOutOfRangeException("offset"); + } + + Debug.Assert(text != null, "text can't be null"); +// oldCaretPos = document.Caret.Offset; + this.document = document; + this.offset = offset; + this.text = text; + } + + /// + /// Undo last operation + /// + public void Undo() + { + // we clear all selection direct, because the redraw + // is done per refresh at the end of the action +// document.SelectionCollection.Clear(); + + document.UndoStack.AcceptChanges = false; + document.Remove(offset, text.Length); +// document.Caret.Offset = Math.Min(document.TextLength, Math.Max(0, oldCaretPos)); + document.UndoStack.AcceptChanges = true; + } + + /// + /// Redo last undone operation + /// + public void Redo() + { + // we clear all selection direct, because the redraw + // is done per refresh at the end of the action +// document.SelectionCollection.Clear(); + + document.UndoStack.AcceptChanges = false; + document.Insert(offset, text); +// document.Caret.Offset = Math.Min(document.TextLength, Math.Max(0, document.Caret.Offset)); + document.UndoStack.AcceptChanges = true; + } + } +} diff --git a/ICSharpCode.TextEditor/Project/Src/Undo/UndoableReplace.cs b/ICSharpCode.TextEditor/Project/Src/Undo/UndoableReplace.cs new file mode 100644 index 0000000..201013e --- /dev/null +++ b/ICSharpCode.TextEditor/Project/Src/Undo/UndoableReplace.cs @@ -0,0 +1,75 @@ +// +// +// +// +// $Revision$ +// + +using System; +using System.Diagnostics; +using ICSharpCode.TextEditor.Document; + +namespace ICSharpCode.TextEditor.Undo +{ + /// + /// This class is for the undo of Document insert operations + /// + public class UndoableReplace : IUndoableOperation + { + IDocument document; +// int oldCaretPos; + int offset; + string text; + string origText; + + /// + /// Creates a new instance of + /// + public UndoableReplace(IDocument document, int offset, string origText, string text) + { + if (document == null) { + throw new ArgumentNullException("document"); + } + if (offset < 0 || offset > document.TextLength) { + throw new ArgumentOutOfRangeException("offset"); + } + + Debug.Assert(text != null, "text can't be null"); +// oldCaretPos = document.Caret.Offset; + this.document = document; + this.offset = offset; + this.text = text; + this.origText = origText; + } + + /// + /// Undo last operation + /// + public void Undo() + { + // we clear all selection direct, because the redraw + // is done per refresh at the end of the action +// document.SelectionCollection.Clear(); + + document.UndoStack.AcceptChanges = false; + document.Replace(offset, text.Length, origText); +// document.Caret.Offset = Math.Min(document.TextLength, Math.Max(0, oldCaretPos)); + document.UndoStack.AcceptChanges = true; + } + + /// + /// Redo last undone operation + /// + public void Redo() + { + // we clear all selection direct, because the redraw + // is done per refresh at the end of the action +// document.SelectionCollection.Clear(); + + document.UndoStack.AcceptChanges = false; + document.Replace(offset, origText.Length, text); +// document.Caret.Offset = Math.Min(document.TextLength, Math.Max(0, document.Caret.Offset)); + document.UndoStack.AcceptChanges = true; + } + } +} diff --git a/ICSharpCode.TextEditor/Project/Src/Util/AugmentableRedBlackTree.cs b/ICSharpCode.TextEditor/Project/Src/Util/AugmentableRedBlackTree.cs new file mode 100644 index 0000000..825f340 --- /dev/null +++ b/ICSharpCode.TextEditor/Project/Src/Util/AugmentableRedBlackTree.cs @@ -0,0 +1,590 @@ +// +// +// +// +// $Revision$ +// + +using System; +using System.Text; +using System.Diagnostics; +using System.Collections.Generic; + +namespace ICSharpCode.TextEditor.Util +{ + internal sealed class RedBlackTreeNode + { + internal RedBlackTreeNode left, right, parent; + internal T val; + internal bool color; + + internal RedBlackTreeNode(T val) + { + this.val = val; + } + + internal RedBlackTreeNode LeftMost { + get { + RedBlackTreeNode node = this; + while (node.left != null) + node = node.left; + return node; + } + } + + internal RedBlackTreeNode RightMost { + get { + RedBlackTreeNode node = this; + while (node.right != null) + node = node.right; + return node; + } + } + } + + internal interface IRedBlackTreeHost : IComparer + { + bool Equals(T a, T b); + + void UpdateAfterChildrenChange(RedBlackTreeNode node); + void UpdateAfterRotateLeft(RedBlackTreeNode node); + void UpdateAfterRotateRight(RedBlackTreeNode node); + } + + /// + /// Description of RedBlackTree. + /// + internal sealed class AugmentableRedBlackTree : ICollection where Host : IRedBlackTreeHost + { + readonly Host host; + int count; + internal RedBlackTreeNode root; + + public AugmentableRedBlackTree(Host host) + { + if (host == null) throw new ArgumentNullException("host"); + this.host = host; + } + + public int Count { + get { return count; } + } + + public void Clear() + { + root = null; + count = 0; + } + + #region Debugging code + #if DEBUG + /// + /// Check tree for consistency and being balanced. + /// + [Conditional("DATACONSISTENCYTEST")] + void CheckProperties() + { + int blackCount = -1; + CheckNodeProperties(root, null, RED, 0, ref blackCount); + + int nodeCount = 0; + foreach (T val in this) { + nodeCount++; + } + Debug.Assert(count == nodeCount); + } + + /* + 1. A node is either red or black. + 2. The root is black. + 3. All leaves are black. (The leaves are the NIL children.) + 4. Both children of every red node are black. (So every red node must have a black parent.) + 5. Every simple path from a node to a descendant leaf contains the same number of black nodes. (Not counting the leaf node.) + */ + void CheckNodeProperties(RedBlackTreeNode node, RedBlackTreeNode parentNode, bool parentColor, int blackCount, ref int expectedBlackCount) + { + if (node == null) return; + + Debug.Assert(node.parent == parentNode); + + if (parentColor == RED) { + Debug.Assert(node.color == BLACK); + } + if (node.color == BLACK) { + blackCount++; + } + if (node.left == null && node.right == null) { + // node is a leaf node: + if (expectedBlackCount == -1) + expectedBlackCount = blackCount; + else + Debug.Assert(expectedBlackCount == blackCount); + } + CheckNodeProperties(node.left, node, node.color, blackCount, ref expectedBlackCount); + CheckNodeProperties(node.right, node, node.color, blackCount, ref expectedBlackCount); + } + + public string GetTreeAsString() + { + StringBuilder b = new StringBuilder(); + AppendTreeToString(root, b, 0); + return b.ToString(); + } + + static void AppendTreeToString(RedBlackTreeNode node, StringBuilder b, int indent) + { + if (node.color == RED) + b.Append("RED "); + else + b.Append("BLACK "); + b.AppendLine(node.val.ToString()); + indent += 2; + if (node.left != null) { + b.Append(' ', indent); + b.Append("L: "); + AppendTreeToString(node.left, b, indent); + } + if (node.right != null) { + b.Append(' ', indent); + b.Append("R: "); + AppendTreeToString(node.right, b, indent); + } + } + #endif + #endregion + + #region Add + public void Add(T item) + { + AddInternal(new RedBlackTreeNode(item)); + #if DEBUG + CheckProperties(); + #endif + } + + void AddInternal(RedBlackTreeNode newNode) + { + Debug.Assert(newNode.color == BLACK); + if (root == null) { + count = 1; + root = newNode; + return; + } + // Insert into the tree + RedBlackTreeNode parentNode = root; + while (true) { + if (host.Compare(newNode.val, parentNode.val) <= 0) { + if (parentNode.left == null) { + InsertAsLeft(parentNode, newNode); + return; + } + parentNode = parentNode.left; + } else { + if (parentNode.right == null) { + InsertAsRight(parentNode, newNode); + return; + } + parentNode = parentNode.right; + } + } + } + + internal void InsertAsLeft(RedBlackTreeNode parentNode, RedBlackTreeNode newNode) + { + Debug.Assert(parentNode.left == null); + parentNode.left = newNode; + newNode.parent = parentNode; + newNode.color = RED; + host.UpdateAfterChildrenChange(parentNode); + FixTreeOnInsert(newNode); + count++; + } + + internal void InsertAsRight(RedBlackTreeNode parentNode, RedBlackTreeNode newNode) + { + Debug.Assert(parentNode.right == null); + parentNode.right = newNode; + newNode.parent = parentNode; + newNode.color = RED; + host.UpdateAfterChildrenChange(parentNode); + FixTreeOnInsert(newNode); + count++; + } + + void FixTreeOnInsert(RedBlackTreeNode node) + { + Debug.Assert(node != null); + Debug.Assert(node.color == RED); + Debug.Assert(node.left == null || node.left.color == BLACK); + Debug.Assert(node.right == null || node.right.color == BLACK); + + RedBlackTreeNode parentNode = node.parent; + if (parentNode == null) { + // we inserted in the root -> the node must be black + // since this is a root node, making the node black increments the number of black nodes + // on all paths by one, so it is still the same for all paths. + node.color = BLACK; + return; + } + if (parentNode.color == BLACK) { + // if the parent node where we inserted was black, our red node is placed correctly. + // since we inserted a red node, the number of black nodes on each path is unchanged + // -> the tree is still balanced + return; + } + // parentNode is red, so there is a conflict here! + + // because the root is black, parentNode is not the root -> there is a grandparent node + RedBlackTreeNode grandparentNode = parentNode.parent; + RedBlackTreeNode uncleNode = Sibling(parentNode); + if (uncleNode != null && uncleNode.color == RED) { + parentNode.color = BLACK; + uncleNode.color = BLACK; + grandparentNode.color = RED; + FixTreeOnInsert(grandparentNode); + return; + } + // now we know: parent is red but uncle is black + // First rotation: + if (node == parentNode.right && parentNode == grandparentNode.left) { + RotateLeft(parentNode); + node = node.left; + } else if (node == parentNode.left && parentNode == grandparentNode.right) { + RotateRight(parentNode); + node = node.right; + } + // because node might have changed, reassign variables: + parentNode = node.parent; + grandparentNode = parentNode.parent; + + // Now recolor a bit: + parentNode.color = BLACK; + grandparentNode.color = RED; + // Second rotation: + if (node == parentNode.left && parentNode == grandparentNode.left) { + RotateRight(grandparentNode); + } else { + // because of the first rotation, this is guaranteed: + Debug.Assert(node == parentNode.right && parentNode == grandparentNode.right); + RotateLeft(grandparentNode); + } + } + + void ReplaceNode(RedBlackTreeNode replacedNode, RedBlackTreeNode newNode) + { + if (replacedNode.parent == null) { + Debug.Assert(replacedNode == root); + root = newNode; + } else { + if (replacedNode.parent.left == replacedNode) + replacedNode.parent.left = newNode; + else + replacedNode.parent.right = newNode; + } + if (newNode != null) { + newNode.parent = replacedNode.parent; + } + replacedNode.parent = null; + } + + void RotateLeft(RedBlackTreeNode p) + { + // let q be p's right child + RedBlackTreeNode q = p.right; + Debug.Assert(q != null); + Debug.Assert(q.parent == p); + // set q to be the new root + ReplaceNode(p, q); + + // set p's right child to be q's left child + p.right = q.left; + if (p.right != null) p.right.parent = p; + // set q's left child to be p + q.left = p; + p.parent = q; + host.UpdateAfterRotateLeft(p); + } + + void RotateRight(RedBlackTreeNode p) + { + // let q be p's left child + RedBlackTreeNode q = p.left; + Debug.Assert(q != null); + Debug.Assert(q.parent == p); + // set q to be the new root + ReplaceNode(p, q); + + // set p's left child to be q's right child + p.left = q.right; + if (p.left != null) p.left.parent = p; + // set q's right child to be p + q.right = p; + p.parent = q; + host.UpdateAfterRotateRight(p); + } + + RedBlackTreeNode Sibling(RedBlackTreeNode node) + { + if (node == node.parent.left) + return node.parent.right; + else + return node.parent.left; + } + #endregion + + #region Remove + public void RemoveAt(RedBlackTreeIterator iterator) + { + RedBlackTreeNode node = iterator.node; + if (node == null) + throw new ArgumentException("Invalid iterator"); + while (node.parent != null) + node = node.parent; + if (node != root) + throw new ArgumentException("Iterator does not belong to this tree"); + RemoveNode(iterator.node); + #if DEBUG + CheckProperties(); + #endif + } + + internal void RemoveNode(RedBlackTreeNode removedNode) + { + if (removedNode.left != null && removedNode.right != null) { + // replace removedNode with it's in-order successor + + RedBlackTreeNode leftMost = removedNode.right.LeftMost; + RemoveNode(leftMost); // remove leftMost from its current location + + // and overwrite the removedNode with it + ReplaceNode(removedNode, leftMost); + leftMost.left = removedNode.left; + if (leftMost.left != null) leftMost.left.parent = leftMost; + leftMost.right = removedNode.right; + if (leftMost.right != null) leftMost.right.parent = leftMost; + leftMost.color = removedNode.color; + + host.UpdateAfterChildrenChange(leftMost); + if (leftMost.parent != null) host.UpdateAfterChildrenChange(leftMost.parent); + return; + } + + count--; + + // now either removedNode.left or removedNode.right is null + // get the remaining child + RedBlackTreeNode parentNode = removedNode.parent; + RedBlackTreeNode childNode = removedNode.left ?? removedNode.right; + ReplaceNode(removedNode, childNode); + if (parentNode != null) host.UpdateAfterChildrenChange(parentNode); + if (removedNode.color == BLACK) { + if (childNode != null && childNode.color == RED) { + childNode.color = BLACK; + } else { + FixTreeOnDelete(childNode, parentNode); + } + } + } + + static RedBlackTreeNode Sibling(RedBlackTreeNode node, RedBlackTreeNode parentNode) + { + Debug.Assert(node == null || node.parent == parentNode); + if (node == parentNode.left) + return parentNode.right; + else + return parentNode.left; + } + + const bool RED = true; + const bool BLACK = false; + + static bool GetColor(RedBlackTreeNode node) + { + return node != null ? node.color : BLACK; + } + + void FixTreeOnDelete(RedBlackTreeNode node, RedBlackTreeNode parentNode) + { + Debug.Assert(node == null || node.parent == parentNode); + if (parentNode == null) + return; + + // warning: node may be null + RedBlackTreeNode sibling = Sibling(node, parentNode); + if (sibling.color == RED) { + parentNode.color = RED; + sibling.color = BLACK; + if (node == parentNode.left) { + RotateLeft(parentNode); + } else { + RotateRight(parentNode); + } + + sibling = Sibling(node, parentNode); // update value of sibling after rotation + } + + if (parentNode.color == BLACK + && sibling.color == BLACK + && GetColor(sibling.left) == BLACK + && GetColor(sibling.right) == BLACK) + { + sibling.color = RED; + FixTreeOnDelete(parentNode, parentNode.parent); + return; + } + + if (parentNode.color == RED + && sibling.color == BLACK + && GetColor(sibling.left) == BLACK + && GetColor(sibling.right) == BLACK) + { + sibling.color = RED; + parentNode.color = BLACK; + return; + } + + if (node == parentNode.left && + sibling.color == BLACK && + GetColor(sibling.left) == RED && + GetColor(sibling.right) == BLACK) + { + sibling.color = RED; + sibling.left.color = BLACK; + RotateRight(sibling); + } + else if (node == parentNode.right && + sibling.color == BLACK && + GetColor(sibling.right) == RED && + GetColor(sibling.left) == BLACK) + { + sibling.color = RED; + sibling.right.color = BLACK; + RotateLeft(sibling); + } + sibling = Sibling(node, parentNode); // update value of sibling after rotation + + sibling.color = parentNode.color; + parentNode.color = BLACK; + if (node == parentNode.left) { + if (sibling.right != null) { + Debug.Assert(sibling.right.color == RED); + sibling.right.color = BLACK; + } + RotateLeft(parentNode); + } else { + if (sibling.left != null) { + Debug.Assert(sibling.left.color == RED); + sibling.left.color = BLACK; + } + RotateRight(parentNode); + } + } + #endregion + + #region Find/LowerBound/UpperBound/GetEnumerator + /// + /// Returns the iterator pointing to the specified item, or an iterator in End state if the item is not found. + /// + public RedBlackTreeIterator Find(T item) + { + RedBlackTreeIterator it = LowerBound(item); + while (it.IsValid && host.Compare(it.Current, item) == 0) { + if (host.Equals(it.Current, item)) + return it; + it.MoveNext(); + } + return default(RedBlackTreeIterator); + } + + /// + /// Returns the iterator pointing to the first item greater or equal to . + /// + public RedBlackTreeIterator LowerBound(T item) + { + RedBlackTreeNode node = root; + RedBlackTreeNode resultNode = null; + while (node != null) { + if (host.Compare(node.val, item) < 0) { + node = node.right; + } else { + resultNode = node; + node = node.left; + } + } + return new RedBlackTreeIterator(resultNode); + } + + /// + /// Returns the iterator pointing to the first item greater than . + /// + public RedBlackTreeIterator UpperBound(T item) + { + RedBlackTreeIterator it = LowerBound(item); + while (it.IsValid && host.Compare(it.Current, item) == 0) { + it.MoveNext(); + } + return it; + } + + /// + /// Gets a tree iterator that starts on the first node. + /// + public RedBlackTreeIterator Begin() + { + if (root == null) return default(RedBlackTreeIterator); + return new RedBlackTreeIterator(root.LeftMost); + } + + /// + /// Gets a tree iterator that starts one node before the first node. + /// + public RedBlackTreeIterator GetEnumerator() + { + if (root == null) return default(RedBlackTreeIterator); + RedBlackTreeNode dummyNode = new RedBlackTreeNode(default(T)); + dummyNode.right = root; + return new RedBlackTreeIterator(dummyNode); + } + #endregion + + #region ICollection members + public bool Contains(T item) + { + return Find(item).IsValid; + } + + public bool Remove(T item) + { + RedBlackTreeIterator it = Find(item); + if (!it.IsValid) { + return false; + } else { + RemoveAt(it); + return true; + } + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + bool ICollection.IsReadOnly { + get { return false; } + } + + public void CopyTo(T[] array, int arrayIndex) + { + if (array == null) throw new ArgumentNullException("array"); + foreach (T val in this) { + array[arrayIndex++] = val; + } + } + #endregion + } +} diff --git a/ICSharpCode.TextEditor/Project/Src/Util/CheckedList.cs b/ICSharpCode.TextEditor/Project/Src/Util/CheckedList.cs new file mode 100644 index 0000000..1b24999 --- /dev/null +++ b/ICSharpCode.TextEditor/Project/Src/Util/CheckedList.cs @@ -0,0 +1,148 @@ +// +// +// +// +// $Revision$ +// + +using System; +using System.Collections.Generic; +using System.Threading; + +namespace ICSharpCode.TextEditor.Util +{ + /// + /// A IList{T} that checks that it is only accessed on the thread that created it, and that + /// it is not modified while an enumerator is running. + /// + sealed class CheckedList : IList + { + readonly int threadID; + readonly IList baseList; + int enumeratorCount; + + public CheckedList() : this(new List()) {} + + public CheckedList(IList baseList) + { + if (baseList == null) + throw new ArgumentNullException("baseList"); + this.baseList = baseList; + this.threadID = Thread.CurrentThread.ManagedThreadId; + } + + void CheckRead() + { + if (Thread.CurrentThread.ManagedThreadId != threadID) + throw new InvalidOperationException("CheckList cannot be accessed from this thread!"); + } + + void CheckWrite() + { + if (Thread.CurrentThread.ManagedThreadId != threadID) + throw new InvalidOperationException("CheckList cannot be accessed from this thread!"); + if (enumeratorCount != 0) + throw new InvalidOperationException("CheckList cannot be written to while enumerators are active!"); + } + + public T this[int index] { + get { + CheckRead(); + return baseList[index]; + } + set { + CheckWrite(); + baseList[index] = value; + } + } + + public int Count { + get { + CheckRead(); + return baseList.Count; + } + } + + public bool IsReadOnly { + get { + CheckRead(); + return baseList.IsReadOnly; + } + } + + public int IndexOf(T item) + { + CheckRead(); + return baseList.IndexOf(item); + } + + public void Insert(int index, T item) + { + CheckWrite(); + baseList.Insert(index, item); + } + + public void RemoveAt(int index) + { + CheckWrite(); + baseList.RemoveAt(index); + } + + public void Add(T item) + { + CheckWrite(); + baseList.Add(item); + } + + public void Clear() + { + CheckWrite(); + baseList.Clear(); + } + + public bool Contains(T item) + { + CheckRead(); + return baseList.Contains(item); + } + + public void CopyTo(T[] array, int arrayIndex) + { + CheckRead(); + baseList.CopyTo(array, arrayIndex); + } + + public bool Remove(T item) + { + CheckWrite(); + return baseList.Remove(item); + } + + public IEnumerator GetEnumerator() + { + CheckRead(); + return Enumerate(); + } + + System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() + { + CheckRead(); + return Enumerate(); + } + + IEnumerator Enumerate() + { + CheckRead(); + try { + enumeratorCount++; + foreach (T val in baseList) { + yield return val; + CheckRead(); + } + } finally { + enumeratorCount--; + CheckRead(); + } + } + } +} diff --git a/ICSharpCode.TextEditor/Project/Src/Util/FileReader.cs b/ICSharpCode.TextEditor/Project/Src/Util/FileReader.cs new file mode 100644 index 0000000..811cfde --- /dev/null +++ b/ICSharpCode.TextEditor/Project/Src/Util/FileReader.cs @@ -0,0 +1,152 @@ +// +// +// +// +// $Revision$ +// + +using System; +using System.IO; +using System.Text; + +namespace ICSharpCode.TextEditor.Util +{ + /// + /// Class that can open text files with auto-detection of the encoding. + /// + public static class FileReader + { + public static bool IsUnicode(Encoding encoding) + { + int codepage = encoding.CodePage; + // return true if codepage is any UTF codepage + return codepage == 65001 || codepage == 65000 || codepage == 1200 || codepage == 1201; + } + + public static string ReadFileContent(Stream fs, ref Encoding encoding) + { + using (StreamReader reader = OpenStream(fs, encoding)) { + reader.Peek(); + encoding = reader.CurrentEncoding; + return reader.ReadToEnd(); + } + } + + public static string ReadFileContent(string fileName, Encoding encoding) + { + using (FileStream fs = new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)) { + return ReadFileContent(fs, ref encoding); + } + } + + public static StreamReader OpenStream(Stream fs, Encoding defaultEncoding) + { + if (fs == null) + throw new ArgumentNullException("fs"); + + if (fs.Length >= 2) { + // the autodetection of StreamReader is not capable of detecting the difference + // between ISO-8859-1 and UTF-8 without BOM. + int firstByte = fs.ReadByte(); + int secondByte = fs.ReadByte(); + switch ((firstByte << 8) | secondByte) { + case 0x0000: // either UTF-32 Big Endian or a binary file; use StreamReader + case 0xfffe: // Unicode BOM (UTF-16 LE or UTF-32 LE) + case 0xfeff: // UTF-16 BE BOM + case 0xefbb: // start of UTF-8 BOM + // StreamReader autodetection works + fs.Position = 0; + return new StreamReader(fs); + default: + return AutoDetect(fs, (byte)firstByte, (byte)secondByte, defaultEncoding); + } + } else { + if (defaultEncoding != null) { + return new StreamReader(fs, defaultEncoding); + } else { + return new StreamReader(fs); + } + } + } + + static StreamReader AutoDetect(Stream fs, byte firstByte, byte secondByte, Encoding defaultEncoding) + { + int max = (int)Math.Min(fs.Length, 500000); // look at max. 500 KB + const int ASCII = 0; + const int Error = 1; + const int UTF8 = 2; + const int UTF8Sequence = 3; + int state = ASCII; + int sequenceLength = 0; + byte b; + for (int i = 0; i < max; i++) { + if (i == 0) { + b = firstByte; + } else if (i == 1) { + b = secondByte; + } else { + b = (byte)fs.ReadByte(); + } + if (b < 0x80) { + // normal ASCII character + if (state == UTF8Sequence) { + state = Error; + break; + } + } else if (b < 0xc0) { + // 10xxxxxx : continues UTF8 byte sequence + if (state == UTF8Sequence) { + --sequenceLength; + if (sequenceLength < 0) { + state = Error; + break; + } else if (sequenceLength == 0) { + state = UTF8; + } + } else { + state = Error; + break; + } + } else if (b >= 0xc2 && b < 0xf5) { + // beginning of byte sequence + if (state == UTF8 || state == ASCII) { + state = UTF8Sequence; + if (b < 0xe0) { + sequenceLength = 1; // one more byte following + } else if (b < 0xf0) { + sequenceLength = 2; // two more bytes following + } else { + sequenceLength = 3; // three more bytes following + } + } else { + state = Error; + break; + } + } else { + // 0xc0, 0xc1, 0xf5 to 0xff are invalid in UTF-8 (see RFC 3629) + state = Error; + break; + } + } + fs.Position = 0; + switch (state) { + case ASCII: + case Error: + // when the file seems to be ASCII or non-UTF8, + // we read it using the user-specified encoding so it is saved again + // using that encoding. + if (IsUnicode(defaultEncoding)) { + // the file is not Unicode, so don't read it using Unicode even if the + // user has choosen Unicode as the default encoding. + + // If we don't do this, SD will end up always adding a Byte Order Mark + // to ASCII files. + defaultEncoding = Encoding.Default; // use system encoding instead + } + return new StreamReader(fs, defaultEncoding); + default: + return new StreamReader(fs); + } + } + } +} diff --git a/ICSharpCode.TextEditor/Project/Src/Util/LoggingService.cs b/ICSharpCode.TextEditor/Project/Src/Util/LoggingService.cs new file mode 100644 index 0000000..b936b59 --- /dev/null +++ b/ICSharpCode.TextEditor/Project/Src/Util/LoggingService.cs @@ -0,0 +1,24 @@ +// +// +// +// +// $Revision$ +// + +using System; + +namespace ICSharpCode.TextEditor.Util +{ + /// + /// Central location for logging calls in the text editor. + /// + static class LoggingService + { + public static void Debug(string text) + { + #if DEBUG + Console.WriteLine(text); + #endif + } + } +} diff --git a/ICSharpCode.TextEditor/Project/Src/Util/LookupTable.cs b/ICSharpCode.TextEditor/Project/Src/Util/LookupTable.cs new file mode 100644 index 0000000..3e90c72 --- /dev/null +++ b/ICSharpCode.TextEditor/Project/Src/Util/LookupTable.cs @@ -0,0 +1,156 @@ +// +// +// +// +// $Revision$ +// + +using System; +using ICSharpCode.TextEditor.Document; + +namespace ICSharpCode.TextEditor.Util +{ + /// + /// This class implements a keyword map. It implements a digital search trees (tries) to find + /// a word. + /// + public class LookupTable + { + Node root = new Node(null, null); + bool casesensitive; + int length; + + /// + /// The number of elements in the table + /// + public int Count { + get { + return length; + } + } + + /// + /// Get the object, which was inserted under the keyword (line, at offset, with length length), + /// returns null, if no such keyword was inserted. + /// + public object this[IDocument document, LineSegment line, int offset, int length] { + get { + if(length == 0) { + return null; + } + Node next = root; + + int wordOffset = line.Offset + offset; + if (casesensitive) { + for (int i = 0; i < length; ++i) { + int index = ((int)document.GetCharAt(wordOffset + i)) % 256; + next = next[index]; + + if (next == null) { + return null; + } + + if (next.color != null && TextUtility.RegionMatches(document, wordOffset, length, next.word)) { + return next.color; + } + } + } else { + for (int i = 0; i < length; ++i) { + int index = ((int)char.ToUpper(document.GetCharAt(wordOffset + i))) % 256; + + next = next[index]; + + if (next == null) { + return null; + } + + if (next.color != null && TextUtility.RegionMatches(document, casesensitive, wordOffset, length, next.word)) { + return next.color; + } + } + } + return null; + } + } + + /// + /// Inserts an object in the tree, under keyword + /// + public object this[string keyword] { + set { + Node node = root; + Node next = root; + if (!casesensitive) { + keyword = keyword.ToUpper(); + } + ++length; + + // insert word into the tree + for (int i = 0; i < keyword.Length; ++i) { + int index = ((int)keyword[i]) % 256; // index of curchar + bool d = keyword[i] == '\\'; + + next = next[index]; // get node to this index + + if (next == null) { // no node created -> insert word here + node[index] = new Node(value, keyword); + break; + } + + if (next.word != null && next.word.Length != i) { // node there, take node content and insert them again + string tmpword = next.word; // this word will be inserted 1 level deeper (better, don't need too much + object tmpcolor = next.color; // string comparisons for finding.) + next.color = next.word = null; + this[tmpword] = tmpcolor; + } + + if (i == keyword.Length - 1) { // end of keyword reached, insert node there, if a node was here it was + next.word = keyword; // reinserted, if it has the same length (keyword EQUALS this word) it will be overwritten + next.color = value; + break; + } + + node = next; + } + } + } + + /// + /// Creates a new instance of + /// + public LookupTable(bool casesensitive) + { + this.casesensitive = casesensitive; + } + + class Node + { + public Node(object color, string word) + { + this.word = word; + this.color = color; + } + + public string word; + public object color; + + // Lazily initialize children array. Saves 200 KB of memory for the C# highlighting + // because we don't have to store the array for leaf nodes. + public Node this[int index] { + get { + if (children != null) + return children[index]; + else + return null; + } + set { + if (children == null) + children = new Node[256]; + children[index] = value; + } + } + + private Node[] children; + } + } +} diff --git a/ICSharpCode.TextEditor/Project/Src/Util/MouseWheelHandler.cs b/ICSharpCode.TextEditor/Project/Src/Util/MouseWheelHandler.cs new file mode 100644 index 0000000..f344502 --- /dev/null +++ b/ICSharpCode.TextEditor/Project/Src/Util/MouseWheelHandler.cs @@ -0,0 +1,36 @@ +// +// +// +// +// $Revision$ +// + +using System; +using System.Windows.Forms; + +namespace ICSharpCode.TextEditor.Util +{ + /// + /// Accumulates mouse wheel deltas and reports the actual number of lines to scroll. + /// + class MouseWheelHandler + { + // CODE DUPLICATION: See ICSharpCode.SharpDevelop.Widgets.MouseWheelHandler + + const int WHEEL_DELTA = 120; + + int mouseWheelDelta; + + public int GetScrollAmount(MouseEventArgs e) + { + // accumulate the delta to support high-resolution mice + mouseWheelDelta += e.Delta; + + int linesPerClick = Math.Max(SystemInformation.MouseWheelScrollLines, 1); + + int scrollDistance = mouseWheelDelta * linesPerClick / WHEEL_DELTA; + mouseWheelDelta %= Math.Max(1, WHEEL_DELTA / linesPerClick); + return scrollDistance; + } + } +} diff --git a/ICSharpCode.TextEditor/Project/Src/Util/RedBlackTreeIterator.cs b/ICSharpCode.TextEditor/Project/Src/Util/RedBlackTreeIterator.cs new file mode 100644 index 0000000..fba108f --- /dev/null +++ b/ICSharpCode.TextEditor/Project/Src/Util/RedBlackTreeIterator.cs @@ -0,0 +1,85 @@ +// +// +// +// +// $Revision$ +// + +using System; +using System.Diagnostics; +using System.Collections.Generic; + +namespace ICSharpCode.TextEditor.Util +{ + internal struct RedBlackTreeIterator : IEnumerator + { + internal RedBlackTreeNode node; + + internal RedBlackTreeIterator(RedBlackTreeNode node) + { + this.node = node; + } + + public bool IsValid { + get { return node != null; } + } + + public T Current { + get { + if (node != null) + return node.val; + else + throw new InvalidOperationException(); + } + } + + object System.Collections.IEnumerator.Current { + get { + return this.Current; + } + } + + void IDisposable.Dispose() + { + } + + void System.Collections.IEnumerator.Reset() + { + throw new NotSupportedException(); + } + + public bool MoveNext() + { + if (node == null) + return false; + if (node.right != null) { + node = node.right.LeftMost; + } else { + RedBlackTreeNode oldNode; + do { + oldNode = node; + node = node.parent; + // we are on the way up from the right part, don't output node again + } while (node != null && node.right == oldNode); + } + return node != null; + } + + public bool MoveBack() + { + if (node == null) + return false; + if (node.left != null) { + node = node.left.RightMost; + } else { + RedBlackTreeNode oldNode; + do { + oldNode = node; + node = node.parent; + // we are on the way up from the left part, don't output node again + } while (node != null && node.left == oldNode); + } + return node != null; + } + } +} diff --git a/ICSharpCode.TextEditor/Project/Src/Util/RtfWriter.cs b/ICSharpCode.TextEditor/Project/Src/Util/RtfWriter.cs new file mode 100644 index 0000000..53f036b --- /dev/null +++ b/ICSharpCode.TextEditor/Project/Src/Util/RtfWriter.cs @@ -0,0 +1,190 @@ +// +// +// +// +// $Revision$ +// + +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Text; + +using ICSharpCode.TextEditor.Document; + +namespace ICSharpCode.TextEditor.Util +{ + public class RtfWriter + { + static Dictionary colors; + static int colorNum; + static StringBuilder colorString; + + public static string GenerateRtf(TextArea textArea) + { + colors = new Dictionary(); + colorNum = 0; + colorString = new StringBuilder(); + + + StringBuilder rtf = new StringBuilder(); + + rtf.Append(@"{\rtf1\ansi\ansicpg1252\deff0\deflang1031"); + BuildFontTable(textArea.Document, rtf); + rtf.Append('\n'); + + string fileContent = BuildFileContent(textArea); + BuildColorTable(textArea.Document, rtf); + rtf.Append('\n'); + rtf.Append(@"\viewkind4\uc1\pard"); + rtf.Append(fileContent); + rtf.Append("}"); + return rtf.ToString(); + } + + static void BuildColorTable(IDocument doc, StringBuilder rtf) + { + rtf.Append(@"{\colortbl ;"); + rtf.Append(colorString.ToString()); + rtf.Append("}"); + } + + static void BuildFontTable(IDocument doc, StringBuilder rtf) + { + rtf.Append(@"{\fonttbl"); + rtf.Append(@"{\f0\fmodern\fprq1\fcharset0 " + doc.TextEditorProperties.Font.Name + ";}"); + rtf.Append("}"); + } + + static string BuildFileContent(TextArea textArea) + { + StringBuilder rtf = new StringBuilder(); + bool firstLine = true; + Color curColor = Color.Black; + bool oldItalic = false; + bool oldBold = false; + bool escapeSequence = false; + + foreach (ISelection selection in textArea.SelectionManager.SelectionCollection) { + int selectionOffset = textArea.Document.PositionToOffset(selection.StartPosition); + int selectionEndOffset = textArea.Document.PositionToOffset(selection.EndPosition); + for (int i = selection.StartPosition.Y; i <= selection.EndPosition.Y; ++i) { + LineSegment line = textArea.Document.GetLineSegment(i); + int offset = line.Offset; + if (line.Words == null) { + continue; + } + + foreach (TextWord word in line.Words) { + switch (word.Type) { + case TextWordType.Space: + if (selection.ContainsOffset(offset)) { + rtf.Append(' '); + } + ++offset; + break; + + case TextWordType.Tab: + if (selection.ContainsOffset(offset)) { + rtf.Append(@"\tab"); + } + ++offset; + escapeSequence = true; + break; + + case TextWordType.Word: + Color c = word.Color; + + if (offset + word.Word.Length > selectionOffset && offset < selectionEndOffset) { + string colorstr = c.R + ", " + c.G + ", " + c.B; + + if (!colors.ContainsKey(colorstr)) { + colors[colorstr] = ++colorNum; + colorString.Append(@"\red" + c.R + @"\green" + c.G + @"\blue" + c.B + ";"); + } + if (c != curColor || firstLine) { + rtf.Append(@"\cf" + colors[colorstr].ToString()); + curColor = c; + escapeSequence = true; + } + + if (oldItalic != word.Italic) { + if (word.Italic) { + rtf.Append(@"\i"); + } else { + rtf.Append(@"\i0"); + } + oldItalic = word.Italic; + escapeSequence = true; + } + + if (oldBold != word.Bold) { + if (word.Bold) { + rtf.Append(@"\b"); + } else { + rtf.Append(@"\b0"); + } + oldBold = word.Bold; + escapeSequence = true; + } + + if (firstLine) { + rtf.Append(@"\f0\fs" + Math.Round(textArea.TextEditorProperties.Font.Size * 2)); // Stef_H + firstLine = false; + } + if (escapeSequence) { + rtf.Append(' '); + escapeSequence = false; + } + string printWord; + if (offset < selectionOffset) { + printWord = word.Word.Substring(selectionOffset - offset); + } else if (offset + word.Word.Length > selectionEndOffset) { + printWord = word.Word.Substring(0, (offset + word.Word.Length) - selectionEndOffset); + } else { + printWord = word.Word; + } + + AppendText(rtf, printWord); + } + offset += word.Length; + break; + } + } + if (offset < selectionEndOffset) { + rtf.Append(@"\par"); + } + rtf.Append('\n'); + } + } + + return rtf.ToString(); + } + + static void AppendText(StringBuilder rtfOutput, string text) + { + //rtf.Append(printWord.Replace(@"\", @"\\").Replace("{", "\\{").Replace("}", "\\}")); + foreach (char c in text) { + switch (c) { + case '\\': + rtfOutput.Append(@"\\"); + break; + case '{': + rtfOutput.Append("\\{"); + break; + case '}': + rtfOutput.Append("\\}"); + break; + default: + if (c < 256) { + rtfOutput.Append(c); + } else { + // yes, RTF really expects signed 16-bit integers! + rtfOutput.Append("\\u" + unchecked((short)c).ToString() + "?"); + } + break; + } + } + } + } +} diff --git a/ICSharpCode.TextEditor/Project/Src/Util/TextUtility.cs b/ICSharpCode.TextEditor/Project/Src/Util/TextUtility.cs new file mode 100644 index 0000000..d38fc8f --- /dev/null +++ b/ICSharpCode.TextEditor/Project/Src/Util/TextUtility.cs @@ -0,0 +1,81 @@ +// +// +// +// +// $Revision$ +// + +using System; + +using ICSharpCode.TextEditor.Document; + +namespace ICSharpCode.TextEditor.Util +{ + public class TextUtility + { + + public static bool RegionMatches(IDocument document, int offset, int length, string word) + { + if (length != word.Length || document.TextLength < offset + length) { + return false; + } + + for (int i = 0; i < length; ++i) { + if (document.GetCharAt(offset + i) != word[i]) { + return false; + } + } + return true; + } + + public static bool RegionMatches(IDocument document, bool casesensitive, int offset, int length, string word) + { + if (casesensitive) { + return RegionMatches(document, offset, length, word); + } + + if (length != word.Length || document.TextLength < offset + length) { + return false; + } + + for (int i = 0; i < length; ++i) { + if (char.ToUpper(document.GetCharAt(offset + i)) != char.ToUpper(word[i])) { + return false; + } + } + return true; + } + + public static bool RegionMatches(IDocument document, int offset, int length, char[] word) + { + if (length != word.Length || document.TextLength < offset + length) { + return false; + } + + for (int i = 0; i < length; ++i) { + if (document.GetCharAt(offset + i) != word[i]) { + return false; + } + } + return true; + } + + public static bool RegionMatches(IDocument document, bool casesensitive, int offset, int length, char[] word) + { + if (casesensitive) { + return RegionMatches(document, offset, length, word); + } + + if (length != word.Length || document.TextLength < offset + length) { + return false; + } + + for (int i = 0; i < length; ++i) { + if (char.ToUpper(document.GetCharAt(offset + i)) != char.ToUpper(word[i])) { + return false; + } + } + return true; + } + } +} diff --git a/ICSharpCode.TextEditor/Project/Src/Util/TipPainter.cs b/ICSharpCode.TextEditor/Project/Src/Util/TipPainter.cs new file mode 100644 index 0000000..cb2bb2f --- /dev/null +++ b/ICSharpCode.TextEditor/Project/Src/Util/TipPainter.cs @@ -0,0 +1,193 @@ +// +// +// +// +// $Revision$ +// + +using System.Drawing; +using System.Drawing.Text; +using System.Windows.Forms; + +namespace ICSharpCode.TextEditor.Util +{ + static class TipPainter + { + const float HorizontalBorder = 2; + const float VerticalBorder = 1; + + //static StringFormat centerTipFormat = CreateTipStringFormat(); + + public static Size GetTipSize(Control control, Graphics graphics, Font font, string description) + { + return GetTipSize(control, graphics, new TipText (graphics, font, description)); + } + + static Rectangle GetWorkingArea(Control control) + { + Form ownerForm = control.FindForm(); + if (ownerForm.Owner != null) { + ownerForm = ownerForm.Owner; + } + + return Screen.GetWorkingArea(ownerForm); + } + + public static Size GetTipSize(Control control, Graphics graphics, TipSection tipData) + { + Size tipSize = Size.Empty; + SizeF tipSizeF = SizeF.Empty; + + RectangleF workingArea = GetWorkingArea(control); + + PointF screenLocation = control.PointToScreen(Point.Empty); + + SizeF maxLayoutSize = new SizeF(workingArea.Right - screenLocation.X - HorizontalBorder * 2, + workingArea.Bottom - screenLocation.Y - VerticalBorder * 2); + + if (maxLayoutSize.Width > 0 && maxLayoutSize.Height > 0) { + graphics.TextRenderingHint = + TextRenderingHint.AntiAliasGridFit; + + tipData.SetMaximumSize(maxLayoutSize); + tipSizeF = tipData.GetRequiredSize(); + tipData.SetAllocatedSize(tipSizeF); + + tipSizeF += new SizeF(HorizontalBorder * 2, + VerticalBorder * 2); + tipSize = Size.Ceiling(tipSizeF); + } + + if (control.ClientSize != tipSize) { + control.ClientSize = tipSize; + } + + return tipSize; + } + + public static Size GetLeftHandSideTipSize(Control control, Graphics graphics, TipSection tipData, Point p) + { + Size tipSize = Size.Empty; + SizeF tipSizeF = SizeF.Empty; + + RectangleF workingArea = GetWorkingArea(control); + + PointF screenLocation = p; + + SizeF maxLayoutSize = new SizeF(screenLocation.X - HorizontalBorder * 2, + workingArea.Bottom - screenLocation.Y - VerticalBorder * 2); + + if (maxLayoutSize.Width > 0 && maxLayoutSize.Height > 0) { + graphics.TextRenderingHint = + TextRenderingHint.AntiAliasGridFit; + + tipData.SetMaximumSize(maxLayoutSize); + tipSizeF = tipData.GetRequiredSize(); + tipData.SetAllocatedSize(tipSizeF); + + tipSizeF += new SizeF(HorizontalBorder * 2, + VerticalBorder * 2); + tipSize = Size.Ceiling(tipSizeF); + } + + return tipSize; + } + + public static Size DrawTip(Control control, Graphics graphics, Font font, string description) + { + return DrawTip(control, graphics, new TipText (graphics, font, description)); + } + + public static Size DrawTip(Control control, Graphics graphics, TipSection tipData) + { + Size tipSize = Size.Empty; + SizeF tipSizeF = SizeF.Empty; + + PointF screenLocation = control.PointToScreen(Point.Empty); + + RectangleF workingArea = GetWorkingArea(control); + + SizeF maxLayoutSize = new SizeF(workingArea.Right - screenLocation.X - HorizontalBorder * 2, + workingArea.Bottom - screenLocation.Y - VerticalBorder * 2); + + if (maxLayoutSize.Width > 0 && maxLayoutSize.Height > 0) { + graphics.TextRenderingHint = + TextRenderingHint.AntiAliasGridFit; + + tipData.SetMaximumSize(maxLayoutSize); + tipSizeF = tipData.GetRequiredSize(); + tipData.SetAllocatedSize(tipSizeF); + + tipSizeF += new SizeF(HorizontalBorder * 2, + VerticalBorder * 2); + tipSize = Size.Ceiling(tipSizeF); + } + + if (control.ClientSize != tipSize) { + control.ClientSize = tipSize; + } + + if (tipSize != Size.Empty) { + Rectangle borderRectangle = new Rectangle + (Point.Empty, tipSize - new Size(1, 1)); + + RectangleF displayRectangle = new RectangleF + (HorizontalBorder, VerticalBorder, + tipSizeF.Width - HorizontalBorder * 2, + tipSizeF.Height - VerticalBorder * 2); + + // DrawRectangle draws from Left to Left + Width. A bug? :-/ + graphics.DrawRectangle(SystemPens.WindowFrame, + borderRectangle); + tipData.Draw(new PointF(HorizontalBorder, VerticalBorder)); + } + return tipSize; + } + + public static Size DrawFixedWidthTip(Control control, Graphics graphics, TipSection tipData) + { + Size tipSize = Size.Empty; + SizeF tipSizeF = SizeF.Empty; + + PointF screenLocation = control.PointToScreen(new Point(control.Width, 0)); + + RectangleF workingArea = GetWorkingArea(control); + + SizeF maxLayoutSize = new SizeF(screenLocation.X - HorizontalBorder * 2, + workingArea.Bottom - screenLocation.Y - VerticalBorder * 2); + + if (maxLayoutSize.Width > 0 && maxLayoutSize.Height > 0) { + graphics.TextRenderingHint = + TextRenderingHint.AntiAliasGridFit; + + tipData.SetMaximumSize(maxLayoutSize); + tipSizeF = tipData.GetRequiredSize(); + tipData.SetAllocatedSize(tipSizeF); + + tipSizeF += new SizeF(HorizontalBorder * 2, + VerticalBorder * 2); + tipSize = Size.Ceiling(tipSizeF); + } + + if (control.Height != tipSize.Height) { + control.Height = tipSize.Height; + } + + if (tipSize != Size.Empty) { + Rectangle borderRectangle = new Rectangle + (Point.Empty, control.Size - new Size(1, 1)); + + RectangleF displayRectangle = new RectangleF + (HorizontalBorder, VerticalBorder, + tipSizeF.Width - HorizontalBorder * 2, + tipSizeF.Height - VerticalBorder * 2); + + // DrawRectangle draws from Left to Left + Width. A bug? :-/ + graphics.DrawRectangle(SystemPens.WindowFrame, + borderRectangle); + tipData.Draw(new PointF(HorizontalBorder, VerticalBorder)); + } + return tipSize; + } + } +} diff --git a/ICSharpCode.TextEditor/Project/Src/Util/TipPainterTools.cs b/ICSharpCode.TextEditor/Project/Src/Util/TipPainterTools.cs new file mode 100644 index 0000000..3f9969a --- /dev/null +++ b/ICSharpCode.TextEditor/Project/Src/Util/TipPainterTools.cs @@ -0,0 +1,301 @@ +// +// +// +// +// $Revision$ +// + +using System; +using System.Drawing; +using System.Windows.Forms; + +namespace ICSharpCode.TextEditor.Util +{ + static class TipPainterTools + { + const int SpacerSize = 4; + + public static Size GetLeftHandSideDrawingSizeHelpTipFromCombinedDescription(Control control, + Graphics graphics, + Font font, + string countMessage, + string description, + Point p) + { + string basicDescription = null; + string documentation = null; + + if (IsVisibleText(description)) { + string[] splitDescription = description.Split(new char[] { '\n' }, 2); + + if (splitDescription.Length > 0) { + basicDescription = splitDescription[0]; + + if (splitDescription.Length > 1) { + documentation = splitDescription[1].Trim(); + } + } + } + + return GetLeftHandSideDrawingSizeDrawHelpTip(control, graphics, font, countMessage, basicDescription, documentation, p); + } + + public static Size GetDrawingSizeHelpTipFromCombinedDescription(Control control, + Graphics graphics, + Font font, + string countMessage, + string description) + { + string basicDescription = null; + string documentation = null; + + if (IsVisibleText(description)) { + string[] splitDescription = description.Split(new char[] { '\n' }, 2); + + if (splitDescription.Length > 0) { + basicDescription = splitDescription[0]; + + if (splitDescription.Length > 1) { + documentation = splitDescription[1].Trim(); + } + } + } + + return GetDrawingSizeDrawHelpTip(control, graphics, font, countMessage, basicDescription, documentation); + } + + public static Size DrawHelpTipFromCombinedDescription(Control control, + Graphics graphics, + Font font, + string countMessage, + string description) + { + string basicDescription = null; + string documentation = null; + + if (IsVisibleText(description)) { + string[] splitDescription = description.Split + (new char[] { '\n' }, 2); + + if (splitDescription.Length > 0) { + basicDescription = splitDescription[0]; + + if (splitDescription.Length > 1) { + documentation = splitDescription[1].Trim(); + } + } + } + + return DrawHelpTip(control, graphics, font, countMessage, + basicDescription, documentation); + } + + public static Size DrawFixedWidthHelpTipFromCombinedDescription(Control control, + Graphics graphics, + Font font, + string countMessage, + string description) + { + string basicDescription = null; + string documentation = null; + + if (IsVisibleText(description)) { + string[] splitDescription = description.Split + (new char[] { '\n' }, 2); + + if (splitDescription.Length > 0) { + basicDescription = splitDescription[0]; + + if (splitDescription.Length > 1) { + documentation = splitDescription[1].Trim(); + } + } + } + + return DrawFixedWidthHelpTip(control, graphics, font, countMessage, + basicDescription, documentation); + } + + // btw. I know it's ugly. + public static Rectangle DrawingRectangle1; + public static Rectangle DrawingRectangle2; + + public static Size GetDrawingSizeDrawHelpTip(Control control, + Graphics graphics, Font font, + string countMessage, + string basicDescription, + string documentation) + { + if (IsVisibleText(countMessage) || + IsVisibleText(basicDescription) || + IsVisibleText(documentation)) { + // Create all the TipSection objects. + CountTipText countMessageTip = new CountTipText(graphics, font, countMessage); + + TipSpacer countSpacer = new TipSpacer(graphics, new SizeF(IsVisibleText(countMessage) ? 4 : 0, 0)); + + TipText descriptionTip = new TipText(graphics, font, basicDescription); + + TipSpacer docSpacer = new TipSpacer(graphics, new SizeF(0, IsVisibleText(documentation) ? 4 : 0)); + + TipText docTip = new TipText(graphics, font, documentation); + + // Now put them together. + TipSplitter descSplitter = new TipSplitter(graphics, false, + descriptionTip, + docSpacer + ); + + TipSplitter mainSplitter = new TipSplitter(graphics, true, + countMessageTip, + countSpacer, + descSplitter); + + TipSplitter mainSplitter2 = new TipSplitter(graphics, false, + mainSplitter, + docTip); + + // Show it. + Size size = TipPainter.GetTipSize(control, graphics, mainSplitter2); + DrawingRectangle1 = countMessageTip.DrawingRectangle1; + DrawingRectangle2 = countMessageTip.DrawingRectangle2; + return size; + } + return Size.Empty; + } + public static Size GetLeftHandSideDrawingSizeDrawHelpTip(Control control, + Graphics graphics, Font font, + string countMessage, + string basicDescription, + string documentation, + Point p) + { + if (IsVisibleText(countMessage) || + IsVisibleText(basicDescription) || + IsVisibleText(documentation)) { + // Create all the TipSection objects. + CountTipText countMessageTip = new CountTipText(graphics, font, countMessage); + + TipSpacer countSpacer = new TipSpacer(graphics, new SizeF(IsVisibleText(countMessage) ? 4 : 0, 0)); + + TipText descriptionTip = new TipText(graphics, font, basicDescription); + + TipSpacer docSpacer = new TipSpacer(graphics, new SizeF(0, IsVisibleText(documentation) ? 4 : 0)); + + TipText docTip = new TipText(graphics, font, documentation); + + // Now put them together. + TipSplitter descSplitter = new TipSplitter(graphics, false, + descriptionTip, + docSpacer + ); + + TipSplitter mainSplitter = new TipSplitter(graphics, true, + countMessageTip, + countSpacer, + descSplitter); + + TipSplitter mainSplitter2 = new TipSplitter(graphics, false, + mainSplitter, + docTip); + + // Show it. + Size size = TipPainter.GetLeftHandSideTipSize(control, graphics, mainSplitter2, p); + return size; + } + return Size.Empty; + } + public static Size DrawHelpTip(Control control, + Graphics graphics, Font font, + string countMessage, + string basicDescription, + string documentation) + { + if (IsVisibleText(countMessage) || + IsVisibleText(basicDescription) || + IsVisibleText(documentation)) { + // Create all the TipSection objects. + CountTipText countMessageTip = new CountTipText(graphics, font, countMessage); + + TipSpacer countSpacer = new TipSpacer(graphics, new SizeF(IsVisibleText(countMessage) ? 4 : 0, 0)); + + TipText descriptionTip = new TipText(graphics, font, basicDescription); + + TipSpacer docSpacer = new TipSpacer(graphics, new SizeF(0, IsVisibleText(documentation) ? 4 : 0)); + + TipText docTip = new TipText(graphics, font, documentation); + + // Now put them together. + TipSplitter descSplitter = new TipSplitter(graphics, false, + descriptionTip, + docSpacer + ); + + TipSplitter mainSplitter = new TipSplitter(graphics, true, + countMessageTip, + countSpacer, + descSplitter); + + TipSplitter mainSplitter2 = new TipSplitter(graphics, false, + mainSplitter, + docTip); + + // Show it. + Size size = TipPainter.DrawTip(control, graphics, mainSplitter2); + DrawingRectangle1 = countMessageTip.DrawingRectangle1; + DrawingRectangle2 = countMessageTip.DrawingRectangle2; + return size; + } + return Size.Empty; + } + + public static Size DrawFixedWidthHelpTip(Control control, + Graphics graphics, Font font, + string countMessage, + string basicDescription, + string documentation) + { + if (IsVisibleText(countMessage) || + IsVisibleText(basicDescription) || + IsVisibleText(documentation)) { + // Create all the TipSection objects. + CountTipText countMessageTip = new CountTipText(graphics, font, countMessage); + + TipSpacer countSpacer = new TipSpacer(graphics, new SizeF(IsVisibleText(countMessage) ? 4 : 0, 0)); + + TipText descriptionTip = new TipText(graphics, font, basicDescription); + + TipSpacer docSpacer = new TipSpacer(graphics, new SizeF(0, IsVisibleText(documentation) ? 4 : 0)); + + TipText docTip = new TipText(graphics, font, documentation); + + // Now put them together. + TipSplitter descSplitter = new TipSplitter(graphics, false, + descriptionTip, + docSpacer + ); + + TipSplitter mainSplitter = new TipSplitter(graphics, true, + countMessageTip, + countSpacer, + descSplitter); + + TipSplitter mainSplitter2 = new TipSplitter(graphics, false, + mainSplitter, + docTip); + + // Show it. + Size size = TipPainter.DrawFixedWidthTip(control, graphics, mainSplitter2); + DrawingRectangle1 = countMessageTip.DrawingRectangle1; + DrawingRectangle2 = countMessageTip.DrawingRectangle2; + return size; + } + return Size.Empty; + } + + static bool IsVisibleText(string text) + { + return text != null && text.Length > 0; + } + } +} diff --git a/ICSharpCode.TextEditor/Project/Src/Util/TipSection.cs b/ICSharpCode.TextEditor/Project/Src/Util/TipSection.cs new file mode 100644 index 0000000..6d87180 --- /dev/null +++ b/ICSharpCode.TextEditor/Project/Src/Util/TipSection.cs @@ -0,0 +1,84 @@ +// +// +// +// +// $Revision$ +// + +using System; +using System.Diagnostics; +using System.Drawing; + +namespace ICSharpCode.TextEditor.Util +{ + abstract class TipSection + { + SizeF tipAllocatedSize; + Graphics tipGraphics; + SizeF tipMaxSize; + SizeF tipRequiredSize; + + protected TipSection(Graphics graphics) + { + tipGraphics = graphics; + } + + public abstract void Draw(PointF location); + + public SizeF GetRequiredSize() + { + return tipRequiredSize; + } + + public void SetAllocatedSize(SizeF allocatedSize) + { + Debug.Assert(allocatedSize.Width >= tipRequiredSize.Width && + allocatedSize.Height >= tipRequiredSize.Height); + + tipAllocatedSize = allocatedSize; OnAllocatedSizeChanged(); + } + + public void SetMaximumSize(SizeF maximumSize) + { + tipMaxSize = maximumSize; OnMaximumSizeChanged(); + } + + protected virtual void OnAllocatedSizeChanged() + { + + } + + protected virtual void OnMaximumSizeChanged() + { + + } + + protected void SetRequiredSize(SizeF requiredSize) + { + requiredSize.Width = Math.Max(0, requiredSize.Width); + requiredSize.Height = Math.Max(0, requiredSize.Height); + requiredSize.Width = Math.Min(tipMaxSize.Width, requiredSize.Width); + requiredSize.Height = Math.Min(tipMaxSize.Height, requiredSize.Height); + + tipRequiredSize = requiredSize; + } + + protected Graphics Graphics { + get { + return tipGraphics; + } + } + + protected SizeF AllocatedSize { + get { + return tipAllocatedSize; + } + } + + protected SizeF MaximumSize { + get { + return tipMaxSize; + } + } + } +} diff --git a/ICSharpCode.TextEditor/Project/Src/Util/TipSpacer.cs b/ICSharpCode.TextEditor/Project/Src/Util/TipSpacer.cs new file mode 100644 index 0000000..69780b3 --- /dev/null +++ b/ICSharpCode.TextEditor/Project/Src/Util/TipSpacer.cs @@ -0,0 +1,36 @@ +// +// +// +// +// $Revision$ +// + +using System; +using System.Drawing; + +namespace ICSharpCode.TextEditor.Util +{ + class TipSpacer: TipSection + { + SizeF spacerSize; + + public TipSpacer(Graphics graphics, SizeF size): base(graphics) + { + spacerSize = size; + } + + public override void Draw(PointF location) + { + + } + + protected override void OnMaximumSizeChanged() + { + base.OnMaximumSizeChanged(); + + SetRequiredSize(new SizeF + (Math.Min(MaximumSize.Width, spacerSize.Width), + Math.Min(MaximumSize.Height, spacerSize.Height))); + } + } +} diff --git a/ICSharpCode.TextEditor/Project/Src/Util/TipSplitter.cs b/ICSharpCode.TextEditor/Project/Src/Util/TipSplitter.cs new file mode 100644 index 0000000..f25317e --- /dev/null +++ b/ICSharpCode.TextEditor/Project/Src/Util/TipSplitter.cs @@ -0,0 +1,99 @@ +// +// +// +// +// $Revision$ +// + +using System; +using System.Diagnostics; +using System.Drawing; + +namespace ICSharpCode.TextEditor.Util +{ + class TipSplitter: TipSection + { + bool isHorizontal; + float [] offsets; + TipSection[] tipSections; + + public TipSplitter(Graphics graphics, bool horizontal, params TipSection[] sections): base(graphics) + { + Debug.Assert(sections != null); + + isHorizontal = horizontal; + offsets = new float[sections.Length]; + tipSections = (TipSection[])sections.Clone(); + } + + public override void Draw(PointF location) + { + if (isHorizontal) { + for (int i = 0; i < tipSections.Length; i ++) { + tipSections[i].Draw + (new PointF(location.X + offsets[i], location.Y)); + } + } else { + for (int i = 0; i < tipSections.Length; i ++) { + tipSections[i].Draw + (new PointF(location.X, location.Y + offsets[i])); + } + } + } + + protected override void OnMaximumSizeChanged() + { + base.OnMaximumSizeChanged(); + + float currentDim = 0; + float otherDim = 0; + SizeF availableArea = MaximumSize; + + for (int i = 0; i < tipSections.Length; i ++) { + TipSection section = (TipSection)tipSections[i]; + + section.SetMaximumSize(availableArea); + + SizeF requiredArea = section.GetRequiredSize(); + offsets[i] = currentDim; + + // It's best to start on pixel borders, so this will + // round up to the nearest pixel. Otherwise there are + // weird cutoff artifacts. + float pixelsUsed; + + if (isHorizontal) { + pixelsUsed = (float)Math.Ceiling(requiredArea.Width); + currentDim += pixelsUsed; + + availableArea.Width = Math.Max + (0, availableArea.Width - pixelsUsed); + + otherDim = Math.Max(otherDim, requiredArea.Height); + } else { + pixelsUsed = (float)Math.Ceiling(requiredArea.Height); + currentDim += pixelsUsed; + + availableArea.Height = Math.Max + (0, availableArea.Height - pixelsUsed); + + otherDim = Math.Max(otherDim, requiredArea.Width); + } + } + + foreach (TipSection section in tipSections) { + if (isHorizontal) { + section.SetAllocatedSize(new SizeF(section.GetRequiredSize().Width, otherDim)); + } else { + section.SetAllocatedSize(new SizeF(otherDim, section.GetRequiredSize().Height)); + } + } + + if (isHorizontal) { + SetRequiredSize(new SizeF(currentDim, otherDim)); + } else { + SetRequiredSize(new SizeF(otherDim, currentDim)); + } + } + } +} diff --git a/ICSharpCode.TextEditor/Project/Src/Util/TipText.cs b/ICSharpCode.TextEditor/Project/Src/Util/TipText.cs new file mode 100644 index 0000000..2dc472d --- /dev/null +++ b/ICSharpCode.TextEditor/Project/Src/Util/TipText.cs @@ -0,0 +1,183 @@ +// +// +// +// +// $Revision$ +// + +using System; +using System.Drawing; + +namespace ICSharpCode.TextEditor.Util +{ + class CountTipText: TipText + { + float triHeight = 10; + float triWidth = 10; + + public CountTipText(Graphics graphics, Font font, string text) : base(graphics, font, text) + { + } + + void DrawTriangle(float x, float y, bool flipped) + { + Brush brush = BrushRegistry.GetBrush(Color.FromArgb(192, 192, 192)); + base.Graphics.FillRectangle(brush, new RectangleF(x, y, triHeight, triHeight)); + float triHeight2 = triHeight / 2; + float triHeight4 = triHeight / 4; + brush = Brushes.Black; + if (flipped) { + base.Graphics.FillPolygon(brush, new PointF[] { + new PointF(x, y + triHeight2 - triHeight4), + new PointF(x + triWidth / 2, y + triHeight2 + triHeight4), + new PointF(x + triWidth, y + triHeight2 - triHeight4), + }); + + } else { + base.Graphics.FillPolygon(brush, new PointF[] { + new PointF(x, y + triHeight2 + triHeight4), + new PointF(x + triWidth / 2, y + triHeight2 - triHeight4), + new PointF(x + triWidth, y + triHeight2 + triHeight4), + }); + } + } + + public Rectangle DrawingRectangle1; + public Rectangle DrawingRectangle2; + + public override void Draw(PointF location) + { + if (tipText != null && tipText.Length > 0) { + base.Draw(new PointF(location.X + triWidth + 4, location.Y)); + DrawingRectangle1 = new Rectangle((int)location.X + 2, + (int)location.Y + 2, + (int)(triWidth), + (int)(triHeight)); + DrawingRectangle2 = new Rectangle((int)(location.X + base.AllocatedSize.Width - triWidth - 2), + (int)location.Y + 2, + (int)(triWidth), + (int)(triHeight)); + DrawTriangle(location.X + 2, location.Y + 2, false); + DrawTriangle(location.X + base.AllocatedSize.Width - triWidth - 2, location.Y + 2, true); + } + } + + protected override void OnMaximumSizeChanged() + { + if (IsTextVisible()) { + SizeF tipSize = Graphics.MeasureString + (tipText, tipFont, MaximumSize, + GetInternalStringFormat()); + tipSize.Width += triWidth * 2 + 8; + SetRequiredSize(tipSize); + } else { + SetRequiredSize(SizeF.Empty); + } + } + + } + + class TipText: TipSection + { + protected StringAlignment horzAlign; + protected StringAlignment vertAlign; + protected Color tipColor; + protected Font tipFont; + protected StringFormat tipFormat; + protected string tipText; + + public TipText(Graphics graphics, Font font, string text): + base(graphics) + { + tipFont = font; tipText = text; + if (text != null && text.Length > short.MaxValue) + throw new ArgumentException("TipText: text too long (max. is " + short.MaxValue + " characters)", "text"); + + Color = SystemColors.InfoText; + HorizontalAlignment = StringAlignment.Near; + VerticalAlignment = StringAlignment.Near; + } + + public override void Draw(PointF location) + { + if (IsTextVisible()) { + RectangleF drawRectangle = new RectangleF(location, AllocatedSize); + + Graphics.DrawString(tipText, tipFont, + BrushRegistry.GetBrush(Color), + drawRectangle, + GetInternalStringFormat()); + } + } + + protected StringFormat GetInternalStringFormat() + { + if (tipFormat == null) { + tipFormat = CreateTipStringFormat(horzAlign, vertAlign); + } + + return tipFormat; + } + + protected override void OnMaximumSizeChanged() + { + base.OnMaximumSizeChanged(); + + if (IsTextVisible()) { + SizeF tipSize = Graphics.MeasureString + (tipText, tipFont, MaximumSize, + GetInternalStringFormat()); + + SetRequiredSize(tipSize); + } else { + SetRequiredSize(SizeF.Empty); + } + } + + static StringFormat CreateTipStringFormat(StringAlignment horizontalAlignment, StringAlignment verticalAlignment) + { + StringFormat format = (StringFormat)StringFormat.GenericTypographic.Clone(); + format.FormatFlags = StringFormatFlags.FitBlackBox | StringFormatFlags.MeasureTrailingSpaces; + // note: Align Near, Line Center seemed to do something before + + format.Alignment = horizontalAlignment; + format.LineAlignment = verticalAlignment; + + return format; + } + + protected bool IsTextVisible() + { + return tipText != null && tipText.Length > 0; + } + + public Color Color { + get { + return tipColor; + } + set { + tipColor = value; + } + } + + public StringAlignment HorizontalAlignment { + get { + return horzAlign; + } + set { + horzAlign = value; + tipFormat = null; + } + } + + public StringAlignment VerticalAlignment { + get { + return vertAlign; + } + set { + vertAlign = value; + tipFormat = null; + } + } + } +} diff --git a/ICSharpCode.TextEditor/Project/Src/Util/WeakCollection.cs b/ICSharpCode.TextEditor/Project/Src/Util/WeakCollection.cs new file mode 100644 index 0000000..43bdfb7 --- /dev/null +++ b/ICSharpCode.TextEditor/Project/Src/Util/WeakCollection.cs @@ -0,0 +1,136 @@ +// +// +// +// +// $Revision$ +// + +using System; +using System.Collections.Generic; + +namespace ICSharpCode.TextEditor.Util +{ + /// + /// A collection that does not allows its elements to be garbage-collected (unless there are other + /// references to the elements). Elements will disappear from the collection when they are + /// garbage-collected. + /// + /// The WeakCollection is not thread-safe, not even for read-only access! + /// No methods may be called on the WeakCollection while it is enumerated, not even a Contains or + /// creating a second enumerator. + /// The WeakCollection does not preserve any order among its contents; the ordering may be different each + /// time the collection is enumerated. + /// + /// Since items may disappear at any time when they are garbage collected, this class + /// cannot provide a useful implementation for Count and thus cannot implement the ICollection interface. + /// + public class WeakCollection : IEnumerable where T : class + { + readonly List innerList = new List(); + + /// + /// Adds an element to the collection. Runtime: O(n). + /// + public void Add(T item) + { + if (item == null) + throw new ArgumentNullException("item"); + CheckNoEnumerator(); + if (innerList.Count == innerList.Capacity || (innerList.Count % 32) == 31) + innerList.RemoveAll(delegate(WeakReference r) { return !r.IsAlive; }); + innerList.Add(new WeakReference(item)); + } + + /// + /// Removes all elements from the collection. Runtime: O(n). + /// + public void Clear() + { + innerList.Clear(); + CheckNoEnumerator(); + } + + /// + /// Checks if the collection contains an item. Runtime: O(n). + /// + public bool Contains(T item) + { + if (item == null) + throw new ArgumentNullException("item"); + CheckNoEnumerator(); + foreach (T element in this) { + if (item.Equals(element)) + return true; + } + return false; + } + + /// + /// Removes an element from the collection. Returns true if the item is found and removed, + /// false when the item is not found. + /// Runtime: O(n). + /// + public bool Remove(T item) + { + if (item == null) + throw new ArgumentNullException("item"); + CheckNoEnumerator(); + for (int i = 0; i < innerList.Count;) { + T element = (T)innerList[i].Target; + if (element == null) { + RemoveAt(i); + } else if (element == item) { + RemoveAt(i); + return true; + } else { + i++; + } + } + return false; + } + + void RemoveAt(int i) + { + int lastIndex = innerList.Count - 1; + innerList[i] = innerList[lastIndex]; + innerList.RemoveAt(lastIndex); + } + + bool hasEnumerator; + + void CheckNoEnumerator() + { + if (hasEnumerator) + throw new InvalidOperationException("The WeakCollection is already being enumerated, it cannot be modified at the same time. Ensure you dispose the first enumerator before modifying the WeakCollection."); + } + + /// + /// Enumerates the collection. + /// Each MoveNext() call on the enumerator is O(1), thus the enumeration is O(n). + /// + public IEnumerator GetEnumerator() + { + if (hasEnumerator) + throw new InvalidOperationException("The WeakCollection is already being enumerated, it cannot be enumerated twice at the same time. Ensure you dispose the first enumerator before using another enumerator."); + try { + hasEnumerator = true; + for (int i = 0; i < innerList.Count;) { + T element = (T)innerList[i].Target; + if (element == null) { + RemoveAt(i); + } else { + yield return element; + i++; + } + } + } finally { + hasEnumerator = false; + } + } + + System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + } +} diff --git a/ICSharpCode.TextEditor/Project/SyntaxModes.cs b/ICSharpCode.TextEditor/Project/SyntaxModes.cs new file mode 100644 index 0000000..3a54612 --- /dev/null +++ b/ICSharpCode.TextEditor/Project/SyntaxModes.cs @@ -0,0 +1,24 @@ + +namespace ICSharpCode.TextEditorEx +{ + public static class SyntaxModes + { + public const string ASP_XHTML = "ASP/XHTML"; + public const string BAT = "BAT"; + public const string Boo = "Boo"; + public const string Coco = "Coco"; + public const string CPPNET = "C++.NET"; + public const string CSharp = "C#"; + public const string HTML = "HTML"; + public const string Java = "Java"; + public const string JavaScript = "JavaScript"; + public const string JSON = "JSON"; + public const string Patch = "Patch"; + public const string PHP = "PHP"; + public const string TeX = "TeX"; + public const string VBNET = "VBNET"; + public const string XML = "XML"; + public const string Lua = "Lua"; + public const string SQL = "SQL"; + } +} \ No newline at end of file diff --git a/ICSharpCode.TextEditor/Project/UserControls/ContextMenuStripEx.Designer.cs b/ICSharpCode.TextEditor/Project/UserControls/ContextMenuStripEx.Designer.cs new file mode 100644 index 0000000..cece929 --- /dev/null +++ b/ICSharpCode.TextEditor/Project/UserControls/ContextMenuStripEx.Designer.cs @@ -0,0 +1,16 @@ +namespace ICSharpCode.TextEditor.UserControls +{ + partial class ContextMenuStripEx + { + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + //DisposeEventsHandlers(); + + base.Dispose(disposing); + } + } +} diff --git a/ICSharpCode.TextEditor/Project/UserControls/ContextMenuStripEx.cs b/ICSharpCode.TextEditor/Project/UserControls/ContextMenuStripEx.cs new file mode 100644 index 0000000..758fc94 --- /dev/null +++ b/ICSharpCode.TextEditor/Project/UserControls/ContextMenuStripEx.cs @@ -0,0 +1,47 @@ +using System; +using System.Drawing; +using System.Windows.Forms; +using ICSharpCode.TextEditor.Utils; + +namespace ICSharpCode.TextEditor.UserControls +{ + public partial class ContextMenuStripEx : ContextMenuStrip + { + public ToolStripMenuItem AddToolStripMenuItem(string text, Bitmap bitmap, EventHandler clickEvent, Keys keys, Func enabled) + { + var menuItem = new ToolStripMenuItem(text); + if (bitmap != null) + { + menuItem.Image = bitmap; + } + + if (keys != Keys.None) + { + menuItem.ShortcutKeys = keys; + } + + if (clickEvent != null) + { + menuItem.Click += new WeakEventHandler(clickEvent).Handler; + } + + Items.Add(menuItem); + + EventHandler toolstripOpening = (sender, args) => + { + menuItem.Enabled = enabled(); + }; + Opening += new WeakEventHandler(toolstripOpening).Handler; + + return menuItem; + } + + public ToolStripSeparator AddToolStripSeparator() + { + var strip = new ToolStripSeparator(); + Items.Add(strip); + + return strip; + } + } +} \ No newline at end of file diff --git a/ICSharpCode.TextEditor/Project/UserControls/FindAndReplaceForm.cs b/ICSharpCode.TextEditor/Project/UserControls/FindAndReplaceForm.cs new file mode 100644 index 0000000..e3a3785 --- /dev/null +++ b/ICSharpCode.TextEditor/Project/UserControls/FindAndReplaceForm.cs @@ -0,0 +1,556 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Drawing; +using System.IO; +using System.Windows.Forms; +using ICSharpCode.TextEditor.Document; + +namespace ICSharpCode.TextEditor.UserControls +{ + public partial class FindAndReplaceForm : Form + { + public FindAndReplaceForm() + { + InitializeComponent(); + _search = new TextEditorSearcher(); + } + + readonly TextEditorSearcher _search; + TextEditorControl _editor; + TextEditorControl Editor + { + set + { + _editor = value; + _search.Document = _editor.Document; + UpdateTitleBar(); + } + } + + private void UpdateTitleBar() + { + string text = ReplaceMode ? "Find & replace" : "Find"; + if (_editor != null && _editor.FileName != null) + text += " - " + Path.GetFileName(_editor.FileName); + if (_search.HasScanRegion) + text += " (selection only)"; + Text = text; + } + + public void ShowFor(TextEditorControl editor, bool replaceMode) + { + Editor = editor; + + _search.ClearScanRegion(); + var sm = editor.ActiveTextAreaControl.SelectionManager; + if (sm.HasSomethingSelected && sm.SelectionCollection.Count == 1) + { + var sel = sm.SelectionCollection[0]; + if (sel.StartPosition.Line == sel.EndPosition.Line) + txtLookFor.Text = sm.SelectedText; + else + _search.SetScanRegion(sel); + } + else + { + // Get the current word that the caret is on + Caret caret = editor.ActiveTextAreaControl.Caret; + int start = TextUtilities.FindWordStart(editor.Document, caret.Offset); + int endAt = TextUtilities.FindWordEnd(editor.Document, caret.Offset); + txtLookFor.Text = editor.Document.GetText(start, endAt - start); + } + + ReplaceMode = replaceMode; + + Owner = (Form)editor.TopLevelControl; + Show(); + + txtLookFor.SelectAll(); + txtLookFor.Focus(); + } + + public bool ReplaceMode + { + get { return txtReplaceWith.Visible; } + set + { + btnReplace.Visible = btnReplaceAll.Visible = value; + lblReplaceWith.Visible = txtReplaceWith.Visible = value; + btnHighlightAll.Visible = !value; + AcceptButton = value ? btnReplace : btnFindNext; + UpdateTitleBar(); + } + } + + private void btnFindPrevious_Click(object sender, EventArgs e) + { + FindNext(false, true, "Text not found"); + } + private void btnFindNext_Click(object sender, EventArgs e) + { + FindNext(false, false, "Text not found"); + } + + public bool LastSearchWasBackward = false; + public bool LastSearchLoopedAround; + + public TextRange FindNext(bool viaF3, bool searchBackward, string messageIfNotFound) + { + if (string.IsNullOrEmpty(txtLookFor.Text)) + { + MessageBox.Show("No string specified to look for!"); + return null; + } + LastSearchWasBackward = searchBackward; + _search.LookFor = txtLookFor.Text; + _search.MatchCase = chkMatchCase.Checked; + _search.MatchWholeWordOnly = chkMatchWholeWord.Checked; + + var caret = _editor.ActiveTextAreaControl.Caret; + if (viaF3 && _search.HasScanRegion && !caret.Offset. + IsInRange(_search.BeginOffset, _search.EndOffset)) + { + // user moved outside of the originally selected region + _search.ClearScanRegion(); + UpdateTitleBar(); + } + + int startFrom = caret.Offset - (searchBackward ? 1 : 0); + TextRange range = _search.FindNext(startFrom, searchBackward, out LastSearchLoopedAround); + if (range != null) + SelectResult(range); + else if (messageIfNotFound != null) + MessageBox.Show(messageIfNotFound); + return range; + } + + private void SelectResult(TextRange range) + { + TextLocation p1 = _editor.Document.OffsetToPosition(range.Offset); + TextLocation p2 = _editor.Document.OffsetToPosition(range.Offset + range.Length); + _editor.ActiveTextAreaControl.SelectionManager.SetSelection(p1, p2); + _editor.ActiveTextAreaControl.ScrollTo(p1.Line, p1.Column); + // Also move the caret to the end of the selection, because when the user + // presses F3, the caret is where we start searching next time. + _editor.ActiveTextAreaControl.Caret.Position = + _editor.Document.OffsetToPosition(range.Offset + range.Length); + } + + readonly Dictionary _highlightGroups = new Dictionary(); + + private void btnHighlightAll_Click(object sender, EventArgs e) + { + if (!_highlightGroups.ContainsKey(_editor)) + _highlightGroups[_editor] = new HighlightGroup(_editor); + HighlightGroup group = _highlightGroups[_editor]; + + if (string.IsNullOrEmpty(LookFor)) + // Clear highlights + group.ClearMarkers(); + else + { + _search.LookFor = txtLookFor.Text; + _search.MatchCase = chkMatchCase.Checked; + _search.MatchWholeWordOnly = chkMatchWholeWord.Checked; + + int offset = 0, count = 0; + for (; ; ) + { + bool looped; + TextRange range = _search.FindNext(offset, false, out looped); + if (range == null || looped) + break; + offset = range.Offset + range.Length; + count++; + + var m = new TextMarker(range.Offset, range.Length, + TextMarkerType.SolidBlock, Color.Yellow, Color.Black); + group.AddMarker(m); + } + if (count == 0) + MessageBox.Show("Search text not found."); + else + Close(); + } + } + + private void FindAndReplaceForm_FormClosing(object sender, FormClosingEventArgs e) + { // Prevent dispose, as this form can be re-used + if (e.CloseReason != CloseReason.FormOwnerClosing) + { + if (Owner != null) + Owner.Select(); // prevent another app from being activated instead + + e.Cancel = true; + Hide(); + + // Discard search region + _search.ClearScanRegion(); + _editor.Refresh(); // must repaint manually + } + } + + private void btnCancel_Click(object sender, EventArgs e) + { + Close(); + } + + private void btnReplace_Click(object sender, EventArgs e) + { + var sm = _editor.ActiveTextAreaControl.SelectionManager; + if (string.Equals(sm.SelectedText, txtLookFor.Text, StringComparison.OrdinalIgnoreCase)) + InsertText(txtReplaceWith.Text); + FindNext(false, LastSearchWasBackward, "Text not found."); + } + + private void btnReplaceAll_Click(object sender, EventArgs e) + { + int count = 0; + // BUG FIX: if the replacement string contains the original search string + // (e.g. replace "red" with "very red") we must avoid looping around and + // replacing forever! To fix, start replacing at beginning of region (by + // moving the caret) and stop as soon as we loop around. + _editor.ActiveTextAreaControl.Caret.Position = + _editor.Document.OffsetToPosition(_search.BeginOffset); + + _editor.Document.UndoStack.StartUndoGroup(); + try + { + while (FindNext(false, false, null) != null) + { + if (LastSearchLoopedAround) + break; + + // Replace + count++; + InsertText(txtReplaceWith.Text); + } + } + finally + { + _editor.Document.UndoStack.EndUndoGroup(); + } + if (count == 0) + MessageBox.Show("No occurrances found."); + else + { + MessageBox.Show(string.Format("Replaced {0} occurrances.", count)); + Close(); + } + } + + private void InsertText(string text) + { + var textArea = _editor.ActiveTextAreaControl.TextArea; + textArea.Document.UndoStack.StartUndoGroup(); + try + { + if (textArea.SelectionManager.HasSomethingSelected) + { + textArea.Caret.Position = textArea.SelectionManager.SelectionCollection[0].StartPosition; + textArea.SelectionManager.RemoveSelectedText(); + } + + textArea.InsertString(text); + } + finally + { + textArea.Document.UndoStack.EndUndoGroup(); + } + } + + public string LookFor { get { return txtLookFor.Text; } } + } + + public class TextRange : AbstractSegment + { + public TextRange(int offset, int length) + { + this.offset = offset; + this.length = length; + } + } + + /// This class finds occurrances of a search string in a text + /// editor's IDocument... it's like Find box without a GUI. + public class TextEditorSearcher : IDisposable + { + IDocument _document; + public IDocument Document + { + get { return _document; } + set + { + if (_document != value) + { + ClearScanRegion(); + _document = value; + } + } + } + + // I would have used the TextAnchor class to represent the beginning and + // end of the region to scan while automatically adjusting to changes in + // the document--but for some reason it is sealed and its constructor is + // internal. Instead I use a TextMarker, which is perhaps even better as + // it gives me the opportunity to highlight the region. Note that all the + // markers and coloring information is associated with the text document, + // not the editor control, so TextEditorSearcher doesn't need a reference + // to the TextEditorControl. After adding the marker to the document, we + // must remember to remove it when it is no longer needed. + TextMarker _region; + /// Sets the region to search. The region is updated + /// automatically as the document changes. + public void SetScanRegion(ISelection sel) + { + SetScanRegion(sel.Offset, sel.Length); + } + + /// Sets the region to search. The region is updated + /// automatically as the document changes. + public void SetScanRegion(int offset, int length) + { + var bkgColor = _document.HighlightingStrategy.GetColorFor("Default").BackgroundColor; + _region = new TextMarker(offset, length, TextMarkerType.SolidBlock, + bkgColor.HalfMix(Color.FromArgb(160, 160, 160))); + _document.MarkerStrategy.AddMarker(_region); + } + + public bool HasScanRegion + { + get { return _region != null; } + } + + public void ClearScanRegion() + { + if (_region != null) + { + _document.MarkerStrategy.RemoveMarker(_region); + _region = null; + } + } + + public void Dispose() + { + ClearScanRegion(); + GC.SuppressFinalize(this); + } + + ~TextEditorSearcher() + { + Dispose(); + } + + /// Begins the start offset for searching + public int BeginOffset + { + get + { + if (_region != null) + return _region.Offset; + return 0; + } + } + /// Begins the end offset for searching + public int EndOffset + { + get + { + if (_region != null) + return _region.EndOffset; + return _document.TextLength; + } + } + + public bool MatchCase; + + public bool MatchWholeWordOnly; + + string _lookFor; + string _lookFor2; // uppercase in case-insensitive mode + public string LookFor + { + get { return _lookFor; } + set { _lookFor = value; } + } + + /// Finds next instance of LookFor, according to the search rules + /// (MatchCase, MatchWholeWordOnly). + /// Offset in Document at which to begin the search + /// + /// + /// If there is a match at beginAtOffset precisely, it will be returned. + /// Region of document that matches the search string + public TextRange FindNext(int beginAtOffset, bool searchBackward, out bool loopedAround) + { + Debug.Assert(!string.IsNullOrEmpty(_lookFor)); + loopedAround = false; + + int startAt = BeginOffset, endAt = EndOffset; + int curOffs = beginAtOffset.InRange(startAt, endAt); + + _lookFor2 = MatchCase ? _lookFor : _lookFor.ToUpperInvariant(); + + TextRange result; + if (searchBackward) + { + result = FindNextIn(startAt, curOffs, true); + if (result == null) + { + loopedAround = true; + result = FindNextIn(curOffs, endAt, true); + } + } + else + { + result = FindNextIn(curOffs, endAt, false); + if (result == null) + { + loopedAround = true; + result = FindNextIn(startAt, curOffs, false); + } + } + return result; + } + + private TextRange FindNextIn(int offset1, int offset2, bool searchBackward) + { + Debug.Assert(offset2 >= offset1); + offset2 -= _lookFor.Length; + + // Make behavior decisions before starting search loop + Func matchFirstCh; + Func matchWord; + if (MatchCase) + matchFirstCh = (lookFor, c) => (lookFor == c); + else + matchFirstCh = (lookFor, c) => (lookFor == char.ToUpperInvariant(c)); + if (MatchWholeWordOnly) + matchWord = IsWholeWordMatch; + else + matchWord = IsPartWordMatch; + + // Search + char lookForCh = _lookFor2[0]; + if (searchBackward) + { + for (int offset = offset2; offset >= offset1; offset--) + { + if (matchFirstCh(lookForCh, _document.GetCharAt(offset)) + && matchWord(offset)) + return new TextRange(offset, _lookFor.Length); + } + } + else + { + for (int offset = offset1; offset <= offset2; offset++) + { + if (matchFirstCh(lookForCh, _document.GetCharAt(offset)) + && matchWord(offset)) + return new TextRange(offset, _lookFor.Length); + } + } + return null; + } + + private bool IsWholeWordMatch(int offset) + { + if (IsWordBoundary(offset) && IsWordBoundary(offset + _lookFor.Length)) + return IsPartWordMatch(offset); + + return false; + } + + private bool IsWordBoundary(int offset) + { + return offset <= 0 || offset >= _document.TextLength || + !IsAlphaNumeric(offset - 1) || !IsAlphaNumeric(offset); + } + + private bool IsAlphaNumeric(int offset) + { + char c = _document.GetCharAt(offset); + return char.IsLetterOrDigit(c) || c == '_'; + } + + private bool IsPartWordMatch(int offset) + { + string substr = _document.GetText(offset, _lookFor.Length); + if (!MatchCase) + substr = substr.ToUpperInvariant(); + return substr == _lookFor2; + } + } + + /// Bundles a group of markers together so that they can be cleared together. + public class HighlightGroup : IDisposable + { + readonly List _markers = new List(); + readonly TextEditorControl _editor; + readonly IDocument _document; + + public HighlightGroup(TextEditorControl editor) + { + _editor = editor; + _document = editor.Document; + } + + public void AddMarker(TextMarker marker) + { + _markers.Add(marker); + _document.MarkerStrategy.AddMarker(marker); + } + + public void ClearMarkers() + { + foreach (TextMarker m in _markers) + _document.MarkerStrategy.RemoveMarker(m); + + _markers.Clear(); + _editor.Refresh(); + } + + public void Dispose() + { + ClearMarkers(); + GC.SuppressFinalize(this); + } + + ~HighlightGroup() + { + Dispose(); + } + + public IList Markers + { + get + { + return _markers.AsReadOnly(); + } + } + } + + public static class Extensions + { + public static int InRange(this int x, int lo, int hi) + { + Debug.Assert(lo <= hi); + return x < lo ? lo : (x > hi ? hi : x); + } + + public static bool IsInRange(this int x, int lo, int hi) + { + return x >= lo && x <= hi; + } + + public static Color HalfMix(this Color one, Color two) + { + return Color.FromArgb( + (one.A + two.A) >> 1, + (one.R + two.R) >> 1, + (one.G + two.G) >> 1, + (one.B + two.B) >> 1); + } + } +} diff --git a/ICSharpCode.TextEditor/Project/UserControls/FindAndReplaceForm.designer.cs b/ICSharpCode.TextEditor/Project/UserControls/FindAndReplaceForm.designer.cs new file mode 100644 index 0000000..5cf232a --- /dev/null +++ b/ICSharpCode.TextEditor/Project/UserControls/FindAndReplaceForm.designer.cs @@ -0,0 +1,216 @@ +namespace ICSharpCode.TextEditor.UserControls +{ + partial class FindAndReplaceForm + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(FindAndReplaceForm)); + this.label1 = new System.Windows.Forms.Label(); + this.lblReplaceWith = new System.Windows.Forms.Label(); + this.txtLookFor = new System.Windows.Forms.TextBox(); + this.txtReplaceWith = new System.Windows.Forms.TextBox(); + this.btnFindNext = new System.Windows.Forms.Button(); + this.btnReplace = new System.Windows.Forms.Button(); + this.btnReplaceAll = new System.Windows.Forms.Button(); + this.chkMatchWholeWord = new System.Windows.Forms.CheckBox(); + this.chkMatchCase = new System.Windows.Forms.CheckBox(); + this.btnHighlightAll = new System.Windows.Forms.Button(); + this.btnCancel = new System.Windows.Forms.Button(); + this.btnFindPrevious = new System.Windows.Forms.Button(); + this.SuspendLayout(); + // + // label1 + // + this.label1.AutoSize = true; + this.label1.Location = new System.Drawing.Point(12, 9); + this.label1.Name = "label1"; + this.label1.Size = new System.Drawing.Size(56, 13); + this.label1.TabIndex = 0; + this.label1.Text = "Fi&nd what:"; + // + // lblReplaceWith + // + this.lblReplaceWith.AutoSize = true; + this.lblReplaceWith.Location = new System.Drawing.Point(12, 35); + this.lblReplaceWith.Name = "lblReplaceWith"; + this.lblReplaceWith.Size = new System.Drawing.Size(72, 13); + this.lblReplaceWith.TabIndex = 2; + this.lblReplaceWith.Text = "Re&place with:"; + // + // txtLookFor + // + this.txtLookFor.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.txtLookFor.Location = new System.Drawing.Point(90, 6); + this.txtLookFor.Name = "txtLookFor"; + this.txtLookFor.Size = new System.Drawing.Size(232, 20); + this.txtLookFor.TabIndex = 1; + // + // txtReplaceWith + // + this.txtReplaceWith.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.txtReplaceWith.Location = new System.Drawing.Point(90, 32); + this.txtReplaceWith.Name = "txtReplaceWith"; + this.txtReplaceWith.Size = new System.Drawing.Size(232, 20); + this.txtReplaceWith.TabIndex = 3; + // + // btnFindNext + // + this.btnFindNext.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); + this.btnFindNext.Location = new System.Drawing.Point(247, 81); + this.btnFindNext.Name = "btnFindNext"; + this.btnFindNext.Size = new System.Drawing.Size(75, 23); + this.btnFindNext.TabIndex = 6; + this.btnFindNext.Text = "&Find next"; + this.btnFindNext.UseVisualStyleBackColor = true; + this.btnFindNext.Click += new System.EventHandler(this.btnFindNext_Click); + // + // btnReplace + // + this.btnReplace.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); + this.btnReplace.Location = new System.Drawing.Point(85, 110); + this.btnReplace.Name = "btnReplace"; + this.btnReplace.Size = new System.Drawing.Size(75, 23); + this.btnReplace.TabIndex = 7; + this.btnReplace.Text = "&Replace"; + this.btnReplace.UseVisualStyleBackColor = true; + this.btnReplace.Click += new System.EventHandler(this.btnReplace_Click); + // + // btnReplaceAll + // + this.btnReplaceAll.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); + this.btnReplaceAll.Location = new System.Drawing.Point(166, 110); + this.btnReplaceAll.Name = "btnReplaceAll"; + this.btnReplaceAll.Size = new System.Drawing.Size(75, 23); + this.btnReplaceAll.TabIndex = 9; + this.btnReplaceAll.Text = "Replace &All"; + this.btnReplaceAll.UseVisualStyleBackColor = true; + this.btnReplaceAll.Click += new System.EventHandler(this.btnReplaceAll_Click); + // + // chkMatchWholeWord + // + this.chkMatchWholeWord.AutoSize = true; + this.chkMatchWholeWord.Location = new System.Drawing.Point(178, 58); + this.chkMatchWholeWord.Name = "chkMatchWholeWord"; + this.chkMatchWholeWord.Size = new System.Drawing.Size(113, 17); + this.chkMatchWholeWord.TabIndex = 5; + this.chkMatchWholeWord.Text = "Match &whole word"; + this.chkMatchWholeWord.UseVisualStyleBackColor = true; + // + // chkMatchCase + // + this.chkMatchCase.AutoSize = true; + this.chkMatchCase.Location = new System.Drawing.Point(90, 58); + this.chkMatchCase.Name = "chkMatchCase"; + this.chkMatchCase.Size = new System.Drawing.Size(82, 17); + this.chkMatchCase.TabIndex = 4; + this.chkMatchCase.Text = "Match &case"; + this.chkMatchCase.UseVisualStyleBackColor = true; + // + // btnHighlightAll + // + this.btnHighlightAll.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); + this.btnHighlightAll.Location = new System.Drawing.Point(105, 110); + this.btnHighlightAll.Name = "btnHighlightAll"; + this.btnHighlightAll.Size = new System.Drawing.Size(136, 23); + this.btnHighlightAll.TabIndex = 8; + this.btnHighlightAll.Text = "Find && highlight &all"; + this.btnHighlightAll.UseVisualStyleBackColor = true; + this.btnHighlightAll.Visible = false; + this.btnHighlightAll.Click += new System.EventHandler(this.btnHighlightAll_Click); + // + // btnCancel + // + this.btnCancel.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); + this.btnCancel.DialogResult = System.Windows.Forms.DialogResult.Cancel; + this.btnCancel.Location = new System.Drawing.Point(247, 110); + this.btnCancel.Name = "btnCancel"; + this.btnCancel.Size = new System.Drawing.Size(75, 23); + this.btnCancel.TabIndex = 6; + this.btnCancel.Text = "Cancel"; + this.btnCancel.UseVisualStyleBackColor = true; + this.btnCancel.Click += new System.EventHandler(this.btnCancel_Click); + // + // btnFindPrevious + // + this.btnFindPrevious.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); + this.btnFindPrevious.Location = new System.Drawing.Point(157, 81); + this.btnFindPrevious.Name = "btnFindPrevious"; + this.btnFindPrevious.Size = new System.Drawing.Size(84, 23); + this.btnFindPrevious.TabIndex = 6; + this.btnFindPrevious.Text = "Find pre&vious"; + this.btnFindPrevious.UseVisualStyleBackColor = true; + this.btnFindPrevious.Click += new System.EventHandler(this.btnFindPrevious_Click); + // + // FindAndReplaceForm + // + this.AcceptButton = this.btnReplace; + this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.CancelButton = this.btnCancel; + this.ClientSize = new System.Drawing.Size(334, 145); + this.Controls.Add(this.chkMatchCase); + this.Controls.Add(this.chkMatchWholeWord); + this.Controls.Add(this.btnReplaceAll); + this.Controls.Add(this.btnReplace); + this.Controls.Add(this.btnHighlightAll); + this.Controls.Add(this.btnCancel); + this.Controls.Add(this.btnFindPrevious); + this.Controls.Add(this.btnFindNext); + this.Controls.Add(this.txtReplaceWith); + this.Controls.Add(this.txtLookFor); + this.Controls.Add(this.lblReplaceWith); + this.Controls.Add(this.label1); + this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog; + this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon"))); + this.MaximizeBox = false; + this.MinimizeBox = false; + this.Name = "FindAndReplaceForm"; + this.Text = "Find and replace"; + this.FormClosing += new System.Windows.Forms.FormClosingEventHandler(this.FindAndReplaceForm_FormClosing); + this.ResumeLayout(false); + this.PerformLayout(); + + } + + #endregion + + private System.Windows.Forms.Label label1; + private System.Windows.Forms.Label lblReplaceWith; + private System.Windows.Forms.TextBox txtLookFor; + private System.Windows.Forms.TextBox txtReplaceWith; + private System.Windows.Forms.Button btnFindNext; + private System.Windows.Forms.Button btnReplace; + private System.Windows.Forms.Button btnReplaceAll; + private System.Windows.Forms.CheckBox chkMatchWholeWord; + private System.Windows.Forms.CheckBox chkMatchCase; + private System.Windows.Forms.Button btnHighlightAll; + private System.Windows.Forms.Button btnCancel; + private System.Windows.Forms.Button btnFindPrevious; + } +} \ No newline at end of file diff --git a/ICSharpCode.TextEditor/Project/UserControls/FindAndReplaceForm.resx b/ICSharpCode.TextEditor/Project/UserControls/FindAndReplaceForm.resx new file mode 100644 index 0000000..f72c3b4 --- /dev/null +++ b/ICSharpCode.TextEditor/Project/UserControls/FindAndReplaceForm.resx @@ -0,0 +1,145 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + + + AAABAAEAEBAAAAAAIABoBAAAFgAAACgAAAAQAAAAIAAAAAEAIAAAAAAAQAQAAAAAAAAAAAAAAAAAAAAA + AAAAAAAnAAAAQQAAAEEAAABBAAAAQQAAAEEAAAAn////Af///wEAAAAnAAAAQQAAAEEAAABBAAAAQQAA + AEEAAAAnFRUVsxsbG/8bGxv/Gxsb/xsbG/8bGxv/FRUVs////wH///8BFRUVsxsbG/8bGxv/Gxsb/xsb + G/8bGxv/FRUVsxkZGf+ZmZn/bm5u/1BQUP9KSkr/Xl5e/xkZGf////8B////ARkZGf9eXl7/SkpK/1BQ + UP9ubm7/mZmZ/xkZGf8NDQ3/UlJS/zs7O/8rKyv/Jycn/zIyMv8NDQ3/////Af///wENDQ3/MjIy/ycn + J/8rKyv/Ozs7/1JSUv8NDQ3/GRkZ/6SkpP9+fn7/Y2Nj/15eXv9wcHD/GRkZ/////wH///8BGRkZ/3Bw + cP9eXl7/Y2Nj/35+fv+kpKT/GRkZ/xkZGf+ZmZn/bm5u/1BQUP9KSkr/Xl5e/xkZGf////8B////ARkZ + Gf9eXl7/SkpK/1BQUP9ubm7/mZmZ/xkZGf8ZGRn/mZmZ/25ubv9QUFD/SkpK/15eXv8ZGRn/////Af// + /wEZGRn/Xl5e/0pKSv9QUFD/bm5u/5mZmf8ZGRn/GRkZ/5mZmf9ubm7/UFBQ/0RERP88PDz/GRkZ/wAA + AEEAAABBGRkZ/zw8PP9ERET/UFBQ/25ubv+ZmZn/GRkZ/xgYGNO4uLj/i4uL/1BQUP87Ozv/GRkZ/woK + Cv8RERH/ERER/woKCv8ZGRn/Ozs7/1BQUP+Li4v/uLi4/xgYGNMZGRl7GRkZzRkZGf8TExP/BAQE/woK + Cv9gYGD/kZGR/5GRkf9gYGD/CgoK/wQEBP8EBAT/CQkJ/xkZGc0ZGRl7////Af///wEZGRn/jY2N/11d + Xf8EBAT/KCgo/0pKSv9KSkr/KCgo/wQEBP9fX1//jY2N/xkZGf////8B////Af///wH///8BGRkZ/42N + jf9dXV3/BgYG/2BgYP+RkZH/kZGR/2BgYP8GBgb/X19f/42Njf8ZGRn/////Af///wH///8B////ARkZ + Gf+1tbX/cHBw/1RUVP8ZGRn/MDAw/zAwMP8ZGRn/VFRU/ykpKf+1tbX/GRkZ/////wH///8B////Af// + /wEcHByHGRkZ/xkZGf8ZGRn/HBwch////wH///8BHBwchxkZGf8ZGRn/GRkZ/xwcHIf///8B////Af// + /wH///8B////ARYWFrukpKT/GRkZ/////wH///8B////Af///wEZGRn/pKSk/xYWFrv///8B////Af// + /wH///8B////Af///wEZGRn/GRkZ/xkZGf////8B////Af///wH///8BGRkZ/xkZGf8ZGRn/////Af// + /wH///8BAAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA + //8AAP//AAD//w== + + + \ No newline at end of file diff --git a/ICSharpCode.TextEditor/Project/UserControls/FormatCodeHtml.cs b/ICSharpCode.TextEditor/Project/UserControls/FormatCodeHtml.cs new file mode 100644 index 0000000..1ba17a0 --- /dev/null +++ b/ICSharpCode.TextEditor/Project/UserControls/FormatCodeHtml.cs @@ -0,0 +1,115 @@ +using System; +using System.Windows.Forms; +using ICSharpCode.TextEditorEx; + +namespace ICSharpCode.TextEditor.UserControls +{ + /// + /// http://minicsharplab.codeplex.com/ + /// + public partial class FormatCodeHtml : Form + { + /// + /// Initializes a new instance of the class. + /// + /// The code to format. + /// The default language. + public FormatCodeHtml(string codeToFormat, string defaultLanguage) + { + CodeToFormat = codeToFormat; + + InitializeComponent(); + + switch (defaultLanguage) + { + case SyntaxModes.CSharp: + rbCSharp.Checked = true; + break; + case SyntaxModes.VBNET: + rbVB.Checked = true; + break; + case SyntaxModes.XML: + rbHtml.Checked = true; + break; + case SyntaxModes.JavaScript: + rbJS.Checked = true; + break; + case SyntaxModes.Java: + rbJava.Checked = true; + break; + case SyntaxModes.SQL: + rbtsql.Checked = true; + break; + case SyntaxModes.CPPNET: + rbCSharp.Checked = true; + break; + default: + rbCSharp.Checked = true; + break; + } + } + + /// + /// Handles the Click event of the btnCopy control. + /// + /// The source of the event. + /// The instance containing the event data. + private void btnCopy_Click(object sender, EventArgs e) + { + /* + SourceFormat sf = null; + + if (rbCSharp.Checked) + { + sf = new CSharpFormat(); + } + else if (rbVB.Checked) + { + sf = new VisualBasicFormat(); + } + else if (rbtsql.Checked) + { + sf = new TsqlFormat(); + } + else if (rbHtml.Checked) + { + sf = new HtmlFormat(); + } + else if (rbJS.Checked) + { + sf = new JavaScriptFormat(); + } + else if (rbJava.Checked) + { + sf = new MshFormat(); + } + else { return; } + + sf.TabSpaces = 4; + sf.LineNumbers = cbLineNumbers.Checked; + sf.EmbedStyleSheet = cbEmbedCss.Checked; + sf.Alternate = cbAlternate.Checked; + string formatedCode = sf.FormatCode(CodeToFormat); + //Clipboard.SetText(formatedCode, TextDataFormat.Html); + Clipboard.SetText(formatedCode);*/ + } + + /// + /// Handles the Click event of the btnClose control. + /// + /// The source of the event. + /// The instance containing the event data. + private void btnClose_Click(object sender, EventArgs e) + { + btnCopy_Click(sender, e); + this.Close(); + } + + /// + /// Gets or sets the code to format. + /// + /// The code to format. + public string CodeToFormat { get; set; } + + } +} diff --git a/ICSharpCode.TextEditor/Project/UserControls/FormatCodeHtml.designer.cs b/ICSharpCode.TextEditor/Project/UserControls/FormatCodeHtml.designer.cs new file mode 100644 index 0000000..14a7140 --- /dev/null +++ b/ICSharpCode.TextEditor/Project/UserControls/FormatCodeHtml.designer.cs @@ -0,0 +1,261 @@ +namespace ICSharpCode.TextEditor.UserControls +{ + partial class FormatCodeHtml + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) { + if (disposing && (components != null)) { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() { + this.btnClose = new System.Windows.Forms.Button(); + this.btnCopy = new System.Windows.Forms.Button(); + this.gbLanguage = new System.Windows.Forms.GroupBox(); + this.flowLayoutPanel2 = new System.Windows.Forms.FlowLayoutPanel(); + this.rbCSharp = new System.Windows.Forms.RadioButton(); + this.rbVB = new System.Windows.Forms.RadioButton(); + this.rbHtml = new System.Windows.Forms.RadioButton(); + this.rbtsql = new System.Windows.Forms.RadioButton(); + this.rbJS = new System.Windows.Forms.RadioButton(); + this.gbSettings = new System.Windows.Forms.GroupBox(); + this.flowLayoutPanel1 = new System.Windows.Forms.FlowLayoutPanel(); + this.cbLineNumbers = new System.Windows.Forms.CheckBox(); + this.cbEmbedCss = new System.Windows.Forms.CheckBox(); + this.cbAlternate = new System.Windows.Forms.CheckBox(); + this.rbJava = new System.Windows.Forms.RadioButton(); + this.gbLanguage.SuspendLayout(); + this.flowLayoutPanel2.SuspendLayout(); + this.gbSettings.SuspendLayout(); + this.flowLayoutPanel1.SuspendLayout(); + this.SuspendLayout(); + // + // btnClose + // + this.btnClose.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); + this.btnClose.Location = new System.Drawing.Point(261, 150); + this.btnClose.Name = "btnClose"; + this.btnClose.Size = new System.Drawing.Size(91, 23); + this.btnClose.TabIndex = 0; + this.btnClose.Text = "Copy && Close"; + this.btnClose.UseVisualStyleBackColor = true; + this.btnClose.Click += new System.EventHandler(this.btnClose_Click); + // + // btnCopy + // + this.btnCopy.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); + this.btnCopy.Location = new System.Drawing.Point(180, 150); + this.btnCopy.Name = "btnCopy"; + this.btnCopy.Size = new System.Drawing.Size(75, 23); + this.btnCopy.TabIndex = 1; + this.btnCopy.Text = "Copy"; + this.btnCopy.UseVisualStyleBackColor = true; + this.btnCopy.Click += new System.EventHandler(this.btnCopy_Click); + // + // gbLanguage + // + this.gbLanguage.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) + | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.gbLanguage.Controls.Add(this.flowLayoutPanel2); + this.gbLanguage.Location = new System.Drawing.Point(12, 12); + this.gbLanguage.Name = "gbLanguage"; + this.gbLanguage.Size = new System.Drawing.Size(340, 75); + this.gbLanguage.TabIndex = 2; + this.gbLanguage.TabStop = false; + this.gbLanguage.Text = "Language"; + // + // flowLayoutPanel2 + // + this.flowLayoutPanel2.Controls.Add(this.rbCSharp); + this.flowLayoutPanel2.Controls.Add(this.rbVB); + this.flowLayoutPanel2.Controls.Add(this.rbHtml); + this.flowLayoutPanel2.Controls.Add(this.rbtsql); + this.flowLayoutPanel2.Controls.Add(this.rbJS); + this.flowLayoutPanel2.Controls.Add(this.rbJava); + this.flowLayoutPanel2.Dock = System.Windows.Forms.DockStyle.Fill; + this.flowLayoutPanel2.Location = new System.Drawing.Point(3, 16); + this.flowLayoutPanel2.Name = "flowLayoutPanel2"; + this.flowLayoutPanel2.Padding = new System.Windows.Forms.Padding(5); + this.flowLayoutPanel2.Size = new System.Drawing.Size(334, 56); + this.flowLayoutPanel2.TabIndex = 12; + // + // rbCSharp + // + this.rbCSharp.AutoSize = true; + this.rbCSharp.Checked = true; + this.rbCSharp.Location = new System.Drawing.Point(8, 8); + this.rbCSharp.Name = "rbCSharp"; + this.rbCSharp.Size = new System.Drawing.Size(39, 17); + this.rbCSharp.TabIndex = 5; + this.rbCSharp.TabStop = true; + this.rbCSharp.Text = "C#"; + this.rbCSharp.UseVisualStyleBackColor = true; + // + // rbVB + // + this.rbVB.AutoSize = true; + this.rbVB.Location = new System.Drawing.Point(53, 8); + this.rbVB.Name = "rbVB"; + this.rbVB.Size = new System.Drawing.Size(64, 17); + this.rbVB.TabIndex = 4; + this.rbVB.Text = "VB.NET"; + this.rbVB.UseVisualStyleBackColor = true; + // + // rbHtml + // + this.rbHtml.AutoSize = true; + this.rbHtml.Location = new System.Drawing.Point(123, 8); + this.rbHtml.Name = "rbHtml"; + this.rbHtml.Size = new System.Drawing.Size(73, 17); + this.rbHtml.TabIndex = 6; + this.rbHtml.Text = "Html/XML"; + this.rbHtml.UseVisualStyleBackColor = true; + // + // rbtsql + // + this.rbtsql.AutoSize = true; + this.rbtsql.Location = new System.Drawing.Point(202, 8); + this.rbtsql.Name = "rbtsql"; + this.rbtsql.Size = new System.Drawing.Size(56, 17); + this.rbtsql.TabIndex = 3; + this.rbtsql.Text = "T-SQL"; + this.rbtsql.UseVisualStyleBackColor = true; + // + // rbJS + // + this.rbJS.AutoSize = true; + this.rbJS.Location = new System.Drawing.Point(8, 31); + this.rbJS.Name = "rbJS"; + this.rbJS.Size = new System.Drawing.Size(75, 17); + this.rbJS.TabIndex = 7; + this.rbJS.Text = "JavaScript"; + this.rbJS.UseVisualStyleBackColor = true; + // + // gbSettings + // + this.gbSettings.Controls.Add(this.flowLayoutPanel1); + this.gbSettings.Location = new System.Drawing.Point(12, 95); + this.gbSettings.Name = "gbSettings"; + this.gbSettings.Size = new System.Drawing.Size(337, 51); + this.gbSettings.TabIndex = 3; + this.gbSettings.TabStop = false; + this.gbSettings.Text = "Settings"; + // + // flowLayoutPanel1 + // + this.flowLayoutPanel1.Controls.Add(this.cbLineNumbers); + this.flowLayoutPanel1.Controls.Add(this.cbEmbedCss); + this.flowLayoutPanel1.Controls.Add(this.cbAlternate); + this.flowLayoutPanel1.Dock = System.Windows.Forms.DockStyle.Fill; + this.flowLayoutPanel1.Location = new System.Drawing.Point(3, 16); + this.flowLayoutPanel1.Name = "flowLayoutPanel1"; + this.flowLayoutPanel1.Padding = new System.Windows.Forms.Padding(5); + this.flowLayoutPanel1.Size = new System.Drawing.Size(331, 32); + this.flowLayoutPanel1.TabIndex = 1; + // + // cbLineNumbers + // + this.cbLineNumbers.AutoSize = true; + this.cbLineNumbers.Location = new System.Drawing.Point(8, 8); + this.cbLineNumbers.Name = "cbLineNumbers"; + this.cbLineNumbers.Size = new System.Drawing.Size(91, 17); + this.cbLineNumbers.TabIndex = 0; + this.cbLineNumbers.Text = "Line Numbers"; + this.cbLineNumbers.UseVisualStyleBackColor = true; + // + // cbEmbedCss + // + this.cbEmbedCss.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.cbEmbedCss.AutoSize = true; + this.cbEmbedCss.Location = new System.Drawing.Point(105, 8); + this.cbEmbedCss.Name = "cbEmbedCss"; + this.cbEmbedCss.Size = new System.Drawing.Size(79, 17); + this.cbEmbedCss.TabIndex = 1; + this.cbEmbedCss.Text = "Embed Css"; + this.cbEmbedCss.UseVisualStyleBackColor = true; + // + // cbAlternate + // + this.cbAlternate.AutoSize = true; + this.cbAlternate.Location = new System.Drawing.Point(190, 8); + this.cbAlternate.Name = "cbAlternate"; + this.cbAlternate.Size = new System.Drawing.Size(123, 17); + this.cbAlternate.TabIndex = 2; + this.cbAlternate.Text = "Alternate Line Colors"; + this.cbAlternate.UseVisualStyleBackColor = true; + // + // rbJava + // + this.rbJava.AutoSize = true; + this.rbJava.Location = new System.Drawing.Point(89, 31); + this.rbJava.Name = "rbJava"; + this.rbJava.Size = new System.Drawing.Size(48, 17); + this.rbJava.TabIndex = 8; + this.rbJava.Text = "Java"; + this.rbJava.UseVisualStyleBackColor = true; + // + // FormatCodeHtml + // + this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.ClientSize = new System.Drawing.Size(364, 185); + this.Controls.Add(this.gbSettings); + this.Controls.Add(this.gbLanguage); + this.Controls.Add(this.btnCopy); + this.Controls.Add(this.btnClose); + this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog; + this.MaximizeBox = false; + this.MinimizeBox = false; + this.Name = "FormatCodeHtml"; + this.ShowIcon = false; + this.ShowInTaskbar = false; + this.SizeGripStyle = System.Windows.Forms.SizeGripStyle.Hide; + this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent; + this.Text = "Copy Code as Html"; + this.gbLanguage.ResumeLayout(false); + this.flowLayoutPanel2.ResumeLayout(false); + this.flowLayoutPanel2.PerformLayout(); + this.gbSettings.ResumeLayout(false); + this.flowLayoutPanel1.ResumeLayout(false); + this.flowLayoutPanel1.PerformLayout(); + this.ResumeLayout(false); + + } + + #endregion + + private System.Windows.Forms.Button btnClose; + private System.Windows.Forms.Button btnCopy; + private System.Windows.Forms.GroupBox gbLanguage; + private System.Windows.Forms.GroupBox gbSettings; + private System.Windows.Forms.FlowLayoutPanel flowLayoutPanel1; + private System.Windows.Forms.FlowLayoutPanel flowLayoutPanel2; + private System.Windows.Forms.RadioButton rbCSharp; + private System.Windows.Forms.RadioButton rbVB; + private System.Windows.Forms.RadioButton rbHtml; + private System.Windows.Forms.RadioButton rbtsql; + private System.Windows.Forms.RadioButton rbJS; + private System.Windows.Forms.CheckBox cbLineNumbers; + private System.Windows.Forms.CheckBox cbEmbedCss; + private System.Windows.Forms.CheckBox cbAlternate; + private System.Windows.Forms.RadioButton rbJava; + } +} \ No newline at end of file diff --git a/ICSharpCode.TextEditor/Project/UserControls/FormatCodeHtml.resx b/ICSharpCode.TextEditor/Project/UserControls/FormatCodeHtml.resx new file mode 100644 index 0000000..d58980a --- /dev/null +++ b/ICSharpCode.TextEditor/Project/UserControls/FormatCodeHtml.resx @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/ICSharpCode.TextEditor/Project/UserControls/GotoForm.Designer.cs b/ICSharpCode.TextEditor/Project/UserControls/GotoForm.Designer.cs new file mode 100644 index 0000000..d09f4ca --- /dev/null +++ b/ICSharpCode.TextEditor/Project/UserControls/GotoForm.Designer.cs @@ -0,0 +1,106 @@ +namespace ICSharpCode.TextEditor.UserControls +{ + partial class GotoForm + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(GotoForm)); + this.lblLineNumber = new System.Windows.Forms.Label(); + this.btnOk = new System.Windows.Forms.Button(); + this.btnCancel = new System.Windows.Forms.Button(); + this.txtLineNumber = new ICSharpCode.TextEditor.UserControls.Int32TextBox(); + this.SuspendLayout(); + // + // lblLineNumber + // + this.lblLineNumber.AutoSize = true; + this.lblLineNumber.Location = new System.Drawing.Point(13, 13); + this.lblLineNumber.Name = "lblLineNumber"; + this.lblLineNumber.Size = new System.Drawing.Size(110, 13); + this.lblLineNumber.TabIndex = 0; + this.lblLineNumber.Text = "Line number (1 - 999):"; + // + // btnOk + // + this.btnOk.DialogResult = System.Windows.Forms.DialogResult.OK; + this.btnOk.Location = new System.Drawing.Point(114, 56); + this.btnOk.Name = "btnOk"; + this.btnOk.Size = new System.Drawing.Size(75, 23); + this.btnOk.TabIndex = 2; + this.btnOk.Text = "OK"; + this.btnOk.UseVisualStyleBackColor = true; + // + // btnCancel + // + this.btnCancel.DialogResult = System.Windows.Forms.DialogResult.Cancel; + this.btnCancel.Location = new System.Drawing.Point(195, 56); + this.btnCancel.Name = "btnCancel"; + this.btnCancel.Size = new System.Drawing.Size(75, 23); + this.btnCancel.TabIndex = 3; + this.btnCancel.Text = "Cancel"; + this.btnCancel.UseVisualStyleBackColor = true; + // + // txtLineNumber + // + this.txtLineNumber.Location = new System.Drawing.Point(16, 30); + this.txtLineNumber.Max = 2147483647; + this.txtLineNumber.Min = 1; + this.txtLineNumber.Name = "txtLineNumber"; + this.txtLineNumber.Size = new System.Drawing.Size(254, 20); + this.txtLineNumber.TabIndex = 1; + this.txtLineNumber.Text = "1"; + // + // GotoForm + // + this.AcceptButton = this.btnOk; + this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.CancelButton = this.btnCancel; + this.ClientSize = new System.Drawing.Size(282, 87); + this.Controls.Add(this.btnCancel); + this.Controls.Add(this.btnOk); + this.Controls.Add(this.txtLineNumber); + this.Controls.Add(this.lblLineNumber); + this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog; + this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon"))); + this.MaximizeBox = false; + this.MinimizeBox = false; + this.Name = "GotoForm"; + this.Text = "Go To Line"; + this.ResumeLayout(false); + this.PerformLayout(); + + } + + #endregion + + private System.Windows.Forms.Label lblLineNumber; + private System.Windows.Forms.Button btnOk; + private System.Windows.Forms.Button btnCancel; + private Int32TextBox txtLineNumber; + } +} \ No newline at end of file diff --git a/ICSharpCode.TextEditor/Project/UserControls/GotoForm.cs b/ICSharpCode.TextEditor/Project/UserControls/GotoForm.cs new file mode 100644 index 0000000..3e02175 --- /dev/null +++ b/ICSharpCode.TextEditor/Project/UserControls/GotoForm.cs @@ -0,0 +1,67 @@ +using System.Globalization; +using System.Windows.Forms; + +namespace ICSharpCode.TextEditor.UserControls +{ + public partial class GotoForm : Form + { + private int _firstLineNumber; + public int FirstLineNumber + { + get { return _firstLineNumber; } + set + { + _firstLineNumber = value; + UpdateLineNumberLabel(); + } + } + + private int _lastLineNumber; + public int LastLineNumber + { + get { return _lastLineNumber; } + set + { + _lastLineNumber = value; + UpdateLineNumberLabel(); + } + } + + public int SelectedLineNumber + { + get + { + int selectedLineNumber; + if (!string.IsNullOrEmpty(txtLineNumber.Text) && int.TryParse(txtLineNumber.Text, out selectedLineNumber)) + { + return selectedLineNumber; + } + + return 0; + } + + set + { + txtLineNumber.Text = value.ToString(CultureInfo.InvariantCulture); + } + } + + public GotoForm() + { + InitializeComponent(); + } + + public DialogResult ShowDialogEx() + { + txtLineNumber.Min = _firstLineNumber; + txtLineNumber.Max = _lastLineNumber; + + return ShowDialog(); + } + + private void UpdateLineNumberLabel() + { + lblLineNumber.Text = string.Format("Line number ({0} - {1}):", _firstLineNumber, _lastLineNumber); + } + } +} \ No newline at end of file diff --git a/ICSharpCode.TextEditor/Project/UserControls/GotoForm.resx b/ICSharpCode.TextEditor/Project/UserControls/GotoForm.resx new file mode 100644 index 0000000..0c12da8 --- /dev/null +++ b/ICSharpCode.TextEditor/Project/UserControls/GotoForm.resx @@ -0,0 +1,145 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + + + AAABAAEAEBAAAAAAIABoBAAAFgAAACgAAAAQAAAAIAAAAAEAIAAAAAAAQAQAAAAAAAAAAAAAAAAAAAAA + AAD///8B////Af///wH///8B////Af///wH///8B////Af///wH///8B////Af///wH///8B////Af// + /wH///8B////Af///wH///8B////Af///wH///8B////Af///wH///8B////AWcsBBf///8B////Af// + /wH///8B////Af///wH///8B////Af///wH///8B////Af///wH///8B////AaZODyuYRxBtmEcOL/// + /wH///8B////Af///wH///8B////Af///wH///8B////Af///wH///8B////Af///wGtWBtHl0oW/6NR + GW2eUBkv////Af///wH///8B////Af///wH///8B////Af///wH///8B////Af///wH///8BtGUqR6lW + HP+uXiD/r14jbaRcJi////8B////Af///wF3SiUXd0olF3dKJRd3SiUXd0olF3dKJRd3SiUXd0olF6xq + Ni+8dDtHu2Ii/8FrJv+8bC9tvHQ7K////wHFg00rx3k7bcd5O23HeTttx3k7bcd5O23HeTttx3k7bcd5 + O23HeTttx3k7bch0M4nWfzD/yW0m/8WDTUfFg00rzpJfR9FyKf/YfC7/2Hwu/9h8Lv/YfC7/2Hwu/9h8 + Lv/YfC7/2Hwu/9h8Lv/ehTP/7Zs+/+CINP/Rcin/zpJfR9eicCvXjVBt141QbdeNUG3XjVBt141QbdeN + UG3XjVBt141QbdeNUG3XjVBt1oRCieOKN//Wdiz/16JwR9eicCv///8B37CBD9+wgQ/fsIEP37CBD9+w + gQ/fsIEP37CBD9+wgQ/fsIEr37CBR9mCNv/gjT3/3Jpebd+wgSv///8B////Af///wH///8B////Af// + /wH///8B////Af///wH///8B5ryQR9mRQ//gnkz/4Khsbea8kCv///8B////Af///wH///8B////Af// + /wH///8B////Af///wH///8B////AezHnEfRcin/359mbezHnCv///8B////Af///wH///8B////Af// + /wH///8B////Af///wH///8B////Af///wHwzqQr4aNqbfDOpCv///8B////Af///wH///8B////Af// + /wH///8B////Af///wH///8B////Af///wH///8B////AfDOpA////8B////Af///wH///8B////Af// + /wH///8B////Af///wH///8B////Af///wH///8B////Af///wH///8B////Af///wH///8B////Af// + /wH///8B////Af///wH///8B////Af///wH///8B////Af///wH///8B////Af///wH///8B////Af// + /wH///8BAAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA + //8AAP//AAD//w== + + + \ No newline at end of file diff --git a/ICSharpCode.TextEditor/Project/UserControls/Int32TextBox.cs b/ICSharpCode.TextEditor/Project/UserControls/Int32TextBox.cs new file mode 100644 index 0000000..1d8d49b --- /dev/null +++ b/ICSharpCode.TextEditor/Project/UserControls/Int32TextBox.cs @@ -0,0 +1,58 @@ +using System; +using System.Windows.Forms; + +namespace ICSharpCode.TextEditor.UserControls +{ + class Int32TextBox : TextBox + { + public int Min { get; set; } + public int Max { get; set; } + + public Int32TextBox() + : this(1, int.MaxValue) + { + } + + public Int32TextBox(int min, int max) + { + Min = min; + Max = max; + } + + protected override void OnTextChanged(EventArgs e) + { + base.OnTextChanged(e); + + if (!IsValidNumber(Text)) + { + Text = string.Empty; + } + } + + protected override void WndProc(ref Message m) + { + const int WM_PASTE = 0x0302; + + if (m.Msg == WM_PASTE) + { + string text = Clipboard.GetText(); + + if (!IsValidNumber(text)) + return; + } + + base.WndProc(ref m); + } + + private bool IsValidNumber(string text) + { + int i; + if (int.TryParse(text, out i)) + { + return i <= Max && i >= Min; + } + + return false; + } + } +} \ No newline at end of file diff --git a/ICSharpCode.TextEditor/Project/Utils/EventHelperUtils.cs b/ICSharpCode.TextEditor/Project/Utils/EventHelperUtils.cs new file mode 100644 index 0000000..2b269c6 --- /dev/null +++ b/ICSharpCode.TextEditor/Project/Utils/EventHelperUtils.cs @@ -0,0 +1,121 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Reflection; + +namespace ICSharpCode.TextEditor.Utils; + +/// +/// http://www.codeproject.com/Articles/103542/Removing-Event-Handlers-using-Reflection +/// +static public class EventHelperUtils +{ + static readonly Dictionary> DicEventFieldInfos = new Dictionary>(); + + static BindingFlags AllBindings + { + get { return BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static; } + } + + static IEnumerable GetTypeEventFields(Type t) + { + List? value; + if (DicEventFieldInfos.TryGetValue(t, out value)) + { + return value; + } + + var lst = new List(); + BuildEventFields(t, lst); + DicEventFieldInfos.Add(t, lst); + return lst; + } + + static void BuildEventFields(Type t, List lst) + { + // Type.GetEvent(s) gets all Events for the type AND it's ancestors + // Type.GetField(s) gets only Fields for the exact type. + // (BindingFlags.FlattenHierarchy only works on PROTECTED & PUBLIC + // doesn't work because Fieds are PRIVATE) + + // NEW version of this routine uses .GetEvents and then uses .DeclaringType + // to get the correct ancestor type so that we can get the FieldInfo. + foreach (EventInfo ei in t.GetEvents(AllBindings)) + { + Type? dt = ei.DeclaringType; + if (dt != null) + { + FieldInfo? fi = dt.GetField(ei.Name, AllBindings); + if (fi != null) + { + lst.Add(fi); + } + } + } + } + + static EventHandlerList GetStaticEventHandlerList(Type t, object obj) + { + MethodInfo mi = t.GetMethod("get_Events", AllBindings)!; + return (EventHandlerList)mi.Invoke(obj, new object[] { })!; + } + + public static void RemoveAllEventHandlers(object obj) + { + RemoveEventHandler(obj, string.Empty); + } + + public static void RemoveEventHandler(object obj, string eventName) + { + if (obj == null) + return; + + Type t = obj.GetType(); + IEnumerable eventFields = GetTypeEventFields(t); + EventHandlerList? staticEventHandlers = null; + + foreach (FieldInfo fi in eventFields) + { + if (eventName != string.Empty && string.Compare(eventName, fi.Name, StringComparison.OrdinalIgnoreCase) != 0) + continue; + + // After hours and hours of research and trial and error, it turns out that + // STATIC Events have to be treated differently from INSTANCE Events... + if (fi.IsStatic) + { + // STATIC EVENT + if (staticEventHandlers == null) + { + staticEventHandlers = GetStaticEventHandlerList(t, obj); + } + + object idx = fi.GetValue(obj)!; + Delegate? eh = staticEventHandlers[idx]; + if (eh == null) + continue; + + Delegate[] dels = eh.GetInvocationList(); + EventInfo? ei = t.GetEvent(fi.Name, AllBindings); + foreach (Delegate del in dels) + { + ei?.RemoveEventHandler(obj, del); + } + } + else + { + // INSTANCE EVENT + EventInfo? ei = t.GetEvent(fi.Name, AllBindings); + if (ei != null) + { + object? val = fi.GetValue(obj); + Delegate? mdel = val as Delegate; + if (mdel != null) + { + foreach (Delegate del in mdel.GetInvocationList()) + ei.RemoveEventHandler(obj, del); + } + } + } + } + } +} diff --git a/ICSharpCode.TextEditor/Project/Utils/WeakEventHandler.cs b/ICSharpCode.TextEditor/Project/Utils/WeakEventHandler.cs new file mode 100644 index 0000000..bf7fd0b --- /dev/null +++ b/ICSharpCode.TextEditor/Project/Utils/WeakEventHandler.cs @@ -0,0 +1,37 @@ +using System; +using System.Diagnostics; +using System.Reflection; + +namespace ICSharpCode.TextEditor.Utils +{ + /// + /// http://paulstovell.com/blog/weakevents + /// + /// The type of the event args. + [DebuggerNonUserCode] + public sealed class WeakEventHandler where TEventArgs : EventArgs + { + private readonly WeakReference _targetReference; + private readonly MethodInfo _method; + + public WeakEventHandler(EventHandler callback) + { + _method = callback.Method; + _targetReference = new WeakReference(callback.Target, true); + } + + [DebuggerNonUserCode] + public void Handler(object sender, TEventArgs e) + { + var target = _targetReference.Target; + if (target != null) + { + var callback = (Action)Delegate.CreateDelegate(typeof(Action), target, _method, true); + if (callback != null) + { + callback(sender, e); + } + } + } + } +} diff --git a/QuickTools/Form1.Designer.cs b/QuickTools/Form1.Designer.cs new file mode 100644 index 0000000..a011ece --- /dev/null +++ b/QuickTools/Form1.Designer.cs @@ -0,0 +1,185 @@ +namespace QuickTools +{ + partial class Form1 + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + btnEncode = new Button(); + tableLayoutPanel1 = new TableLayoutPanel(); + panel1 = new Panel(); + label1 = new Label(); + btnDecode = new Button(); + txtProtoId = new TextBox(); + groupBox1 = new GroupBox(); + groupBox2 = new GroupBox(); + txtProto = new TextBox(); + this.txtJson = new ICSharpCode.TextEditor.TextEditorControlEx(); + tableLayoutPanel1.SuspendLayout(); + panel1.SuspendLayout(); + groupBox1.SuspendLayout(); + groupBox2.SuspendLayout(); + SuspendLayout(); + // + // btnEncode + // + btnEncode.Location = new Point(3, 49); + btnEncode.Name = "btnEncode"; + btnEncode.Size = new Size(75, 23); + btnEncode.TabIndex = 0; + btnEncode.Text = "→"; + btnEncode.UseVisualStyleBackColor = true; + btnEncode.Click += btnEncode_Click; + // + // tableLayoutPanel1 + // + tableLayoutPanel1.Anchor = AnchorStyles.Top | AnchorStyles.Bottom | AnchorStyles.Left | AnchorStyles.Right; + tableLayoutPanel1.ColumnCount = 3; + tableLayoutPanel1.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 50F)); + tableLayoutPanel1.ColumnStyles.Add(new ColumnStyle(SizeType.Absolute, 89F)); + tableLayoutPanel1.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 50F)); + tableLayoutPanel1.Controls.Add(panel1, 1, 0); + tableLayoutPanel1.Controls.Add(groupBox1, 0, 0); + tableLayoutPanel1.Controls.Add(groupBox2, 2, 0); + tableLayoutPanel1.Location = new Point(12, 12); + tableLayoutPanel1.Name = "tableLayoutPanel1"; + tableLayoutPanel1.RowCount = 1; + tableLayoutPanel1.RowStyles.Add(new RowStyle(SizeType.Percent, 100F)); + tableLayoutPanel1.Size = new Size(815, 444); + tableLayoutPanel1.TabIndex = 2; + // + // panel1 + // + panel1.Controls.Add(label1); + panel1.Controls.Add(btnDecode); + panel1.Controls.Add(btnEncode); + panel1.Controls.Add(txtProtoId); + panel1.Dock = DockStyle.Fill; + panel1.Location = new Point(366, 3); + panel1.Name = "panel1"; + panel1.Size = new Size(83, 438); + panel1.TabIndex = 0; + // + // label1 + // + label1.AutoSize = true; + label1.Location = new Point(3, 0); + label1.Name = "label1"; + label1.Size = new Size(74, 17); + label1.TabIndex = 1; + label1.Text = "Protocol ID"; + label1.TextAlign = ContentAlignment.MiddleCenter; + // + // btnDecode + // + btnDecode.Location = new Point(3, 78); + btnDecode.Name = "btnDecode"; + btnDecode.Size = new Size(75, 23); + btnDecode.TabIndex = 0; + btnDecode.Text = "←"; + btnDecode.UseVisualStyleBackColor = true; + btnDecode.Click += btnDecode_Click; + // + // txtProtoId + // + txtProtoId.Location = new Point(3, 20); + txtProtoId.Name = "txtProtoId"; + txtProtoId.Size = new Size(74, 23); + txtProtoId.TabIndex = 0; + // + // groupBox1 + // + groupBox1.Controls.Add(this.txtJson); + groupBox1.Dock = DockStyle.Fill; + groupBox1.Location = new Point(3, 3); + groupBox1.Name = "groupBox1"; + groupBox1.Size = new Size(357, 438); + groupBox1.TabIndex = 1; + groupBox1.TabStop = false; + groupBox1.Text = "JSON"; + // + // groupBox2 + // + groupBox2.Controls.Add(txtProto); + groupBox2.Dock = DockStyle.Fill; + groupBox2.Location = new Point(455, 3); + groupBox2.Name = "groupBox2"; + groupBox2.Size = new Size(357, 438); + groupBox2.TabIndex = 1; + groupBox2.TabStop = false; + groupBox2.Text = "Protobuf(Base64)"; + // + // txtProto + // + txtProto.Dock = DockStyle.Fill; + txtProto.Location = new Point(3, 19); + txtProto.Multiline = true; + txtProto.Name = "txtProto"; + txtProto.ScrollBars = ScrollBars.Both; + txtProto.Size = new Size(351, 416); + txtProto.TabIndex = 1; + // + // txtJson + // + this.txtJson.Font = new Font("Courier New", 10F, FontStyle.Regular, GraphicsUnit.Point); + this.txtJson.Location = new Point(6, 19); + this.txtJson.Name = "txtJson"; + this.txtJson.Size = new Size(351, 416); + this.txtJson.SyntaxHighlighting = "JSON"; + this.txtJson.FoldingStrategy = "JSON"; + this.txtJson.TabIndex = 0; + // + // Form1 + // + AutoScaleDimensions = new SizeF(7F, 17F); + AutoScaleMode = AutoScaleMode.Font; + ClientSize = new Size(832, 460); + Controls.Add(tableLayoutPanel1); + Name = "Form1"; + Text = "Form1"; + tableLayoutPanel1.ResumeLayout(false); + panel1.ResumeLayout(false); + panel1.PerformLayout(); + groupBox1.ResumeLayout(false); + groupBox2.ResumeLayout(false); + groupBox2.PerformLayout(); + ResumeLayout(false); + } + + #endregion + + private Button btnEncode; + private TableLayoutPanel tableLayoutPanel1; + private Panel panel1; + private Label label1; + private Button btnDecode; + private TextBox txtProtoId; + private GroupBox groupBox1; + private GroupBox groupBox2; + private TextBox txtProto; + private ICSharpCode.TextEditor.TextEditorControlEx txtJson; + } +} \ No newline at end of file diff --git a/QuickTools/Form1.cs b/QuickTools/Form1.cs new file mode 100644 index 0000000..c8c907c --- /dev/null +++ b/QuickTools/Form1.cs @@ -0,0 +1,139 @@ +using Flurl.Http; +using Newtonsoft.Json.Linq; +using System.Diagnostics; + +namespace QuickTools +{ + public partial class Form1 : Form + { + public Form1() + { + InitializeComponent(); + } + + private async void btnEncode_Click(object sender, EventArgs e) + { + if (txtProtoId.Text.Trim() == string.Empty) + { + MessageBox.Show("ProtocolID Ϊգ", "", MessageBoxButtons.OK, MessageBoxIcon.Warning); + return; + } + int protocolId; + try + { + protocolId = Convert.ToInt32(txtProtoId.Text.Trim()); + } + catch + { + MessageBox.Show("ProtocolID ֻΪ֣", "", MessageBoxButtons.OK, MessageBoxIcon.Warning); + return; + } + var url = "http://localhost:9999/api/v1/emoney/converter/request/encode"; + try + { + string r = await url.PostJsonAsync(new + { + protocolId, + protocolBody = txtJson.Text, + }).Result.Content.ReadAsStringAsync(); + if (r != null) + { + JObject jo = JObject.Parse(r); + if (jo["code"].Value() == 0) + { + txtProto.Text = jo["result"].Value(); + } + else + { + MessageBox.Show(jo["msg"].Value(), "", MessageBoxButtons.OK, MessageBoxIcon.Warning); + } + } + } + catch (Exception ex) + { + Debug.WriteLine(ex.StackTrace); + MessageBox.Show($"ת JSON Ϊ Protobuf ִ{ex.Message}", "", MessageBoxButtons.OK, MessageBoxIcon.Warning); + } + } + + private async void btnDecode_Click(object sender, EventArgs e) + { + + if (txtProtoId.Text.Trim() == string.Empty) + { + MessageBox.Show("ProtocolID Ϊգ", "", MessageBoxButtons.OK, MessageBoxIcon.Warning); + return; + } + int protocolId; + try + { + protocolId = Convert.ToInt32(txtProtoId.Text.Trim()); + } + catch + { + MessageBox.Show("ProtocolID ֻΪ֣", "", MessageBoxButtons.OK, MessageBoxIcon.Warning); + return; + } + var url = "http://localhost:9999/api/v1/emoney/converter/request/decode"; + try + { + string r = await url.PostJsonAsync(new + { + protocolId, + protocolBody = txtProto.Text, + }).Result.Content.ReadAsStringAsync(); + if (r != null) + { + JObject jo = JObject.Parse(r); + if (jo["code"].Value() == 0) + { + txtJson.Text = ClearSizeInfo(jo["result"]).ToString(); + } + else + { + MessageBox.Show(jo["msg"].Value(), "", MessageBoxButtons.OK, MessageBoxIcon.Warning); + } + } + } + catch (Exception ex) + { + Debug.WriteLine(ex.StackTrace); + MessageBox.Show($"ת Protobuf Ϊ JSON ִ{ex.Message}", "", MessageBoxButtons.OK, MessageBoxIcon.Warning); + } + } + + public static JToken ClearSizeInfo(JToken j) + { + if (j == null) return null; + if (j is JArray ja) + { + for (int i = 0; i < ja.Count; i++) + { + ja[i] = ClearSizeInfo(ja[i]); + } + return ja; + } + if (j is JObject jo) + { + if (jo.ContainsKey("cachedSize")) + { + jo.Remove("cachedSize"); + } + if (jo.ContainsKey("serializedSize")) + { + jo.Remove("serializedSize"); + } + foreach (var p in jo) + { + string key = p.Key; + JToken val = p.Value; + jo[key] = ClearSizeInfo(val); + } + return jo; + } + return j; + } + } + + +} \ No newline at end of file diff --git a/QuickTools/Form1.resx b/QuickTools/Form1.resx new file mode 100644 index 0000000..f298a7b --- /dev/null +++ b/QuickTools/Form1.resx @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/QuickTools/Program.cs b/QuickTools/Program.cs new file mode 100644 index 0000000..3838d51 --- /dev/null +++ b/QuickTools/Program.cs @@ -0,0 +1,17 @@ +namespace QuickTools +{ + internal static class Program + { + /// + /// The main entry point for the application. + /// + [STAThread] + static void Main() + { + // To customize application configuration such as set high DPI settings or default font, + // see https://aka.ms/applicationconfiguration. + ApplicationConfiguration.Initialize(); + Application.Run(new Form1()); + } + } +} \ No newline at end of file diff --git a/QuickTools/QuickTools.csproj b/QuickTools/QuickTools.csproj new file mode 100644 index 0000000..6a0efb8 --- /dev/null +++ b/QuickTools/QuickTools.csproj @@ -0,0 +1,15 @@ + + + + WinExe + net6.0-windows + enable + true + enable + + + + + + + \ No newline at end of file