├── .gitignore ├── .gitmodules ├── LICENSE ├── Microsoft.VisualStudio.MiniEditor.sln ├── Microsoft.VisualStudio.MiniEditor ├── BaseViewImpl │ ├── ITextViewFactoryService.cs │ ├── MockBraceCompletionAdornmentService.cs │ ├── MockExperimentationService.cs │ ├── MockTextStructureNavigator.cs │ ├── MockTooltipService.cs │ ├── TestCommandHandlers.cs │ ├── TestSmartIndentationService.cs │ ├── TestTextCaret.cs │ ├── TestTextSelection.cs │ ├── TestTextView.cs │ ├── TestTextViewLine.cs │ ├── TestTextViewLineCollection.cs │ ├── TestViewScroller.cs │ ├── TextViewFactoryService.cs │ ├── TextViewRoleSet.cs │ └── ViewOptionsCompat.cs ├── CustomDef │ ├── EditorOptionsExtensions.cs │ ├── IAsyncCompletionSessionOperations2.cs │ └── IDynamicCommandHandler.cs ├── CustomErrorHandler.cs ├── CustomImpl │ ├── CompletionUtilities.cs │ ├── DiagnosticLogger.cs │ ├── EditorCommandHandlerService.cs │ └── System.Windows.Clipboard.cs ├── CustomTextModel │ ├── TextDocument.cs │ └── TextDocumentFactoryService.cs ├── EditorEnvironment.cs ├── IFileSystemAbstraction.cs ├── Microsoft.VisualStudio.MiniEditor.csproj └── MiniEditorSetup.cs ├── NuGet.Config ├── README.md ├── azure-pipelines.yml ├── public.snk └── version.json /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 5 | 6 | # User-specific files 7 | *.suo 8 | *.user 9 | *.userosscache 10 | *.sln.docstates 11 | 12 | # User-specific files (MonoDevelop/Xamarin Studio) 13 | *.userprefs 14 | 15 | # Build results 16 | [Dd]ebug/ 17 | [Dd]ebugPublic/ 18 | [Rr]elease/ 19 | [Rr]eleases/ 20 | x64/ 21 | x86/ 22 | bld/ 23 | [Bb]in/ 24 | [Oo]bj/ 25 | [Ll]og/ 26 | 27 | # Visual Studio 2015/2017 cache/options directory 28 | .vs/ 29 | # Uncomment if you have tasks that create the project's static files in wwwroot 30 | #wwwroot/ 31 | 32 | # Visual Studio 2017 auto generated files 33 | Generated\ Files/ 34 | 35 | # MSTest test Results 36 | [Tt]est[Rr]esult*/ 37 | [Bb]uild[Ll]og.* 38 | 39 | # NUNIT 40 | *.VisualState.xml 41 | TestResult.xml 42 | 43 | # Build Results of an ATL Project 44 | [Dd]ebugPS/ 45 | [Rr]eleasePS/ 46 | dlldata.c 47 | 48 | # Benchmark Results 49 | BenchmarkDotNet.Artifacts/ 50 | 51 | # .NET Core 52 | project.lock.json 53 | project.fragment.lock.json 54 | artifacts/ 55 | **/Properties/launchSettings.json 56 | 57 | # StyleCop 58 | StyleCopReport.xml 59 | 60 | # Files built by Visual Studio 61 | *_i.c 62 | *_p.c 63 | *_i.h 64 | *.ilk 65 | *.meta 66 | *.obj 67 | *.iobj 68 | *.pch 69 | *.pdb 70 | *.ipdb 71 | *.pgc 72 | *.pgd 73 | *.rsp 74 | *.sbr 75 | *.tlb 76 | *.tli 77 | *.tlh 78 | *.tmp 79 | *.tmp_proj 80 | *.log 81 | *.vspscc 82 | *.vssscc 83 | .builds 84 | *.pidb 85 | *.svclog 86 | *.scc 87 | 88 | # Chutzpah Test files 89 | _Chutzpah* 90 | 91 | # Visual C++ cache files 92 | ipch/ 93 | *.aps 94 | *.ncb 95 | *.opendb 96 | *.opensdf 97 | *.sdf 98 | *.cachefile 99 | *.VC.db 100 | *.VC.VC.opendb 101 | 102 | # Visual Studio profiler 103 | *.psess 104 | *.vsp 105 | *.vspx 106 | *.sap 107 | 108 | # Visual Studio Trace Files 109 | *.e2e 110 | 111 | # TFS 2012 Local Workspace 112 | $tf/ 113 | 114 | # Guidance Automation Toolkit 115 | *.gpState 116 | 117 | # ReSharper is a .NET coding add-in 118 | _ReSharper*/ 119 | *.[Rr]e[Ss]harper 120 | *.DotSettings.user 121 | 122 | # JustCode is a .NET coding add-in 123 | .JustCode 124 | 125 | # TeamCity is a build add-in 126 | _TeamCity* 127 | 128 | # DotCover is a Code Coverage Tool 129 | *.dotCover 130 | 131 | # AxoCover is a Code Coverage Tool 132 | .axoCover/* 133 | !.axoCover/settings.json 134 | 135 | # Visual Studio code coverage results 136 | *.coverage 137 | *.coveragexml 138 | 139 | # NCrunch 140 | _NCrunch_* 141 | .*crunch*.local.xml 142 | nCrunchTemp_* 143 | 144 | # MightyMoose 145 | *.mm.* 146 | AutoTest.Net/ 147 | 148 | # Web workbench (sass) 149 | .sass-cache/ 150 | 151 | # Installshield output folder 152 | [Ee]xpress/ 153 | 154 | # DocProject is a documentation generator add-in 155 | DocProject/buildhelp/ 156 | DocProject/Help/*.HxT 157 | DocProject/Help/*.HxC 158 | DocProject/Help/*.hhc 159 | DocProject/Help/*.hhk 160 | DocProject/Help/*.hhp 161 | DocProject/Help/Html2 162 | DocProject/Help/html 163 | 164 | # Click-Once directory 165 | publish/ 166 | 167 | # Publish Web Output 168 | *.[Pp]ublish.xml 169 | *.azurePubxml 170 | # Note: Comment the next line if you want to checkin your web deploy settings, 171 | # but database connection strings (with potential passwords) will be unencrypted 172 | *.pubxml 173 | *.publishproj 174 | 175 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 176 | # checkin your Azure Web App publish settings, but sensitive information contained 177 | # in these scripts will be unencrypted 178 | PublishScripts/ 179 | 180 | # NuGet Packages 181 | *.nupkg 182 | # The packages folder can be ignored because of Package Restore 183 | **/[Pp]ackages/* 184 | # except build/, which is used as an MSBuild target. 185 | !**/[Pp]ackages/build/ 186 | # Uncomment if necessary however generally it will be regenerated when needed 187 | #!**/[Pp]ackages/repositories.config 188 | # NuGet v3's project.json files produces more ignorable files 189 | *.nuget.props 190 | *.nuget.targets 191 | 192 | # Microsoft Azure Build Output 193 | csx/ 194 | *.build.csdef 195 | 196 | # Microsoft Azure Emulator 197 | ecf/ 198 | rcf/ 199 | 200 | # Windows Store app package directories and files 201 | AppPackages/ 202 | BundleArtifacts/ 203 | Package.StoreAssociation.xml 204 | _pkginfo.txt 205 | *.appx 206 | 207 | # Visual Studio cache files 208 | # files ending in .cache can be ignored 209 | *.[Cc]ache 210 | # but keep track of directories ending in .cache 211 | !*.[Cc]ache/ 212 | 213 | # Others 214 | ClientBin/ 215 | ~$* 216 | *~ 217 | *.dbmdl 218 | *.dbproj.schemaview 219 | *.jfm 220 | *.pfx 221 | *.publishsettings 222 | orleans.codegen.cs 223 | 224 | # Including strong name files can present a security risk 225 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 226 | #*.snk 227 | 228 | # Since there are multiple workflows, uncomment next line to ignore bower_components 229 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 230 | #bower_components/ 231 | 232 | # RIA/Silverlight projects 233 | Generated_Code/ 234 | 235 | # Backup & report files from converting an old project file 236 | # to a newer Visual Studio version. Backup files are not needed, 237 | # because we have git ;-) 238 | _UpgradeReport_Files/ 239 | Backup*/ 240 | UpgradeLog*.XML 241 | UpgradeLog*.htm 242 | ServiceFabricBackup/ 243 | *.rptproj.bak 244 | 245 | # SQL Server files 246 | *.mdf 247 | *.ldf 248 | *.ndf 249 | 250 | # Business Intelligence projects 251 | *.rdl.data 252 | *.bim.layout 253 | *.bim_*.settings 254 | *.rptproj.rsuser 255 | 256 | # Microsoft Fakes 257 | FakesAssemblies/ 258 | 259 | # GhostDoc plugin setting file 260 | *.GhostDoc.xml 261 | 262 | # Node.js Tools for Visual Studio 263 | .ntvs_analysis.dat 264 | node_modules/ 265 | 266 | # Visual Studio 6 build log 267 | *.plg 268 | 269 | # Visual Studio 6 workspace options file 270 | *.opt 271 | 272 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 273 | *.vbw 274 | 275 | # Visual Studio LightSwitch build output 276 | **/*.HTMLClient/GeneratedArtifacts 277 | **/*.DesktopClient/GeneratedArtifacts 278 | **/*.DesktopClient/ModelManifest.xml 279 | **/*.Server/GeneratedArtifacts 280 | **/*.Server/ModelManifest.xml 281 | _Pvt_Extensions 282 | 283 | # Paket dependency manager 284 | .paket/paket.exe 285 | paket-files/ 286 | 287 | # FAKE - F# Make 288 | .fake/ 289 | 290 | # JetBrains Rider 291 | .idea/ 292 | *.sln.iml 293 | 294 | # CodeRush 295 | .cr/ 296 | 297 | # Python Tools for Visual Studio (PTVS) 298 | __pycache__/ 299 | *.pyc 300 | 301 | # Cake - Uncomment if you are using it 302 | # tools/** 303 | # !tools/packages.config 304 | 305 | # Tabs Studio 306 | *.tss 307 | 308 | # Telerik's JustMock configuration file 309 | *.jmconfig 310 | 311 | # BizTalk build output 312 | *.btp.cs 313 | *.btm.cs 314 | *.odx.cs 315 | *.xsd.cs 316 | 317 | # OpenCover UI analysis results 318 | OpenCover/ 319 | 320 | # Azure Stream Analytics local run output 321 | ASALocalRun/ 322 | 323 | # MSBuild Binary and Structured Log 324 | *.binlog 325 | 326 | # NVidia Nsight GPU debugger configuration file 327 | *.nvuser 328 | 329 | # MFractors (Xamarin productivity tool) working folder 330 | .mfractor/ 331 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "Microsoft.VisualStudio.MiniEditor/vs-editor-api"] 2 | path = Microsoft.VisualStudio.MiniEditor/vs-editor-api 3 | url = https://github.com/microsoft/vs-editor-api.git 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) Microsoft Corporation. All rights reserved. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Microsoft.VisualStudio.MiniEditor.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.VisualStudio.MiniEditor", "Microsoft.VisualStudio.MiniEditor\Microsoft.VisualStudio.MiniEditor.csproj", "{DD2087D3-ED47-49E8-AF08-CD3B3867C84D}" 5 | EndProject 6 | Global 7 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 8 | Debug|Any CPU = Debug|Any CPU 9 | Release|Any CPU = Release|Any CPU 10 | EndGlobalSection 11 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 12 | {DD2087D3-ED47-49E8-AF08-CD3B3867C84D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 13 | {DD2087D3-ED47-49E8-AF08-CD3B3867C84D}.Debug|Any CPU.Build.0 = Debug|Any CPU 14 | {DD2087D3-ED47-49E8-AF08-CD3B3867C84D}.Release|Any CPU.ActiveCfg = Release|Any CPU 15 | {DD2087D3-ED47-49E8-AF08-CD3B3867C84D}.Release|Any CPU.Build.0 = Release|Any CPU 16 | EndGlobalSection 17 | EndGlobal 18 | -------------------------------------------------------------------------------- /Microsoft.VisualStudio.MiniEditor/BaseViewImpl/ITextViewFactoryService.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace Microsoft.VisualStudio.Text.Editor 8 | { 9 | public interface ITextViewFactoryService 10 | { 11 | ITextView CreateTextView (ITextBuffer buffer); 12 | 13 | ITextView CreateTextView (); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Microsoft.VisualStudio.MiniEditor/BaseViewImpl/MockBraceCompletionAdornmentService.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel.Composition; 2 | 3 | using Microsoft.VisualStudio.Text.Editor; 4 | 5 | namespace Microsoft.VisualStudio.Text.BraceCompletion.Implementation 6 | { 7 | class MockBraceCompletionAdornmentService : IBraceCompletionAdornmentService 8 | { 9 | public ITrackingPoint Point { 10 | get => throw new System.NotImplementedException (); 11 | set { } 12 | } 13 | } 14 | 15 | [Export(typeof (IBraceCompletionAdornmentServiceFactory))] 16 | class MockBraceCompletionAdornmentServiceFactory : IBraceCompletionAdornmentServiceFactory 17 | { 18 | IBraceCompletionAdornmentService service = new MockBraceCompletionAdornmentService (); 19 | 20 | public IBraceCompletionAdornmentService GetOrCreateService (ITextView textView) => service; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Microsoft.VisualStudio.MiniEditor/BaseViewImpl/MockExperimentationService.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel.Composition; 2 | using Microsoft.VisualStudio.Text.Utilities; 3 | 4 | namespace Microsoft.VisualStudio.MiniEditor 5 | { 6 | [Export (typeof (IExperimentationServiceInternal))] 7 | class MockExperimentationService : IExperimentationServiceInternal 8 | { 9 | public bool IsCachedFlightEnabled (string flightName) => true; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Microsoft.VisualStudio.MiniEditor/BaseViewImpl/MockTextStructureNavigator.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.ComponentModel.Composition; 3 | using Microsoft.VisualStudio.Text; 4 | using Microsoft.VisualStudio.Text.Operations; 5 | using Microsoft.VisualStudio.Utilities; 6 | 7 | namespace Microsoft.VisualStudio.MiniEditor.BaseViewImpl 8 | { 9 | [Export (typeof (ITextStructureNavigatorProvider))] 10 | [ContentType ("any")] 11 | class MockTextStructureNavigatorProvider : ITextStructureNavigatorProvider 12 | { 13 | public ITextStructureNavigator CreateTextStructureNavigator (ITextBuffer textBuffer) 14 | => new MockTextStructureNavigator (textBuffer); 15 | } 16 | 17 | class MockTextStructureNavigator : ITextStructureNavigator 18 | { 19 | readonly ITextBuffer textBuffer; 20 | 21 | public MockTextStructureNavigator (ITextBuffer textBuffer) 22 | { 23 | this.textBuffer = textBuffer; 24 | } 25 | 26 | public IContentType ContentType => textBuffer.ContentType; 27 | 28 | public TextExtent GetExtentOfWord (SnapshotPoint currentPosition) 29 | { 30 | throw new NotImplementedException (); 31 | } 32 | 33 | // this is just enough to get some expand selection tests working 34 | public SnapshotSpan GetSpanOfEnclosing (SnapshotSpan activeSpan) 35 | => new SnapshotSpan (activeSpan.Snapshot, 0, activeSpan.Snapshot.Length); 36 | 37 | public SnapshotSpan GetSpanOfFirstChild (SnapshotSpan activeSpan) 38 | { 39 | throw new NotImplementedException (); 40 | } 41 | 42 | public SnapshotSpan GetSpanOfNextSibling (SnapshotSpan activeSpan) 43 | { 44 | throw new NotImplementedException (); 45 | } 46 | 47 | public SnapshotSpan GetSpanOfPreviousSibling (SnapshotSpan activeSpan) 48 | { 49 | throw new NotImplementedException (); 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /Microsoft.VisualStudio.MiniEditor/BaseViewImpl/MockTooltipService.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel.Composition; 4 | using Microsoft.VisualStudio.Text.Adornments; 5 | 6 | namespace Microsoft.VisualStudio.Text.Editor.Implementation 7 | { 8 | [Export(typeof(IToolTipService))] 9 | class MockTooltipService : IToolTipService 10 | { 11 | public IToolTipPresenter CreatePresenter(ITextView textView, ToolTipParameters parameters = null) 12 | { 13 | return new MockToolTipPresenter(); 14 | } 15 | } 16 | 17 | class MockToolTipPresenter : IToolTipPresenter 18 | { 19 | public event EventHandler Dismissed; 20 | 21 | public void Dismiss() 22 | { 23 | Dismissed?.Invoke(this, EventArgs.Empty); 24 | } 25 | 26 | public void StartOrUpdate(ITrackingSpan applicableToSpan, IEnumerable content) 27 | { 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /Microsoft.VisualStudio.MiniEditor/BaseViewImpl/TestCommandHandlers.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel.Composition; 2 | 3 | using Microsoft.VisualStudio.Commanding; 4 | using Microsoft.VisualStudio.Text.Editor; 5 | using Microsoft.VisualStudio.Text.Editor.Commanding.Commands; 6 | using Microsoft.VisualStudio.Text.Operations; 7 | using Microsoft.VisualStudio.Utilities; 8 | 9 | namespace Microsoft.VisualStudio.MiniEditor.BaseViewImpl 10 | { 11 | [Name (Name)] 12 | [ContentType (StandardContentTypeNames.Any)] 13 | [TextViewRole (PredefinedTextViewRoles.Interactive)] 14 | [Export (typeof (ICommandHandler))] 15 | public class TestCommandHandlers : 16 | ICommandHandler, 17 | ICommandHandler 18 | { 19 | const string Name = nameof (TestCommandHandlers); 20 | 21 | [Import] 22 | IEditorOperationsFactoryService OperationsServiceFactory { get; set; } 23 | IEditorOperations3 GetOperations (ITextView tv) => 24 | (IEditorOperations3) OperationsServiceFactory.GetEditorOperations (tv); 25 | 26 | public string DisplayName => Name; 27 | 28 | public bool ExecuteCommand (TypeCharCommandArgs args, CommandExecutionContext executionContext) 29 | => GetOperations (args.TextView).InsertText (args.TypedChar.ToString ()); 30 | 31 | public CommandState GetCommandState (TypeCharCommandArgs args) 32 | => args.TypedChar == '\0'? CommandState.Unavailable : CommandState.Available; 33 | 34 | public CommandState GetCommandState (ReturnKeyCommandArgs args) 35 | => CommandState.Available; 36 | 37 | public bool ExecuteCommand (ReturnKeyCommandArgs args, CommandExecutionContext executionContext) 38 | => GetOperations (args.TextView).InsertNewLine (); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /Microsoft.VisualStudio.MiniEditor/BaseViewImpl/TestSmartIndentationService.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel.Composition; 4 | using Microsoft.VisualStudio.Text; 5 | using Microsoft.VisualStudio.Text.Editor; 6 | using Microsoft.VisualStudio.Utilities; 7 | 8 | namespace Microsoft.VisualStudio.MiniEditor.BaseViewImpl 9 | { 10 | [Export (typeof (ISmartIndentationService))] 11 | class MockSmartIndentationService : ISmartIndentationService, ISmartIndent 12 | { 13 | [ImportMany] 14 | internal List> Providers { get; set; } 15 | 16 | [Import] 17 | internal IGuardedOperations GuardedOperations { get; set; } 18 | 19 | [Import] 20 | internal IContentTypeRegistryService ContentTypeRegistry { get; set; } 21 | 22 | public int? GetDesiredIndentation (ITextView textView, ITextSnapshotLine line) 23 | { 24 | var indenter = textView.Properties 25 | .GetOrCreateSingletonProperty (typeof (MockSmartIndentationService), () => 26 | GuardedOperations.InvokeBestMatchingFactory ( 27 | Providers, 28 | textView.TextBuffer.ContentType, 29 | p => p.CreateSmartIndent (textView), 30 | ContentTypeRegistry, 31 | this) 32 | ?? this 33 | ); 34 | return indenter.GetDesiredIndentation (line); 35 | } 36 | 37 | // null implementation so we can always return one 38 | int? ISmartIndent.GetDesiredIndentation (ITextSnapshotLine line) => null; 39 | void IDisposable.Dispose () {} 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /Microsoft.VisualStudio.MiniEditor/BaseViewImpl/TestTextCaret.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.VisualStudio.Text.Formatting; 3 | using Microsoft.VisualStudio.Text.MultiSelection; 4 | 5 | namespace Microsoft.VisualStudio.Text.Editor.Implementation 6 | { 7 | internal class TestTextCaret : ITextCaret 8 | { 9 | readonly ITextView textView; 10 | readonly ISmartIndentationService indentService; 11 | readonly IMultiSelectionBroker multiSelectionBroker; 12 | 13 | public TestTextCaret (ITextView textView, ISmartIndentationService indentService, IMultiSelectionBroker multiSelectionBroker) 14 | { 15 | this.textView = textView; 16 | this.indentService = indentService; 17 | this.multiSelectionBroker = multiSelectionBroker; 18 | multiSelectionBroker.MultiSelectionSessionChanged += MultiSelectionSessionChanged; 19 | 20 | var point = new SnapshotPoint (textView.TextBuffer.CurrentSnapshot, 0); 21 | Position = new CaretPosition ( 22 | new VirtualSnapshotPoint (point), 23 | textView.BufferGraph.CreateMappingPoint (point, PointTrackingMode.Positive), 24 | PositionAffinity.Successor 25 | ); 26 | } 27 | 28 | #region ITextCaret Members 29 | 30 | public bool IsHidden { 31 | get => throw new NotImplementedException (); 32 | set => throw new NotImplementedException (); 33 | } 34 | 35 | public double Bottom => throw new NotImplementedException (); 36 | 37 | public void CapturePreferredYCoordinate () => throw new NotImplementedException (); 38 | 39 | public ITextViewLine ContainingTextViewLine 40 | => textView.GetTextViewLineContainingBufferPosition (Position.BufferPosition); 41 | 42 | public void EnsureVisible () 43 | { 44 | // no-op, we don't do scrolling 45 | } 46 | 47 | public double Height => throw new NotImplementedException (); 48 | 49 | public double Left => throw new NotImplementedException (); 50 | 51 | public CaretPosition MoveTo (SnapshotPoint bufferPosition) 52 | => MoveTo (bufferPosition, PositionAffinity.Successor); 53 | 54 | public CaretPosition MoveTo (SnapshotPoint bufferPosition, PositionAffinity caretAffinity) 55 | => MoveTo (bufferPosition, caretAffinity, true); 56 | 57 | public CaretPosition MoveTo (SnapshotPoint bufferPosition, PositionAffinity caretAffinity, bool captureHorizontalPosition) 58 | => MoveTo (new VirtualSnapshotPoint (bufferPosition), caretAffinity, captureHorizontalPosition); 59 | 60 | public CaretPosition MoveTo (ITextViewLine textLine) 61 | => MoveTo (textLine, 0.0, true); 62 | 63 | public CaretPosition MoveTo (ITextViewLine textLine, double xCoordinate) 64 | => MoveTo (textLine, xCoordinate, true); 65 | 66 | public CaretPosition MoveTo (ITextViewLine textLine, double xCoordinate, bool captureHorizontalPosition) 67 | { 68 | var xCoord = textLine.MapXCoordinate (textView, 0.0, indentService, false); 69 | var pos = textLine.GetInsertionBufferPositionFromXCoordinate (xCoord); 70 | return MoveTo (pos, PositionAffinity.Successor, captureHorizontalPosition); 71 | } 72 | 73 | public CaretPosition MoveTo (VirtualSnapshotPoint position) 74 | => MoveTo (position, PositionAffinity.Successor, true); 75 | 76 | public CaretPosition MoveTo (VirtualSnapshotPoint position, PositionAffinity caretAffinity) 77 | => MoveTo (position, caretAffinity, true); 78 | 79 | public CaretPosition MoveTo (VirtualSnapshotPoint position, PositionAffinity caretAffinity, bool captureHorizontalPosition) 80 | { 81 | multiSelectionBroker.SetSelection (new Selection (position, caretAffinity)); 82 | return Position; 83 | } 84 | 85 | public CaretPosition MoveToNextCaretPosition () 86 | { 87 | throw new NotImplementedException (); 88 | } 89 | 90 | public CaretPosition MoveToPreviousCaretPosition () 91 | { 92 | throw new NotImplementedException (); 93 | } 94 | 95 | public bool OverwriteMode { get; set; } 96 | 97 | public bool InVirtualSpace { 98 | get { 99 | return false; 100 | } 101 | } 102 | 103 | public CaretPosition Position { get; private set; } 104 | 105 | void MultiSelectionSessionChanged (object sender, EventArgs e) 106 | { 107 | Update (); 108 | } 109 | 110 | public void Update () 111 | { 112 | var oldPosition = Position; 113 | 114 | var snapshot = textView.TextBuffer.CurrentSnapshot; 115 | bool snapshotChanged = snapshot != oldPosition.BufferPosition.Snapshot; 116 | var translatedBrokerPos = multiSelectionBroker.PrimarySelection.InsertionPoint.TranslateTo (snapshot); 117 | var posChanged = oldPosition.VirtualBufferPosition.TranslateTo (snapshot) != translatedBrokerPos; 118 | var affinityChanged = multiSelectionBroker.PrimarySelection.InsertionPointAffinity != oldPosition.Affinity; 119 | 120 | if (snapshotChanged || posChanged || affinityChanged) { 121 | Position = new CaretPosition ( 122 | translatedBrokerPos, 123 | textView.BufferGraph.CreateMappingPoint (translatedBrokerPos.Position, PointTrackingMode.Positive), 124 | multiSelectionBroker.PrimarySelection.InsertionPointAffinity 125 | ); 126 | } 127 | 128 | if (posChanged) { 129 | PositionChanged?.Invoke (this, new CaretPositionChangedEventArgs (this.textView, oldPosition, Position)); 130 | } 131 | } 132 | 133 | public event EventHandler PositionChanged; 134 | 135 | public double PreferredYCoordinate { 136 | get { throw new NotImplementedException (); } 137 | } 138 | 139 | public double Right { 140 | get { throw new NotImplementedException (); } 141 | } 142 | 143 | public double Top { 144 | get { throw new NotImplementedException (); } 145 | } 146 | 147 | public double Width { 148 | get { 149 | throw new NotImplementedException (); 150 | } 151 | set { 152 | throw new NotImplementedException (); 153 | } 154 | } 155 | 156 | public CaretPosition MoveToPreferredCoordinates () 157 | { 158 | throw new NotImplementedException (); 159 | } 160 | 161 | #endregion 162 | 163 | } 164 | } 165 | -------------------------------------------------------------------------------- /Microsoft.VisualStudio.MiniEditor/BaseViewImpl/TestTextSelection.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.ObjectModel; 3 | using Microsoft.VisualStudio.Text.Formatting; 4 | 5 | namespace Microsoft.VisualStudio.Text.Editor.Implementation 6 | { 7 | class TestTextSelection : ITextSelection 8 | { 9 | readonly IMultiSelectionBroker multiSelectionBroker; 10 | 11 | public TestTextSelection (ITextView textView, IMultiSelectionBroker multiSelectionBroker) 12 | { 13 | TextView = textView; 14 | this.multiSelectionBroker = multiSelectionBroker; 15 | multiSelectionBroker.MultiSelectionSessionChanged += MultiSelectionSessionChanged; 16 | } 17 | 18 | void MultiSelectionSessionChanged (object sender, EventArgs e) 19 | { 20 | SelectionChanged?.Invoke (this, e); 21 | } 22 | 23 | public ITextView TextView { get; private set; } 24 | 25 | public NormalizedSnapshotSpanCollection SelectedSpans 26 | => new NormalizedSnapshotSpanCollection (StreamSelectionSpan.SnapshotSpan); 27 | 28 | public ReadOnlyCollection VirtualSelectedSpans 29 | => new ReadOnlyCollection(new VirtualSnapshotSpan[] { StreamSelectionSpan }); 30 | 31 | public VirtualSnapshotSpan StreamSelectionSpan => multiSelectionBroker.PrimarySelection.Extent; 32 | 33 | public TextSelectionMode Mode { get => throw new NotImplementedException(); set => throw new NotImplementedException(); } 34 | 35 | public bool IsReversed => multiSelectionBroker.PrimarySelection.IsReversed; 36 | 37 | public bool IsEmpty => multiSelectionBroker.PrimarySelection.IsEmpty; 38 | 39 | public bool IsActive { get; set; } 40 | public bool ActivationTracksFocus { get; set; } 41 | 42 | public VirtualSnapshotPoint ActivePoint => multiSelectionBroker.PrimarySelection.ActivePoint; 43 | 44 | public VirtualSnapshotPoint AnchorPoint => multiSelectionBroker.PrimarySelection.AnchorPoint; 45 | 46 | public VirtualSnapshotPoint Start => multiSelectionBroker.PrimarySelection.Start; 47 | 48 | public VirtualSnapshotPoint End => multiSelectionBroker.PrimarySelection.End; 49 | 50 | public event EventHandler SelectionChanged; 51 | 52 | public void Clear () 53 | => multiSelectionBroker.SetSelection (new Selection (multiSelectionBroker.PrimarySelection.InsertionPoint)); 54 | 55 | public VirtualSnapshotSpan? GetSelectionOnTextViewLine(ITextViewLine line) 56 | { 57 | throw new NotImplementedException(); 58 | } 59 | 60 | public void Select (SnapshotSpan selectionSpan, bool isReversed) 61 | => multiSelectionBroker.SetSelection (new Selection (selectionSpan, isReversed)); 62 | 63 | public void Select (VirtualSnapshotPoint anchorPoint, VirtualSnapshotPoint activePoint) 64 | => multiSelectionBroker.SetSelection (new Selection (anchorPoint, activePoint)); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /Microsoft.VisualStudio.MiniEditor/BaseViewImpl/TestTextView.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using Microsoft.VisualStudio.Text.Formatting; 4 | using Microsoft.VisualStudio.Text.Projection; 5 | using Microsoft.VisualStudio.Text.Utilities; 6 | using Microsoft.VisualStudio.Utilities; 7 | 8 | namespace Microsoft.VisualStudio.Text.Editor.Implementation 9 | { 10 | class TestTextView : ITextView3 11 | { 12 | TestTextCaret _caret; 13 | 14 | //we pretend each char is a simple square 15 | const double charSize = 20; 16 | 17 | //enormous viewport makes things simpler 18 | const double viewportSize = 20000.0; 19 | 20 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 21 | #region Private Data Members 22 | 23 | 24 | private ITextBuffer _textBuffer; 25 | readonly TextViewFactoryService _factoryService; 26 | 27 | #endregion 28 | 29 | #region Construction 30 | public TestTextView (ITextBuffer textBuffer, TextViewFactoryService factoryService) 31 | { 32 | _textBuffer = textBuffer; 33 | _factoryService = factoryService; 34 | 35 | Options = factoryService.EditorOptionsFactory.CreateOptions (true); 36 | factoryService.EditorOptionsFactory.TryBindToScope (Options, this); 37 | 38 | TextSnapshot = textBuffer.CurrentSnapshot; 39 | TextDataModel = new VacuousTextDataModel (textBuffer); 40 | TextViewModel = new VacuousTextViewModel (TextDataModel); 41 | MultiSelectionBroker = _factoryService.MultiSelectionBrokerFactory.CreateBroker (this); 42 | 43 | CreateLines (); 44 | 45 | Selection = new TestTextSelection (this, MultiSelectionBroker); 46 | _caret = new TestTextCaret (this, _factoryService.SmartIndentationService, MultiSelectionBroker); 47 | 48 | textBuffer.ChangedLowPriority += TextBufferChangedLowPriority; 49 | 50 | var listeners = UIExtensionSelector.SelectMatchingExtensions ( 51 | _factoryService.TextViewCreationListeners, _textBuffer.ContentType, null, Roles); 52 | foreach (var listener in listeners) { 53 | listener.Value.TextViewCreated (this); 54 | } 55 | } 56 | 57 | #endregion 58 | 59 | void TextBufferChangedLowPriority (object sender, TextContentChangedEventArgs e) 60 | { 61 | PerformLayout (); 62 | } 63 | 64 | // this is EXTREMELY naive 65 | void PerformLayout () 66 | { 67 | if (TextBuffer.CurrentSnapshot == TextSnapshot) { 68 | return; 69 | } 70 | 71 | CreateLines (); 72 | TextSnapshot = TextBuffer.CurrentSnapshot; 73 | 74 | // this never changes 75 | var viewState = new ViewState (this, ViewportWidth, ViewportHeight); 76 | 77 | // HACK we don't track which lines changed, so broadcast all of them 78 | IList translatedLines = TextViewLines; 79 | IList newOrReformattedLines = TextViewLines; 80 | 81 | // the MultiCaretBroker needs this in order to update its position 82 | this?.LayoutChanged (this, new TextViewLayoutChangedEventArgs (viewState, viewState, newOrReformattedLines, translatedLines)); 83 | _caret.Update (); 84 | } 85 | 86 | void CreateLines () 87 | { 88 | ITextSnapshot snapshot = TextBuffer.CurrentSnapshot; 89 | int topLine = 0; 90 | int bottomLine = snapshot.LineCount - 1; 91 | 92 | var lines = new TestTextViewLineCollection (this); 93 | for (int i = topLine; i <= bottomLine; i++) { 94 | var l = snapshot.GetLineFromLineNumber (i); 95 | double top = charSize * i; 96 | var line = new TestTextViewLine (this, l, 0, top, l.Length * charSize, top + LineHeight); 97 | lines.Add (line); 98 | } 99 | 100 | TextViewLines = lines; 101 | } 102 | 103 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 104 | #region ITextView Members 105 | 106 | public event EventHandler LayoutChanged; 107 | 108 | public event EventHandler MouseHover; 109 | 110 | public event EventHandler ViewportWidthChanged; 111 | 112 | public event EventHandler ViewportLeftChanged; 113 | 114 | public event EventHandler ViewportHeightChanged; 115 | 116 | public ITextCaret Caret => _caret; 117 | 118 | public void DisplayTextLineContainingBufferPosition (SnapshotPoint position, double verticalDistance, ViewRelativePosition relativeTo) => throw new NotImplementedException (); 119 | 120 | public void DisplayTextLineContainingBufferPosition (SnapshotPoint position, double verticalDistance, ViewRelativePosition relativeTo, double? width, double? height) => throw new NotImplementedException (); 121 | 122 | public SnapshotSpan GetTextElementSpan (SnapshotPoint position) => TextViewLines.GetTextElementSpan (position); 123 | 124 | public bool InLayout => throw new NotImplementedException (); 125 | 126 | public double MaxTextRightCoordinate => throw new NotImplementedException (); 127 | 128 | public ITrackingSpan ProvisionalTextHighlight { 129 | get { 130 | throw new NotImplementedException (); 131 | } 132 | set { 133 | throw new NotImplementedException (); 134 | } 135 | } 136 | 137 | public ITextViewLineCollection TextViewLines { get; private set; } 138 | 139 | public ITextSelection Selection { get; } 140 | 141 | public int TabSize { 142 | get { 143 | throw new NotImplementedException (); 144 | } 145 | set { 146 | throw new NotImplementedException (); 147 | } 148 | } 149 | 150 | public ITextViewRoleSet Roles => new TextViewRoleSet (new string[] { 151 | PredefinedTextViewRoles.Analyzable, 152 | PredefinedTextViewRoles.Document, 153 | PredefinedTextViewRoles.Editable, 154 | PredefinedTextViewRoles.Interactive, 155 | PredefinedTextViewRoles.Structured, 156 | PredefinedTextViewRoles.Zoomable 157 | }); 158 | 159 | public ITextBuffer TextBuffer => (_textBuffer); 160 | 161 | public ITextSnapshot TextSnapshot { get; private set; } 162 | 163 | // we have a vacuous model so this is the same 164 | public ITextSnapshot VisualSnapshot => TextSnapshot; 165 | 166 | public ITextViewModel TextViewModel { get; } 167 | 168 | public ITextDataModel TextDataModel { get; } 169 | 170 | public IBufferGraph BufferGraph => _factoryService.BufferGraphFactoryService.CreateBufferGraph (_textBuffer); 171 | 172 | public IViewScroller ViewScroller => TestViewScroller.Instance; 173 | 174 | public double ViewportBottom => ViewportTop + ViewportHeight; 175 | 176 | public double ViewportHeight => viewportSize; 177 | 178 | public double LineHeight => charSize; 179 | 180 | public double ViewportLeft { 181 | get => 0.0; 182 | set => throw new NotImplementedException (); 183 | } 184 | 185 | public double ViewportRight => ViewportLeft + ViewportWidth; 186 | 187 | public double ViewportTop => 0.0; 188 | 189 | public double ViewportWidth => viewportSize; 190 | 191 | public double ZoomLevel { 192 | get { throw new NotImplementedException (); } 193 | set { throw new NotImplementedException (); } 194 | } 195 | 196 | public void Close () 197 | { 198 | IsClosed = true; 199 | Closed?.Invoke (this, EventArgs.Empty); 200 | } 201 | 202 | public event EventHandler Closed; 203 | public event EventHandler IsKeyboardFocusedChanged; 204 | public event EventHandler MaxTextRightCoordinateChanged; 205 | 206 | public bool IsClosed { get; private set; } 207 | 208 | public IEditorOptions Options { get; } 209 | 210 | public bool IsMouseOverViewOrAdornments => throw new NotImplementedException (); 211 | 212 | public bool HasAggregateFocus => throw new NotImplementedException (); 213 | 214 | public event EventHandler LostAggregateFocus { 215 | add { } 216 | remove { } 217 | } 218 | 219 | public event EventHandler GotAggregateFocus { 220 | add { } 221 | remove { } 222 | } 223 | 224 | public void QueueSpaceReservationStackRefresh () => throw new NotImplementedException (); 225 | 226 | public IViewSynchronizationManager SynchronizationManager { get; set; } 227 | #endregion 228 | 229 | public ITextViewLine GetTextViewLineContainingBufferPosition (SnapshotPoint bufferPosition) 230 | => TextViewLines.GetTextViewLineContainingBufferPosition (bufferPosition); 231 | 232 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 233 | #region IPropertyOwner Members 234 | 235 | public PropertyCollection Properties { get; } = new PropertyCollection(); 236 | 237 | public ITextViewLineSource FormattedLineSource => throw new NotImplementedException (); 238 | 239 | public bool IsKeyboardFocused => throw new NotImplementedException (); 240 | 241 | public bool InOuterLayout => throw new NotImplementedException (); 242 | 243 | public IMultiSelectionBroker MultiSelectionBroker { get; } 244 | 245 | #endregion 246 | 247 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 248 | #region Internal Surface 249 | 250 | internal void FireClosed () => this.Closed?.Invoke (this, EventArgs.Empty); 251 | 252 | public IXPlatAdornmentLayer GetXPlatAdornmentLayer (string name) 253 | { 254 | throw new NotImplementedException (); 255 | } 256 | 257 | public void Focus () 258 | { 259 | throw new NotImplementedException (); 260 | } 261 | 262 | public void QueuePostLayoutAction (Action action) 263 | { 264 | throw new NotImplementedException (); 265 | } 266 | 267 | public bool TryGetTextViewLines (out ITextViewLineCollection textViewLines) 268 | { 269 | throw new NotImplementedException (); 270 | } 271 | 272 | public bool TryGetTextViewLineContainingBufferPosition (SnapshotPoint bufferPosition, out ITextViewLine textViewLine) 273 | { 274 | throw new NotImplementedException (); 275 | } 276 | 277 | #endregion 278 | } 279 | } 280 | -------------------------------------------------------------------------------- /Microsoft.VisualStudio.MiniEditor/BaseViewImpl/TestTextViewLine.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.VisualStudio.Text.Formatting; 3 | using System.Collections.ObjectModel; 4 | 5 | namespace Microsoft.VisualStudio.Text.Editor.Implementation 6 | { 7 | class TestTextViewLine : ITextViewLine 8 | { 9 | const double padding = 0.2; 10 | 11 | readonly ITextSnapshotLine bufferLine; 12 | readonly TestTextView view; 13 | 14 | public TestTextViewLine(TestTextView view, ITextSnapshotLine bufferLine, double left, double top, double right, double bottom) 15 | { 16 | Left = left; 17 | Right = right; 18 | Bottom = bottom; 19 | Top = top; 20 | this.bufferLine = bufferLine; 21 | } 22 | 23 | public object IdentityTag => this; 24 | 25 | public ITextSnapshot Snapshot => bufferLine.Snapshot; 26 | 27 | public bool IsFirstTextViewLineForSnapshotLine => true; 28 | 29 | public bool IsLastTextViewLineForSnapshotLine => true; 30 | 31 | public double Baseline => throw new NotImplementedException(); 32 | 33 | public SnapshotSpan Extent => new SnapshotSpan(bufferLine.Start, bufferLine.End); 34 | 35 | IMappingSpan extentAsMappingSpan; 36 | public IMappingSpan ExtentAsMappingSpan => extentAsMappingSpan ?? 37 | (extentAsMappingSpan = view.BufferGraph.CreateMappingSpan(Extent, SpanTrackingMode.EdgeInclusive)); 38 | 39 | public SnapshotSpan ExtentIncludingLineBreak => new SnapshotSpan(bufferLine.Start, bufferLine.EndIncludingLineBreak); 40 | 41 | IMappingSpan extentIncludingLineBreakAsMappingSpan; 42 | public IMappingSpan ExtentIncludingLineBreakAsMappingSpan => extentIncludingLineBreakAsMappingSpan ?? 43 | (extentIncludingLineBreakAsMappingSpan = view.BufferGraph.CreateMappingSpan(ExtentIncludingLineBreak, SpanTrackingMode.EdgeInclusive)); 44 | 45 | public SnapshotPoint Start => bufferLine.Start; 46 | 47 | public int Length => bufferLine.Length; 48 | 49 | public int LengthIncludingLineBreak => bufferLine.LengthIncludingLineBreak; 50 | 51 | public SnapshotPoint End => bufferLine.End; 52 | 53 | public SnapshotPoint EndIncludingLineBreak => bufferLine.EndIncludingLineBreak; 54 | 55 | public int LineBreakLength => bufferLine.LineBreakLength; 56 | 57 | public double Left { get; } 58 | 59 | public double Top { get; } 60 | 61 | public double Height => Bottom - Top; 62 | 63 | public double TextTop => Top + padding; 64 | 65 | public double TextBottom => Bottom - padding; 66 | 67 | public double TextHeight => Bottom - Top - padding - padding; 68 | 69 | public double TextLeft => Left + padding; 70 | 71 | //FIXME 72 | public double TextRight => Right - padding; 73 | 74 | //FIXME 75 | public double TextWidth => Right - Left - padding - padding; 76 | 77 | public double Width => Right - Left; 78 | 79 | public double Bottom { get; } 80 | 81 | public double Right { get; } 82 | 83 | public double EndOfLineWidth => 0; 84 | 85 | public double VirtualSpaceWidth => 0; 86 | 87 | public bool IsValid => true; 88 | 89 | public LineTransform LineTransform => default; 90 | 91 | public LineTransform DefaultLineTransform => default; 92 | 93 | public VisibilityState VisibilityState => VisibilityState.FullyVisible; 94 | 95 | public double DeltaY => throw new NotImplementedException(); 96 | 97 | public TextViewLineChange Change => throw new NotImplementedException(); 98 | 99 | public bool ContainsBufferPosition(SnapshotPoint bufferPosition) => ExtentIncludingLineBreak.Contains(bufferPosition); 100 | 101 | public TextBounds? GetAdornmentBounds(object identityTag) => throw new NotImplementedException(); 102 | 103 | public ReadOnlyCollection GetAdornmentTags(object providerTag) => throw new NotImplementedException(); 104 | 105 | public SnapshotPoint? GetBufferPositionFromXCoordinate(double xCoordinate, bool textOnly) 106 | { 107 | throw new NotImplementedException(); 108 | } 109 | 110 | public SnapshotPoint? GetBufferPositionFromXCoordinate(double xCoordinate) 111 | { 112 | throw new NotImplementedException(); 113 | } 114 | 115 | public TextBounds GetCharacterBounds(SnapshotPoint bufferPosition) 116 | => GetCharacterBounds (new VirtualSnapshotPoint (bufferPosition)); 117 | 118 | public TextBounds GetCharacterBounds(VirtualSnapshotPoint bufferPosition) 119 | { 120 | var col = bufferPosition.Position - bufferLine.Start; 121 | 122 | //pretend each character is a perfect square 123 | return new TextBounds (Left + col * Height, Top, Height, Height, Top, Height); 124 | } 125 | 126 | public TextBounds GetExtendedCharacterBounds(SnapshotPoint bufferPosition) 127 | => GetCharacterBounds (bufferPosition); 128 | 129 | public TextBounds GetExtendedCharacterBounds (VirtualSnapshotPoint bufferPosition) 130 | => GetCharacterBounds (bufferPosition); 131 | 132 | public VirtualSnapshotPoint GetInsertionBufferPositionFromXCoordinate(double xCoordinate) 133 | { 134 | throw new NotImplementedException(); 135 | } 136 | 137 | public Collection GetNormalizedTextBounds(SnapshotSpan bufferSpan) 138 | { 139 | throw new NotImplementedException(); 140 | } 141 | 142 | //FIXME surrogate chars, elision, etc 143 | public SnapshotSpan GetTextElementSpan(SnapshotPoint bufferPosition) => new SnapshotSpan (bufferPosition, 1); 144 | 145 | public VirtualSnapshotPoint GetVirtualBufferPositionFromXCoordinate(double xCoordinate) 146 | { 147 | throw new NotImplementedException(); 148 | } 149 | 150 | public bool IntersectsBufferSpan(SnapshotSpan bufferSpan) => Extent.IntersectsWith(bufferSpan); 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /Microsoft.VisualStudio.MiniEditor/BaseViewImpl/TestTextViewLineCollection.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.VisualStudio.Text.Formatting; 3 | using System.Collections.Generic; 4 | using System.Collections.ObjectModel; 5 | using System.Linq; 6 | 7 | namespace Microsoft.VisualStudio.Text.Editor.Implementation 8 | { 9 | class TestTextViewLineCollection : List, ITextViewLineCollection 10 | { 11 | readonly TestTextView testTextView; 12 | 13 | public TestTextViewLineCollection(TestTextView testTextView) 14 | { 15 | this.testTextView = testTextView; 16 | } 17 | 18 | public ITextViewLine FirstVisibleLine => this.FirstOrDefault(); 19 | 20 | public ITextViewLine LastVisibleLine => this.LastOrDefault(); 21 | 22 | public SnapshotSpan FormattedSpan => throw new NotImplementedException(); 23 | 24 | public bool IsValid => true; 25 | 26 | public bool ContainsBufferPosition(SnapshotPoint bufferPosition) 27 | => false; 28 | 29 | public TextBounds GetCharacterBounds(SnapshotPoint bufferPosition) 30 | { 31 | throw new NotImplementedException(); 32 | } 33 | 34 | public int GetIndexOfTextLine(ITextViewLine textLine) 35 | => -1; 36 | 37 | public Collection GetNormalizedTextBounds(SnapshotSpan bufferSpan) 38 | { 39 | throw new NotImplementedException(); 40 | } 41 | 42 | public SnapshotSpan GetTextElementSpan(SnapshotPoint bufferPosition) 43 | { 44 | var line = GetTextViewLineContainingBufferPosition(bufferPosition); 45 | if (line == null) 46 | { 47 | throw new ArgumentException(); 48 | } 49 | return line.GetTextElementSpan(bufferPosition); 50 | } 51 | 52 | public ITextViewLine GetTextViewLineContainingBufferPosition(SnapshotPoint bufferPosition) 53 | { 54 | //FIXME: binary search 55 | foreach (var l in this) 56 | { 57 | if (l.ContainsBufferPosition(bufferPosition)) 58 | return l; 59 | } 60 | 61 | // ContainsBufferPosition includes the start position of the line but not the 62 | // position after the last char on the line, so we have to explicitly handle 63 | // the case where the caret is after the last char in the buffer 64 | var last = this.LastOrDefault (); 65 | if (last != null && bufferPosition == last.EndIncludingLineBreak) { 66 | return last; 67 | } 68 | 69 | return null; 70 | } 71 | 72 | public ITextViewLine GetTextViewLineContainingYCoordinate(double y) 73 | => null; 74 | 75 | public Collection GetTextViewLinesIntersectingSpan(SnapshotSpan bufferSpan) 76 | => null; 77 | 78 | public bool IntersectsBufferSpan(SnapshotSpan bufferSpan) 79 | => false; 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /Microsoft.VisualStudio.MiniEditor/BaseViewImpl/TestViewScroller.cs: -------------------------------------------------------------------------------- 1 | namespace Microsoft.VisualStudio.Text.Editor.Implementation 2 | { 3 | internal class TestViewScroller : IViewScroller 4 | { 5 | public static readonly TestViewScroller Instance = new TestViewScroller(); 6 | 7 | public void EnsureSpanVisible(SnapshotSpan span) 8 | { 9 | } 10 | 11 | public void EnsureSpanVisible(SnapshotSpan span, EnsureSpanVisibleOptions options) 12 | { 13 | } 14 | 15 | public void EnsureSpanVisible(VirtualSnapshotSpan span, EnsureSpanVisibleOptions options) 16 | { 17 | } 18 | 19 | public void ScrollViewportHorizontallyByPixels(double distanceToScroll) 20 | { 21 | } 22 | 23 | public void ScrollViewportVerticallyByLine(ScrollDirection direction) 24 | { 25 | } 26 | 27 | public void ScrollViewportVerticallyByLines(ScrollDirection direction, int count) 28 | { 29 | } 30 | 31 | public bool ScrollViewportVerticallyByPage(ScrollDirection direction) => true; 32 | 33 | public void ScrollViewportVerticallyByPixels(double distanceToScroll) 34 | { 35 | } 36 | } 37 | } -------------------------------------------------------------------------------- /Microsoft.VisualStudio.MiniEditor/BaseViewImpl/TextViewFactoryService.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel.Composition; 4 | using Microsoft.VisualStudio.Text.Projection; 5 | using Microsoft.VisualStudio.Text.Utilities; 6 | 7 | namespace Microsoft.VisualStudio.Text.Editor.Implementation 8 | { 9 | [Export (typeof (ITextViewFactoryService))] 10 | public class TextViewFactoryService : ITextViewFactoryService 11 | { 12 | [Import] 13 | public ITextBufferFactoryService BufferFactoryService { get; set; } 14 | 15 | [Import] 16 | public IBufferGraphFactoryService BufferGraphFactoryService { get; set; } 17 | 18 | [Import] 19 | public IMultiSelectionBrokerFactory MultiSelectionBrokerFactory { get; set; } 20 | 21 | [Import] 22 | public ISmartIndentationService SmartIndentationService { get; set; } 23 | 24 | [Import] 25 | public IEditorOptionsFactoryService2 EditorOptionsFactory { get; set; } 26 | 27 | [ImportMany] 28 | public List> TextViewCreationListeners { get; set; } 29 | 30 | public ITextView CreateTextView (ITextBuffer buffer) 31 | => new TestTextView (buffer, this); 32 | 33 | public ITextView CreateTextView () => CreateTextView (BufferFactoryService.CreateTextBuffer ()); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Microsoft.VisualStudio.MiniEditor/BaseViewImpl/TextViewRoleSet.cs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) Microsoft Corporation. All rights reserved. 3 | // Licensed under the MIT License. See License.txt in the project root for license information. 4 | // 5 | // This file contain implementations details that are subject to change without notice. 6 | // Use at your own risk. 7 | // 8 | namespace Microsoft.VisualStudio.Text.Editor.Implementation 9 | { 10 | using System; 11 | using System.Collections; 12 | using System.Collections.Generic; 13 | 14 | internal class TextViewRoleSet : ITextViewRoleSet 15 | { 16 | private List roles; 17 | 18 | public TextViewRoleSet(IEnumerable roles) 19 | { 20 | if (roles == null) 21 | { 22 | throw new ArgumentNullException(nameof(roles)); 23 | } 24 | this.roles = new List(); 25 | foreach (string role in roles) 26 | { 27 | if (role == null) 28 | { 29 | throw new ArgumentNullException(nameof(roles)); 30 | } 31 | else 32 | { 33 | this.roles.Add(role.ToUpperInvariant()); 34 | } 35 | } 36 | } 37 | 38 | public bool Contains(string textViewRole) 39 | { 40 | if (textViewRole == null) 41 | { 42 | throw new ArgumentNullException(nameof(textViewRole)); 43 | } 44 | string upperTextViewRole = textViewRole.ToUpperInvariant(); 45 | foreach (string role in this.roles) 46 | { 47 | if (string.Equals(role, upperTextViewRole, StringComparison.Ordinal)) 48 | { 49 | return true; 50 | } 51 | } 52 | return false; 53 | } 54 | 55 | public bool ContainsAny(IEnumerable textViewRoles) 56 | { 57 | if (textViewRoles == null) 58 | { 59 | throw new ArgumentNullException(nameof(textViewRoles)); 60 | } 61 | foreach (string textViewRole in textViewRoles) 62 | { 63 | if (textViewRole != null) 64 | { 65 | string upperTextViewRole = textViewRole.ToUpperInvariant(); 66 | foreach (string role in this.roles) 67 | { 68 | if (string.Equals(role, upperTextViewRole, StringComparison.Ordinal)) 69 | { 70 | return true; 71 | } 72 | } 73 | } 74 | } 75 | return false; 76 | } 77 | 78 | public bool ContainsAll(IEnumerable textViewRoles) 79 | { 80 | if (textViewRoles == null) 81 | { 82 | throw new ArgumentNullException(nameof(textViewRoles)); 83 | } 84 | foreach (string textViewRole in textViewRoles) 85 | { 86 | if (textViewRole != null) 87 | { 88 | bool found = false; 89 | string upperTextViewRole = textViewRole.ToUpperInvariant(); 90 | foreach (string role in this.roles) 91 | { 92 | if (string.Equals(role, upperTextViewRole, StringComparison.Ordinal)) 93 | { 94 | found = true; 95 | break; 96 | } 97 | } 98 | if (!found) 99 | { 100 | return false; 101 | } 102 | } 103 | } 104 | return true; 105 | } 106 | 107 | public ITextViewRoleSet UnionWith(ITextViewRoleSet roleSet) 108 | { 109 | if (roleSet == null) 110 | { 111 | throw new ArgumentNullException(nameof(roleSet)); 112 | } 113 | var resultRoles = new HashSet(this.roles); 114 | foreach (string role in roleSet) 115 | { 116 | if (!resultRoles.Contains(role)) 117 | { 118 | resultRoles.Add(role); 119 | } 120 | } 121 | return new TextViewRoleSet(resultRoles); 122 | } 123 | 124 | IEnumerator IEnumerable.GetEnumerator() 125 | { 126 | foreach (string role in this.roles) 127 | { 128 | yield return role; 129 | } 130 | } 131 | 132 | IEnumerator IEnumerable.GetEnumerator() 133 | { 134 | foreach (string role in this.roles) 135 | { 136 | yield return role; 137 | } 138 | } 139 | 140 | public override string ToString() 141 | { 142 | // NOTE: Don't change this! There is code in VsCodeWindowAdapter that 143 | // relies on the format returned by this method. 144 | return string.Join(",", this.roles); 145 | } 146 | } 147 | 148 | } -------------------------------------------------------------------------------- /Microsoft.VisualStudio.MiniEditor/BaseViewImpl/ViewOptionsCompat.cs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) Microsoft Corporation. All rights reserved. 3 | // Licensed under the MIT License. See License.txt in the project root for license information. 4 | // 5 | using System; 6 | using System.ComponentModel.Composition; 7 | using Microsoft.VisualStudio.Utilities; 8 | 9 | 10 | #pragma warning disable CS0436 // Type conflicts with imported type 11 | 12 | namespace Microsoft.VisualStudio.Text.Editor.OptionsExtensionMethods 13 | { 14 | /// 15 | /// Provides methods for -related options. 16 | /// 17 | public static class TextViewOptionExtensions 18 | { 19 | public static bool IsVisibleWhitespaceOnlyWhenSelectedEnabled (this IEditorOptions options) 20 | { 21 | if (options == null) 22 | throw new ArgumentNullException (nameof (options)); 23 | 24 | return options.GetOptionValue (DefaultTextViewOptions.UseVisibleWhitespaceOnlyWhenSelectedId); 25 | } 26 | 27 | public static DefaultTextViewOptions.IncludeWhitespaces VisibleWhitespaceEnabledTypes (this IEditorOptions options) 28 | { 29 | if (options == null) 30 | throw new ArgumentNullException (nameof (options)); 31 | 32 | return options.GetOptionValue (DefaultTextViewOptions.UseVisibleWhitespaceIncludeId); 33 | } 34 | 35 | /// 36 | /// Determines if the caret should be moved to the end of the selection after performing the "select all" operation. 37 | /// 38 | public static bool ShouldMoveCaretOnSelectAll (this IEditorOptions options) 39 | { 40 | if (options == null) 41 | throw new ArgumentNullException (nameof (options)); 42 | 43 | return options.GetOptionValue (DefaultTextViewOptions.ShouldMoveCaretOnSelectAllId); 44 | } 45 | } 46 | } 47 | 48 | namespace Microsoft.VisualStudio.Text.Editor 49 | { 50 | /// 51 | /// Defines common options. 52 | /// 53 | public static class DefaultTextViewOptions 54 | { 55 | #region Option identifiers 56 | 57 | /// 58 | /// Determines whether cut and copy causes a blank line to be cut or copied when the selection is empty. 59 | /// 60 | public static readonly EditorOptionKey CutOrCopyBlankLineIfNoSelectionId = new EditorOptionKey (CutOrCopyBlankLineIfNoSelectionName); 61 | public const string CutOrCopyBlankLineIfNoSelectionName = "TextView/CutOrCopyBlankLineIfNoSelection"; 62 | 63 | /// 64 | /// Determines whether to prohibit user input. The text in the view's 65 | /// buffer can still be modified, and other views on the same buffer may allow user input. 66 | /// 67 | public static readonly EditorOptionKey ViewProhibitUserInputId = new EditorOptionKey (ViewProhibitUserInputName); 68 | public const string ViewProhibitUserInputName = "TextView/ProhibitUserInput"; 69 | 70 | /// 71 | /// Gets the word wrap style for the underlying view. 72 | /// 73 | /// Turning word wrap on will always hide the host's horizontal scroll bar. Turning word wrap off 74 | /// will always expose the host's horizontal scroll bar. 75 | public static readonly EditorOptionKey WordWrapStyleId = new EditorOptionKey (WordWrapStyleName); 76 | public const string WordWrapStyleName = "TextView/WordWrapStyle"; 77 | 78 | /// 79 | /// Determines whether to enable virtual space in the view. 80 | /// 81 | public static readonly EditorOptionKey UseVirtualSpaceId = new EditorOptionKey (UseVirtualSpaceName); 82 | public const string UseVirtualSpaceName = "TextView/UseVirtualSpace"; 83 | 84 | /// 85 | /// Determines whether the view's ViewportLeft property is clipped to the text width. 86 | /// 87 | public static readonly EditorOptionKey IsViewportLeftClippedId = new EditorOptionKey (IsViewportLeftClippedName); 88 | public const string IsViewportLeftClippedName = "TextView/IsViewportLeftClipped"; 89 | 90 | /// 91 | /// Determines whether overwrite mode is enabled. 92 | /// 93 | public static readonly EditorOptionKey OverwriteModeId = new EditorOptionKey (OverwriteModeName); 94 | public const string OverwriteModeName = "TextView/OverwriteMode"; 95 | 96 | /// 97 | /// Determines whether the view should auto-scroll on text changes. 98 | /// 99 | /// 100 | /// If this option is enabled, whenever a text change occurs and the caret is on the last line, 101 | /// the view will be scrolled to make the caret visible. 102 | /// 103 | public static readonly EditorOptionKey AutoScrollId = new EditorOptionKey (AutoScrollName); 104 | public const string AutoScrollName = "TextView/AutoScroll"; 105 | 106 | /// 107 | /// Determines whether to show spaces and tabs as visible glyphs. 108 | /// 109 | public static readonly EditorOptionKey UseVisibleWhitespaceId = new EditorOptionKey (UseVisibleWhitespaceName); 110 | public const string UseVisibleWhitespaceName = "TextView/UseVisibleWhitespace"; 111 | 112 | /// 113 | /// Determines whether to show spaces, tabs and EndOfLine as visible glyphs only under selection. 114 | /// 115 | public static readonly EditorOptionKey UseVisibleWhitespaceOnlyWhenSelectedId = new EditorOptionKey (UseVisibleWhitespaceOnlyWhenSelectedName); 116 | public const string UseVisibleWhitespaceOnlyWhenSelectedName = "TextView/UseVisibleWhitespace/OnlyWhenSelected"; 117 | 118 | /// 119 | /// Determines whether to show spaces, tabs and EndOfLine as visible glyphs only for specific type of whitespace. 120 | /// 121 | public static readonly EditorOptionKey UseVisibleWhitespaceIncludeId = new EditorOptionKey (UseVisibleWhitespaceIncludeName); 122 | public const string UseVisibleWhitespaceIncludeName = "TextView/UseVisibleWhitespace/Include"; 123 | 124 | [Flags] 125 | public enum IncludeWhitespaces 126 | { 127 | None = 0x0, 128 | Spaces = 0x1, 129 | Tabs = 0x2, 130 | LineEndings = 0x4, 131 | Ideographics = 0x8, 132 | All = 0xf, 133 | } 134 | 135 | /// 136 | /// Enables or disables the code block structure visualizer text adornment feature. 137 | /// 138 | public static readonly EditorOptionKey ShowBlockStructureId = new EditorOptionKey (ShowBlockStructureName); 139 | public const string ShowBlockStructureName = "TextView/ShowBlockStructure"; 140 | 141 | /// 142 | /// Should the carets be rendered. 143 | /// 144 | public static readonly EditorOptionKey ShouldCaretsBeRenderedId = new EditorOptionKey (ShouldCaretsBeRenderedName); 145 | public const string ShouldCaretsBeRenderedName = "TextView/ShouldCaretsBeRendered"; 146 | 147 | /// 148 | /// Should the selections be rendered. 149 | /// 150 | public static readonly EditorOptionKey ShouldSelectionsBeRenderedId = new EditorOptionKey (ShouldSelectionsBeRenderedName); 151 | public const string ShouldSelectionsBeRenderedName = "TextView/ShouldSelectionsBeRendered"; 152 | 153 | /// 154 | /// Whether or not to replace the coding characters and special symbols (such as (,),{,},etc.) with their textual representation 155 | /// for automated objects to produce friendly text for screen readers. 156 | /// 157 | public static readonly EditorOptionKey ProduceScreenReaderFriendlyTextId = new EditorOptionKey (ProduceScreenReaderFriendlyTextName); 158 | public const string ProduceScreenReaderFriendlyTextName = "TextView/ProduceScreenReaderFriendlyText"; 159 | 160 | /// 161 | /// The default option that determines whether outlining is undoable. 162 | /// 163 | public static readonly EditorOptionKey OutliningUndoOptionId = new EditorOptionKey (OutliningUndoOptionName); 164 | public const string OutliningUndoOptionName = "TextView/OutliningUndo"; 165 | 166 | /// 167 | /// Determines whether URLs should be displayed as hyperlinks. 168 | /// 169 | public static readonly EditorOptionKey DisplayUrlsAsHyperlinksId = new EditorOptionKey (DisplayUrlsAsHyperlinksName); 170 | public const string DisplayUrlsAsHyperlinksName = "TextView/DisplayUrlsAsHyperlinks"; 171 | 172 | /// 173 | /// The default option that determines whether drag/drop editing is enabled. 174 | /// 175 | public static readonly EditorOptionKey DragDropEditingId = new EditorOptionKey (DragDropEditingName); 176 | public const string DragDropEditingName = "TextView/DragDrop"; 177 | 178 | /// 179 | /// Determines if automatic brace completion is enabled. 180 | /// 181 | public const string BraceCompletionEnabledOptionName = "BraceCompletion/Enabled"; 182 | public readonly static EditorOptionKey BraceCompletionEnabledOptionId = new EditorOptionKey (BraceCompletionEnabledOptionName); 183 | 184 | /// 185 | /// Defines how wide the caret should be rendered. This is typically used to support accessibility requirements. 186 | /// 187 | public const string CaretWidthOptionName = "TextView/CaretWidth"; 188 | public readonly static EditorOptionKey CaretWidthId = new EditorOptionKey (CaretWidthOptionName); 189 | 190 | /// 191 | /// Determines whether to enable the highlight current line adornment. 192 | /// 193 | public static readonly EditorOptionKey EnableHighlightCurrentLineId = new EditorOptionKey (EnableHighlightCurrentLineName); 194 | public const string EnableHighlightCurrentLineName = "Adornments/HighlightCurrentLine/Enable"; 195 | 196 | /// 197 | /// Determines whether to enable the highlight current line adornment. 198 | /// 199 | public static readonly EditorOptionKey EnableSimpleGraphicsId = new EditorOptionKey (EnableSimpleGraphicsName); 200 | public const string EnableSimpleGraphicsName = "Graphics/Simple/Enable"; 201 | 202 | /// 203 | /// Determines whether the opacity of text markers and selection is reduced in high contrast mode. 204 | /// 205 | public static readonly EditorOptionKey UseReducedOpacityForHighContrastOptionId = new EditorOptionKey (UseReducedOpacityForHighContrastOptionName); 206 | public const string UseReducedOpacityForHighContrastOptionName = "UseReducedOpacityForHighContrast"; 207 | 208 | /// 209 | /// Determines whether to enable mouse wheel zooming 210 | /// 211 | public static readonly EditorOptionKey EnableMouseWheelZoomId = new EditorOptionKey (EnableMouseWheelZoomName); 212 | public const string EnableMouseWheelZoomName = "TextView/MouseWheelZoom"; 213 | 214 | /// 215 | /// Determines the appearance category of a view, which selects a ClassificationFormatMap and EditorFormatMap. 216 | /// 217 | public static readonly EditorOptionKey AppearanceCategory = new EditorOptionKey (AppearanceCategoryName); 218 | public const string AppearanceCategoryName = "Appearance/Category"; 219 | 220 | /// 221 | /// Determines the view zoom level. 222 | /// 223 | public static readonly EditorOptionKey ZoomLevelId = new EditorOptionKey (ZoomLevelName); 224 | public const string ZoomLevelName = "TextView/ZoomLevel"; 225 | 226 | /// 227 | /// Determines the minimum view zoom level. 228 | /// 229 | public static readonly EditorOptionKey MinZoomLevelId = new EditorOptionKey (MinZoomLevelName); 230 | public const string MinZoomLevelName = "TextView/MinZoomLevel"; 231 | 232 | /// 233 | /// Determines the maximum view zoom level. 234 | /// 235 | public static readonly EditorOptionKey MaxZoomLevelId = new EditorOptionKey (MaxZoomLevelName); 236 | public const string MaxZoomLevelName = "TextView/MaxZoomLevel"; 237 | 238 | /// 239 | /// Determines whether to enable mouse click + modifier keypress for go to definition. 240 | /// 241 | public const string ClickGoToDefEnabledName = "TextView/ClickGoToDefEnabled"; 242 | public static readonly EditorOptionKey ClickGoToDefEnabledId = new EditorOptionKey (ClickGoToDefEnabledName); 243 | 244 | /// 245 | /// Determines whether to open definition target in Peek view for mouse click + modifier keypress. 246 | /// 247 | public const string ClickGoToDefOpensPeekName = "TextView/ClickGoToDefOpensPeek"; 248 | public static readonly EditorOptionKey ClickGoToDefOpensPeekId = new EditorOptionKey (ClickGoToDefOpensPeekName); 249 | 250 | /// 251 | /// The default option that determines whether to move the caret when performing the "select all" operation. 252 | /// 253 | public static readonly EditorOptionKey ShouldMoveCaretOnSelectAllId = new EditorOptionKey (ShouldMoveCaretOnSelectAllName); 254 | public const string ShouldMoveCaretOnSelectAllName = "TextView/ShouldMoveCaretOnSelectAll"; 255 | #endregion 256 | } 257 | 258 | /// 259 | /// Names of common host-related options. 260 | /// 261 | public static class DefaultTextViewHostOptions 262 | { 263 | #region Option identifiers 264 | 265 | /// 266 | /// Determines whether to have a vertical scroll bar. 267 | /// 268 | public static readonly EditorOptionKey VerticalScrollBarId = new EditorOptionKey (VerticalScrollBarName); 269 | public const string VerticalScrollBarName = "TextViewHost/VerticalScrollBar"; 270 | 271 | /// 272 | /// Determines whether to have a horizontal scroll bar. 273 | /// 274 | public static readonly EditorOptionKey HorizontalScrollBarId = new EditorOptionKey (HorizontalScrollBarName); 275 | public const string HorizontalScrollBarName = "TextViewHost/HorizontalScrollBar"; 276 | 277 | /// 278 | /// Determines whether to have a glyph margin. 279 | /// 280 | public static readonly EditorOptionKey GlyphMarginId = new EditorOptionKey (GlyphMarginName); 281 | public const string GlyphMarginName = "TextViewHost/GlyphMargin"; 282 | 283 | /// 284 | /// Determines whether to have a suggestion margin. 285 | /// 286 | public static readonly EditorOptionKey SuggestionMarginId = new EditorOptionKey (SuggestionMarginName); 287 | public const string SuggestionMarginName = "TextViewHost/SuggestionMargin"; 288 | 289 | /// 290 | /// Determines whether to have a selection margin. 291 | /// 292 | public static readonly EditorOptionKey SelectionMarginId = new EditorOptionKey (SelectionMarginName); 293 | public const string SelectionMarginName = "TextViewHost/SelectionMargin"; 294 | 295 | /// 296 | /// Determines whether to have a line number margin. 297 | /// 298 | public static readonly EditorOptionKey LineNumberMarginId = new EditorOptionKey (LineNumberMarginName); 299 | public const string LineNumberMarginName = "TextViewHost/LineNumberMargin"; 300 | 301 | /// 302 | /// Determines whether to have the change tracking margin. 303 | /// 304 | /// The change tracking margins will "reset" (lose the change history) when this option is turned off. 305 | /// If it is turned back on, it will track changes from the time the margin is turned on. 306 | public static readonly EditorOptionKey ChangeTrackingId = new EditorOptionKey (ChangeTrackingName); 307 | public const string ChangeTrackingName = "TextViewHost/ChangeTracking"; 308 | 309 | /// 310 | /// Determines whether to have an outlining margin. 311 | /// 312 | public static readonly EditorOptionKey OutliningMarginId = new EditorOptionKey (OutliningMarginName); 313 | public const string OutliningMarginName = "TextViewHost/OutliningMargin"; 314 | 315 | /// 316 | /// Determines whether to have a zoom control. 317 | /// 318 | public static readonly EditorOptionKey ZoomControlId = new EditorOptionKey (ZoomControlName); 319 | public const string ZoomControlName = "TextViewHost/ZoomControl"; 320 | 321 | /// 322 | /// Determines whether the editor is in either "Extra Contrast" or "High Contrast" modes. 323 | /// 324 | public static readonly EditorOptionKey IsInContrastModeId = new EditorOptionKey (IsInContrastModeName); 325 | public const string IsInContrastModeName = "TextViewHost/IsInContrastMode"; 326 | 327 | /// 328 | /// Determines whether any annotations are shown over the vertical scroll bar. 329 | /// 330 | public const string ShowScrollBarAnnotationsOptionName = "OverviewMargin/ShowScrollBarAnnotationsOption"; 331 | public readonly static EditorOptionKey ShowScrollBarAnnotationsOptionId = new EditorOptionKey (ShowScrollBarAnnotationsOptionName); 332 | 333 | /// 334 | /// Determines whether the vertical scroll bar is shown as a standard WPF scroll bar or the new enhanced scroll bar. 335 | /// 336 | public const string ShowEnhancedScrollBarOptionName = "OverviewMargin/ShowEnhancedScrollBar"; 337 | public readonly static EditorOptionKey ShowEnhancedScrollBarOptionId = new EditorOptionKey (ShowEnhancedScrollBarOptionName); 338 | 339 | /// 340 | /// Determines whether changes are shown over the vertical scroll bar. 341 | /// 342 | public const string ShowChangeTrackingMarginOptionName = "OverviewMargin/ShowChangeTracking"; 343 | public readonly static EditorOptionKey ShowChangeTrackingMarginOptionId = new EditorOptionKey (ShowChangeTrackingMarginOptionName); 344 | 345 | /// 346 | /// Determines the width of the change tracking margin. 347 | /// 348 | public const string ChangeTrackingMarginWidthOptionName = "OverviewMargin/ChangeTrackingWidth"; 349 | public readonly static EditorOptionKey ChangeTrackingMarginWidthOptionId = new EditorOptionKey (ChangeTrackingMarginWidthOptionName); 350 | 351 | /// 352 | /// Determines whether a preview tip is shown when the mouse moves over the vertical scroll bar. 353 | /// 354 | public const string ShowPreviewOptionName = "OverviewMargin/ShowPreview"; 355 | public readonly static EditorOptionKey ShowPreviewOptionId = new EditorOptionKey (ShowPreviewOptionName); 356 | 357 | /// 358 | /// Determines the size (in lines of text) of the default tip. 359 | /// 360 | public const string PreviewSizeOptionName = "OverviewMargin/PreviewSize"; 361 | public readonly static EditorOptionKey PreviewSizeOptionId = new EditorOptionKey (PreviewSizeOptionName); 362 | 363 | /// 364 | /// Determines whether the vertical margin shows the location of the caret. 365 | /// 366 | public const string ShowCaretPositionOptionName = "OverviewMargin/ShowCaretPosition"; 367 | public readonly static EditorOptionKey ShowCaretPositionOptionId = new EditorOptionKey (ShowCaretPositionOptionName); 368 | 369 | /// 370 | /// Determines whether the source image margin is displayed. 371 | /// 372 | /// 373 | /// This margin is only shown if this option and the ShowEnhancedScrollBarOption, and the SourceImageMarginWidth is >= 25.0. 374 | /// 375 | public const string SourceImageMarginEnabledOptionName = "OverviewMargin/ShowSourceImageMargin"; 376 | public readonly static EditorOptionKey SourceImageMarginEnabledOptionId = new EditorOptionKey (SourceImageMarginEnabledOptionName); 377 | 378 | /// 379 | /// Determines the width of the source image margin. 380 | /// 381 | public const string SourceImageMarginWidthOptionName = "OverviewMargin/SourceImageMarginWidth"; 382 | public readonly static EditorOptionKey SourceImageMarginWidthOptionId = new EditorOptionKey (SourceImageMarginWidthOptionName); 383 | 384 | /// 385 | /// Determines whether marks (bookmarks, breakpoints, etc.) are shown over the vertical scroll bar. 386 | /// 387 | public const string ShowMarksOptionName = "OverviewMargin/ShowMarks"; 388 | public readonly static EditorOptionKey ShowMarksOptionId = new EditorOptionKey (ShowMarksOptionName); 389 | 390 | /// 391 | /// Determines whether errors are shown over the vertical scroll bar. 392 | /// 393 | public const string ShowErrorsOptionName = "OverviewMargin/ShowErrors"; 394 | public readonly static EditorOptionKey ShowErrorsOptionId = new EditorOptionKey (ShowErrorsOptionName); 395 | 396 | /// 397 | /// Determines the width of the marks margin. 398 | /// 399 | public const string MarkMarginWidthOptionName = "OverviewMargin/MarkMarginWidth"; 400 | public readonly static EditorOptionKey MarkMarginWidthOptionId = new EditorOptionKey (MarkMarginWidthOptionName); 401 | 402 | /// 403 | /// Determines the width of the error margin. 404 | /// 405 | public const string ErrorMarginWidthOptionName = "OverviewMargin/ErrorMarginWidth"; 406 | public readonly static EditorOptionKey ErrorMarginWidthOptionId = new EditorOptionKey (ErrorMarginWidthOptionName); 407 | 408 | /// 409 | /// Determines whether to have a file health indicator. 410 | /// 411 | public static readonly EditorOptionKey EnableFileHealthIndicatorOptionId = new EditorOptionKey (EnableFileHealthIndicatorOptionName); 412 | public const string EnableFileHealthIndicatorOptionName = "TextViewHost/FileHealthIndicator"; 413 | 414 | #endregion 415 | } 416 | 417 | /// 418 | /// Defines the Use Visible Whitespace option. 419 | /// 420 | [Export (typeof (EditorOptionDefinition))] 421 | [Name (DefaultTextViewOptions.UseVisibleWhitespaceOnlyWhenSelectedName)] 422 | public sealed class UseVisibleWhitespaceOnlyWhenSelected : ViewOptionDefinition 423 | { 424 | /// 425 | /// Gets the default value, which is false. 426 | /// 427 | public override bool Default { get { return false; } } 428 | 429 | /// 430 | /// Gets the default text view host value. 431 | /// 432 | public override EditorOptionKey Key { get { return DefaultTextViewOptions.UseVisibleWhitespaceOnlyWhenSelectedId; } } 433 | } 434 | 435 | /// 436 | /// Defines the Use Visible Whitespace option. 437 | /// 438 | [Export (typeof (EditorOptionDefinition))] 439 | [Name (DefaultTextViewOptions.UseVisibleWhitespaceIncludeName)] 440 | public sealed class UseVisibleWhitespaceEnabledTypes : ViewOptionDefinition 441 | { 442 | /// 443 | /// Gets the default value, which is false. 444 | /// 445 | public override DefaultTextViewOptions.IncludeWhitespaces Default { get { return DefaultTextViewOptions.IncludeWhitespaces.All; } } 446 | 447 | /// 448 | /// Gets the default text view host value. 449 | /// 450 | public override EditorOptionKey Key { get { return DefaultTextViewOptions.UseVisibleWhitespaceIncludeId; } } 451 | } 452 | 453 | /// 454 | /// Represents the option to highlight the current line. 455 | /// 456 | [Export (typeof (EditorOptionDefinition))] 457 | [Name (DefaultTextViewOptions.EnableHighlightCurrentLineName)] 458 | public sealed class HighlightCurrentLineOption : EditorOptionDefinition 459 | { 460 | /// 461 | /// Gets the default value. 462 | /// 463 | public override bool Default { get { return true; } } 464 | 465 | /// 466 | /// Gets the key for the highlight current line option. 467 | /// 468 | public override EditorOptionKey Key { get { return DefaultTextViewOptions.EnableHighlightCurrentLineId; } } 469 | } 470 | 471 | /// 472 | /// Represents the option to draw a selection gradient as opposed to a solid color selection. 473 | /// 474 | [Export (typeof (EditorOptionDefinition))] 475 | [Name (DefaultTextViewOptions.EnableSimpleGraphicsName)] 476 | public sealed class SimpleGraphicsOption : EditorOptionDefinition 477 | { 478 | /// 479 | /// Gets the default value. 480 | /// 481 | public override bool Default { get { return false; } } 482 | 483 | /// 484 | /// Gets the key for the simple graphics option. 485 | /// 486 | public override EditorOptionKey Key { get { return DefaultTextViewOptions.EnableSimpleGraphicsId; } } 487 | } 488 | 489 | [Export (typeof (EditorOptionDefinition))] 490 | [Name (DefaultTextViewOptions.UseReducedOpacityForHighContrastOptionName)] 491 | public sealed class UseReducedOpacityForHighContrastOption : EditorOptionDefinition 492 | { 493 | /// 494 | /// Gets the default value. 495 | /// 496 | public override bool Default { get { return false; } } 497 | 498 | /// 499 | /// Gets the key for the use reduced opacity option. 500 | /// 501 | public override EditorOptionKey Key { get { return DefaultTextViewOptions.UseReducedOpacityForHighContrastOptionId; } } 502 | } 503 | 504 | /// 505 | /// Defines the option to enable the mouse wheel zoom 506 | /// 507 | [Export (typeof (EditorOptionDefinition))] 508 | [Name (DefaultTextViewOptions.EnableMouseWheelZoomName)] 509 | public sealed class MouseWheelZoomEnabled : EditorOptionDefinition 510 | { 511 | /// 512 | /// Gets the default value, which is true. 513 | /// 514 | public override bool Default { get { return true; } } 515 | 516 | /// 517 | /// Gets the wpf text view value. 518 | /// 519 | public override EditorOptionKey Key { get { return DefaultTextViewOptions.EnableMouseWheelZoomId; } } 520 | } 521 | 522 | /// 523 | /// Defines the appearance category. 524 | /// 525 | [Export (typeof (EditorOptionDefinition))] 526 | [Name (DefaultTextViewOptions.AppearanceCategoryName)] 527 | public sealed class AppearanceCategoryOption : EditorOptionDefinition 528 | { 529 | /// 530 | /// Gets the default value. 531 | /// 532 | public override string Default { get { return "text"; } } 533 | 534 | /// 535 | /// Gets the key for the appearance category option. 536 | /// 537 | public override EditorOptionKey Key { get { return DefaultTextViewOptions.AppearanceCategory; } } 538 | } 539 | 540 | /// 541 | /// Defines the zoomlevel. 542 | /// 543 | [Export (typeof (EditorOptionDefinition))] 544 | [Name (DefaultTextViewOptions.ZoomLevelName)] 545 | public sealed class ZoomLevel : EditorOptionDefinition 546 | { 547 | /// 548 | /// Gets the default value. 549 | /// 550 | public override double Default { get { return (int)ZoomConstants.DefaultZoom; } } 551 | 552 | /// 553 | /// Gets the key for the text view zoom level. 554 | /// 555 | public override EditorOptionKey Key { get { return DefaultTextViewOptions.ZoomLevelId; } } 556 | } 557 | 558 | /// 559 | /// Defines the minimum zoomlevel. 560 | /// 561 | [Export (typeof (EditorOptionDefinition))] 562 | [Name (DefaultTextViewOptions.MinZoomLevelName)] 563 | public sealed class MinZoomLevel : EditorOptionDefinition 564 | { 565 | /// 566 | /// Gets the default value. 567 | /// 568 | public override double Default => ZoomConstants.MinZoom; 569 | 570 | /// 571 | /// Gets the key for the text view zoom level. 572 | /// 573 | public override EditorOptionKey Key => DefaultTextViewOptions.MinZoomLevelId; 574 | } 575 | 576 | /// 577 | /// Defines the maximum zoomlevel. 578 | /// 579 | [Export (typeof (EditorOptionDefinition))] 580 | [Name (DefaultTextViewOptions.MaxZoomLevelName)] 581 | public sealed class MaxZoomLevel : EditorOptionDefinition 582 | { 583 | /// 584 | /// Gets the default value. 585 | /// 586 | public override double Default => ZoomConstants.MaxZoom; 587 | 588 | /// 589 | /// Gets the key for the text view zoom level. 590 | /// 591 | public override EditorOptionKey Key => DefaultTextViewOptions.MaxZoomLevelId; 592 | } 593 | 594 | /// 595 | /// Determines whether to enable mouse click + modifier keypress for go to definition. 596 | /// 597 | [Export (typeof (EditorOptionDefinition))] 598 | [Name (DefaultTextViewOptions.ClickGoToDefEnabledName)] 599 | public sealed class ClickGotoDefEnabledOption : EditorOptionDefinition 600 | { 601 | /// 602 | /// Gets the default value. 603 | /// 604 | public override bool Default => true; 605 | 606 | /// 607 | /// Gets the key for the option. 608 | /// 609 | public override EditorOptionKey Key => DefaultTextViewOptions.ClickGoToDefEnabledId; 610 | } 611 | 612 | /// 613 | /// Determines whether to open definition target in Peek view for mouse click + modifier keypress. 614 | /// 615 | [Export (typeof (EditorOptionDefinition))] 616 | [Name (DefaultTextViewOptions.ClickGoToDefOpensPeekName)] 617 | public sealed class ClickGotoDefOpensPeekOption : EditorOptionDefinition 618 | { 619 | /// 620 | /// Gets the default value. 621 | /// 622 | public override bool Default => false; 623 | 624 | /// 625 | /// Gets the key for the option. 626 | /// 627 | public override EditorOptionKey Key => DefaultTextViewOptions.ClickGoToDefOpensPeekId; 628 | } 629 | 630 | /// 631 | /// The option definition that determines if the caret should be moved to the end of the selection after performing the "select all" operation. 632 | /// 633 | [Export (typeof (EditorOptionDefinition))] 634 | [Name (DefaultTextViewOptions.ShouldMoveCaretOnSelectAllName)] 635 | internal sealed class ShouldMoveCaretOnSelectAll : EditorOptionDefinition 636 | { 637 | /// 638 | /// Gets the default value (true). 639 | /// 640 | public override bool Default { get => true; } 641 | 642 | /// 643 | /// Gets the editor option key. 644 | /// 645 | public override EditorOptionKey Key => DefaultTextViewOptions.ShouldMoveCaretOnSelectAllId; 646 | } 647 | } 648 | 649 | #pragma warning restore CS0436 // Type conflicts with imported type -------------------------------------------------------------------------------- /Microsoft.VisualStudio.MiniEditor/CustomDef/EditorOptionsExtensions.cs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) Microsoft Corporation. All rights reserved. 3 | // Licensed under the MIT License. See License.txt in the project root for license information. 4 | // 5 | using System; 6 | 7 | namespace Microsoft.VisualStudio.Text.Editor.OptionsExtensionMethods 8 | { 9 | public static class WpfViewOptionExtensions 10 | { 11 | #region Extension methods 12 | 13 | /// 14 | /// Determines whether the option to highlight the current line is enabled. 15 | /// 16 | /// The . 17 | /// true if the highlight option was enabled, otherwise false. 18 | public static bool IsHighlightCurrentLineEnabled (this IEditorOptions options) 19 | { 20 | if (options == null) 21 | throw new ArgumentNullException (nameof (options)); 22 | 23 | return options.GetOptionValue (DefaultTextViewOptions.EnableHighlightCurrentLineId); 24 | } 25 | 26 | /// 27 | /// Determines whether the option to draw a gradient selection is enabled. 28 | /// 29 | /// The . 30 | /// true if the draw selection gradient option was enabled, otherwise false. 31 | public static bool IsSimpleGraphicsEnabled (this IEditorOptions options) 32 | { 33 | if (options == null) 34 | throw new ArgumentNullException (nameof (options)); 35 | 36 | return options.GetOptionValue (DefaultTextViewOptions.EnableSimpleGraphicsId); 37 | } 38 | 39 | /// 40 | /// Determines whether to allow mouse wheel zooming 41 | /// 42 | /// The set of editor options. 43 | /// true if the mouse wheel zooming is enabled, otherwise false. 44 | /// Disabling the mouse wheel zooming does NOT turn off Zooming (it disables zooming using mouse wheel) 45 | public static bool IsMouseWheelZoomEnabled (this IEditorOptions options) 46 | { 47 | if (options == null) 48 | throw new ArgumentNullException (nameof (options)); 49 | 50 | return options.GetOptionValue (DefaultTextViewOptions.EnableMouseWheelZoomId); 51 | } 52 | 53 | /// 54 | /// Specifies the appearance category. 55 | /// 56 | /// The . 57 | /// The appearance category, which determines where to look up font properties and colors. 58 | public static string AppearanceCategory (this IEditorOptions options) 59 | { 60 | if (options == null) 61 | throw new ArgumentNullException (nameof (options)); 62 | 63 | return options.GetOptionValue (DefaultTextViewOptions.AppearanceCategory); 64 | } 65 | 66 | /// 67 | /// Specifies the persisted zoomlevel. 68 | /// 69 | /// The . 70 | /// The zoomlevel, which scales the view up or down. 71 | public static double ZoomLevel (this IEditorOptions options) 72 | { 73 | if (options == null) 74 | throw new ArgumentNullException (nameof (options)); 75 | 76 | return options.GetOptionValue (DefaultTextViewOptions.ZoomLevelId); 77 | } 78 | 79 | /// 80 | /// Specifies the minimum allowed zoomlevel 81 | /// 82 | /// The . 83 | public static double MinZoom (this IEditorOptions options) 84 | { 85 | if (options == null) 86 | throw new ArgumentNullException (nameof (options)); 87 | 88 | return options.GetOptionValue (DefaultTextViewOptions.MinZoomLevelId); 89 | } 90 | 91 | /// 92 | /// Specifies the maximum allowed zoomlevel 93 | /// 94 | /// The . 95 | public static double MaxZoom (this IEditorOptions options) 96 | { 97 | if (options == null) 98 | throw new ArgumentNullException (nameof (options)); 99 | 100 | return options.GetOptionValue (DefaultTextViewOptions.MaxZoomLevelId); 101 | } 102 | 103 | /// 104 | /// Set the persisted zoomlevel. 105 | /// 106 | /// The . 107 | /// The new zoom level. This value will be 108 | /// clamped to fit between 109 | /// and 110 | public static void SetZoomLevel (this IEditorOptions options, double zoomLevel) 111 | { 112 | if (options == null) 113 | throw new ArgumentNullException (nameof (options)); 114 | 115 | options.SetOptionValue ( 116 | DefaultTextViewOptions.ZoomLevelId, 117 | Math.Min (options.MaxZoom (), Math.Max (options.MinZoom (), zoomLevel))); 118 | } 119 | 120 | /// 121 | /// Set the minimum zoomlevel. 122 | /// 123 | /// The . 124 | /// The new minimum zoom level. 125 | public static void SetMinZoomLevel (this IEditorOptions options, double minZoomLevel) 126 | { 127 | if (options == null) 128 | throw new ArgumentNullException (nameof (options)); 129 | 130 | options.SetOptionValue ( 131 | DefaultTextViewOptions.MinZoomLevelId, 132 | minZoomLevel); 133 | } 134 | 135 | /// 136 | /// Set the maximum zoomlevel. 137 | /// 138 | /// The . 139 | /// The new maximum zoom level. 140 | public static void SetMaxZoomLevel (this IEditorOptions options, double maxZoomLevel) 141 | { 142 | if (options == null) 143 | throw new ArgumentNullException (nameof (options)); 144 | 145 | options.SetOptionValue ( 146 | DefaultTextViewOptions.MaxZoomLevelId, 147 | maxZoomLevel); 148 | } 149 | 150 | #endregion 151 | } 152 | } -------------------------------------------------------------------------------- /Microsoft.VisualStudio.MiniEditor/CustomDef/IAsyncCompletionSessionOperations2.cs: -------------------------------------------------------------------------------- 1 | namespace Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion 2 | { 3 | public interface IAsyncCompletionSessionOperations2 : IAsyncCompletionSessionOperations 4 | { 5 | bool CanToggleFilter (string accessKey); 6 | void ToggleFilter (string accessKey); 7 | } 8 | } -------------------------------------------------------------------------------- /Microsoft.VisualStudio.MiniEditor/CustomDef/IDynamicCommandHandler.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.VisualStudio.Utilities; 2 | 3 | namespace Microsoft.VisualStudio.Commanding 4 | { 5 | /// 6 | /// A command handler that can opt out of . 7 | /// 8 | internal interface IDynamicCommandHandler where T : CommandArgs 9 | { 10 | /// 11 | /// Determines whether should be called. 12 | /// 13 | bool CanExecuteCommand(T args); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Microsoft.VisualStudio.MiniEditor/CustomErrorHandler.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.ComponentModel.Composition; 3 | using Microsoft.VisualStudio.Text; 4 | 5 | namespace Microsoft.VisualStudio.MiniEditor 6 | { 7 | /// 8 | /// Implementation of . 9 | /// Visual Studio provides error handler which writes to activity log and displays messages. 10 | /// This implementation forwards the exception to subscribers of event 11 | /// 12 | [Export (typeof (IExtensionErrorHandler))] 13 | public class CustomErrorHandler : IExtensionErrorHandler 14 | { 15 | // GuardedOperations imports IExtensionErrorHandler via Lazy and 16 | // hence gets its own private instance. to access the event from the host we have to make it static 17 | public static event EventHandler ExceptionHandled; 18 | 19 | public void HandleError (object sender, Exception exception) 20 | => ExceptionHandled?.Invoke (sender, new ExceptionEventArgs (exception)); 21 | 22 | public class ExceptionEventArgs : EventArgs 23 | { 24 | public Exception Exception { get; } 25 | public ExceptionEventArgs (Exception ex) 26 | { 27 | Exception = ex; 28 | } 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /Microsoft.VisualStudio.MiniEditor/CustomImpl/CompletionUtilities.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Collections.Immutable; 4 | using System.ComponentModel.Composition; 5 | using System.Linq; 6 | using System.Threading; 7 | using Microsoft.VisualStudio.Language.Intellisense; 8 | using Microsoft.VisualStudio.Text; 9 | using Microsoft.VisualStudio.Text.Editor; 10 | using Microsoft.VisualStudio.Text.Utilities; 11 | using Microsoft.VisualStudio.Utilities; 12 | 13 | namespace Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion.Implementation 14 | { 15 | internal static class CompletionUtilities 16 | { 17 | /// 18 | /// Maps given point to buffers that contain this point. Requires UI thread. 19 | /// 20 | /// 21 | /// 22 | /// 23 | internal static IEnumerable GetBuffersForPoint(ITextView textView, SnapshotPoint point) 24 | { 25 | // We are looking at the buffer to the left of the caret. 26 | return textView.BufferGraph.GetTextBuffers(n => 27 | textView.BufferGraph.MapDownToBuffer(point, PointTrackingMode.Negative, n, PositionAffinity.Predecessor) != null); 28 | } 29 | 30 | /// 31 | /// Returns whether the is furnished by the debugger, 32 | /// e.g. it is a view in the breakpoint settings window or watch window. 33 | /// We use this to pick an appropriate option with suggestion mode setting. 34 | /// 35 | /// View to examine 36 | /// True if the view has "DEBUGVIEW" text view role. 37 | internal static bool IsDebuggerTextView(ITextView textView) => textView.Roles.Contains("DEBUGVIEW"); 38 | 39 | /// 40 | /// Returns whether the is in immediate window. 41 | /// We use this to make the view temporarily writable during commit (it is typically read-only). 42 | /// 43 | /// View to examine 44 | /// True if the view has "COMMANDVIEW" text view role. 45 | internal static bool IsImmediateTextView(ITextView textView) => textView.Roles.Contains("COMMANDVIEW"); 46 | 47 | static readonly EditorOptionKey SuggestionModeOptionKey = new EditorOptionKey(PredefinedCompletionNames.SuggestionModeInCompletionOptionName); 48 | static readonly EditorOptionKey SuggestionModeInDebuggerCompletionOptionKey = new EditorOptionKey(PredefinedCompletionNames.SuggestionModeInDebuggerCompletionOptionName); 49 | private const bool UseSuggestionModeDefaultValue = false; 50 | private const bool UseSuggestionModeInDebuggerCompletionDefaultValue = true; 51 | 52 | [Export(typeof(EditorOptionDefinition))] 53 | [Name(PredefinedCompletionNames.SuggestionModeInCompletionOptionName)] 54 | class SuggestionModeOptionDefinition : EditorOptionDefinition 55 | { 56 | public override object DefaultValue => UseSuggestionModeDefaultValue; 57 | 58 | public override Type ValueType => typeof(bool); 59 | 60 | public override string Name => PredefinedCompletionNames.SuggestionModeInCompletionOptionName; 61 | } 62 | 63 | [Export(typeof(EditorOptionDefinition))] 64 | [Name(PredefinedCompletionNames.SuggestionModeInDebuggerCompletionOptionName)] 65 | class SuggestionModeInDebuggerCompletionOptionDefinition : EditorOptionDefinition 66 | { 67 | public override object DefaultValue => UseSuggestionModeInDebuggerCompletionDefaultValue; 68 | 69 | public override Type ValueType => typeof(bool); 70 | 71 | public override string Name => PredefinedCompletionNames.SuggestionModeInDebuggerCompletionOptionName; 72 | } 73 | 74 | internal static bool GetSuggestionModeOption(ITextView textView) 75 | { 76 | var options = textView.Options.GlobalOptions; 77 | var useDebuggerViewOption = IsDebuggerTextView(textView) || IsImmediateTextView(textView); 78 | var optionKey = useDebuggerViewOption ? SuggestionModeInDebuggerCompletionOptionKey : SuggestionModeOptionKey; 79 | 80 | if (!(options.IsOptionDefined(optionKey, localScopeOnly: false))) 81 | { 82 | var defaultValue = useDebuggerViewOption ? UseSuggestionModeInDebuggerCompletionDefaultValue : UseSuggestionModeDefaultValue; 83 | options.SetOptionValue(optionKey, defaultValue); 84 | } 85 | return options.GetOptionValue(optionKey); 86 | } 87 | 88 | internal static void SetSuggestionModeOption(ITextView textView, bool value) 89 | { 90 | var options = textView.Options.GlobalOptions; 91 | var useDebuggerViewOption = IsDebuggerTextView(textView) || IsImmediateTextView(textView); 92 | var optionKey = useDebuggerViewOption ? SuggestionModeInDebuggerCompletionOptionKey : SuggestionModeOptionKey; 93 | options.SetOptionValue(optionKey, value); 94 | } 95 | 96 | internal static bool GetNonBlockingCompletionOption(ITextView textView) 97 | { 98 | return textView.Options.GetOptionValue(DefaultOptions.NonBlockingCompletionOptionId); 99 | } 100 | 101 | internal static bool GetResponsiveCompletionOption(ITextView textView) 102 | { 103 | return textView.Options.GetOptionValue(DefaultOptions.ResponsiveCompletionOptionId); 104 | //&& textView.Options.GetOptionValue(DefaultOptions.RemoteControlledResponsiveCompletionOptionId); 105 | } 106 | 107 | internal static int GetResponsiveCompletionThresholdOption(ITextView textView) 108 | { 109 | return textView.Options.GetOptionValue(DefaultOptions.ResponsiveCompletionThresholdOptionId); 110 | } 111 | 112 | internal static CancellationToken GetResponsiveToken(ITextView textView, CancellationToken commandingToken) 113 | { 114 | var inResponisveMode = CompletionUtilities.GetResponsiveCompletionOption(textView); 115 | if (!inResponisveMode) 116 | return commandingToken; 117 | 118 | var responsiveCompletionThreshold = CompletionUtilities.GetResponsiveCompletionThresholdOption(textView); 119 | var responsiveCancellationSource = new CancellationTokenSource(responsiveCompletionThreshold); 120 | var responsiveToken = responsiveCancellationSource.Token; 121 | var combinedCancellationSource = CancellationTokenSource.CreateLinkedTokenSource(commandingToken, responsiveToken); 122 | return combinedCancellationSource.Token; 123 | } 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /Microsoft.VisualStudio.MiniEditor/CustomImpl/DiagnosticLogger.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using Microsoft.VisualStudio.Text.Editor; 4 | 5 | namespace Microsoft.VisualStudio.Text.UI.Utilities 6 | { 7 | public static class DiagnosticLogger 8 | { 9 | private static bool WasLoggingEnabled; 10 | private static List<(long, string)> Log = new List<(long, string)>(); 11 | 12 | public static bool IsLoggingEnabled(ITextView textView) 13 | { 14 | var currentValue = Environment.GetEnvironmentVariable ("DIAGNOSTIC_LOGGER_ENABLED") == bool.TrueString; 15 | if (!WasLoggingEnabled && currentValue) 16 | { 17 | Add("--- Begin new log"); 18 | } 19 | if (WasLoggingEnabled != currentValue) 20 | { 21 | WasLoggingEnabled = currentValue; 22 | } 23 | return currentValue; 24 | } 25 | 26 | public static void Add(string message) 27 | { 28 | Log.Add((DateTime.Now.Ticks, message)); 29 | } 30 | 31 | public static void Add(string message, object param) 32 | { 33 | Log.Add((DateTime.Now.Ticks, message + param.ToString())); 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /Microsoft.VisualStudio.MiniEditor/CustomImpl/EditorCommandHandlerService.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics; 4 | using System.Globalization; 5 | using System.Linq; 6 | using System.Runtime.CompilerServices; 7 | using System.Threading; 8 | using Microsoft.VisualStudio.Commanding; 9 | using Microsoft.VisualStudio.Text; 10 | using Microsoft.VisualStudio.Text.Editor; 11 | using Microsoft.VisualStudio.Text.Editor.Commanding; 12 | using Microsoft.VisualStudio.Text.Editor.Commanding.Commands; 13 | using Microsoft.VisualStudio.Text.Utilities; 14 | using Microsoft.VisualStudio.Utilities; 15 | using ICommandHandlerAndMetadata = System.Lazy; 16 | 17 | namespace Microsoft.VisualStudio.UI.Text.Commanding.Implementation 18 | { 19 | internal class EditorCommandHandlerService : IEditorCommandHandlerService 20 | { 21 | private const string TelemetryEventPrefix = "VS/Editor/Commanding"; 22 | private const string TelemetryPropertyPrefix = "VS.Editor.Commanding"; 23 | 24 | private readonly IEnumerable _commandHandlers; 25 | private readonly EditorCommandHandlerServiceFactory _factory; 26 | private readonly ITextView _textView; 27 | private readonly ICommandingTextBufferResolver _bufferResolver; 28 | private readonly bool _isOldGtkEditor; 29 | private readonly ITypingTelemetrySession _telemetrySession; 30 | 31 | private readonly static IReadOnlyList EmptyHandlerList = new List(0); 32 | private readonly static Action EmptyAction = delegate { }; 33 | private readonly static Func UnavalableCommandFunc = new Func(() => CommandState.Unavailable); 34 | private readonly static string WaitForCommandExecutionString = CommandingStrings.WaitForCommandExecution; 35 | 36 | /// This dictionary acts as a cache so we can avoid having to look through the full list of 37 | /// handlers every time we need handlers of a specific type, for a given content type. 38 | private readonly Dictionary<(Type commandArgType, IContentType contentType), IReadOnlyList> _commandHandlersByTypeAndContentType; 39 | 40 | public EditorCommandHandlerService(EditorCommandHandlerServiceFactory factory, 41 | ITextView textView, 42 | IEnumerable commandHandlers, 43 | ICommandingTextBufferResolver bufferResolver) 44 | { 45 | _commandHandlers = commandHandlers ?? throw new ArgumentNullException(nameof(commandHandlers)); 46 | _factory = factory ?? throw new ArgumentNullException(nameof(factory)); 47 | _textView = textView ?? throw new ArgumentNullException(nameof(textView)); 48 | _isOldGtkEditor = _textView.GetType ().FullName == "MonoDevelop.SourceEditor.ExtensibleTextEditor"; 49 | _commandHandlersByTypeAndContentType = new Dictionary<(Type commandArgType, IContentType contentType), IReadOnlyList>(); 50 | _bufferResolver = bufferResolver ?? throw new ArgumentNullException(nameof(bufferResolver)); 51 | _textView.Properties.TryGetProperty(typeof(ITypingTelemetrySession), out _telemetrySession); 52 | } 53 | 54 | public CommandState GetCommandState(Func argsFactory, Func nextCommandHandler) where T : EditorCommandArgs 55 | { 56 | if (!_factory.JoinableTaskContext.IsOnMainThread) 57 | { 58 | throw new InvalidOperationException($"{nameof(IEditorCommandHandlerService.GetCommandState)} method shoudl only be called on the UI thread."); 59 | } 60 | 61 | // In Razor scenario it's possible that EditorCommandHandlerService is called re-entrantly, 62 | // first by contained language command filter and then by editor command chain. 63 | // To preserve Razor commanding semantics, only execute handlers once. 64 | if (IsReentrantCall()) 65 | { 66 | return nextCommandHandler?.Invoke() ?? CommandState.Unavailable; 67 | } 68 | 69 | using (var reentrancyGuard = new ReentrancyGuard(_textView)) 70 | { 71 | // Build up chain of handlers per buffer 72 | Func handlerChain = nextCommandHandler ?? UnavalableCommandFunc; 73 | foreach (var bufferAndHandler in GetOrderedBuffersAndCommandHandlers().Reverse()) 74 | { 75 | T args = null; 76 | // Declare locals to ensure that we don't end up capturing the wrong thing 77 | var nextHandler = handlerChain; 78 | var handler = bufferAndHandler.handler; 79 | args = args ?? (args = argsFactory(_textView, bufferAndHandler.buffer)); 80 | if (args == null) 81 | { 82 | // Args factory failed, skip command handlers and just call next 83 | return handlerChain(); 84 | } 85 | 86 | handlerChain = () => handler.GetCommandState(args, nextHandler); 87 | } 88 | 89 | // Kick off the first command handler 90 | return handlerChain(); 91 | } 92 | } 93 | 94 | public void Execute(Func argsFactory, Action nextCommandHandler) where T : EditorCommandArgs 95 | { 96 | if (!_factory.JoinableTaskContext.IsOnMainThread) 97 | { 98 | throw new InvalidOperationException($"{nameof(IEditorCommandHandlerService.Execute)} method shoudl only be called on the UI thread."); 99 | } 100 | 101 | // In Razor scenario it's possible that EditorCommandHandlerService is called re-entrantly, 102 | // first by contained language command filter and then by editor command chain. 103 | // To preserve Razor commanding semantics, only execute handlers once. 104 | if (IsReentrantCall()) 105 | { 106 | nextCommandHandler?.Invoke(); 107 | return; 108 | } 109 | 110 | EditorCommandHandlerServiceState state = null; 111 | 112 | using (var reentrancyGuard = new ReentrancyGuard(_textView)) 113 | { 114 | // Build up chain of handlers per buffer 115 | Action handlerChain = nextCommandHandler ?? EmptyAction; 116 | // TODO: realize the chain dynamically and without Reverse() 117 | foreach (var bufferAndHandler in GetOrderedBuffersAndCommandHandlers().Reverse()) 118 | { 119 | T args = null; 120 | // Declare locals to ensure that we don't end up capturing the wrong thing 121 | var nextHandler = handlerChain; 122 | var handler = bufferAndHandler.handler; 123 | args = args ?? (args = argsFactory(_textView, bufferAndHandler.buffer)); 124 | if (args == null) 125 | { 126 | // Args factory failed, skip command handlers and just call next 127 | handlerChain(); 128 | return; 129 | } 130 | 131 | if (handler is IDynamicCommandHandler dynamicCommandHandler && 132 | !dynamicCommandHandler.CanExecuteCommand(args)) 133 | { 134 | // Skip this one as it cannot execute the command. 135 | continue; 136 | } 137 | 138 | if (state == null) 139 | { 140 | state = InitializeExecutionState(args); 141 | } 142 | 143 | handlerChain = () => _factory.GuardedOperations.CallExtensionPoint(handler, 144 | () => 145 | { 146 | state.OnExecutingCommandHandlerBegin(handler); 147 | handler.ExecuteCommand(args, nextHandler, state.ExecutionContext); 148 | state.OnExecutingCommandHandlerEnd(handler); 149 | }, 150 | // Do not guard against cancellation exceptions, they are handled by ExecuteCommandHandlerChain 151 | exceptionGuardFilter: (e) => !IsOperationCancelledException(e)); 152 | } 153 | 154 | if (state == null) 155 | { 156 | // No matching command handlers, just call next 157 | handlerChain(); 158 | return; 159 | } 160 | 161 | _telemetrySession?.BeforeKeyProcessed(); 162 | ExecuteCommandHandlerChain(state, handlerChain, nextCommandHandler); 163 | _telemetrySession?.AfterKeyProcessed(); 164 | } 165 | } 166 | 167 | private EditorCommandHandlerServiceState InitializeExecutionState(T args) where T : EditorCommandArgs 168 | { 169 | var state = new EditorCommandHandlerServiceState(args, IsTypingCommand(args)); 170 | var uiThreadOperationContext = _factory.UIThreadOperationExecutor.BeginExecute( 171 | new UIThreadOperationExecutionOptions( 172 | title: null, // We want same caption as the main window 173 | defaultDescription: WaitForCommandExecutionString, allowCancellation: true, showProgress: true, 174 | timeoutController: new TimeoutController(state, _textView, _factory.LoggingService))); 175 | var commandExecutionContext = new CommandExecutionContext(uiThreadOperationContext); 176 | commandExecutionContext.OperationContext.UserCancellationToken.Register(OnExecutionCancellationRequested, state); 177 | state.ExecutionContext = commandExecutionContext; 178 | return state; 179 | } 180 | 181 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 182 | private static bool IsOperationCancelledException(Exception e) 183 | { 184 | return e is OperationCanceledException || e is AggregateException aggregate && aggregate.InnerExceptions.All(ie => ie is OperationCanceledException); 185 | } 186 | 187 | private void ExecuteCommandHandlerChain( 188 | EditorCommandHandlerServiceState state, 189 | Action handlerChain, 190 | Action nextCommandHandler) 191 | { 192 | try 193 | { 194 | // Kick off the first command handler. 195 | handlerChain(); 196 | if (state.ExecutionContext.OperationContext.UserCancellationToken.IsCancellationRequested) 197 | { 198 | LogCancellationWasIgnored(state); 199 | } 200 | } 201 | catch (OperationCanceledException) 202 | { 203 | OnCommandExecutionCancelled(nextCommandHandler, state); 204 | } 205 | catch (AggregateException aggregate) when (aggregate.InnerExceptions.All(e => e is OperationCanceledException)) 206 | { 207 | OnCommandExecutionCancelled(nextCommandHandler, state); 208 | } 209 | finally 210 | { 211 | state.ExecutionContext?.OperationContext?.Dispose(); 212 | } 213 | } 214 | 215 | private void OnExecutionCancellationRequested(object state) 216 | { 217 | Debug.Assert(!_factory.JoinableTaskContext.IsOnMainThread); 218 | ((EditorCommandHandlerServiceState)state).OnExecutionCancellationRequested(); 219 | } 220 | 221 | private void OnCommandExecutionCancelled(Action nextCommandHandler, EditorCommandHandlerServiceState state) 222 | { 223 | var executingHandler = state.GetCurrentlyExecutingCommandHander(); 224 | var executingCommand = state.ExecutingCommand; 225 | bool userCancelled = !state.ExecutionHasTimedOut; 226 | _factory.JoinableTaskContext.Factory.RunAsync(async () => 227 | { 228 | LogCommandExecutionCancelled(executingHandler, executingCommand, userCancelled); 229 | 230 | string statusBarMessage = string.Format(CultureInfo.CurrentCulture, CommandingStrings.CommandCancelled, executingHandler?.DisplayName); 231 | await _factory.StatusBar.SetTextAsync(statusBarMessage).ConfigureAwait(false); 232 | }); 233 | 234 | nextCommandHandler?.Invoke(); 235 | } 236 | 237 | private class ReentrancyGuard : IDisposable 238 | { 239 | private readonly IPropertyOwner _owner; 240 | 241 | public ReentrancyGuard(IPropertyOwner owner) 242 | { 243 | _owner = owner ?? throw new ArgumentNullException(nameof(owner)); 244 | _owner.Properties[typeof(ReentrancyGuard)] = this; 245 | } 246 | 247 | public void Dispose() 248 | { 249 | _owner.Properties.RemoveProperty(typeof(ReentrancyGuard)); 250 | } 251 | } 252 | 253 | private bool IsReentrantCall() 254 | { 255 | return _textView.Properties.ContainsProperty(typeof(ReentrancyGuard)); 256 | } 257 | 258 | //internal for unit tests 259 | internal IEnumerable<(ITextBuffer buffer, ICommandHandler handler)> GetOrderedBuffersAndCommandHandlers() where T : EditorCommandArgs 260 | { 261 | // This method creates an ordered sequence of (buffer, handler) pairs that define proper order of 262 | // command handling that takes into account the buffer graph and command handlers matching buffers in the graph by 263 | // content types. 264 | 265 | // Currently this method discovers affected buffers based on caret mapping only. 266 | // TODO: this should be an extensibility point as in some scenarios we might want to consider selection too for example. 267 | 268 | // A general idea is that command handlers matching more specifically content type of buffers higher in the buffer 269 | // graph should be executed before those matching buffers lower in the graph or less specific content types. 270 | 271 | // So for example in a projection scenario (projection buffer containing C# buffer), 3 command handlers 272 | // matching "projection", "CSharp" and "any" content types will be ordered like this: 273 | // 1. command handler matching "projection" content type is executed on the projection buffer 274 | // 2. command handler matching "CSharp" content type is executed on the C# buffer 275 | // 3. command handler matching "any" content type is executed on the projection buffer 276 | 277 | // The ordering algorithm is as follows: 278 | // 1. Create an ordered list of all affected buffers in the buffer graph 279 | // by mapping caret position down and up the buffer graph. In a typical projection scenario 280 | // (projection buffer containing C# buffer) that will produce (projection buffer, C# buffer) sequence. 281 | // 2. For each affected buffer get or create a bucket of matching command handlers, 282 | // ordered by [Order] and content type specificity. 283 | // 3. Pick best command handler in all buckets in terms of content type specificity (e.g. 284 | // if one command handler can handle "text" content type, but another can 285 | // handle "CSharp" content type, we pick the latter one: 286 | // 3. Start with top command handler in first non empty bucket. 287 | // 4. Compare it with top command handlers in all other buckets in terms of content type specificity. 288 | // 5. yield return current handler or better one if found, pop it from its bucket 289 | // 6. Repeat starting with #3 utill all buckets are empty. 290 | // In the projection scenario that will result in the following 291 | // list of (buffer, handler) pairs: (projection buffer, projection handler), (C# buffer, C# handler), 292 | // (projection buffer, any handler). 293 | 294 | IReadOnlyList buffers = _bufferResolver.ResolveBuffersForCommand().ToArray(); 295 | if (buffers == null || buffers.Count == 0) 296 | { 297 | yield break; 298 | } 299 | 300 | // An array of per-buffer buckets, each containing cached list of matching command handlers, 301 | // ordered by [Order] and content type specificity 302 | var handlerBuckets = new CommandHandlerBucket[buffers.Count]; 303 | for (int i = 0; i < buffers.Count; i++) 304 | { 305 | handlerBuckets[i] = new CommandHandlerBucket(GetOrCreateOrderedHandlers(buffers[i].ContentType, _textView.Roles)); 306 | } 307 | 308 | while (true) 309 | { 310 | ICommandHandlerAndMetadata currentHandler = null; 311 | int currentHandlerBufferIndex = 0; 312 | 313 | for (int i = 0; i < handlerBuckets.Length; i++) 314 | { 315 | if (!handlerBuckets[i].IsEmpty) 316 | { 317 | currentHandler = handlerBuckets[i].Peek(); 318 | currentHandlerBufferIndex = i; 319 | break; 320 | } 321 | } 322 | 323 | if (currentHandler == null) 324 | { 325 | // All buckets are empty, all done 326 | break; 327 | } 328 | 329 | // Check if any other bucket has a better handler (i.e. can handle more specific content type). 330 | var foundBetterHandler = false; 331 | for (int i = 0; i < buffers.Count; i++) 332 | { 333 | // Search in other buckets only 334 | if (i != currentHandlerBufferIndex) 335 | { 336 | if (!handlerBuckets[i].IsEmpty) 337 | { 338 | var handler = handlerBuckets[i].Peek(); 339 | // Can this handler handle content type more specific than top handler in firstNonEmptyBucket? 340 | if (_factory.ContentTypeComparer.Compare(handler.Metadata.ContentTypes, currentHandler.Metadata.ContentTypes) < 0) 341 | { 342 | foundBetterHandler = true; 343 | handlerBuckets[i].Pop(); 344 | yield return (buffers[i], handler.Value); 345 | break; 346 | } 347 | } 348 | } 349 | } 350 | 351 | if (!foundBetterHandler) 352 | { 353 | yield return (buffers[currentHandlerBufferIndex], currentHandler.Value); 354 | handlerBuckets[currentHandlerBufferIndex].Pop(); 355 | } 356 | } 357 | } 358 | 359 | private IReadOnlyList GetOrCreateOrderedHandlers(IContentType contentType, ITextViewRoleSet textViewRoles) where T : EditorCommandArgs 360 | { 361 | var cacheKey = (commandArgsType: typeof(T), contentType: contentType); 362 | if (!_commandHandlersByTypeAndContentType.TryGetValue(cacheKey, out var commandHandlerList)) 363 | { 364 | IList newCommandHandlerList = null; 365 | foreach (var lazyCommandHandler in SelectMatchingCommandHandlers(_commandHandlers, contentType, textViewRoles)) 366 | { 367 | var commandHandler = _factory.GuardedOperations.InstantiateExtension(this, lazyCommandHandler); 368 | if (commandHandler is ICommandHandler || commandHandler is IChainedCommandHandler) 369 | { 370 | // The old editor in VSmac is not compatible with any command handlers except for those coming from Roslyn 371 | if (_isOldGtkEditor && !commandHandler.GetType().FullName.StartsWith("Microsoft.CodeAnalysis", StringComparison.Ordinal)) 372 | { 373 | continue; 374 | } 375 | 376 | if (newCommandHandlerList == null) 377 | { 378 | newCommandHandlerList = new FrugalList(); 379 | } 380 | 381 | newCommandHandlerList.Add(lazyCommandHandler); 382 | } 383 | } 384 | 385 | if (newCommandHandlerList?.Count > 1) 386 | { 387 | // Order handlers by [Order] across content types, but preserve sort order otherwise 388 | newCommandHandlerList = StableOrderer.Order(newCommandHandlerList).ToArray(); 389 | } 390 | 391 | commandHandlerList = newCommandHandlerList?.ToArray() ?? EmptyHandlerList; 392 | _commandHandlersByTypeAndContentType[cacheKey] = commandHandlerList; 393 | } 394 | 395 | return commandHandlerList; 396 | } 397 | 398 | /// 399 | /// Selects matching command handlers without allocating a new list. 400 | /// 401 | private static IEnumerable SelectMatchingCommandHandlers( 402 | IEnumerable commandHandlers, 403 | IContentType contentType, ITextViewRoleSet textViewRoles) 404 | { 405 | foreach (var handler in commandHandlers) 406 | { 407 | if (MatchesContentType(handler.Metadata, contentType) && 408 | MatchesTextViewRoles(handler.Metadata, textViewRoles)) 409 | { 410 | yield return handler; 411 | } 412 | } 413 | } 414 | 415 | private static bool MatchesContentType(ICommandHandlerMetadata handlerMetadata, IContentType contentType) 416 | { 417 | foreach (var handlerContentType in handlerMetadata.ContentTypes) 418 | { 419 | if (contentType.IsOfType(handlerContentType)) 420 | { 421 | return true; 422 | } 423 | } 424 | 425 | return false; 426 | } 427 | 428 | private static bool MatchesTextViewRoles(ICommandHandlerMetadata handlerMetadata, ITextViewRoleSet roles) 429 | { 430 | // Text view roles are optional 431 | if (handlerMetadata.TextViewRoles == null) 432 | { 433 | return true; 434 | } 435 | 436 | foreach (var handlerRole in handlerMetadata.TextViewRoles) 437 | { 438 | if (roles.Contains(handlerRole)) 439 | { 440 | return true; 441 | } 442 | } 443 | 444 | return false; 445 | } 446 | 447 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 448 | private static bool IsTypingCommand(EditorCommandArgs args) 449 | { 450 | // TODO: temporarily only include typechar to not break Roslyn inline rename and other non-typing scenario, tracked by #657668 451 | return args is TypeCharCommandArgs; 452 | //args is DeleteKeyCommandArgs || 453 | //args is ReturnKeyCommandArgs || 454 | //args is BackspaceKeyCommandArgs || 455 | //args is TabKeyCommandArgs || 456 | //args is UndoCommandArgs || 457 | //args is RedoCommandArgs; 458 | } 459 | 460 | private void LogCommandExecutionCancelled(INamed executingHandler, EditorCommandArgs executingCommand, bool userCancelled) 461 | { 462 | _factory.LoggingService?.PostEvent($"{TelemetryEventPrefix}/ExecutionCancelled", 463 | $"{TelemetryPropertyPrefix}.Command", executingCommand?.GetType().FullName, 464 | $"{TelemetryPropertyPrefix}.CommandHandler", executingHandler?.GetType().FullName, 465 | $"{TelemetryPropertyPrefix}.UserCancelled", userCancelled); 466 | } 467 | 468 | private void LogCancellationWasIgnored(EditorCommandHandlerServiceState state) 469 | { 470 | bool userCancelled = !state.ExecutionHasTimedOut; 471 | var executingCommand = state.ExecutingCommand; 472 | _factory.LoggingService?.PostEvent($"{TelemetryEventPrefix}/IgnoredExecutionCancellation", 473 | $"{TelemetryPropertyPrefix}.Command", executingCommand?.GetType().FullName, 474 | $"{TelemetryPropertyPrefix}.CommandHandler", state.CommandHandlerExecutingDuringCancellationRequest?.GetType().FullName, 475 | $"{TelemetryPropertyPrefix}.UserCancelled", userCancelled); 476 | } 477 | 478 | private class TimeoutController : IUIThreadOperationTimeoutController 479 | { 480 | private readonly EditorCommandHandlerServiceState _state; 481 | private readonly ITextView _textView; 482 | private readonly ILoggingServiceInternal _loggingService; 483 | 484 | public TimeoutController(EditorCommandHandlerServiceState state, ITextView textView, ILoggingServiceInternal loggingService) 485 | { 486 | _state = state; 487 | _textView = textView; 488 | _loggingService = loggingService; 489 | } 490 | 491 | public int CancelAfter 492 | => Timeout.Infinite; 493 | 494 | public bool ShouldCancel() 495 | { 496 | // TODO: this needs to allow non typing command scenarios for example hitting return in inline rename, tracked by #657668 497 | return _state.IsExecutingTypingCommand; 498 | } 499 | 500 | public void OnTimeout(bool wasExecutionCancelled) 501 | { 502 | Debug.Assert(_state.IsExecutingTypingCommand); 503 | _state.ExecutionHasTimedOut = true; 504 | var executingCommand = _state.ExecutingCommand; 505 | 506 | _loggingService?.PostEvent($"{TelemetryEventPrefix}/ExecutionTimeout", 507 | $"{TelemetryPropertyPrefix}.Command", executingCommand?.GetType().FullName, 508 | $"{TelemetryPropertyPrefix}.CommandHandler", _state.GetCurrentlyExecutingCommandHander()?.GetType().FullName, 509 | $"{TelemetryPropertyPrefix}.Timeout", this.CancelAfter, 510 | $"{TelemetryPropertyPrefix}.WasExecutionCancelled", wasExecutionCancelled); 511 | } 512 | 513 | public void OnDelay() 514 | { 515 | var executingCommand = _state.ExecutingCommand; 516 | _loggingService?.PostEvent($"{TelemetryEventPrefix}/WaitDialogShown", 517 | $"{TelemetryPropertyPrefix}.Command", executingCommand?.GetType().FullName, 518 | $"{TelemetryPropertyPrefix}.CommandHandler", _state.GetCurrentlyExecutingCommandHander()?.GetType().FullName); 519 | } 520 | } 521 | } 522 | } 523 | -------------------------------------------------------------------------------- /Microsoft.VisualStudio.MiniEditor/CustomImpl/System.Windows.Clipboard.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | 4 | namespace System.Windows 5 | { 6 | static class Clipboard 7 | { 8 | static IDataObject dataObject; 9 | 10 | public static IDataObject GetDataObject () => dataObject; 11 | 12 | public static void SetDataObject (IDataObject data, bool copy) 13 | => dataObject = data; 14 | 15 | public static bool ContainsText () => 16 | dataObject.GetFormats ()?.Any (f => f == DataFormats.UnicodeText) ?? false; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Microsoft.VisualStudio.MiniEditor/CustomTextModel/TextDocument.cs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) Microsoft Corporation. All rights reserved. 3 | // Licensed under the MIT License. See License.txt in the project root for license information. 4 | // 5 | // This file contain implementations details that are subject to change without notice. 6 | // Use at your own risk. 7 | // 8 | namespace Microsoft.VisualStudio.Text.Implementation 9 | { 10 | using System; 11 | using System.Diagnostics; 12 | using System.IO; 13 | using System.Text; 14 | using Microsoft.VisualStudio.Text.Utilities; 15 | using Microsoft.VisualStudio.Utilities; 16 | using Microsoft.VisualStudio.Text.Editor; 17 | 18 | internal sealed partial class TextDocument : ITextDocument 19 | { 20 | #region Private Members 21 | 22 | private readonly TextDocumentFactoryService _textDocumentFactoryService; 23 | private ITextBuffer _textBuffer; 24 | private Encoding _encoding; 25 | private string _filePath; 26 | //If the user explicitly chooses the encoding, we want to respect their chosen encoding 27 | private bool _explicitEncoding; 28 | 29 | //This corresponds to the tools option "Auto-detect UTF-8 encoding" 30 | //Unfortunately, we cannot dynamically read the option value without adding a dependency on TextLogic which results in a layering violation. 31 | //Therefore we're going to cache this value on creation of the text document and it will persist the lifetime of the document. 32 | private bool _attemptUtf8Detection = true; 33 | 34 | private DateTime _lastSavedTimeUtc; 35 | private DateTime _lastModifiedTimeUtc; 36 | private int _cleanReiteratedVersion; // The ReiteratedVersionNumber at which the document was last clean (saved, opened, or created) 37 | private bool _isDirty; 38 | private bool _isDisposed; 39 | private bool _raisingDirtyStateChangedEvent; 40 | private bool _raisingFileActionChangedEvent; 41 | private bool _reloadingFile; 42 | 43 | #endregion 44 | 45 | #region Construction 46 | internal TextDocument (ITextBuffer textBuffer, string filePath, DateTime lastModifiedTime, TextDocumentFactoryService textDocumentFactoryService) 47 | : this (textBuffer, filePath, lastModifiedTime, textDocumentFactoryService, Encoding.UTF8) { } 48 | 49 | internal TextDocument (ITextBuffer textBuffer, string filePath, DateTime lastModifiedTime, TextDocumentFactoryService textDocumentFactoryService, Encoding encoding, bool explicitEncoding = false, bool attemptUtf8Detection = true) 50 | { 51 | if (textBuffer == null) { 52 | throw new ArgumentNullException (nameof (textBuffer)); 53 | } 54 | if (filePath == null) { 55 | throw new ArgumentNullException (nameof (filePath)); 56 | } 57 | if (textDocumentFactoryService == null) { 58 | throw new ArgumentNullException (nameof (textDocumentFactoryService)); 59 | } 60 | if (encoding == null) { 61 | throw new ArgumentNullException (nameof (encoding)); 62 | } 63 | 64 | _textBuffer = textBuffer; 65 | _filePath = filePath; 66 | _lastModifiedTimeUtc = lastModifiedTime; 67 | _textDocumentFactoryService = textDocumentFactoryService; 68 | _cleanReiteratedVersion = _textBuffer.CurrentSnapshot.Version.ReiteratedVersionNumber; 69 | _isDisposed = false; 70 | _isDirty = false; 71 | _reloadingFile = false; 72 | _raisingDirtyStateChangedEvent = false; 73 | _raisingFileActionChangedEvent = false; 74 | _encoding = encoding; 75 | _explicitEncoding = explicitEncoding; 76 | _attemptUtf8Detection = attemptUtf8Detection; 77 | 78 | // Keep track of when the text buffer has been changed so that we can update the LastContentModifiedTime 79 | _textBuffer.ChangedHighPriority += TextBufferChangedHandler; 80 | 81 | _textBuffer.Properties.AddProperty (typeof (ITextDocument), this); 82 | } 83 | #endregion 84 | 85 | #region ITextDocument Members 86 | 87 | public string FilePath { 88 | get { return _filePath; } 89 | } 90 | 91 | public ITextBuffer TextBuffer { 92 | get { return _textBuffer; } 93 | } 94 | 95 | public bool IsDirty { 96 | get { return _isDirty; } 97 | } 98 | 99 | public DateTime LastSavedTime { 100 | get { return _lastSavedTimeUtc; } 101 | } 102 | 103 | public DateTime LastContentModifiedTime { 104 | get { return _lastModifiedTimeUtc; } 105 | } 106 | 107 | public void Rename (string newFilePath) 108 | { 109 | if (_isDisposed) { 110 | throw new ObjectDisposedException ("ITextDocument"); 111 | } 112 | if (_raisingDirtyStateChangedEvent || _raisingFileActionChangedEvent) { 113 | throw new InvalidOperationException (); 114 | } 115 | if (newFilePath == null) { 116 | throw new ArgumentNullException (nameof (newFilePath)); 117 | } 118 | 119 | _filePath = newFilePath; 120 | 121 | RaiseFileActionChangedEvent (_lastModifiedTimeUtc, FileActionTypes.DocumentRenamed, _filePath); 122 | } 123 | 124 | public ReloadResult Reload () 125 | { 126 | return Reload (EditOptions.None); 127 | } 128 | 129 | private void ReloadBufferFromStream (Stream stream, long fileSize, EditOptions options, Encoding encoding) 130 | { 131 | using (var streamReader = new EncodedStreamReader.NonStreamClosingStreamReader (stream, encoding, detectEncodingFromByteOrderMarks: false)) { 132 | TextBuffer concreteBuffer = _textBuffer as TextBuffer; 133 | if (concreteBuffer != null) { 134 | bool hasConsistentLineEndings; 135 | int longestLineLength; 136 | StringRebuilder newContent = TextImageLoader.Load (streamReader, fileSize, out hasConsistentLineEndings, out longestLineLength); 137 | 138 | if (!hasConsistentLineEndings) { 139 | // leave a sign that line endings are inconsistent. This is rather nasty but for now 140 | // we don't want to pollute the API with this factoid. 141 | concreteBuffer.Properties["InconsistentLineEndings"] = true; 142 | } else { 143 | // this covers a really obscure case where on initial load the file had inconsistent line 144 | // endings, but the UI settings were such that it was ignored, and since then the file has 145 | // acquired consistent line endings and the UI settings have also changed. 146 | concreteBuffer.Properties.RemoveProperty ("InconsistentLineEndings"); 147 | } 148 | // leave a similar sign about the longest line in the buffer. 149 | concreteBuffer.Properties["LongestLineLength"] = longestLineLength; 150 | 151 | concreteBuffer.ReloadContent (newContent, options, editTag: this); 152 | } else { 153 | // we may hit this path if somebody mocks the text buffer in a test. 154 | using (var edit = _textBuffer.CreateEdit (options, null, editTag: this)) { 155 | if (edit.Replace (new Span (0, edit.Snapshot.Length), streamReader.ReadToEnd ())) { 156 | edit.Apply (); 157 | } else { 158 | edit.Cancel (); 159 | } 160 | } 161 | } 162 | } 163 | } 164 | 165 | public ReloadResult Reload (EditOptions options) 166 | { 167 | if (_isDisposed) { 168 | throw new ObjectDisposedException (nameof (ITextDocument)); 169 | } 170 | if (_raisingDirtyStateChangedEvent || _raisingFileActionChangedEvent) { 171 | throw new InvalidOperationException (); 172 | } 173 | 174 | Encoding newEncoding; 175 | var beforeSnapshot = _textBuffer.CurrentSnapshot; 176 | bool characterSubstitutionsOccurred = false; 177 | 178 | try { 179 | _reloadingFile = true; 180 | 181 | // Load the file and read the contents to the text buffer 182 | long fileSize; 183 | 184 | using (var stream = TextDocumentFactoryService.OpenFile (_filePath, out _lastModifiedTimeUtc, out fileSize)) { 185 | var detectors = ExtensionSelector.SelectMatchingExtensions (_textDocumentFactoryService.OrderedEncodingDetectors, _textBuffer.ContentType); 186 | 187 | if (_explicitEncoding) { 188 | // If the user explicitly chose their encoding, we want to respect it. 189 | newEncoding = this.Encoding; 190 | } else { 191 | newEncoding = EncodedStreamReader.DetectEncoding (stream, detectors, _textDocumentFactoryService.GuardedOperations); 192 | } 193 | 194 | if (newEncoding == null && _attemptUtf8Detection) { 195 | try { 196 | var detectorEncoding = new ExtendedCharacterDetector (); 197 | 198 | ReloadBufferFromStream (stream, fileSize, options, detectorEncoding); 199 | 200 | if (detectorEncoding.DecodedExtendedCharacters) { 201 | // Valid UTF-8 but has bytes that are not merely ASCII. 202 | newEncoding = new UTF8Encoding (encoderShouldEmitUTF8Identifier: false); 203 | } else { 204 | // Valid UTF8 but no extended characters, so it looks like valid ASCII. 205 | // However, we don't use ASCII here because of the following scenario: 206 | // The user with a non-English US system encoding opens a code file that happens to contain ASCII-only contents 207 | // Therefore we'll just use their system encoding. 208 | newEncoding = Encoding.Default; 209 | } 210 | } catch (DecoderFallbackException) { 211 | // Not valid UTF-8. 212 | // Proceed to the next if block to try the system's default codepage. 213 | // For example, this occurs when you have extended characters like € in a UTF-8 file or ANSI file. 214 | // We reset the stream so we can continue loading with the default system encoding. 215 | Debug.Assert (newEncoding == null); 216 | Debug.Assert (beforeSnapshot.Version.Next == null); 217 | stream.Position = 0; 218 | } 219 | } 220 | 221 | // If all else didn't work, use system's default encoding. 222 | if (newEncoding == null) { 223 | newEncoding = Encoding.Default; 224 | } 225 | 226 | //If there is no "Next" version of the original snapshot, we have not successfully reloaded the document 227 | if (beforeSnapshot.Version.Next == null) { 228 | //We use this fall back detector to observe whether or not character substitutions 229 | //occur while we're reading the stream 230 | var fallbackDetector = new FallbackDetector (newEncoding.DecoderFallback); 231 | var modifiedEncoding = (Encoding)newEncoding.Clone (); 232 | modifiedEncoding.DecoderFallback = fallbackDetector; 233 | 234 | Debug.Assert (stream.Position == 0); 235 | ReloadBufferFromStream (stream, fileSize, options, modifiedEncoding); 236 | 237 | if (fallbackDetector.FallbackOccurred) { 238 | characterSubstitutionsOccurred = fallbackDetector.FallbackOccurred; 239 | } 240 | } 241 | } 242 | } finally { 243 | _reloadingFile = false; 244 | } 245 | 246 | //The snapshot on a reload will change even if the contents of the before & after files are identical (differences will simply find an 247 | //empty set of changes) so this test is a measure of whether of not the reload succeeded. 248 | if (beforeSnapshot.Version.Next != null) { 249 | // Update status 250 | // set the "clean" reiterated version number to the reiterated version number of the version immediately 251 | // after the before snapshot (which is the state of the buffer after loading the document but before any 252 | // subsequent edits made in the text buffer changed events). 253 | _cleanReiteratedVersion = beforeSnapshot.Version.Next.ReiteratedVersionNumber; 254 | 255 | // TODO: the following event really should be queued up through the buffer group so that it comes before 256 | // the text changed event (and any subsequent text changed event invoked from an event handler) 257 | RaiseFileActionChangedEvent (_lastModifiedTimeUtc, FileActionTypes.ContentLoadedFromDisk, _filePath); 258 | this.Encoding = newEncoding; 259 | return characterSubstitutionsOccurred ? ReloadResult.SucceededWithCharacterSubstitutions : ReloadResult.Succeeded; 260 | } else { 261 | return ReloadResult.Aborted; 262 | } 263 | } 264 | 265 | public bool IsReloading { 266 | get { return _reloadingFile; } 267 | } 268 | 269 | public void Save () 270 | { 271 | if (_isDisposed) { 272 | throw new ObjectDisposedException ("ITextDocument"); 273 | } 274 | if (_raisingDirtyStateChangedEvent || _raisingFileActionChangedEvent) { 275 | throw new InvalidOperationException (); 276 | } 277 | 278 | // Before saving the document check if we need to change the encoding of the file as per codingconventions of the repo. 279 | if (_textBuffer.Properties.TryGetProperty ("EncodingToBeAppliedOnSave", out Encoding encodingToBeAppliedOnSave)) { 280 | this.Encoding = encodingToBeAppliedOnSave; 281 | _textBuffer.Properties.RemoveProperty ("EncodingToBeAppliedOnSave"); 282 | } 283 | 284 | PerformSave (FileMode.Create, _filePath, false); 285 | UpdateSaveStatus (_filePath, false); 286 | } 287 | 288 | public void SaveAs (string filePath, bool overwrite) 289 | { 290 | SaveAs (filePath, overwrite, false); 291 | } 292 | 293 | public void SaveAs (string filePath, bool overwrite, IContentType newContentType) 294 | { 295 | SaveAs (filePath, overwrite, false, newContentType); 296 | } 297 | 298 | public void SaveCopy (string filePath, bool overwrite) 299 | { 300 | SaveCopy (filePath, overwrite, false); 301 | } 302 | 303 | public void SaveAs (string filePath, bool overwrite, bool createFolder) 304 | { 305 | if (_isDisposed) { 306 | throw new ObjectDisposedException ("ITextDocument"); 307 | } 308 | if (_raisingDirtyStateChangedEvent || _raisingFileActionChangedEvent) { 309 | throw new InvalidOperationException (); 310 | } 311 | if (filePath == null) { 312 | throw new ArgumentNullException (nameof (filePath)); 313 | } 314 | 315 | PerformSave (overwrite ? FileMode.Create : FileMode.CreateNew, filePath, createFolder); 316 | UpdateSaveStatus (filePath, !string.Equals (_filePath, filePath, StringComparison.Ordinal)); 317 | 318 | // file path won't be updated if the save fails (in which case PerformSave will throw an exception) 319 | 320 | _filePath = filePath; 321 | } 322 | 323 | public void SaveAs (string filePath, bool overwrite, bool createFolder, IContentType newContentType) 324 | { 325 | if (newContentType == null) { 326 | throw new ArgumentNullException (nameof (newContentType)); 327 | } 328 | SaveAs (filePath, overwrite, createFolder); 329 | // content type won't be changed if the save fails (in which case SaveAs will throw an exception) 330 | _textBuffer.ChangeContentType (newContentType, null); 331 | } 332 | 333 | public void SaveCopy (string filePath, bool overwrite, bool createFolder) 334 | { 335 | if (_isDisposed) { 336 | throw new ObjectDisposedException ("ITextDocument"); 337 | } 338 | if (filePath == null) { 339 | throw new ArgumentNullException (nameof (filePath)); 340 | } 341 | 342 | PerformSave (overwrite ? FileMode.Create : FileMode.CreateNew, filePath, createFolder); 343 | // Don't update save status 344 | } 345 | 346 | private void PerformSave (FileMode fileMode, string filePath, bool createFolder) 347 | { 348 | var filesystem = VisualStudio.MiniEditor.MiniEditorSetup.FileSystem; 349 | if (filesystem != null) { 350 | filesystem.PerformSave (_textBuffer.CurrentSnapshot, fileMode, filePath, _encoding, createFolder); 351 | return; 352 | } 353 | // check whether directory of the path exists 354 | if (createFolder) { 355 | string fileDirectoryName = Path.GetDirectoryName (filePath); 356 | if (!string.IsNullOrEmpty (fileDirectoryName) && !Directory.Exists (fileDirectoryName)) { 357 | Directory.CreateDirectory (fileDirectoryName); 358 | } 359 | } 360 | FileUtilities.SaveSnapshot (_textBuffer.CurrentSnapshot, fileMode, _encoding, filePath); 361 | } 362 | 363 | private void UpdateSaveStatus (string filePath, bool renamed) 364 | { 365 | FileInfo fileInfo = new FileInfo (filePath); 366 | _lastSavedTimeUtc = fileInfo.LastWriteTimeUtc; 367 | _cleanReiteratedVersion = _textBuffer.CurrentSnapshot.Version.ReiteratedVersionNumber; 368 | 369 | FileActionTypes actionType = FileActionTypes.ContentSavedToDisk; 370 | if (renamed) { 371 | actionType |= FileActionTypes.DocumentRenamed; 372 | } 373 | RaiseFileActionChangedEvent (_lastSavedTimeUtc, actionType, filePath); 374 | } 375 | 376 | public void UpdateDirtyState (bool isDirtied, DateTime lastContentModifiedTimeUtc) 377 | { 378 | if (_raisingDirtyStateChangedEvent || _raisingFileActionChangedEvent) { 379 | throw new InvalidOperationException (); 380 | } 381 | 382 | if (_isDisposed) { 383 | throw new ObjectDisposedException ("ITextDocument"); 384 | } 385 | 386 | _lastModifiedTimeUtc = lastContentModifiedTimeUtc; 387 | 388 | RaiseDirtyStateChangedEvent (isDirtied); 389 | } 390 | 391 | public Encoding Encoding { 392 | get { 393 | return _encoding; 394 | } 395 | set { 396 | if (value == null) { 397 | throw new ArgumentNullException (nameof (value)); 398 | } 399 | 400 | Encoding oldEncoding = _encoding; 401 | 402 | _encoding = value; 403 | 404 | if (!_encoding.Equals (oldEncoding)) { 405 | _textDocumentFactoryService.GuardedOperations.RaiseEvent (this, EncodingChanged, new EncodingChangedEventArgs (oldEncoding, _encoding)); 406 | } 407 | } 408 | } 409 | 410 | public void SetEncoderFallback (EncoderFallback fallback) 411 | { 412 | _encoding = Encoding.GetEncoding (_encoding.CodePage, fallback, _encoding.DecoderFallback); 413 | // no event here! 414 | } 415 | 416 | #endregion 417 | 418 | #region IDisposable Members 419 | 420 | public void Dispose () 421 | { 422 | if (_raisingDirtyStateChangedEvent || _raisingFileActionChangedEvent) { 423 | throw new InvalidOperationException (); 424 | } 425 | 426 | if (!_isDisposed) { 427 | _textBuffer.ChangedHighPriority -= TextBufferChangedHandler; 428 | _textBuffer.Properties.RemoveProperty (typeof (ITextDocument)); 429 | 430 | _isDisposed = true; 431 | 432 | _textDocumentFactoryService.RaiseTextDocumentDisposed (this); 433 | GC.SuppressFinalize (this); 434 | 435 | _textBuffer = null; // why? 436 | } 437 | } 438 | 439 | #endregion 440 | 441 | #region Private helpers 442 | 443 | private void TextBufferChangedHandler (object sender, TextContentChangedEventArgs e) 444 | { 445 | // We don't want to process textbuffer changes that were caused by ourselves 446 | if (e.EditTag != this) { 447 | _lastModifiedTimeUtc = DateTime.UtcNow; 448 | 449 | // If the edit was the result of an undo/redo action that took us back to the clean ReiteratedVersionNumber, 450 | // the document is no longer dirty 451 | if (e.AfterVersion.ReiteratedVersionNumber == _cleanReiteratedVersion) { 452 | RaiseDirtyStateChangedEvent (false); 453 | } else { 454 | RaiseDirtyStateChangedEvent (true); 455 | } 456 | } 457 | } 458 | 459 | /// 460 | /// Raises events for when the dirty state changes 461 | /// 462 | private void RaiseDirtyStateChangedEvent (bool newDirtyState) 463 | { 464 | _raisingDirtyStateChangedEvent = true; 465 | 466 | try { 467 | bool dirtyStateChanged = (_isDirty != newDirtyState); 468 | 469 | if (dirtyStateChanged) { 470 | _isDirty = newDirtyState; 471 | 472 | _textDocumentFactoryService.GuardedOperations.RaiseEvent (this, DirtyStateChanged); 473 | } 474 | } finally { 475 | _raisingDirtyStateChangedEvent = false; 476 | } 477 | } 478 | 479 | /// 480 | /// Raises events for when a file load/save occurs. 481 | /// 482 | private void RaiseFileActionChangedEvent (DateTime actionTime, FileActionTypes actionType, string filePath) 483 | { 484 | _raisingFileActionChangedEvent = true; 485 | 486 | try { 487 | if ((actionType & FileActionTypes.ContentLoadedFromDisk) == FileActionTypes.ContentLoadedFromDisk || 488 | (actionType & FileActionTypes.ContentSavedToDisk) == FileActionTypes.ContentSavedToDisk) { 489 | // We did a reload or a save so we probably want to clear the dirty flag unless someone modified 490 | // the buffer -- changing the reiterated version number -- between the reload and when this call was made 491 | // (for example, modifying the buffer in the text buffer changed event). 492 | if (_cleanReiteratedVersion == _textBuffer.CurrentSnapshot.Version.ReiteratedVersionNumber) { 493 | RaiseDirtyStateChangedEvent (false); 494 | } 495 | } 496 | 497 | _textDocumentFactoryService.GuardedOperations.RaiseEvent (this, FileActionOccurred, new TextDocumentFileActionEventArgs (filePath, actionTime, actionType)); 498 | 499 | } finally { 500 | _raisingFileActionChangedEvent = false; 501 | } 502 | } 503 | 504 | #endregion 505 | 506 | public event EventHandler FileActionOccurred; 507 | public event EventHandler DirtyStateChanged; 508 | public event EventHandler EncodingChanged; 509 | 510 | /// 511 | /// An accessor for isDisposed to be used by . 512 | /// 513 | internal bool IsDisposed { 514 | get { return _isDisposed; } 515 | } 516 | } 517 | } 518 | -------------------------------------------------------------------------------- /Microsoft.VisualStudio.MiniEditor/CustomTextModel/TextDocumentFactoryService.cs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) Microsoft Corporation. All rights reserved. 3 | // Licensed under the MIT License. See License.txt in the project root for license information. 4 | // 5 | // This file contain implementations details that are subject to change without notice. 6 | // Use at your own risk. 7 | // 8 | namespace Microsoft.VisualStudio.Text.Implementation 9 | { 10 | using System; 11 | using System.Collections.Generic; 12 | using System.ComponentModel.Composition; 13 | using System.IO; 14 | using System.Linq; 15 | using System.Text; 16 | using Microsoft.VisualStudio.Text.Utilities; 17 | using Microsoft.VisualStudio.Utilities; 18 | using System.Diagnostics; 19 | using Microsoft.VisualStudio.Text.Editor; 20 | using System.Runtime.InteropServices; 21 | 22 | [Export (typeof (ITextDocumentFactoryService))] 23 | internal sealed partial class TextDocumentFactoryService : ITextDocumentFactoryService 24 | { 25 | #region Internal Consumptions 26 | 27 | [Import] 28 | internal ITextBufferFactoryService BufferFactoryService { get; set; } 29 | 30 | [ImportMany] 31 | internal List> UnorderedEncodingDetectors { get; set; } 32 | 33 | [Import] 34 | internal GuardedOperations GuardedOperations { get; set; } 35 | 36 | #endregion 37 | 38 | internal static Encoding DefaultEncoding = Encoding.Default; // Exposed for unit tests. 39 | static Encoding UTF8WithoutBOM = new UTF8Encoding (encoderShouldEmitUTF8Identifier: false); 40 | 41 | #region ITextDocumentFactoryService Members 42 | 43 | public ITextDocument CreateAndLoadTextDocument (string filePath, IContentType contentType) 44 | { 45 | bool unused; 46 | return CreateAndLoadTextDocument (filePath, contentType, attemptUtf8Detection: true, characterSubstitutionsOccurred: out unused); 47 | } 48 | 49 | public ITextDocument CreateAndLoadTextDocument (string filePath, IContentType contentType, Encoding encoding, out bool characterSubstitutionsOccurred) 50 | { 51 | if (filePath == null) { 52 | throw new ArgumentNullException (nameof (filePath)); 53 | } 54 | 55 | if (contentType == null) { 56 | throw new ArgumentNullException (nameof (contentType)); 57 | } 58 | 59 | if (encoding == null) { 60 | throw new ArgumentNullException (nameof (encoding)); 61 | } 62 | 63 | var fallbackDetector = new FallbackDetector (encoding.DecoderFallback); 64 | var modifiedEncoding = (Encoding)encoding.Clone (); 65 | modifiedEncoding.DecoderFallback = fallbackDetector; 66 | 67 | ITextBuffer buffer; 68 | DateTime lastModified; 69 | long fileSize; 70 | using (Stream stream = OpenFile (filePath, out lastModified, out fileSize)) { 71 | // Caller knows best, so don't use byte order marks. 72 | using (StreamReader reader = new StreamReader (stream, modifiedEncoding, detectEncodingFromByteOrderMarks: false)) { 73 | System.Diagnostics.Debug.Assert (encoding.CodePage == reader.CurrentEncoding.CodePage); 74 | buffer = ((BufferFactoryService)BufferFactoryService).CreateTextBuffer (reader, contentType, fileSize, filePath); 75 | } 76 | } 77 | 78 | characterSubstitutionsOccurred = fallbackDetector.FallbackOccurred; 79 | 80 | #if _DEBUG 81 | TextUtilities.TagBuffer(buffer, filePath); 82 | #endif 83 | TextDocument textDocument = new TextDocument (buffer, filePath, lastModified, this, encoding, explicitEncoding: true); 84 | 85 | RaiseTextDocumentCreated (textDocument); 86 | 87 | return textDocument; 88 | } 89 | 90 | public ITextDocument CreateAndLoadTextDocument (string filePath, IContentType contentType, bool attemptUtf8Detection, out bool characterSubstitutionsOccurred) 91 | { 92 | if (filePath == null) { 93 | throw new ArgumentNullException (nameof (filePath)); 94 | } 95 | 96 | if (contentType == null) { 97 | throw new ArgumentNullException (nameof (contentType)); 98 | } 99 | 100 | characterSubstitutionsOccurred = false; 101 | 102 | Encoding chosenEncoding = null; 103 | ITextBuffer buffer = null; 104 | DateTime lastModified; 105 | long fileSize; 106 | 107 | // select matching detectors without instantiating any 108 | var detectors = ExtensionSelector.SelectMatchingExtensions (OrderedEncodingDetectors, contentType); 109 | 110 | using (Stream stream = OpenFile (filePath, out lastModified, out fileSize)) { 111 | // First, look for a byte order marker and let the encoding detecters 112 | // suggest encodings. 113 | chosenEncoding = EncodedStreamReader.DetectEncoding (stream, detectors, GuardedOperations); 114 | 115 | // If that didn't produce a result, tentatively try to open as UTF 8. 116 | if (chosenEncoding == null && attemptUtf8Detection) { 117 | try { 118 | var detectorEncoding = new ExtendedCharacterDetector (); 119 | 120 | using (StreamReader reader = new EncodedStreamReader.NonStreamClosingStreamReader (stream, detectorEncoding, false)) { 121 | buffer = ((BufferFactoryService)BufferFactoryService).CreateTextBuffer (reader, contentType, fileSize, filePath); 122 | characterSubstitutionsOccurred = false; 123 | } 124 | 125 | if (detectorEncoding.DecodedExtendedCharacters) { 126 | // Valid UTF-8 but has bytes that are not merely ASCII. 127 | chosenEncoding = new UTF8Encoding (encoderShouldEmitUTF8Identifier: false); 128 | } else { 129 | // Valid UTF8 but no extended characters, so it's valid ASCII. 130 | // We don't use ASCII here because of the following scenario: 131 | // The user with a non-ENU system encoding opens a code file with ASCII-only contents 132 | if (RuntimeInformation.IsOSPlatform (OSPlatform.Windows)) 133 | chosenEncoding = DefaultEncoding; 134 | else 135 | // To get to this line, it means file doesn't have BOM 136 | // On Windows DefaultEncoding is "ASCII" which doesn't have BOM 137 | // On non-Windows systems DefaultEncoding is UTF8 which emits BOM on save 138 | // which is something we don't want(on file that didn't have BOM to save BOM) 139 | // So instead we use "new UTF8Encoding (encoderShouldEmitUTF8Identifier: false)", which means don't emit BOM when saving. 140 | chosenEncoding = UTF8WithoutBOM; 141 | } 142 | } catch (DecoderFallbackException) { 143 | // Not valid UTF-8. 144 | // Proceed to the next if block to try the system's default codepage. 145 | Debug.Assert (buffer == null); 146 | buffer = null; 147 | stream.Position = 0; 148 | } 149 | } 150 | 151 | Debug.Assert (buffer == null || chosenEncoding != null); 152 | 153 | // If all else didn't work, use system's default encoding. 154 | if (chosenEncoding == null) { 155 | chosenEncoding = DefaultEncoding; 156 | } 157 | 158 | if (buffer == null) { 159 | var fallbackDetector = new FallbackDetector (chosenEncoding.DecoderFallback); 160 | var modifiedEncoding = (Encoding)chosenEncoding.Clone (); 161 | modifiedEncoding.DecoderFallback = fallbackDetector; 162 | 163 | Debug.Assert (stream.Position == 0); 164 | 165 | using (StreamReader reader = new EncodedStreamReader.NonStreamClosingStreamReader (stream, modifiedEncoding, detectEncodingFromByteOrderMarks: false)) { 166 | Debug.Assert (chosenEncoding.CodePage == reader.CurrentEncoding.CodePage); 167 | buffer = ((BufferFactoryService)BufferFactoryService).CreateTextBuffer (reader, contentType, fileSize, filePath); 168 | } 169 | 170 | characterSubstitutionsOccurred = fallbackDetector.FallbackOccurred; 171 | } 172 | } 173 | 174 | TextDocument textDocument = new TextDocument (buffer, filePath, lastModified, this, chosenEncoding, attemptUtf8Detection: attemptUtf8Detection); 175 | 176 | RaiseTextDocumentCreated (textDocument); 177 | 178 | return textDocument; 179 | } 180 | 181 | public ITextDocument CreateTextDocument (ITextBuffer textBuffer, string filePath) 182 | { 183 | if (textBuffer == null) { 184 | throw new ArgumentNullException (nameof (textBuffer)); 185 | } 186 | 187 | if (filePath == null) { 188 | throw new ArgumentNullException (nameof (filePath)); 189 | } 190 | 191 | TextDocument textDocument = new TextDocument (textBuffer, filePath, DateTime.UtcNow, this, Encoding.UTF8); 192 | RaiseTextDocumentCreated (textDocument); 193 | 194 | return textDocument; 195 | } 196 | 197 | public bool TryGetTextDocument (ITextBuffer textBuffer, out ITextDocument textDocument) 198 | { 199 | if (textBuffer == null) { 200 | throw new ArgumentNullException (nameof (textBuffer)); 201 | } 202 | 203 | textDocument = null; 204 | 205 | TextDocument document; 206 | if (textBuffer.Properties.TryGetProperty (typeof (ITextDocument), out document)) { 207 | if (document != null && !document.IsDisposed) { 208 | textDocument = document; 209 | return true; 210 | } else { 211 | Debug.Fail ("There shouldn't be a null or disposed document in the buffer's property bag. Did someone else put it there?"); 212 | } 213 | } 214 | 215 | return false; 216 | } 217 | 218 | public event EventHandler TextDocumentCreated; 219 | 220 | public event EventHandler TextDocumentDisposed; 221 | 222 | #endregion 223 | 224 | #region helpers 225 | 226 | /// 227 | /// Helper method to raise the event. 228 | /// 229 | /// The that was created. 230 | private void RaiseTextDocumentCreated (ITextDocument textDocument) 231 | { 232 | EventHandler documentCreated = this.TextDocumentCreated; 233 | if (documentCreated != null) { 234 | documentCreated.Invoke (this, new TextDocumentEventArgs (textDocument)); 235 | } 236 | } 237 | 238 | /// 239 | /// Helper method to raise the event. 240 | /// 241 | /// The that was disposed. 242 | internal void RaiseTextDocumentDisposed (ITextDocument textDocument) 243 | { 244 | EventHandler documentDisposed = this.TextDocumentDisposed; 245 | if (documentDisposed != null) { 246 | documentDisposed.Invoke (this, new TextDocumentEventArgs (textDocument)); 247 | } 248 | } 249 | 250 | private IList> _orderedEncodingDetectors; 251 | 252 | internal IEnumerable> OrderedEncodingDetectors { 253 | get { 254 | if (_orderedEncodingDetectors == null) { 255 | if (UnorderedEncodingDetectors != null) { 256 | _orderedEncodingDetectors = Orderer.Order (UnorderedEncodingDetectors); 257 | } else { 258 | _orderedEncodingDetectors = new List> (); 259 | } 260 | } 261 | return _orderedEncodingDetectors; 262 | } 263 | set // for unit test helper. 264 | { 265 | _orderedEncodingDetectors = new List> (value); 266 | } 267 | } 268 | 269 | internal static Stream OpenFile (string filePath, out DateTime lastModifiedTimeUtc, out long fileSize) 270 | { 271 | var filesystem = VisualStudio.MiniEditor.MiniEditorSetup.FileSystem; 272 | if (filesystem != null) { 273 | return filesystem.OpenFile (filePath, out lastModifiedTimeUtc, out fileSize); 274 | } else { 275 | return OpenFileGuts (filePath, out lastModifiedTimeUtc, out fileSize); 276 | } 277 | } 278 | 279 | internal static Stream OpenFileGuts (string filePath, out DateTime lastModifiedTimeUtc, out long fileSize) 280 | { 281 | // Sometimes files are held open with FILE_FLAG_DELETE_ON_CLOSE before the editor 282 | // is asked to open them. We should support that by allowing FileShare.Delete. 283 | Stream result = File.Open (filePath, FileMode.Open, FileAccess.Read, FileShare.Read | FileShare.Delete); 284 | FileInfo fileInfo = new FileInfo (filePath); 285 | lastModifiedTimeUtc = fileInfo.LastWriteTimeUtc; 286 | fileSize = fileInfo.Length; 287 | if (fileSize > int.MaxValue) { 288 | throw new InvalidOperationException (Strings.FileTooLarge); 289 | } 290 | 291 | return result; 292 | } 293 | 294 | // For unit testing purposes 295 | internal void Initialize (ITextBufferFactoryService bufferFactoryService) 296 | { 297 | Initialize (bufferFactoryService, null); 298 | } 299 | 300 | internal void Initialize (ITextBufferFactoryService bufferFactoryService, List> detectors) 301 | { 302 | BufferFactoryService = bufferFactoryService; 303 | UnorderedEncodingDetectors = detectors ?? new List> (); 304 | } 305 | 306 | #endregion 307 | } 308 | } 309 | -------------------------------------------------------------------------------- /Microsoft.VisualStudio.MiniEditor/EditorEnvironment.cs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) Microsoft Corporation. All rights reserved. 3 | // Licensed under the MIT License. See License.txt in the project root for license information. 4 | // 5 | namespace Microsoft.VisualStudio.MiniEditor 6 | { 7 | using System; 8 | using System.Collections.Generic; 9 | using System.Collections.Immutable; 10 | using System.Linq; 11 | using System.Reflection; 12 | using System.Runtime.ExceptionServices; 13 | using System.Threading.Tasks; 14 | using Microsoft.VisualStudio.Composition; 15 | using Microsoft.VisualStudio.Text.Editor; 16 | 17 | /// 18 | /// A reusable environment that stores the composition graph. Created using 19 | /// The project that will host the editor must have assemblies specified in in its output directory. /> 20 | /// Any additional MEF parts can be added by using an overload of 21 | /// 22 | public class EditorEnvironment 23 | { 24 | /// 25 | /// Isolated, light weight composition environment 26 | /// 27 | public class Host 28 | { 29 | private ExportProvider _exportProvider; 30 | 31 | /// 32 | /// Creates an export provider which represents a unique container of values. 33 | /// You can create as many of these as you want, 34 | /// typicall each test has its own Host, and an application has a single Host. 35 | /// 36 | internal Host(IExportProviderFactory epf) 37 | { 38 | _exportProvider = epf.CreateExportProvider(); 39 | } 40 | 41 | /// 42 | /// Gets a service of specified type from the composition graph 43 | /// 44 | /// Service type 45 | /// Object of requested type 46 | public T GetService() where T : class 47 | { 48 | return _exportProvider.GetExportedValue(); 49 | } 50 | 51 | /// 52 | /// Gets services of specified type from the composition graph 53 | /// 54 | /// Service type 55 | /// Enumeration of objects of requested type 56 | public IEnumerable GetServices() where T : class 57 | { 58 | return _exportProvider.GetExportedValues(); 59 | } 60 | } 61 | 62 | /// 63 | /// Contains the composition graph, builds independent composition environments 64 | /// 65 | private IExportProviderFactory _epf; 66 | 67 | /// 68 | /// Provides a list of assembly names that contain parts needed to bootstrap editor with its standard features. 69 | /// Also provides names of packages that contain these assemblies 70 | /// 71 | /// 72 | /// Any additional assemblies to the specific integration test should be added using an overload of . 73 | /// 74 | /// An array of assembly names 75 | public static readonly ImmutableArray DefaultAssemblyNames = new [] 76 | { 77 | // Consolidated implementation. Most exports come from here. 78 | "Microsoft.VisualStudio.MiniEditor.dll", 79 | // Exports from this assembly. The file name must match! 80 | //"Microsoft.VisualStudio.Text.StandaloneEditor.dll", 81 | // DefaultWpfViewOptions has necessary EditorOptionDefinition 82 | //"Microsoft.VisualStudio.Text.UI.Wpf.dll", 83 | // DefaultTextViewHostOptions has necessary EditorOptionDefinition 84 | //"Microsoft.VisualStudio.Text.UI.dll", 85 | // DefaultOptions has necessary EditorOptionDefinition 86 | "Microsoft.VisualStudio.Text.Logic.dll", 87 | // WpfTextEditorFactoryService requires an implementation of undo. 88 | //"Microsoft.VisualStudio.Text.Implementation.StandaloneUndo.dll" 89 | }.ToImmutableArray(); 90 | 91 | /// 92 | /// Acommodates modification of the list of assembly names used to boostrap editor with its standard features. 93 | /// This property defaults to and may be changed before is invoked. 94 | /// This is made available so that VS4mac may experiment with WPF-free implementation. Good luck! 95 | /// 96 | public static ImmutableArray DefaultAssemblies { get; set; } = DefaultAssemblyNames; 97 | 98 | /// 99 | /// Gets isolated, light weight composition environment for a specific test. 100 | /// Ideally, each test has its own obtained by calling this method in the test initialization method. 101 | /// 102 | /// Isolated instance of composition environment that provides MEF parts. 103 | public Host GetEditorHost() 104 | { 105 | return new Host(_epf); 106 | } 107 | 108 | /// 109 | /// Contains composition errors. Array is empty if there were no composition errors. 110 | /// 111 | public ImmutableArray CompositionErrors { get; } 112 | 113 | // --- private constructor and public static initialization methods: --- 114 | 115 | /// 116 | /// Creates a reusable environment with a specified composition graph. 117 | /// 118 | private EditorEnvironment(IExportProviderFactory epf, ImmutableArray compositionErrors) 119 | { 120 | _epf = epf; 121 | CompositionErrors = compositionErrors; 122 | } 123 | 124 | /// 125 | /// Creates a reusable environment that consists of default parts needed to bootstrap the editor. 126 | /// 127 | /// Composed environment that can be reused across tests 128 | public static async Task InitializeAsync() 129 | { 130 | return await InitializeAsync(assembliesToLoad: null, typesToLoad: null, typesThatOverride: null); 131 | } 132 | 133 | /// 134 | /// Creates a reusable environment that consists of default parts needed to bootstrap the editor, 135 | /// as well as parts found in the provided assemblies. 136 | /// 137 | /// Filenames of assemblies that will be searched for MEF parts. 138 | /// Composed environment that can be reused across tests 139 | public static async Task InitializeAsync(params string[] assembliesToLoad) 140 | { 141 | return await InitializeAsync(assembliesToLoad, null, null); 142 | } 143 | 144 | /// 145 | /// Creates a reusable environment that consists of default parts needed to bootstrap the editor, 146 | /// as well as parts found in the provided types. 147 | /// 148 | /// Types to include in the MEF catalog. 149 | /// Composed environment that can be reused across tests 150 | public static async Task InitializeAsync(params Type[] typesToLoad) 151 | { 152 | return await InitializeAsync(null, typesToLoad, null); 153 | } 154 | 155 | /// 156 | /// Creates a reusable environment that consists of parts found in the provided 157 | /// as well as default parts needed to bootstrap the editor, except for parts whose metadata matches these from . 158 | /// The overriding of default parts is useful when providing mocks. 159 | /// 160 | /// Types to include in the MEF catalog. 161 | /// Types whose export signatures will be used to filter out the default catalog. 162 | /// Composed environment that can be reused across tests 163 | public static async Task InitializeAsync(IEnumerable typesToLoad, IEnumerable typesThatOverride) 164 | { 165 | return await InitializeAsync(null, typesToLoad, typesThatOverride); 166 | } 167 | 168 | /// 169 | /// Creates a reusable environment that consists of 170 | /// 1) default parts and parts needed to bootstrap the editor, except for parts whose metadata matches these from 171 | /// 2) parts found in the provided assemblies 172 | /// 3) parts found in the provided types. 173 | /// 174 | /// Filenames of assemblies that will be searched for MEF parts. 175 | /// Types to include in the MEF catalog. 176 | /// Types whose export signatures will be used to filter out the default catalog. 177 | /// Composed environment that can be reused across tests 178 | public static async Task InitializeAsync(IEnumerable assembliesToLoad, IEnumerable typesToLoad, IEnumerable typesThatOverride) 179 | { 180 | // Prepare part discovery to support both flavors of MEF attributes. 181 | var discovery = PartDiscovery.Combine( 182 | new AttributedPartDiscovery(Resolver.DefaultInstance, isNonPublicSupported: true), // "NuGet MEF" attributes (Microsoft.Composition) 183 | new AttributedPartDiscoveryV1(Resolver.DefaultInstance)); // ".NET MEF" attributes (System.ComponentModel.Composition) 184 | 185 | var assemblyCatalog = ComposableCatalog.Create(Resolver.DefaultInstance); 186 | 187 | // Create a list of parts that should override the defaults. Used for mocking when Import expects only one part. 188 | IEnumerable partsThatOverride = null; 189 | if (typesThatOverride != null) 190 | { 191 | partsThatOverride = (await discovery.CreatePartsAsync(typesThatOverride)).Parts; 192 | } 193 | 194 | // Load default parts 195 | foreach (var assemblyName in DefaultAssemblies) 196 | { 197 | try 198 | { 199 | var parts = (await discovery.CreatePartsAsync(Assembly.LoadFrom(assemblyName))).Parts; 200 | if (partsThatOverride != null) 201 | { 202 | // Exclude parts whose contract matches the contract of any of the overriding parts 203 | var overriddenParts = parts.Where(defaultPart => defaultPart.ExportedTypes.Any(defaultType => 204 | partsThatOverride.Any(overridingPart => overridingPart.ExportedTypes.Any(overridingType => overridingType.ContractName == defaultType.ContractName)))); 205 | parts = parts.Except(overriddenParts); 206 | } 207 | assemblyCatalog = assemblyCatalog.AddParts(parts); 208 | } 209 | catch (System.IO.FileNotFoundException) 210 | { 211 | // Note, if we provide inner exception, the MSTest runner will display it instead of informativeException. For this reason, we don't provide inner exception. 212 | var informativeException = new InvalidOperationException($"Required assembly `{assemblyName}` not found. Please place it in this process' working directory. You can do it by adding reference to the NuGet package that contains this assembly."); 213 | informativeException.Data["Missing file"] = assemblyName; 214 | throw informativeException; 215 | } 216 | } 217 | 218 | // Load parts from all types in the specified assemblies 219 | if (assembliesToLoad != null) 220 | { 221 | foreach (string assembly in assembliesToLoad) 222 | { 223 | var parts = await discovery.CreatePartsAsync(Assembly.LoadFrom(assembly)); 224 | assemblyCatalog = assemblyCatalog.AddParts(parts); 225 | } 226 | } 227 | 228 | // Load parts from the specified types 229 | if (typesToLoad != null) 230 | { 231 | var parts = await discovery.CreatePartsAsync(typesToLoad); 232 | assemblyCatalog = assemblyCatalog.AddParts(parts); 233 | } 234 | 235 | // TODO: see if this will be useful 236 | assemblyCatalog = assemblyCatalog.WithCompositionService(); // Makes an ICompositionService export available to MEF parts to import. 237 | 238 | // Assemble the parts into a valid graph. 239 | var compositionConfiguration = CompositionConfiguration.Create(assemblyCatalog); 240 | 241 | ImmutableArray compositionErrors = ImmutableArray.Empty; 242 | if (compositionConfiguration.CompositionErrors.Any()) 243 | { 244 | compositionErrors = compositionConfiguration.CompositionErrors.SelectMany(collection => collection.Select(diagnostic => diagnostic.Message)).ToImmutableArray(); 245 | // Uncomment to investigate errors and continue execution 246 | // Debugger.Break(); 247 | 248 | // Uncomment to fail immediately: 249 | // var exception = new InvalidOperationException("There were composition errors"); 250 | // exception.Data["Errors"] = errors; 251 | // throw exception; 252 | } 253 | 254 | // Prepare an ExportProvider factory based on this graph. 255 | // This factory can be now re-used across tests 256 | return new EditorEnvironment(compositionConfiguration.CreateExportProviderFactory(), compositionErrors); 257 | } 258 | } 259 | } 260 | -------------------------------------------------------------------------------- /Microsoft.VisualStudio.MiniEditor/IFileSystemAbstraction.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Text; 4 | using Microsoft.VisualStudio.Text; 5 | 6 | namespace Microsoft.VisualStudio.MiniEditor 7 | { 8 | public interface IFileSystemAbstraction 9 | { 10 | Stream OpenFile (string filePath, out DateTime lastModifiedTimeUtc, out long fileSize); 11 | void PerformSave (ITextSnapshot textSnapshot, FileMode fileMode, string filePath, Encoding encoding, bool createFolder); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /Microsoft.VisualStudio.MiniEditor/Microsoft.VisualStudio.MiniEditor.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net472 5 | latest 6 | $(DefaultItemExcludes);vs-editor-api\**\* 7 | vs-editor-api\src\Editor\ 8 | true 9 | true 10 | ..\public.snk 11 | 16.6.255 12 | 13 | 14 | True 15 | Neteril.VisualStudio.MiniEditor 16 | garuma 17 | Composable subset of the VS-Editor platform that's UI-agnostic to allow cross-platform unit-testing scenarios 18 | Composable subset of the VS-Editor platform that's UI-agnostic to allow cross-platform unit-testing scenarios 19 | https://github.com/garuma/MiniEditor 20 | https://github.com/garuma/MiniEditor 21 | true 22 | snupkg 23 | MIT 24 | Microsoft Corporation 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | 309 | 310 | 311 | 312 | 313 | 314 | 315 | 316 | 317 | 318 | 319 | 320 | 321 | 322 | 323 | 324 | 325 | 326 | 327 | 328 | 329 | 330 | 331 | 332 | ResXFileCodeGenerator 333 | Microsoft.VisualStudio.CoreUtilityImplementation.ContentType.Strings.resources 334 | vs-editor-api\src\Editor\Core\Impl\ContentType\Strings.Designer.cs 335 | Microsoft.VisualStudio.Utilities.Implementation 336 | 337 | 338 | ResXFileCodeGenerator 339 | Microsoft.VisualStudio.Text.Implementation.Strings.resources 340 | vs-editor-api\src\Editor\Text\Impl\TextModel\Strings.Designer.cs 341 | Microsoft.VisualStudio.Text.Implementation 342 | 343 | 344 | ResXFileCodeGenerator 345 | Microsoft.VisualStudio.Language.Intellisense.Implementation.Strings.resources 346 | vs-editor-api\src\Editor\Language\Impl\Language\Strings.Designer.cs 347 | Microsoft.VisualStudio.Language.Intellisense.Implementation 348 | 349 | 350 | ResXFileCodeGenerator 351 | Microsoft.VisualStudio.Text.Classification.Implementation.LookUp.Strings.resources 352 | vs-editor-api\src\Editor\Text\Impl\ClassificationType\Strings.Designer.cs 353 | Microsoft.VisualStudio.Text.Classification.Implementation.LookUp 354 | 355 | 356 | ResXFileCodeGenerator 357 | Microsoft.VisualStudio.UI.Text.Commanding.Implementation.CommandingStrings.resources 358 | vs-editor-api\src\Editor\Text\Impl\Commanding\CommandingStrings.Designer.cs 359 | Microsoft.VisualStudio.UI.Text.Commanding.Implementation 360 | 361 | 362 | ResXFileCodeGenerator 363 | Microsoft.VisualStudio.UI.Text.EditorOperations.Implementation.Strings.resources 364 | vs-editor-api\src\Editor\Text\Impl\EditorOperations\Strings.Designer.cs 365 | Microsoft.VisualStudio.Text.Operations.Implementation 366 | 367 | 368 | ResXFileCodeGenerator 369 | Microsoft.VisualStudio.UI.Text.EditorPrimitives.Implementation.Strings.resources 370 | vs-editor-api\src\Editor\Text\Impl\EditorPrimitives\Strings.Designer.cs 371 | Microsoft.VisualStudio.Text.EditorPrimitives.Implementation 372 | 373 | 374 | ResXFileCodeGenerator 375 | Microsoft.VisualStudio.Logic.Text.BufferUndoManager.Implementation.Strings.resources 376 | vs-editor-api\src\Editor\Text\Impl\TextBufferUndoManager\Strings.Designer.cs 377 | Microsoft.VisualStudio.Text.BufferUndoManager.Implementation 378 | 379 | 380 | ResXFileCodeGenerator 381 | Microsoft.VisualStudio.Text.MultiSelection.Implementation.Strings.resources 382 | vs-editor-api\src\Editor\Text\Impl\XPlat\MultiCaretImpl\Strings.Designer.cs 383 | Microsoft.VisualStudio.Text.MultiSelection.Implementation 384 | 385 | 386 | ResXFileCodeGenerator 387 | Microsoft.VisualStudio.Text.BraceCompletion.Implementation.Strings.resources 388 | vs-editor-api\src\Editor\Text\Impl\BraceCompletion\Strings.Designer.cs 389 | Microsoft.VisualStudio.Text.BraceCompletion.Implementation 390 | 391 | 392 | 393 | 394 | True 395 | True 396 | vs-editor-api\src\Editor\Core\Impl\ContentType\Strings.resx 397 | 398 | 399 | True 400 | True 401 | vs-editor-api\src\Editor\Language\Impl\Language\Strings.resx 402 | 403 | 404 | True 405 | True 406 | vs-editor-api\src\Editor\Text\Impl\TextModel\Strings.resx 407 | 408 | 409 | True 410 | True 411 | vs-editor-api\src\Editor\Text\Impl\ClassificationType\Strings.resx 412 | 413 | 414 | True 415 | True 416 | vs-editor-api\src\Editor\Text\Impl\Commanding\CommandingStrings.resx 417 | 418 | 419 | True 420 | True 421 | vs-editor-api\src\Editor\Text\Impl\EditorOperations\Strings.resx 422 | 423 | 424 | True 425 | True 426 | vs-editor-api\src\Editor\Text\Impl\EditorPrimitives\Strings.resx 427 | 428 | 429 | True 430 | True 431 | vs-editor-api\src\Editor\Text\Impl\TextBufferUndoManager\Strings.resx 432 | 433 | 434 | True 435 | True 436 | vs-editor-api\src\Editor\Text\Impl\XPlat\MultiCaretImpl\Strings.resx 437 | 438 | 439 | True 440 | True 441 | Strings.resx 442 | 443 | 444 | 445 | -------------------------------------------------------------------------------- /Microsoft.VisualStudio.MiniEditor/MiniEditorSetup.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Microsoft.VisualStudio.MiniEditor 4 | { 5 | public static class MiniEditorSetup 6 | { 7 | public static IFileSystemAbstraction FileSystem { get; set; } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /NuGet.Config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # MiniEditor 2 | 3 | NuGet 4 | 5 | Composable subset of the [Visual Studio Editor platform](https://docs.microsoft.com/en-us/visualstudio/extensibility/inside-the-editor) that's UI-agnostic to allow cross-platform unit-testing scenarios 6 | 7 | ## Motivation 8 | 9 | If you are creating editor extensions for both Visual Studio and Visual Studio for Mac that are cross-platform, IDE-agnostic and UI-agnostic then this library should allow you to instantiate and thus unit-test them. 10 | 11 | The library doesn't require a specific test framework and uses [VS-mef](http://github.com/Microsoft/vs-mef) to compose itself at runtime. 12 | 13 | The main testing scenarios supported are currently: 14 | 15 | - Low-level usage of interfaces such as `ITextDocument`, `ITextBuffer`, `ITextSnapshot` and so on. 16 | - [Async completion](https://docs.microsoft.com/en-us/dotnet/api/microsoft.visualstudio.language.intellisense.asynccompletion?view=visualstudiosdk-2017) Intellisense providers. 17 | 18 | ## Setup 19 | 20 | For our scenario we assume your editor extensions are in one .NET library (.NET standard or .NET framework) and your test project (using whatever testing library) references it so that it's copied in its output. 21 | 22 | Depending on your testing needs, you might need to supply a few MEF parts of your own for things to work: 23 | 24 | - If you are trying to test async completion extensions, you should export a `JoinableTaskContext` (see [vs-threading repository](https://github.com/Microsoft/vs-threading) for more information): 25 | ``` csharp 26 | using System.ComponentModel.Composition; 27 | 28 | /* 29 | Set the field to an instance of the class with whatever option you 30 | want *before* initializing the composition, usually in your 31 | unit test framework setup fixture 32 | */ 33 | [Export] 34 | public static Microsoft.VisualStudio.Threading.JoinableTaskContext MefJoinableTaskContext = null; 35 | ``` 36 | - You might be referencing MEF parts that are only available in the full IDE, a classic example is some Content-Type which you may need to export yourself manually: 37 | ``` csharp 38 | using System.ComponentModel.Composition; 39 | using Microsoft.VisualStudio.Utilities; 40 | 41 | [Export] 42 | [Name("xml")] 43 | [BaseDefinition("text")] 44 | public static readonly ContentTypeDefinition XmlContentTypeDefinition = null; 45 | ``` 46 | 47 | MiniEditor also provides a very basic file-system abstraction so that you can instantiate, load and reload `ITextDocument` instances from in-memory content instead of from disk: 48 | 49 | ```csharp 50 | using System.IO; 51 | using System.Text; 52 | using Microsoft.VisualStudio.Text; 53 | using Microsoft.VisualStudio.MiniEditor; 54 | 55 | // Register implementation statically 56 | MiniEditorSetup.FileSystem = new DummyFileSystem (); 57 | 58 | class DummyFileSystem : IFileSystemAbstraction 59 | { 60 | public Stream OpenFile (string filePath, out DateTime lastModifiedTimeUtc, out long fileSize) 61 | { 62 | lastModifiedTimeUtc = DateTime.UtcNow; 63 | var bytes = Encoding.UTF8.GetBytes ("Hello World"); 64 | fileSize = bytes.Length; 65 | return new MemoryStream (bytes); 66 | } 67 | 68 | public void PerformSave ( 69 | ITextSnapshot textSnapshot, 70 | FileMode fileMode, 71 | string filePath, 72 | Encoding encoding, 73 | bool createFolder) 74 | { 75 | Console.WriteLine (textSnapshot.GetText ()); 76 | } 77 | } 78 | ``` 79 | 80 | ## Usage 81 | 82 | With setup out of the way, the first thing you need to do is create a MEF composition container to host the editor subset from the library and (if needed) the extensions that you are providing. 83 | 84 | Usually you would end up doing this in your unit-test framework equivalent of an initialization method and it would look a bit like this: 85 | 86 | ```csharp 87 | using Microsoft.VisualStudio.MiniEditor; 88 | 89 | static EditorEnvironment EditorEnvironment { get; private set; } 90 | 91 | public static void InitializeMiniEditor () 92 | { 93 | // Remember to initialize that JoinableTaskContext if you need it 94 | MefJoinableTaskContext = new JoinableTaskContext (); 95 | 96 | // Create the MEF composition 97 | // can be awaited instead if your framework supports it 98 | EditorEnvironment = EditorEnvironment.InitializeAsync ( 99 | "Your.Assembly.With.Extensions.dll", 100 | "This.Assembly.With.The.Tests.dll" 101 | ).Result; 102 | if (EditorEnvironment.CompositionErrors.Length > 0) { 103 | Console.WriteLine ("Composition Errors:"); 104 | foreach (var error in EditorEnvironment.CompositionErrors) 105 | Console.WriteLine ("\t" + error); 106 | } 107 | 108 | // Register your own logging mechanism to print eventual errors 109 | // in your extensions 110 | var errorHandler = EditorEnvironment 111 | .GetEditorHost () 112 | .GetService (); 113 | errorHandler.ExceptionHandled += (s, e) => Console.WriteLine (e.Exception); 114 | } 115 | ``` 116 | 117 | You can then get all the pieces you need by retrieving an `EditorEnvironment.Host` instance and querying it using its `GetService` method. For instance, here is an helper class that references a few well-known editor services: 118 | 119 | ``` csharp 120 | class EditorCatalog 121 | { 122 | public EditorCatalog (EditorEnvironment env) => Host = env.GetEditorHost (); 123 | 124 | EditorEnvironment.Host Host { get; } 125 | 126 | public ITextViewFactoryService TextViewFactory 127 | => Host.GetService (); 128 | 129 | public ITextDocumentFactoryService TextDocumentFactoryService 130 | => Host.GetService (); 131 | 132 | public IFileToContentTypeService FileToContentTypeService 133 | => Host.GetService (); 134 | 135 | public ITextBufferFactoryService BufferFactoryService 136 | => Host.GetService (); 137 | 138 | public IContentTypeRegistryService ContentTypeRegistryService 139 | => Host.GetService (); 140 | 141 | public IAsyncCompletionBroker AsyncCompletionBroker 142 | => Host.GetService (); 143 | 144 | public IClassifierAggregatorService ClassifierAggregatorService 145 | => Host.GetService (); 146 | 147 | public IClassificationTypeRegistryService ClassificationTypeRegistryService 148 | => Host.GetService (); 149 | 150 | public IBufferTagAggregatorFactoryService BufferTagAggregatorFactoryService 151 | => Host.GetService (); 152 | } 153 | ``` 154 | 155 | ## Examples 156 | 157 | ### Creating a `ITextBuffer` and `ITextView` 158 | 159 | ``` csharp 160 | IContentType contentType = EditorCatalog.ContentTypeRegistryService.GetContentType ("MyContentType"); 161 | ITextBuffer buffer = EditorCatalog.BufferFactoryService.CreateTextBuffer (content, contentType); 162 | ITextView textView = EditorCatalog.TextViewFactory.CreateTextView (buffer); 163 | ``` 164 | 165 | ### Creating a text document 166 | 167 | ``` csharp 168 | // Mock content associated to `filePath` via `MiniEditorSetup.FileSystem` 169 | IContentType contentType = EditorCatalog.FileToContentTypeService.GetContentTypeForFilePath (filePath); 170 | ITextDocument document = EditorCatalog.TextDocumentFactoryService.CreateAndLoadTextDocument (filePath, contentType); 171 | ``` 172 | 173 | ### Instantiating an async completion broker 174 | 175 | ```csharp 176 | using Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion; 177 | using Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion.Data; 178 | 179 | // Use previous examples to get those objects 180 | ITextBuffer buffer = /* ... */; 181 | ITextView view = /* ... */; 182 | int caretPosition = /* where the cursor would be in your text view */; 183 | 184 | IAsyncCompletionBroker broker = EditorCatalog.AsyncCompletionBroker; 185 | ITextSnapshot snapshot = buffer.Snapshot; 186 | var trigger = new CompletionTrigger (CompletionTriggerReason.Invoke, snapshot); 187 | 188 | var context = await broker.GetAggregatedCompletionContextAsync ( 189 | textView, 190 | trigger, 191 | new SnapshotPoint (snapshot, caretPosition), 192 | CancellationToken.None 193 | ); 194 | 195 | // Your completion source should have hopefully filled this up with stuff 196 | var completionItems = context.CompletionContext.Items; 197 | ``` -------------------------------------------------------------------------------- /azure-pipelines.yml: -------------------------------------------------------------------------------- 1 | trigger: 2 | - master 3 | 4 | pool: 5 | name: Hosted Windows 2019 with VS2019 6 | demands: 7 | - msbuild 8 | - visualstudio 9 | 10 | steps: 11 | - checkout: self 12 | submodules: recursive 13 | 14 | - task: NuGetToolInstaller@1 15 | displayName: 'Use NuGet ' 16 | 17 | - task: VSBuild@1 18 | displayName: 'Build solution Microsoft.VisualStudio.MiniEditor.sln' 19 | inputs: 20 | solution: Microsoft.VisualStudio.MiniEditor.sln 21 | msbuildArgs: /restore 22 | configuration: Release 23 | 24 | - task: CopyFiles@2 25 | displayName: 'Copy NuGets' 26 | inputs: 27 | SourceFolder: Microsoft.VisualStudio.MiniEditor/bin/Release 28 | Contents: | 29 | **/*.nupkg 30 | **/*.snupkg 31 | TargetFolder: '$(Build.ArtifactStagingDirectory)' 32 | 33 | - task: NuGetCommand@2 34 | displayName: 'NuGet push' 35 | inputs: 36 | command: push 37 | publishVstsFeed: '6f96278a-705c-4bdc-8ea7-5e61d0ae3a85' 38 | allowPackageConflicts: true 39 | continueOnError: true 40 | condition: and(succeeded(), ne(variables['system.pullrequest.isfork'], true)) 41 | 42 | - task: PublishBuildArtifacts@1 43 | displayName: 'Publish Artifact' 44 | inputs: 45 | ArtifactName: nugets 46 | 47 | -------------------------------------------------------------------------------- /public.snk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/garuma/MiniEditor/10ff113f5953f9898a54fd4114b25e008ae14c63/public.snk -------------------------------------------------------------------------------- /version.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://raw.githubusercontent.com/AArnott/Nerdbank.GitVersioning/master/src/NerdBank.GitVersioning/version.schema.json", 3 | "version": "1.0", 4 | "assemblyVersion": "1.0", 5 | "buildNumberOffset": 0, 6 | "publicReleaseRefSpec": [ 7 | "^refs/heads/master$", 8 | "^refs/heads/v\\d+(?:.\\d+)?$" 9 | ], 10 | "cloudBuild": { 11 | "setVersionVariables": true, 12 | "buildNumber": { 13 | "enabled": true, 14 | "includeCommitId": { 15 | "when": "always", 16 | "where": "buildMetadata" 17 | } 18 | } 19 | } 20 | } 21 | 22 | --------------------------------------------------------------------------------