diff options
author | Lluis Sanchez <llsan@microsoft.com> | 2019-02-20 18:10:48 +0300 |
---|---|---|
committer | Lluis Sanchez <llsan@microsoft.com> | 2019-02-20 18:10:48 +0300 |
commit | 552d55b304b6abe3cf7e410389daf64e26b75188 (patch) | |
tree | 0ad30a67c3d6d580b17fbbfa5026561ff016f939 /main/src | |
parent | 7cfc4085396702861c4000d8f1d36e9ba53e3cea (diff) | |
parent | f803f4f8ae5f1df72134d2a3d4e0e157e19b1507 (diff) |
Merge branch 'new-service-model' into new-doc-model
Diffstat (limited to 'main/src')
281 files changed, 8349 insertions, 6017 deletions
diff --git a/main/src/addins/CSharpBinding/CSharpBinding.addin.xml b/main/src/addins/CSharpBinding/CSharpBinding.addin.xml index 2eac3cb5fb..be59585792 100644 --- a/main/src/addins/CSharpBinding/CSharpBinding.addin.xml +++ b/main/src/addins/CSharpBinding/CSharpBinding.addin.xml @@ -60,6 +60,7 @@ type="array" _label = "Refactory Operations" /> <Command id = "MonoDevelop.CSharp.Refactoring.Commands.SortAndRemoveImports" + defaultHandler = "MonoDevelop.CSharp.Refactoring.RemoveAndSortUsingsHandler" _label = "R_emove and Sort Usings" _displayName = "Remove Unused and Sort (Usings)" /> @@ -193,6 +194,10 @@ <Parser class="MonoDevelop.CSharp.Parser.TypeSystemParser" mimeType = "text/x-csharp" /> </Extension> + <Extension path = "/MonoDevelop/TypeSystem/FoldingParser"> + <Parser class = "MonoDevelop.CSharp.Parser.CSharpFoldingParser" mimeType="text/x-csharp" /> + </Extension> + <Extension path = "/MonoDevelop/TypeSystem/CodeGenerators"> <Generator class="MonoDevelop.CSharp.Refactoring.CSharpCodeGenerator" mimeType = "text/x-csharp" /> </Extension> diff --git a/main/src/addins/CSharpBinding/CSharpBinding.csproj b/main/src/addins/CSharpBinding/CSharpBinding.csproj index e1911f1b85..5013b87b4d 100644 --- a/main/src/addins/CSharpBinding/CSharpBinding.csproj +++ b/main/src/addins/CSharpBinding/CSharpBinding.csproj @@ -159,6 +159,7 @@ <Compile Include="MonoDevelop.CSharp.Completion\CSharpCompletionTextEditorExtension.cs" /> <Compile Include="MonoDevelop.CSharp.Refactoring\CSharpCodeGenerator.cs" /> <Compile Include="MonoDevelop.CSharp.Refactoring\HelperMethods.cs" /> + <Compile Include="MonoDevelop.CSharp.Parser\CSharpFoldingParser.cs" /> <Compile Include="MonoDevelop.CSharp.CodeGeneration\AbstractGenerateAction.cs" /> <Compile Include="MonoDevelop.CSharp.CodeGeneration\CodeGenerationCommands.cs" /> <Compile Include="MonoDevelop.CSharp.CodeGeneration\CodeGenerationOptions.cs" /> diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Completion/CompletionProvider/DelegateCompletionProvider.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Completion/CompletionProvider/DelegateCompletionProvider.cs index 7a11695290..9ff63bcbe9 100644 --- a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Completion/CompletionProvider/DelegateCompletionProvider.cs +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Completion/CompletionProvider/DelegateCompletionProvider.cs @@ -372,7 +372,8 @@ namespace MonoDevelop.CSharp.Completion.Provider { (string beforeText, string afterText, string newMethod) = await GetInsertText (item.Properties); TextChange change; - if (newMethod != null && CompletionWindowManager.IsVisible) { // check for completion window manager to prevent the insertion cursor popup when the changes are queried by code diagnostics. + + if (newMethod != null && RoslynCompletionData.RequestInsertText) { // check for completion window manager to prevent the insertion cursor popup when the changes are queried by code diagnostics. change = new TextChange (new TextSpan (item.Span.Start, item.Span.Length), item.Properties [MethodNameKey] + ";"); var semanticModel = await doc.GetSemanticModelAsync (cancellationToken); if (!doc.IsOpen () || await doc.IsForkedDocumentWithSyntaxChangesAsync (cancellationToken)) diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Formatting/CSharpFormattingPolicy.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Formatting/CSharpFormattingPolicy.cs index 88d34e8340..68c67dd1fa 100644 --- a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Formatting/CSharpFormattingPolicy.cs +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Formatting/CSharpFormattingPolicy.cs @@ -594,22 +594,6 @@ namespace MonoDevelop.CSharp.Formatting #endregion - #region Code Style options - bool placeSystemDirectiveFirst = true; - [Obsolete("Not used anymore.")] - [ItemProperty] - public bool PlaceSystemDirectiveFirst { - get { - return placeSystemDirectiveFirst; - } - - set { - placeSystemDirectiveFirst = value; - } - } - - #endregion - public CSharpFormattingPolicy () { this.options = IdeApp.TypeSystemService.Workspace?.Options; diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Formatting/CSharpTextPasteHandler.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Formatting/CSharpTextPasteHandler.cs index c1288027a1..9dce8b43f9 100644 --- a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Formatting/CSharpTextPasteHandler.cs +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Formatting/CSharpTextPasteHandler.cs @@ -106,6 +106,8 @@ namespace MonoDevelop.CSharp.Formatting indent.Editor.Options.IndentStyle == IndentStyle.Auto) return; var doc = indent.DocumentContext.AnalysisDocument; + if (doc == null) + return; var options = await doc.GetOptionsAsync (); if (!options.GetOption (FeatureOnOffOptions.FormatOnPaste, doc.Project.Language)) return; diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Navigation/FindImplementingMembersHandler.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Navigation/FindImplementingMembersHandler.cs index 3a547a2d07..1e65e6345b 100644 --- a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Navigation/FindImplementingMembersHandler.cs +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Navigation/FindImplementingMembersHandler.cs @@ -39,10 +39,10 @@ namespace MonoDevelop.CSharp.Navigation { class FindImplementingMembersHandler : CommandHandler { - protected override async void Update (CommandInfo info) + protected async override Task UpdateAsync (CommandInfo info, CancellationToken cancelToken) { var sym = await GetNamedTypeAtCaret (IdeApp.Workbench.ActiveDocument); - info.Enabled = sym != null; + info.Enabled = TryGetInterfaceType (sym, out var interfaceType, out var implementingType); info.Bypass = !info.Enabled; } @@ -76,25 +76,18 @@ namespace MonoDevelop.CSharp.Navigation if (info.Node?.Parent.IsKind (SyntaxKind.SimpleBaseType) != true) return null; - + return info; } Task FindImplementingSymbols (Compilation compilation, RefactoringSymbolInfo info, CancellationTokenSource cancellationTokenSource) { - var interfaceType = info.Symbol as ITypeSymbol; - if (interfaceType == null) + if (!TryGetInterfaceType (info, out var interfaceType, out var implementingType)) return Task.FromResult (0); return Task.Run (delegate { var searchMonitor = IdeApp.Workbench.ProgressMonitors.GetSearchProgressMonitor (true, true); using (var monitor = searchMonitor.WithCancellationSource (cancellationTokenSource)) { - var parentTypeNode = info.Node?.Parent?.Parent?.Parent; - if (parentTypeNode == null) - return; - var implementingType = info.Model.GetDeclaredSymbol (parentTypeNode) as INamedTypeSymbol; - if (implementingType == null) - return; foreach (var interfaceMember in interfaceType.GetMembers ()) { if (monitor.CancellationToken.IsCancellationRequested) return; @@ -105,7 +98,7 @@ namespace MonoDevelop.CSharp.Navigation searchMonitor.ReportResult (new MemberReference (impl, loc.SourceTree.FilePath, loc.SourceSpan.Start, loc.SourceSpan.Length)); } foreach (var iFace in interfaceType.AllInterfaces) { - + foreach (var interfaceMember in iFace.GetMembers ()) { if (monitor.CancellationToken.IsCancellationRequested) return; @@ -117,9 +110,23 @@ namespace MonoDevelop.CSharp.Navigation searchMonitor.ReportResult (new MemberReference (impl, loc.SourceTree.FilePath, loc.SourceSpan.Start, loc.SourceSpan.Length)); } } - + } }); } + + static bool TryGetInterfaceType (RefactoringSymbolInfo sym, out ITypeSymbol interfaceType, out INamedTypeSymbol implementingType) + { + interfaceType = null; + implementingType = null; + if (sym == null) + return false; + interfaceType = sym.Symbol as ITypeSymbol; + var parentTypeNode = sym.Node?.Parent?.Parent?.Parent; + if (parentTypeNode == null || interfaceType.TypeKind != TypeKind.Interface) + return false; + implementingType = sym.Model.GetDeclaredSymbol (parentTypeNode) as INamedTypeSymbol; + return implementingType != null; + } } }
\ No newline at end of file diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Parser/CSharpFoldingParser.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Parser/CSharpFoldingParser.cs new file mode 100644 index 0000000000..cf6ba5fb22 --- /dev/null +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Parser/CSharpFoldingParser.cs @@ -0,0 +1,393 @@ +// +// CSharpFoldingParser.cs +// +// Author: +// Mike Krüger <mkrueger@xamarin.com> +// +// Copyright (c) 2012 Xamarin Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +using System; +using System.Collections.Generic; +using MonoDevelop.Ide.TypeSystem; +using MonoDevelop.Ide.Editor; +using MonoDevelop.Core; + +namespace MonoDevelop.CSharp.Parser +{ + unsafe class CSharpFoldingParser : IFoldingParser + { + #region IFoldingParser implementation + + static unsafe bool StartsIdentifier (char* ptr, char* endPtr, string identifier) + { + fixed (char* startId = identifier) { + char* idPtr = startId; + char* endId = startId + identifier.Length; + while (idPtr < endId) { + if (ptr >= endPtr) + return false; + if (*idPtr != *ptr) + return false; + idPtr++; + ptr++; + } + return true; + } + } + + static unsafe void SkipWhitespaces (ref char* ptr, char* endPtr, ref int column) + { + while (ptr < endPtr) { + char ch = *ptr; + if (ch != ' ' && ch != '\t') + return; + column++; + ptr++; + } + } + + static unsafe string ReadToEol (string content, ref char* ptr, char* endPtr, ref int line, ref int column) + { + char* lineBeginPtr = ptr; + char* lineEndPtr = lineBeginPtr; + + while (ptr < endPtr) { + switch (*ptr) { + case '\n': + if (lineEndPtr == lineBeginPtr) + lineEndPtr = ptr; + line++; + column = 1; + ptr++; + fixed (char* startPtr = content) { + return content.Substring ((int)(lineBeginPtr - startPtr), (int)(lineEndPtr - lineBeginPtr)); + } + case '\r': + lineEndPtr = ptr; + if (ptr + 1 < endPtr && *(ptr + 1) == '\n') + ptr++; + goto case '\n'; + } + column++; + ptr++; + } + return ""; + } + + public unsafe ParsedDocument Parse (string fileName, string content) + { + var regionStack = new Stack<Tuple<string, DocumentLocation>> (); + var result = new DefaultParsedDocument (fileName); + bool inSingleComment = false, inMultiLineComment = false; + bool inString = false, inVerbatimString = false; + bool inChar = false; + bool inLineStart = true, hasStartedAtLine = false; + int line = 1, column = 1; + int bracketDepth = 0; + var startLoc = DocumentLocation.Empty; + + fixed (char* startPtr = content) { + char* endPtr = startPtr + content.Length; + char* ptr = startPtr; + char* beginPtr = ptr; + while (ptr < endPtr) { + switch (*ptr) { + case '{': + if (inString || inChar || inVerbatimString || inMultiLineComment || inSingleComment) + break; + bracketDepth++; + break; + case '}': + if (inString || inChar || inVerbatimString || inMultiLineComment || inSingleComment) + break; + bracketDepth--; + break; + case '#': + if (!inLineStart) + break; + inLineStart = false; + ptr++; + + if (StartsIdentifier (ptr, endPtr, "region")) { + var regionLocation = new DocumentLocation (line, column); + column++; + ptr += "region".Length; + column += "region".Length; + SkipWhitespaces (ref ptr, endPtr, ref column); + regionStack.Push (Tuple.Create (ReadToEol (content, ref ptr, endPtr, ref line, ref column), regionLocation)); + continue; + } else if (StartsIdentifier (ptr, endPtr, "endregion")) { + column++; + ptr += "endregion".Length; + column += "endregion".Length; + if (regionStack.Count > 0) { + var beginRegion = regionStack.Pop (); + result.Add (new FoldingRegion ( + beginRegion.Item1, + new DocumentRegion (beginRegion.Item2.Line, beginRegion.Item2.Column, line, column), + FoldType.UserRegion, + true)); + } + continue; + } else { + column++; + } + break; + case '/': + if (inString || inChar || inVerbatimString || inMultiLineComment || inSingleComment) { + inLineStart = false; + break; + } + if (ptr + 1 < endPtr) { + char nextCh = *(ptr + 1); + if (nextCh == '/') { + hasStartedAtLine = inLineStart; + beginPtr = ptr + 2; + startLoc = new DocumentLocation (line, column); + ptr++; + column++; + inSingleComment = true; + } else if (nextCh == '*') { + hasStartedAtLine = inLineStart; + beginPtr = ptr + 2; + startLoc = new DocumentLocation (line, column); + ptr++; + column++; + inMultiLineComment = true; + } + } + inLineStart = false; + break; + case '*': + inLineStart = false; + if (inString || inChar || inVerbatimString || inSingleComment) + break; + if (inMultiLineComment && ptr + 1 < endPtr) { + if (ptr + 1 < endPtr && *(ptr + 1) == '/') { + ptr += 2; + column += 2; + inMultiLineComment = false; + if (bracketDepth <= 1) { + result.Add (new MonoDevelop.Ide.TypeSystem.Comment () { + Region = new DocumentRegion (startLoc, new DocumentLocation (line, column)), + OpenTag = "/*", + CommentType = MonoDevelop.Ide.TypeSystem.CommentType.Block, + Text = content.Substring ((int)(beginPtr - startPtr), (int)(ptr - beginPtr)), + CommentStartsLine = hasStartedAtLine + }); + } + continue; + } + } + break; + case '@': + inLineStart = false; + if (inString || inChar || inVerbatimString || inSingleComment || inMultiLineComment) + break; + if (ptr + 1 < endPtr && *(ptr + 1) == '"') { + ptr++; + column++; + inVerbatimString = true; + } + break; + case '\n': + if (inSingleComment && hasStartedAtLine) { + bool isDocumentation = *beginPtr == '/'; + if (isDocumentation) + beginPtr++; + if (isDocumentation || bracketDepth <= 1) { + // Doesn't matter much that some comments are not correctly recognized - they'll get added later + // It's important that header comments are in. + result.Add (new MonoDevelop.Ide.TypeSystem.Comment () { + Region = new DocumentRegion (startLoc, new DocumentLocation (line, column)), + CommentType = MonoDevelop.Ide.TypeSystem.CommentType.SingleLine, + OpenTag = "//", + Text = content.Substring ((int)(beginPtr - startPtr), (int)(ptr - beginPtr)), + CommentStartsLine = hasStartedAtLine, + IsDocumentation = isDocumentation + }); + } + inSingleComment = false; + } + inString = false; + inChar = false; + inLineStart = true; + line++; + column = 1; + ptr++; + continue; + case '\r': + if (ptr + 1 < endPtr && *(ptr + 1) == '\n') + ptr++; + goto case '\n'; + case '\\': + if (inString || inChar) + ptr++; + break; + case '"': + if (inSingleComment || inMultiLineComment || inChar) + break; + if (inVerbatimString) { + if (ptr + 1 < endPtr && *(ptr + 1) == '"') { + ptr++; + column++; + break; + } + inVerbatimString = false; + break; + } + inString = !inString; + break; + case '\'': + if (inSingleComment || inMultiLineComment || inString || inVerbatimString) + break; + inChar = !inChar; + break; + default: + inLineStart &= *ptr == ' ' || *ptr == '\t'; + break; + } + + column++; + ptr++; + } + } + foreach (var fold in ToFolds (result.GetCommentsAsync().Result)) { + result.Add (fold); + } + return result; + } + #endregion + + static IEnumerable<FoldingRegion> ToFolds (IReadOnlyList<Comment> comments) + { + for (int i = 0; i < comments.Count; i++) { + Comment comment = comments [i]; + + if (comment.CommentType == CommentType.Block) { + int startOffset = 0; + if (comment.Region.BeginLine == comment.Region.EndLine) + continue; + while (startOffset < comment.Text.Length) { + char ch = comment.Text [startOffset]; + if (!char.IsWhiteSpace (ch) && ch != '*') + break; + startOffset++; + } + int endOffset = startOffset; + while (endOffset < comment.Text.Length) { + char ch = comment.Text [endOffset]; + if (ch == '\r' || ch == '\n' || ch == '*') + break; + endOffset++; + } + + string txt; + if (endOffset > startOffset) { + txt = "/* " + GetFirstLine (comment.Text) + " ..."; + } else { + txt = "/* */"; + } + yield return new FoldingRegion (txt, comment.Region, FoldType.Comment); + continue; + } + + if (!comment.CommentStartsLine) + continue; + int j = i; + int curLine = comment.Region.BeginLine - 1; + var end = comment.Region.End; + var commentText = StringBuilderCache.Allocate (); + for (; j < comments.Count; j++) { + Comment curComment = comments [j]; + if (curComment == null || !curComment.CommentStartsLine + || curComment.CommentType != comment.CommentType + || curLine + 1 != curComment.Region.BeginLine) + break; + commentText.Append (curComment.Text); + end = curComment.Region.End; + curLine = curComment.Region.BeginLine; + } + + if (j - i > 1 || (comment.IsDocumentation && comment.Region.BeginLine < comment.Region.EndLine)) { + string txt = null; + if (comment.IsDocumentation) { + string cmtText = commentText.ToString (); + int idx = cmtText.IndexOf ("<summary>", StringComparison.Ordinal); + if (idx >= 0) { + int maxOffset = cmtText.IndexOf ("</summary>", StringComparison.Ordinal); + while (maxOffset > 0 && cmtText [maxOffset - 1] == ' ') + maxOffset--; + if (maxOffset < 0) + maxOffset = cmtText.Length; + int startOffset = idx + "<summary>".Length; + while (startOffset < maxOffset) { + char ch = cmtText [startOffset]; + if (!char.IsWhiteSpace (ch) && ch != '/') + break; + startOffset++; + } + int endOffset = startOffset; + while (endOffset < maxOffset) { + char ch = cmtText [endOffset]; + if (ch == '\r' || ch == '\n') + break; + endOffset++; + } + if (endOffset > startOffset) + txt = "/// <summary> " + cmtText.Substring (startOffset, endOffset - startOffset).Trim () + " ..."; + } + if (txt == null) + txt = "/// " + comment.Text.Trim () + " ..."; + } else { + txt = "// " + comment.Text.Trim () + " ..."; + } + StringBuilderCache.Free (commentText); + yield return new FoldingRegion (txt, + new DocumentRegion (comment.Region.Begin, end), + FoldType.Comment); + i = j - 1; + } + } + } + + static string GetFirstLine (string text) + { + int start = 0; + while (start < text.Length) { + char ch = text [start]; + if (ch != ' ' && ch != '\t') + break; + start++; + } + int end = start; + + while (end < text.Length) { + char ch = text [end]; + if (MonoDevelop.Core.Text.NewLine.IsNewLine (ch)) + break; + end++; + } + if (end <= start) + return ""; + return text.Substring (start, end - start); + } + } +}
\ No newline at end of file diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Project/CSharpCompilerParameters.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Project/CSharpCompilerParameters.cs index 4b12ce6b09..3ae26b0d8c 100644 --- a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Project/CSharpCompilerParameters.cs +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Project/CSharpCompilerParameters.cs @@ -187,11 +187,10 @@ namespace MonoDevelop.CSharp.Project var items = warnings.Split (new [] { ';', ',' }, StringSplitOptions.RemoveEmptyEntries).Distinct (); foreach (string warning in items) { - if (warning.StartsWith ("CS", StringComparison.OrdinalIgnoreCase)) { - yield return warning; - } else { + if (int.TryParse (warning, out _)) yield return "CS" + warning; - } + else + yield return warning; } } diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Refactoring/RefactoryCommands.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Refactoring/RefactoryCommands.cs index bf25ab7901..91301f0eb2 100644 --- a/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Refactoring/RefactoryCommands.cs +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp.Refactoring/RefactoryCommands.cs @@ -58,7 +58,70 @@ namespace MonoDevelop.CSharp.Refactoring SortAndRemoveImports, } - sealed class CurrentRefactoryOperationsHandler : CommandHandler + abstract class RefactoringHandler : CommandHandler + { + protected bool TryGetDocument (out Document analysisDocument, out Ide.Gui.Document doc) + { + doc = IdeApp.Workbench.ActiveDocument; + if (doc == null || doc.FileName == null) { + analysisDocument = null; + return false; + } + + analysisDocument = doc.DocumentContext?.AnalysisDocument; + return doc != null; + } + } + + sealed class RemoveAndSortUsingsHandler : RefactoringHandler + { + protected override void Update (CommandInfo info) + { + info.Enabled = TryGetDocument (out var doc, out var _) && IsSortAndRemoveImportsSupported (doc); + } + + protected override void Run () + { + if (TryGetDocument (out var doc, out var _)) + SortAndRemoveUnusedImports (doc, CancellationToken.None).Ignore (); + } + + internal static bool IsSortAndRemoveImportsSupported (Document document) + { + var workspace = document.Project.Solution.Workspace; + + if (!workspace.CanApplyChange (ApplyChangesKind.ChangeDocument)) { + return false; + } + + if (workspace.Kind == WorkspaceKind.MiscellaneousFiles) { + return false; + } + + return workspace.Services.GetService<IDocumentSupportsFeatureService> ().SupportsRefactorings (document); + } + + internal static async Task SortAndRemoveUnusedImports (Document originalDocument, CancellationToken cancellationToken) + { + if (originalDocument == null) + return; + + var workspace = originalDocument.Project.Solution.Workspace; + + var unnecessaryImportsService = originalDocument.GetLanguageService<IRemoveUnnecessaryImportsService> (); + + // Remove unnecessary imports and sort them + var removedImportsDocument = await unnecessaryImportsService.RemoveUnnecessaryImportsAsync (originalDocument, cancellationToken); + var resultDocument = await OrganizeImportsService.OrganizeImportsAsync (removedImportsDocument, cancellationToken); + + // Apply the document change if needed + if (resultDocument != originalDocument) { + workspace.ApplyDocumentChanges (resultDocument, cancellationToken); + } + } + } + + sealed class CurrentRefactoryOperationsHandler : RefactoringHandler { protected override void Run (object dataItem) { @@ -69,9 +132,7 @@ namespace MonoDevelop.CSharp.Refactoring protected override async Task UpdateAsync (CommandArrayInfo ainfo, CancellationToken cancelToken) { - var doc = IdeApp.Workbench.ActiveDocument; - var analysisDocument = doc.DocumentContext.AnalysisDocument; - if (doc == null || doc.FileName == FilePath.Null || analysisDocument == null) + if (!TryGetDocument (out var analysisDocument, out var doc)) return; var semanticModel = await analysisDocument.GetSemanticModelAsync (cancelToken); if (semanticModel == null) @@ -87,12 +148,12 @@ namespace MonoDevelop.CSharp.Refactoring })); } - bool isSortAndRemoveUsingsSupported = IsSortAndRemoveImportsSupported (analysisDocument); + bool isSortAndRemoveUsingsSupported = RemoveAndSortUsingsHandler.IsSortAndRemoveImportsSupported (analysisDocument); if (isSortAndRemoveUsingsSupported) { var sortAndRemoveImportsInfo = IdeApp.CommandService.GetCommandInfo (Commands.SortAndRemoveImports); sortAndRemoveImportsInfo.Enabled = true; ainfo.Add (sortAndRemoveImportsInfo, new Action (async delegate { - await SortAndRemoveUnusedImports (analysisDocument, cancelToken); + await RemoveAndSortUsingsHandler.SortAndRemoveUnusedImports (analysisDocument, cancelToken); })); } @@ -140,40 +201,6 @@ namespace MonoDevelop.CSharp.Refactoring } } - static bool IsSortAndRemoveImportsSupported (Document document) - { - var workspace = document.Project.Solution.Workspace; - - if (!workspace.CanApplyChange (ApplyChangesKind.ChangeDocument)) { - return false; - } - - if (workspace.Kind == WorkspaceKind.MiscellaneousFiles) { - return false; - } - - return workspace.Services.GetService<IDocumentSupportsFeatureService> ().SupportsRefactorings (document); - } - - static async Task SortAndRemoveUnusedImports (Document originalDocument, CancellationToken cancellationToken) - { - if (originalDocument == null) - return; - - var workspace = originalDocument.Project.Solution.Workspace; - - var unnecessaryImportsService = originalDocument.GetLanguageService<IRemoveUnnecessaryImportsService> (); - - // Remove unnecessary imports and sort them - var removedImportsDocument = await unnecessaryImportsService.RemoveUnnecessaryImportsAsync (originalDocument, cancellationToken); - var resultDocument = await OrganizeImportsService.OrganizeImportsAsync (removedImportsDocument, cancellationToken); - - // Apply the document change if needed - if (resultDocument != originalDocument) { - workspace.ApplyDocumentChanges (resultDocument, cancellationToken); - } - } - static string FormatFileName (string fileName) { if (fileName == null) diff --git a/main/src/addins/CSharpBinding/MonoDevelop.CSharp/CSharpBindingCompilerManager.cs b/main/src/addins/CSharpBinding/MonoDevelop.CSharp/CSharpBindingCompilerManager.cs index 29fa4a5f5d..28f5ec22b8 100644 --- a/main/src/addins/CSharpBinding/MonoDevelop.CSharp/CSharpBindingCompilerManager.cs +++ b/main/src/addins/CSharpBinding/MonoDevelop.CSharp/CSharpBindingCompilerManager.cs @@ -167,7 +167,7 @@ namespace MonoDevelop.CSharp } } - if (alreadyAddedReference.Any (reference => SystemAssemblyService.ContainsReferenceToSystemRuntime (reference))) { + if (alreadyAddedReference.Any (reference => SystemAssemblyService.RequiresFacadeAssembliesAsync (reference).WaitAndGetResult (monitor.CancellationToken))) { LoggingService.LogInfo ("Found PCLv2 assembly."); var facades = runtime.FindFacadeAssembliesForPCL (project.TargetFramework); foreach (var facade in facades) diff --git a/main/src/addins/Deployment/MonoDevelop.Deployment/MonoDevelop.Deployment/DefaultDeployServiceExtension.cs b/main/src/addins/Deployment/MonoDevelop.Deployment/MonoDevelop.Deployment/DefaultDeployServiceExtension.cs index 4cec8f48b9..ad47d39987 100644 --- a/main/src/addins/Deployment/MonoDevelop.Deployment/MonoDevelop.Deployment/DefaultDeployServiceExtension.cs +++ b/main/src/addins/Deployment/MonoDevelop.Deployment/MonoDevelop.Deployment/DefaultDeployServiceExtension.cs @@ -26,7 +26,7 @@ namespace MonoDevelop.Deployment evalCtx.ItemsToEvaluate.Add ("AllPublishItemsFullPathWithTargetPath"); if (project.MSBuildProject.UseMSBuildEngine) { - var result = project.RunTarget (null, "GetCopyToPublishDirectoryItems", configuration, evalCtx).Result; + var result = project.RunTarget (new ProgressMonitor (), "GetCopyToPublishDirectoryItems", configuration, evalCtx).Result; foreach (var item in result.Items) { if (item.Name == "AllPublishItemsFullPathWithTargetPath") { var fromPath = MSBuildProjectService.FromMSBuildPath (project.ItemDirectory, item.Include); diff --git a/main/src/addins/GnomePlatform/GnomePlatform.cs b/main/src/addins/GnomePlatform/GnomePlatform.cs index b6d044a59c..1fea9cd7de 100644 --- a/main/src/addins/GnomePlatform/GnomePlatform.cs +++ b/main/src/addins/GnomePlatform/GnomePlatform.cs @@ -175,8 +175,10 @@ namespace MonoDevelop.Platform CreateNoWindow = true, UseShellExecute = false, }; - foreach (var env in environmentVariables) - psi.EnvironmentVariables [env.Key] = env.Value; + if (environmentVariables != null) { + foreach (var env in environmentVariables) + psi.EnvironmentVariables [env.Key] = env.Value; + } ProcessWrapper proc = new ProcessWrapper (); if (terminal_command.Contains ("gnome-terminal")) { diff --git a/main/src/addins/MacPlatform/BasicAuthenticationHandler.cs b/main/src/addins/MacPlatform/BasicAuthenticationHandler.cs new file mode 100644 index 0000000000..ce01118728 --- /dev/null +++ b/main/src/addins/MacPlatform/BasicAuthenticationHandler.cs @@ -0,0 +1,112 @@ +// +// BasicAuthenticationHandler.cs +// +// Based on based on Mono's mono/mcs/class/System/System.Net/BasicClient.cs +// +// Authors: +// Gonzalo Paniagua Javier (gonzalo@ximian.com) +// Matt Ward <matt.ward@microsoft.com> +// +// (C) 2003 Ximian, Inc (http://www.ximian.com) +// Copyright (c) 2018 Microsoft +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +using System; +using System.Net; +using System.Net.Http; +using System.Net.Http.Headers; + +namespace MacPlatform +{ + /// <summary> + /// NSUrlSessionHandler does not handle all WWW-Authenticate basic auth responses. VSTS NuGet packages sources return + /// WWW-Authenticate: Bearer authorization_uri=https://login.windows.net/, Basic realm="https://pkg.visualstudio.com/", TFS-Federated + /// This basic auth challenge is not delivered to NSUrlSessionHandlerDelegate's DidReceiveChallenge. + /// WWW-Authenticate headers that start with 'Basic' do seem to be passed to DidReceiveChallenge. + /// </summary> + static class BasicAuthenticationHandler + { + internal static bool Authenticate (HttpRequestMessage request, HttpResponseMessage response, ICredentials credentials) + { + if (credentials == null) + return false; + + if (!IsBasicAuthentication (response)) + return false; + + return AddBasicAuthenticationHeader (request, credentials); + } + + static bool IsBasicAuthentication (HttpResponseMessage response) + { + foreach (string authHeader in response.Headers.GetValues ("WWW-Authenticate")) { + if (string.IsNullOrEmpty (authHeader)) + continue; + if (authHeader.IndexOf ("basic", StringComparison.OrdinalIgnoreCase) >= 0) + return true; + } + return false; + } + + static bool AddBasicAuthenticationHeader (HttpRequestMessage request, ICredentials credentials) + { + string authHeader = GetBasicAuthenticationHeader (request.RequestUri, credentials); + if (authHeader == null) + return false; + + request.Headers.Authorization = new AuthenticationHeaderValue ("Basic", authHeader); + + return true; + } + + static string GetBasicAuthenticationHeader (Uri requestUri, ICredentials credentials) + { + var foundCredential = credentials.GetCredential (requestUri, "basic"); + if (foundCredential == null) + return null; + + string userName = foundCredential.UserName; + if (string.IsNullOrEmpty (userName)) + return null; + + string password = foundCredential.Password; + string domain = foundCredential.Domain; + byte [] bytes; + + // If domain is set, MS sends "domain\user:password". + if (string.IsNullOrEmpty (domain) || domain.Trim () == "") + bytes = GetBytes (userName + ":" + password); + else + bytes = GetBytes (domain + "\\" + userName + ":" + password); + + return Convert.ToBase64String (bytes); + } + + static byte [] GetBytes (string str) + { + int i = str.Length; + byte [] result = new byte [i]; + for (--i; i >= 0; i--) + result [i] = (byte)str [i]; + + return result; + } + } +} diff --git a/main/src/core/MonoDevelop.Core/MonoDevelop.FSW/Mono/FileSystemWatcher.cs b/main/src/addins/MacPlatform/Dialogs/HttpClientOptionsPanel.cs index efee8951aa..77a7c3eac8 100644 --- a/main/src/core/MonoDevelop.Core/MonoDevelop.FSW/Mono/FileSystemWatcher.cs +++ b/main/src/addins/MacPlatform/Dialogs/HttpClientOptionsPanel.cs @@ -1,10 +1,10 @@ -// -// FileSystemWatcher.cs +// +// HttpClientOptionsPanel.cs // // Author: -// ludovic <ludovic.henry@xamarin.com> +// Matt Ward <matt.ward@microsoft.com> // -// Copyright (c) 2017 ludovic +// Copyright (c) 2018 Microsoft // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal @@ -24,53 +24,39 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. -namespace MonoDevelop.FSW.Mono +using MonoDevelop.Components;
+using MonoDevelop.Ide.Gui.Dialogs; + +namespace MonoDevelop.MacIntegration { - internal class FileSystemWatcher : System.IO.FileSystemWatcher + class HttpClientOptionsPanel : OptionsPanel { - public FileSystemWatcher () - : base () - { - } - - public FileSystemWatcher (string path) - : base (path) - { - } - - public FileSystemWatcher (string path, string filter) - : base (path, filter) - { - } + Control control; + HttpClientOptionsWidget widget; - protected internal new void OnChanged (System.IO.FileSystemEventArgs e) + public override void ApplyChanges () { - base.OnChanged (e); + widget.ApplyChanges (); } - protected internal new void OnCreated (System.IO.FileSystemEventArgs e) + public override Control CreatePanelWidget () { - base.OnCreated (e); - } - - protected internal new void OnDeleted (System.IO.FileSystemEventArgs e) - { - base.OnDeleted (e); - } + if (control == null) { + widget = new HttpClientOptionsWidget (); + control = new XwtControl (widget); + } - protected internal new void OnError (System.IO.ErrorEventArgs e) - { - base.OnError (e); - } - - protected internal new void OnRenamed (System.IO.RenamedEventArgs e) - { - base.OnRenamed (e); + return control; } - protected internal new void Dispose (bool disposing) + public override void Dispose () { - base.Dispose (disposing); + if (control != null) { + // No need to dispose Control. This is done automatically. + // XwtControl does not dispose its widget though. + widget.Dispose (); + } + base.Dispose (); } } } diff --git a/main/src/addins/MacPlatform/Dialogs/HttpClientOptionsWidget.UI.cs b/main/src/addins/MacPlatform/Dialogs/HttpClientOptionsWidget.UI.cs new file mode 100644 index 0000000000..d100b35f5e --- /dev/null +++ b/main/src/addins/MacPlatform/Dialogs/HttpClientOptionsWidget.UI.cs @@ -0,0 +1,76 @@ +// +// HttpClientOptionsWidget.UI.cs +// +// Author: +// Matt Ward <matt.ward@microsoft.com> +// +// Copyright (c) 2018 Microsoft +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +using MonoDevelop.Components.AtkCocoaHelper; +using MonoDevelop.Core; +using Xwt; + +namespace MonoDevelop.MacIntegration +{ + partial class HttpClientOptionsWidget : Widget + { + ComboBox httpClientHandlerComboBox; + HttpClientImplementation nsUrlSessionHttpClientImplementation = new HttpClientImplementation { + Name = GettextCatalog.GetString ("NSUrlSession (default)") + }; + HttpClientImplementation managedHttpClientImplementation = new HttpClientImplementation { + Name = GettextCatalog.GetString ("Managed") + }; + + void Build () + { + var mainHBox = new HBox (); + + var httpClientHandlerLabel = new Label (); + httpClientHandlerLabel.Name = nameof (httpClientHandlerLabel); + httpClientHandlerLabel.Text = GettextCatalog.GetString ("HttpClient implementation:"); + mainHBox.PackStart (httpClientHandlerLabel); + + httpClientHandlerComboBox = new ComboBox (); + httpClientHandlerComboBox.Name = nameof (httpClientHandlerComboBox); + httpClientHandlerComboBox.SetCommonAccessibilityAttributes ( + nameof (httpClientHandlerComboBox), + httpClientHandlerLabel, + GettextCatalog.GetString ("Select HttpClient implementation")); + mainHBox.PackStart (httpClientHandlerComboBox); + + httpClientHandlerComboBox.Items.Add (nsUrlSessionHttpClientImplementation); + httpClientHandlerComboBox.Items.Add (managedHttpClientImplementation); + + Content = mainHBox; + } + + class HttpClientImplementation + { + public string Name; + + public override string ToString () + { + return Name; + } + } + } +} diff --git a/main/src/addins/MacPlatform/Dialogs/HttpClientOptionsWidget.cs b/main/src/addins/MacPlatform/Dialogs/HttpClientOptionsWidget.cs new file mode 100644 index 0000000000..b33c2a1344 --- /dev/null +++ b/main/src/addins/MacPlatform/Dialogs/HttpClientOptionsWidget.cs @@ -0,0 +1,57 @@ +// +// HttpClientOptionsWidget.cs +// +// Author: +// Matt Ward <matt.ward@microsoft.com> +// +// Copyright (c) 2018 Microsoft +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +using MonoDevelop.Core; + +namespace MonoDevelop.MacIntegration +{ + partial class HttpClientOptionsWidget + { + public HttpClientOptionsWidget () + { + Build (); + SelectHttpClientImplementation (); + } + + /// <summary> + /// NSUrlSession is used by default. + /// </summary> + void SelectHttpClientImplementation () + { + if (MacPlatformSettings.UseNSUrlSessionHandler.Value) { + httpClientHandlerComboBox.SelectedItem = nsUrlSessionHttpClientImplementation; + } else { + httpClientHandlerComboBox.SelectedItem = managedHttpClientImplementation; + } + } + + public void ApplyChanges () + { + bool nsUrlSessionSelected = httpClientHandlerComboBox.SelectedItem == nsUrlSessionHttpClientImplementation; + MacPlatformSettings.UseNSUrlSessionHandler.Value = nsUrlSessionSelected; + } + } +} diff --git a/main/src/addins/MacPlatform/Dialogs/MacCommonFileDialogHandler.cs b/main/src/addins/MacPlatform/Dialogs/MacCommonFileDialogHandler.cs index 132474886f..12c03059c9 100644 --- a/main/src/addins/MacPlatform/Dialogs/MacCommonFileDialogHandler.cs +++ b/main/src/addins/MacPlatform/Dialogs/MacCommonFileDialogHandler.cs @@ -30,6 +30,7 @@ using Foundation; using MonoDevelop.Components; using MonoDevelop.Components.Extensions; using MonoDevelop.Core; +using MonoDevelop.Ide; namespace MonoDevelop.MacIntegration { @@ -91,7 +92,9 @@ namespace MonoDevelop.MacIntegration if (!string.IsNullOrEmpty (data.CurrentFolder)) panel.DirectoryUrl = new NSUrl (data.CurrentFolder, true); - panel.ParentWindow = NSApplication.SharedApplication.KeyWindow ?? NSApplication.SharedApplication.MainWindow; + var parent = IdeServices.DesktopService.GetFocusedTopLevelWindow (); + if (parent != null) + panel.ParentWindow = parent; if (panel is NSOpenPanel openPanel) { openPanel.AllowsMultipleSelection = data.SelectMultiple; diff --git a/main/src/addins/MacPlatform/MacHttpMessageHandlerProvider.cs b/main/src/addins/MacPlatform/MacHttpMessageHandlerProvider.cs new file mode 100644 index 0000000000..6601c8e4ca --- /dev/null +++ b/main/src/addins/MacPlatform/MacHttpMessageHandlerProvider.cs @@ -0,0 +1,66 @@ + +// +// MacHttpMessageHandlerProvider.cs +// +// Author: +// Matt Ward <matt.ward@microsoft.com> +// +// Copyright (c) 2018 Microsoft +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +using System; +using System.Net.Http;
+using MonoDevelop.Core.Web; +using Foundation; + +namespace MonoDevelop.MacIntegration +{ + class MacHttpMessageHandlerProvider : HttpMessageHandlerProvider + { + readonly DefaultHttpMessageHandlerProvider defaultProvider = new DefaultHttpMessageHandlerProvider (); + + public override HttpMessageHandler CreateHttpMessageHandler (Uri uri, HttpClientSettings settings) + { + if (MacPlatformSettings.UseNSUrlSessionHandler) { + return CreateNSUrlSessionHandler (uri, settings); + } + + return defaultProvider.CreateHttpMessageHandler (uri, settings); + } + + HttpMessageHandler CreateNSUrlSessionHandler (Uri uri, HttpClientSettings settings) + { + var config = NSUrlSessionConfiguration.DefaultSessionConfiguration; + config.RequestCachePolicy = NSUrlRequestCachePolicy.ReloadIgnoringLocalCacheData; + config.URLCache = null; + + var sessionHandler = new NSUrlSessionCredentialsHandler (config) { + DisableCaching = true, + AllowAutoRedirect = settings.AllowAutoRedirect, + }; + + if (!settings.SourceAuthenticationRequired) { + return sessionHandler; + } + + return new HttpSourceAuthenticationHandler (uri, sessionHandler, sessionHandler); + } + } +} diff --git a/main/src/addins/MacPlatform/MacInterop/ProcessManager.cs b/main/src/addins/MacPlatform/MacInterop/ProcessManager.cs index 6861c33696..565ed251a1 100644 --- a/main/src/addins/MacPlatform/MacInterop/ProcessManager.cs +++ b/main/src/addins/MacPlatform/MacInterop/ProcessManager.cs @@ -33,28 +33,6 @@ namespace MonoDevelop.MacInterop { public static class ProcessManager { - const string CARBON = "/System/Library/Frameworks/Carbon.framework/Versions/A/Carbon"; - - [DllImport (CARBON)] - static extern OSStatus GetProcessPID (ref ProcessSerialNumber psn, out int pid); - - [DllImport (CARBON)] - static extern OSStatus KillProcess (ref ProcessSerialNumber process); - - public static int GetProcessPid (ProcessSerialNumber psn) - { - int pid; - if (GetProcessPID (ref psn, out pid) == OSStatus.Ok) - return pid; - return -1; - } - - [Obsolete ("Use KillProcess (int pid) instead")] - public static bool KillProcess (ProcessSerialNumber psn) - { - return KillProcess (ref psn) == OSStatus.Ok; - } - public static bool KillProcess (int pid) { NSRunningApplication runningApp = NSRunningApplication.GetRunningApplication (pid); @@ -74,20 +52,5 @@ namespace MonoDevelop.MacInterop Ok = 0 } } - - public struct ProcessSerialNumber - { - uint high; - uint low; - - public ProcessSerialNumber (uint high, uint low) - { - this.high = high; - this.low = low; - } - - public uint High { get { return high; } } - public uint Low { get { return low; } } - } } diff --git a/main/src/addins/MacPlatform/MacPlatform.addin.xml b/main/src/addins/MacPlatform/MacPlatform.addin.xml index 72a1fc3287..96915b12c9 100644 --- a/main/src/addins/MacPlatform/MacPlatform.addin.xml +++ b/main/src/addins/MacPlatform/MacPlatform.addin.xml @@ -89,4 +89,14 @@ <StockIcon stockid = "project" resource = "project.png" size="Menu" /> <StockIcon stockid = "status-stop" resource = "status-stop-16.png" size="Menu" /> </Extension> + + <Extension path = "/MonoDevelop/Ide/GlobalOptionsDialog/Other"> + <Section id = "HttpClientImplementation" + _label = "Network" + class = "MonoDevelop.MacIntegration.HttpClientOptionsPanel" /> + </Extension> + + <Extension path = "/MonoDevelop/Core/HttpMessageHandlerProviders"> + <Class id = "NSUrlSessionHandlerProvider" class = "MonoDevelop.MacIntegration.MacHttpMessageHandlerProvider" /> + </Extension> </ExtensionModel> diff --git a/main/src/addins/MacPlatform/MacPlatform.cs b/main/src/addins/MacPlatform/MacPlatform.cs index 4ffaffa7a7..8cefaa6858 100644 --- a/main/src/addins/MacPlatform/MacPlatform.cs +++ b/main/src/addins/MacPlatform/MacPlatform.cs @@ -266,6 +266,7 @@ namespace MonoDevelop.MacIntegration e.Reply = NSApplicationTerminateReply.Now; } }; + appDelegate.ShowDockMenu += AppDelegate_ShowDockMenu; } // Listen to the AtkCocoa notification for the presence of VoiceOver @@ -298,6 +299,27 @@ namespace MonoDevelop.MacIntegration return loaded; } + void AppDelegate_ShowDockMenu (object sender, ShowDockMenuArgs e) + { + if (((FilePath)NSBundle.MainBundle.BundlePath).Extension != ".app") + return; + var menu = new NSMenu (); + var newInstanceMenuItem = new NSMenuItem (); + newInstanceMenuItem.Title = GettextCatalog.GetString ("New Instance"); + newInstanceMenuItem.Activated += NewInstanceMenuItem_Activated; + menu.AddItem (newInstanceMenuItem); + e.DockMenu = menu; + } + + static void NewInstanceMenuItem_Activated (object sender, EventArgs e) + { + var bundlePath = NSBundle.MainBundle.BundlePath; + NSWorkspace.SharedWorkspace.LaunchApplication (NSUrl.FromFilename (bundlePath), NSWorkspaceLaunchOptions.NewInstance, new NSDictionary (), out NSError error); + if (error != null) + LoggingService.LogError ($"Failed to start new instance: {error.LocalizedDescription}"); + } + + const string EnabledKey = "com.monodevelop.AccessibilityEnabled"; static void ShowVoiceOverNotice () { @@ -934,6 +956,16 @@ namespace MonoDevelop.MacIntegration NSApplication.SharedApplication.ActivateIgnoringOtherApps (true); } + public override Window GetParentForModalWindow () + { + return NSApplication.SharedApplication.KeyWindow ?? NSApplication.SharedApplication.MainWindow; + } + + public override Window GetFocusedTopLevelWindow () + { + return NSApplication.SharedApplication.KeyWindow; + } + public override void FocusWindow (Window window) { try { @@ -1325,6 +1357,14 @@ namespace MonoDevelop.MacIntegration public class ThemedMacDialogBackend : Xwt.Mac.DialogBackend { + public ThemedMacDialogBackend () + { + } + + public ThemedMacDialogBackend (IntPtr ptr) : base (ptr) + { + } + public override void InitializeBackend (object frontend, Xwt.Backends.ApplicationContext context) { base.InitializeBackend (frontend, context); @@ -1334,6 +1374,14 @@ namespace MonoDevelop.MacIntegration public class ThemedMacAlertDialogBackend : Xwt.Mac.AlertDialogBackend { + public ThemedMacAlertDialogBackend () + { + } + + public ThemedMacAlertDialogBackend (IntPtr ptr) : base (ptr) + { + } + public override void Initialize (Xwt.Backends.ApplicationContext actx) { base.Initialize (actx); diff --git a/main/src/addins/MacPlatform/MacPlatform.csproj b/main/src/addins/MacPlatform/MacPlatform.csproj index 15b8e10c59..d1a4fea78e 100644 --- a/main/src/addins/MacPlatform/MacPlatform.csproj +++ b/main/src/addins/MacPlatform/MacPlatform.csproj @@ -26,6 +26,7 @@ <HintPath>..\..\..\packages\System.ValueTuple.4.4.0\lib\net47\System.ValueTuple.dll</HintPath> <Private>False</Private> </Reference> + <Reference Include="System.Net.Http" /> </ItemGroup> <ItemGroup> <ProjectReference Include="..\..\core\MonoDevelop.Core\MonoDevelop.Core.csproj"> @@ -111,6 +112,15 @@ <Compile Include="MacTelemetryDetails.cs" /> <Compile Include="Interop.cs" /> <Compile Include="KernelInterop.cs" /> + <Compile Include="Dialogs\HttpClientOptionsPanel.cs" /> + <Compile Include="Dialogs\HttpClientOptionsWidget.cs" /> + <Compile Include="Dialogs\HttpClientOptionsWidget.UI.cs"> + <DependentUpon>HttpClientOptionsWidget.cs</DependentUpon> + </Compile> + <Compile Include="MacPlatformSettings.cs" /> + <Compile Include="MacHttpMessageHandlerProvider.cs" /> + <Compile Include="NSUrlSessionCredentialsHandler.cs" /> + <Compile Include="BasicAuthenticationHandler.cs" /> </ItemGroup> <ItemGroup> <None Include="packages.config" /> diff --git a/main/src/addins/MacPlatform/MacPlatformSettings.cs b/main/src/addins/MacPlatform/MacPlatformSettings.cs new file mode 100644 index 0000000000..8f465304bd --- /dev/null +++ b/main/src/addins/MacPlatform/MacPlatformSettings.cs @@ -0,0 +1,36 @@ +// +// MacPlatformSettings.cs +// +// Author: +// Matt Ward <matt.ward@microsoft.com> +// +// Copyright (c) 2018 Microsoft +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +using MonoDevelop.Core; + +namespace MonoDevelop.MacIntegration +{ + static class MacPlatformSettings + { + public static readonly ConfigurationProperty<bool> UseNSUrlSessionHandler + = ConfigurationProperty.Create ("MonoDevelop.MacIntegration.UseNSUrlSessionHandler", true); + } +} diff --git a/main/src/addins/MacPlatform/MainToolbar/SelectorView.cs b/main/src/addins/MacPlatform/MainToolbar/SelectorView.cs index ffaace2c12..6eb52e52e5 100644 --- a/main/src/addins/MacPlatform/MainToolbar/SelectorView.cs +++ b/main/src/addins/MacPlatform/MainToolbar/SelectorView.cs @@ -124,22 +124,10 @@ namespace MonoDevelop.MacIntegration.MainToolbar internal void OnSizeChanged () { - if (SizeChanged != null) { - SizeChanged (this, EventArgs.Empty); - } + SizeChanged?.Invoke (this, EventArgs.Empty); } - public override bool BecomeFirstResponder() - { - if (Window.FirstResponder != RealSelectorView) - return Window.MakeFirstResponder(RealSelectorView); - return false; - } - - public override bool AcceptsFirstResponder() - { - return Window.FirstResponder != RealSelectorView; - } + public override bool AcceptsFirstResponder () => false; #region PathSelectorView [Register] @@ -574,6 +562,28 @@ namespace MonoDevelop.MacIntegration.MainToolbar int focusedCellIndex = 0; NSPathComponentCellFocusable focusedItem; + bool UpdatePreviousCellForResponderChain (int fromPosition) + { + for (focusedCellIndex = fromPosition; focusedCellIndex >= 0; focusedCellIndex--) { + var cell = Cells [focusedCellIndex].Cell; + if (PathComponentCells.Contains (cell) && cell.Enabled) { + return true; + } + } + return false; + } + + bool UpdateNextCellForResponderChain (int fromPosition) + { + for (focusedCellIndex = fromPosition; focusedCellIndex < Cells.Length; focusedCellIndex++) { + var cell = Cells [focusedCellIndex].Cell; + if (PathComponentCells.Contains (cell) && cell.Enabled) { + return true; + } + } + return false; + } + public override void KeyDown (NSEvent theEvent) { if (theEvent.KeyCode == (ushort) KeyCodes.Space) { @@ -585,28 +595,30 @@ namespace MonoDevelop.MacIntegration.MainToolbar // 0x30 is Tab if (theEvent.KeyCode == (ushort)KeyCodes.Tab) { if ((theEvent.ModifierFlags & NSEventModifierMask.ShiftKeyMask) == NSEventModifierMask.ShiftKeyMask) { - focusedCellIndex--; - if (focusedCellIndex < 0) { + if (focusedCellIndex <= 0) { if (PreviousKeyView != null) { SetSelection (); focusedCellIndex = 0; focusedItem = null; } } else { - SetSelection (); - return; + if (UpdatePreviousCellForResponderChain (focusedCellIndex - 1)) { + SetSelection (); + return; + } } } else { - focusedCellIndex++; - if (focusedCellIndex >= VisibleCellIds.Length) { + if (focusedCellIndex >= VisibleCellIds.Length - 1) { if (NextKeyView != null) { SetSelection (); focusedCellIndex = 0; focusedItem = null; } } else { - SetSelection (); - return; + if (UpdateNextCellForResponderChain (focusedCellIndex + 1)) { + SetSelection (); + return; + } } } } @@ -616,15 +628,20 @@ namespace MonoDevelop.MacIntegration.MainToolbar void SetSelection () { - if (focusedItem != null) { - focusedItem.HasFocus = false; - } + //ensures our cells are in the correct enabled state if (focusedCellIndex >= 0 && focusedCellIndex < Cells.Length) { - var item = Cells [focusedCellIndex].Cell as NSPathComponentCellFocusable; - focusedItem = item; - if (item != null) - item.HasFocus = true; + focusedItem = Cells [focusedCellIndex].Cell as NSPathComponentCellFocusable; + if (focusedItem != null) + focusedItem.HasFocus = true; + } + + //we want ensure our state is correct in other elements + for (int i = 0; i < Cells.Length; i++) { + if (i != focusedCellIndex && Cells [i].Cell is NSPathComponentCellFocusable focusable) { + focusable.HasFocus = false; + } } + SetNeedsDisplay (); } @@ -639,9 +656,9 @@ namespace MonoDevelop.MacIntegration.MainToolbar if (currentEvent.Type == NSEventType.KeyDown) { if (currentEvent.KeyCode == (ushort) KeyCodes.Tab) { if ((currentEvent.ModifierFlags & NSEventModifierMask.ShiftKeyMask) == NSEventModifierMask.ShiftKeyMask) { - focusedCellIndex = Cells.Length - 1; + UpdatePreviousCellForResponderChain (Cells.Length - 1); } else { - focusedCellIndex = 0; + UpdateNextCellForResponderChain (0); } } } diff --git a/main/src/addins/MacPlatform/NSUrlSessionCredentialsHandler.cs b/main/src/addins/MacPlatform/NSUrlSessionCredentialsHandler.cs new file mode 100644 index 0000000000..5931326060 --- /dev/null +++ b/main/src/addins/MacPlatform/NSUrlSessionCredentialsHandler.cs @@ -0,0 +1,70 @@ +// +// NSUrlSessionCredentialsHandler.cs +// +// Author: +// Matt Ward <matt.ward@microsoft.com> +// +// Copyright (c) 2018 Microsoft +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +using System.Net;
+using System.Net.Http;
+using System.Threading;
+using System.Threading.Tasks;
+using Foundation; +using MacPlatform; +using MonoDevelop.Core.Web; + +namespace MonoDevelop.MacIntegration +{ + class NSUrlSessionCredentialsHandler : NSUrlSessionHandler, IHttpCredentialsHandler + { + public NSUrlSessionCredentialsHandler (NSUrlSessionConfiguration configuration) + : base (configuration) + { + } + + /// <summary> + /// Not supported. + /// </summary> + public bool UseDefaultCredentials { get; set; } + + /// <summary> + /// Not all WWW-Authenticate basic auth responses are handled by the NSUrlSessionHandler, such as those + /// from VSTS NuGet package sources, so an Authorization header is added to the request and re-sent + /// if basic auth credentials can be found. + /// </summary> + protected override async Task<HttpResponseMessage> SendAsync (HttpRequestMessage request, CancellationToken cancellationToken) + { + bool retry = false; + while (true) { + var response = await base.SendAsync (request, cancellationToken).ConfigureAwait (false); + + if (retry || response.StatusCode != HttpStatusCode.Unauthorized) + return response; + + if (!BasicAuthenticationHandler.Authenticate (request, response, Credentials)) + return response; + + retry = true; + } + } + } +} diff --git a/main/src/addins/MonoDevelop.AspNetCore/Properties/MonoDevelop.AspNetCore.addin.xml b/main/src/addins/MonoDevelop.AspNetCore/Properties/MonoDevelop.AspNetCore.addin.xml index 6e0428798e..a91a288055 100644 --- a/main/src/addins/MonoDevelop.AspNetCore/Properties/MonoDevelop.AspNetCore.addin.xml +++ b/main/src/addins/MonoDevelop.AspNetCore/Properties/MonoDevelop.AspNetCore.addin.xml @@ -83,7 +83,7 @@ <Template
id="Microsoft.Web.Empty.CSharp"
templateId="Microsoft.Web.Empty.CSharp.3.0"
- _overrideName="ASP.NET Core Empty"
+ _overrideName="Empty"
_overrideDescription="An empty project template for creating an ASP.NET Core application. This template does not have any content in it."
path="${DotNetCoreSdk.3.0.Templates.Web.ProjectTemplates.nupkg}"
icon="md-netcore-empty-project"
@@ -95,7 +95,7 @@ <Template
id="Microsoft.Web.Empty.FSharp"
templateId="Microsoft.Web.Empty.FSharp.3.0"
- _overrideName="ASP.NET Core Empty"
+ _overrideName="Empty"
_overrideDescription="An empty project template for creating an ASP.NET Core application. This template does not have any content in it."
path="${DotNetCoreSdk.3.0.Templates.Web.ProjectTemplates.nupkg}"
icon="md-netcore-empty-project"
@@ -107,7 +107,7 @@ <Template
id="Microsoft.Web.WebApi.CSharp"
templateId="Microsoft.Web.WebApi.CSharp.3.0"
- _overrideName="ASP.NET Core Web API"
+ _overrideName="API"
_overrideDescription="A project template for creating an ASP.NET Core application with an example Controller for a RESTful HTTP service. This template can also be used for ASP.NET Core MVC Views and Controllers."
path="${DotNetCoreSdk.3.0.Templates.Web.ProjectTemplates.nupkg}"
icon="md-netcore-empty-project"
@@ -119,7 +119,7 @@ <Template
id="Microsoft.Web.WebApi.FSharp"
templateId="Microsoft.Web.WebApi.FSharp.3.0"
- _overrideName="ASP.NET Core Web API"
+ _overrideName="API"
_overrideDescription="A project template for creating an ASP.NET Core application with an example Controller for a RESTful HTTP service. This template can also be used for ASP.NET Core MVC Views and Controllers."
path="${DotNetCoreSdk.3.0.Templates.Web.ProjectTemplates.nupkg}"
icon="md-netcore-empty-project"
@@ -132,7 +132,7 @@ <Template
id="Microsoft.Web.RazorPages.CSharp"
templateId="Microsoft.Web.RazorPages.CSharp.3.0"
- _overrideName="ASP.NET Core Web App"
+ _overrideName="Web Application"
_overrideDescription="A project template for creating an ASP.NET Core application with example ASP.NET Razor Pages content."
path="${DotNetCoreSdk.3.0.Templates.Web.ProjectTemplates.nupkg}"
icon="md-netcore-empty-project"
@@ -146,7 +146,7 @@ <Template
id="Microsoft.Web.Mvc.CSharp"
templateId="Microsoft.Web.Mvc.CSharp.3.0"
- _overrideName="ASP.NET Core Web App (MVC)"
+ _overrideName="Web Application (Model-View-Controller)"
_overrideDescription="A project template for creating an ASP.NET Core application with example ASP.NET Core MVC Views and Controllers. This template can also be used for RESTful HTTP services."
path="${DotNetCoreSdk.3.0.Templates.Web.ProjectTemplates.nupkg}"
icon="md-netcore-empty-project"
@@ -159,7 +159,7 @@ <Template
id="Microsoft.Web.Mvc.FSharp"
templateId="Microsoft.Web.Mvc.FSharp.3.0"
- _overrideName="ASP.NET Core Web App (MVC)"
+ _overrideName="Web Application (Model-View-Controller)"
_overrideDescription="A project template for creating an ASP.NET Core application with example ASP.NET Core MVC Views and Controllers. This template can also be used for RESTful HTTP services."
path="${DotNetCoreSdk.3.0.Templates.Web.ProjectTemplates.nupkg}"
icon="md-netcore-empty-project"
@@ -174,7 +174,7 @@ <Template
id="Microsoft.Web.Empty.CSharp"
templateId="Microsoft.Web.Empty.CSharp.2.2"
- _overrideName="ASP.NET Core Empty"
+ _overrideName="Empty"
_overrideDescription="An empty project template for creating an ASP.NET Core application. This template does not have any content in it."
path="${DotNetCoreSdk.2.2.Templates.Web.ProjectTemplates.nupkg}"
icon="md-netcore-empty-project"
@@ -186,7 +186,7 @@ <Template
id="Microsoft.Web.Empty.FSharp"
templateId="Microsoft.Web.Empty.FSharp.2.2"
- _overrideName="ASP.NET Core Empty"
+ _overrideName="Empty"
_overrideDescription="An empty project template for creating an ASP.NET Core application. This template does not have any content in it."
path="${DotNetCoreSdk.2.2.Templates.Web.ProjectTemplates.nupkg}"
icon="md-netcore-empty-project"
@@ -198,7 +198,7 @@ <Template
id="Microsoft.Web.WebApi.CSharp"
templateId="Microsoft.Web.WebApi.CSharp.2.2"
- _overrideName="ASP.NET Core Web API"
+ _overrideName="API"
_overrideDescription="A project template for creating an ASP.NET Core application with an example Controller for a RESTful HTTP service. This template can also be used for ASP.NET Core MVC Views and Controllers."
path="${DotNetCoreSdk.2.2.Templates.Web.ProjectTemplates.nupkg}"
icon="md-netcore-empty-project"
@@ -210,7 +210,7 @@ <Template
id="Microsoft.Web.WebApi.FSharp"
templateId="Microsoft.Web.WebApi.FSharp.2.2"
- _overrideName="ASP.NET Core Web API"
+ _overrideName="API"
_overrideDescription="A project template for creating an ASP.NET Core application with an example Controller for a RESTful HTTP service. This template can also be used for ASP.NET Core MVC Views and Controllers."
path="${DotNetCoreSdk.2.2.Templates.Web.ProjectTemplates.nupkg}"
icon="md-netcore-empty-project"
@@ -223,7 +223,7 @@ <Template
id="Microsoft.Web.RazorPages.CSharp"
templateId="Microsoft.Web.RazorPages.CSharp.2.2"
- _overrideName="ASP.NET Core Web App"
+ _overrideName="Web Application"
_overrideDescription="A project template for creating an ASP.NET Core application with example ASP.NET Razor Pages content."
path="${DotNetCoreSdk.2.2.Templates.Web.ProjectTemplates.nupkg}"
icon="md-netcore-empty-project"
@@ -237,7 +237,7 @@ <Template
id="Microsoft.Web.Mvc.CSharp"
templateId="Microsoft.Web.Mvc.CSharp.2.2"
- _overrideName="ASP.NET Core Web App (MVC)"
+ _overrideName="Web Application (Model-View-Controller)"
_overrideDescription="A project template for creating an ASP.NET Core application with example ASP.NET Core MVC Views and Controllers. This template can also be used for RESTful HTTP services."
path="${DotNetCoreSdk.2.2.Templates.Web.ProjectTemplates.nupkg}"
icon="md-netcore-empty-project"
@@ -250,7 +250,7 @@ <Template
id="Microsoft.Web.Mvc.FSharp"
templateId="Microsoft.Web.Mvc.FSharp.2.2"
- _overrideName="ASP.NET Core Web App (MVC)"
+ _overrideName="Web Application (Model-View-Controller)"
_overrideDescription="A project template for creating an ASP.NET Core application with example ASP.NET Core MVC Views and Controllers. This template can also be used for RESTful HTTP services."
path="${DotNetCoreSdk.2.2.Templates.Web.ProjectTemplates.nupkg}"
icon="md-netcore-empty-project"
@@ -265,7 +265,7 @@ <Template
id="Microsoft.Web.Empty.CSharp"
templateId="Microsoft.Web.Empty.CSharp.2.1"
- _overrideName="ASP.NET Core Empty"
+ _overrideName="Empty"
_overrideDescription="An empty project template for creating an ASP.NET Core application. This template does not have any content in it."
path="${DotNetCoreSdk.2.1.Templates.Web.ProjectTemplates.nupkg}"
icon="md-netcore-empty-project"
@@ -277,7 +277,7 @@ <Template
id="Microsoft.Web.Empty.FSharp"
templateId="Microsoft.Web.Empty.FSharp.2.1"
- _overrideName="ASP.NET Core Empty"
+ _overrideName="Empty"
_overrideDescription="An empty project template for creating an ASP.NET Core application. This template does not have any content in it."
path="${DotNetCoreSdk.2.1.Templates.Web.ProjectTemplates.nupkg}"
icon="md-netcore-empty-project"
@@ -289,7 +289,7 @@ <Template
id="Microsoft.Web.WebApi.CSharp"
templateId="Microsoft.Web.WebApi.CSharp.2.1"
- _overrideName="ASP.NET Core Web API"
+ _overrideName="API"
_overrideDescription="A project template for creating an ASP.NET Core application with an example Controller for a RESTful HTTP service. This template can also be used for ASP.NET Core MVC Views and Controllers."
path="${DotNetCoreSdk.2.1.Templates.Web.ProjectTemplates.nupkg}"
icon="md-netcore-empty-project"
@@ -301,7 +301,7 @@ <Template
id="Microsoft.Web.WebApi.FSharp"
templateId="Microsoft.Web.WebApi.FSharp.2.1"
- _overrideName="ASP.NET Core Web API"
+ _overrideName="API"
_overrideDescription="A project template for creating an ASP.NET Core application with an example Controller for a RESTful HTTP service. This template can also be used for ASP.NET Core MVC Views and Controllers."
path="${DotNetCoreSdk.2.1.Templates.Web.ProjectTemplates.nupkg}"
icon="md-netcore-empty-project"
@@ -314,7 +314,7 @@ <Template
id="Microsoft.Web.RazorPages.CSharp"
templateId="Microsoft.Web.RazorPages.CSharp.2.1"
- _overrideName="ASP.NET Core Web App"
+ _overrideName="Web Application"
_overrideDescription="A project template for creating an ASP.NET Core application with example ASP.NET Razor Pages content."
path="${DotNetCoreSdk.2.1.Templates.Web.ProjectTemplates.nupkg}"
icon="md-netcore-empty-project"
@@ -328,7 +328,7 @@ <Template
id="Microsoft.Web.Mvc.CSharp"
templateId="Microsoft.Web.Mvc.CSharp.2.1"
- _overrideName="ASP.NET Core Web App (MVC)"
+ _overrideName="Web Application (Model-View-Controller)"
_overrideDescription="A project template for creating an ASP.NET Core application with example ASP.NET Core MVC Views and Controllers. This template can also be used for RESTful HTTP services."
path="${DotNetCoreSdk.2.1.Templates.Web.ProjectTemplates.nupkg}"
icon="md-netcore-empty-project"
@@ -341,7 +341,7 @@ <Template
id="Microsoft.Web.Mvc.FSharp"
templateId="Microsoft.Web.Mvc.FSharp.2.1"
- _overrideName="ASP.NET Core Web App (MVC)"
+ _overrideName="Web Application (Model-View-Controller)"
_overrideDescription="A project template for creating an ASP.NET Core application with example ASP.NET Core MVC Views and Controllers. This template can also be used for RESTful HTTP services."
path="${DotNetCoreSdk.2.1.Templates.Web.ProjectTemplates.nupkg}"
icon="md-netcore-empty-project"
@@ -356,7 +356,7 @@ <Template
id="Microsoft.Web.Empty.CSharp"
templateId="Microsoft.Web.Empty.CSharp.2.0"
- _overrideName="ASP.NET Core Empty"
+ _overrideName="Empty"
_overrideDescription="An empty project template for creating an ASP.NET Core application. This template does not have any content in it."
path="Templates/Microsoft.DotNet.Web.ProjectTemplates.2.0.1.0.0-beta2-20170727-301.nupkg"
icon="md-netcore-empty-project"
@@ -368,7 +368,7 @@ <Template
id="Microsoft.Web.Empty.FSharp"
templateId="Microsoft.Web.Empty.FSharp.2.0"
- _overrideName="ASP.NET Core Empty"
+ _overrideName="Empty"
_overrideDescription="An empty project template for creating an ASP.NET Core application. This template does not have any content in it."
path="Templates/Microsoft.DotNet.Web.ProjectTemplates.2.0.1.0.0-beta2-20170727-301.nupkg"
icon="md-netcore-empty-project"
@@ -380,7 +380,7 @@ <Template
id="Microsoft.Web.WebApi.CSharp"
templateId="Microsoft.Web.WebApi.CSharp.2.0"
- _overrideName="ASP.NET Core Web API"
+ _overrideName="API"
_overrideDescription="A project template for creating an ASP.NET Core application with an example Controller for a RESTful HTTP service. This template can also be used for ASP.NET Core MVC Views and Controllers."
path="Templates/Microsoft.DotNet.Web.ProjectTemplates.2.0.1.0.0-beta2-20170727-301.nupkg"
icon="md-netcore-empty-project"
@@ -392,7 +392,7 @@ <Template
id="Microsoft.Web.WebApi.FSharp"
templateId="Microsoft.Web.WebApi.FSharp.2.0"
- _overrideName="ASP.NET Core Web API"
+ _overrideName="API"
_overrideDescription="A project template for creating an ASP.NET Core application with an example Controller for a RESTful HTTP service. This template can also be used for ASP.NET Core MVC Views and Controllers."
path="Templates/Microsoft.DotNet.Web.ProjectTemplates.2.0.1.0.0-beta2-20170727-301.nupkg"
icon="md-netcore-empty-project"
@@ -405,7 +405,7 @@ <Template
id="Microsoft.Web.RazorPages.CSharp"
templateId="Microsoft.Web.RazorPages.CSharp.2.0"
- _overrideName="ASP.NET Core Web App"
+ _overrideName="Web Application"
_overrideDescription="A project template for creating an ASP.NET Core application with example ASP.NET Razor Pages content."
path="Templates/Microsoft.DotNet.Web.ProjectTemplates.2.0.1.0.0-beta2-20170727-301.nupkg"
icon="md-netcore-empty-project"
@@ -419,7 +419,7 @@ <Template
id="Microsoft.Web.Mvc.CSharp"
templateId="Microsoft.Web.Mvc.CSharp.2.0"
- _overrideName="ASP.NET Core Web App (MVC)"
+ _overrideName="Web Application (Model-View-Controller)"
_overrideDescription="A project template for creating an ASP.NET Core application with example ASP.NET Core MVC Views and Controllers. This template can also be used for RESTful HTTP services."
path="Templates/Microsoft.DotNet.Web.ProjectTemplates.2.0.1.0.0-beta2-20170727-301.nupkg"
icon="md-netcore-empty-project"
@@ -432,7 +432,7 @@ <Template
id="Microsoft.Web.Mvc.FSharp"
templateId="Microsoft.Web.Mvc.FSharp.2.0"
- _overrideName="ASP.NET Core Web App (MVC)"
+ _overrideName="Web Application (Model-View-Controller)"
_overrideDescription="A project template for creating an ASP.NET Core application with example ASP.NET Core MVC Views and Controllers. This template can also be used for RESTful HTTP services."
path="Templates/Microsoft.DotNet.Web.ProjectTemplates.2.0.1.0.0-beta2-20170727-301.nupkg"
icon="md-netcore-empty-project"
@@ -446,7 +446,7 @@ <Condition id="AspNetCoreSdkInstalled" sdkVersion="1.*">
<Template
id="Microsoft.Web.Empty.CSharp"
- _overrideName="ASP.NET Core Empty"
+ _overrideName="Empty"
_overrideDescription="An empty project template for creating an ASP.NET Core application. This template does not have any content in it."
path="Templates/Microsoft.DotNet.Web.ProjectTemplates.1.x.1.0.0-beta2-20170430-208.nupkg"
icon="md-netcore-empty-project"
@@ -457,7 +457,7 @@ defaultParameters="IncludeLaunchSettings=true" />
<Template
id="Microsoft.Web.Empty.FSharp"
- _overrideName="ASP.NET Core Empty"
+ _overrideName="Empty"
_overrideDescription="An empty project template for creating an ASP.NET Core application. This template does not have any content in it."
path="Templates/Microsoft.DotNet.Web.ProjectTemplates.1.x.1.0.0-beta2-20170430-208.nupkg"
icon="md-netcore-empty-project"
@@ -468,7 +468,7 @@ defaultParameters="IncludeLaunchSettings=true" />
<Template
id="Microsoft.Web.WebApi.CSharp"
- _overrideName="ASP.NET Core Web API"
+ _overrideName="API"
_overrideDescription="A project template for creating an ASP.NET Core application with an example Controller for a RESTful HTTP service. This template can also be used for ASP.NET Core MVC Views and Controllers."
path="Templates/Microsoft.DotNet.Web.ProjectTemplates.1.x.1.0.0-beta2-20170430-208.nupkg"
icon="md-netcore-empty-project"
@@ -479,7 +479,7 @@ defaultParameters="IncludeLaunchSettings=true" />
<Template
id="Microsoft.Web.Mvc.CSharp"
- _overrideName="ASP.NET Core Web App (MVC)"
+ _overrideName="Web Application (Model-View-Controller)"
_overrideDescription="A project template for creating an ASP.NET Core application with example ASP.NET Core MVC Views and Controllers. This template can also be used for RESTful HTTP services."
path="Templates/Microsoft.DotNet.Web.ProjectTemplates.1.x.1.0.0-beta2-20170430-208.nupkg"
icon="md-netcore-empty-project"
@@ -491,7 +491,7 @@ defaultParameters="IncludeLaunchSettings=true" />
<Template
id="Microsoft.Web.Mvc.FSharp"
- _overrideName="ASP.NET Core Web App (MVC)"
+ _overrideName="Web Application (Model-View-Controller)"
_overrideDescription="A project template for creating an ASP.NET Core application with example ASP.NET Core MVC Views and Controllers. This template can also be used for RESTful HTTP services."
path="Templates/Microsoft.DotNet.Web.ProjectTemplates.1.x.1.0.0-beta2-20170430-208.nupkg"
icon="md-netcore-empty-project"
diff --git a/main/src/addins/MonoDevelop.Debugger.VSCodeDebugProtocol/MonoDevelop.Debugger.VsCodeDebugProtocol/MonoDevelop.Debugger.VsCodeDebugProtocol.csproj b/main/src/addins/MonoDevelop.Debugger.VSCodeDebugProtocol/MonoDevelop.Debugger.VsCodeDebugProtocol/MonoDevelop.Debugger.VsCodeDebugProtocol.csproj index 25833d91f9..cced3d764b 100644 --- a/main/src/addins/MonoDevelop.Debugger.VSCodeDebugProtocol/MonoDevelop.Debugger.VsCodeDebugProtocol/MonoDevelop.Debugger.VsCodeDebugProtocol.csproj +++ b/main/src/addins/MonoDevelop.Debugger.VSCodeDebugProtocol/MonoDevelop.Debugger.VsCodeDebugProtocol/MonoDevelop.Debugger.VsCodeDebugProtocol.csproj @@ -16,7 +16,7 @@ <Private>False</Private> </Reference> <Reference Include="Microsoft.VisualStudio.Shared.VSCodeDebugProtocol"> - <HintPath>..\..\..\..\packages\Microsoft.VisualStudio.Shared.VsCodeDebugProtocol.15.0.10815.1\lib\net45\Microsoft.VisualStudio.Shared.VSCodeDebugProtocol.dll</HintPath> + <HintPath>..\..\..\..\packages\Microsoft.VisualStudio.Shared.VsCodeDebugProtocol.15.8.20719.1\lib\net45\Microsoft.VisualStudio.Shared.VSCodeDebugProtocol.dll</HintPath> </Reference> </ItemGroup> <ItemGroup> diff --git a/main/src/addins/MonoDevelop.Debugger.VSCodeDebugProtocol/MonoDevelop.Debugger.VsCodeDebugProtocol/packages.config b/main/src/addins/MonoDevelop.Debugger.VSCodeDebugProtocol/MonoDevelop.Debugger.VsCodeDebugProtocol/packages.config index 89dde6f38d..9a5ab42712 100644 --- a/main/src/addins/MonoDevelop.Debugger.VSCodeDebugProtocol/MonoDevelop.Debugger.VsCodeDebugProtocol/packages.config +++ b/main/src/addins/MonoDevelop.Debugger.VSCodeDebugProtocol/MonoDevelop.Debugger.VsCodeDebugProtocol/packages.config @@ -1,5 +1,5 @@ <?xml version="1.0" encoding="utf-8"?> <packages> - <package id="Microsoft.VisualStudio.Shared.VsCodeDebugProtocol" version="15.0.10815.1" targetFramework="net461" /> + <package id="Microsoft.VisualStudio.Shared.VsCodeDebugProtocol" version="15.8.20719.1" targetFramework="net471" /> <package id="Newtonsoft.Json" version="10.0.3" targetFramework="net45" /> </packages>
\ No newline at end of file diff --git a/main/src/addins/MonoDevelop.Debugger/MonoDevelop.Debugger.Converters/DebugValueConverter.cs b/main/src/addins/MonoDevelop.Debugger/MonoDevelop.Debugger.Converters/DebugValueConverter.cs index d66bfa7f65..f93edacefe 100644 --- a/main/src/addins/MonoDevelop.Debugger/MonoDevelop.Debugger.Converters/DebugValueConverter.cs +++ b/main/src/addins/MonoDevelop.Debugger/MonoDevelop.Debugger.Converters/DebugValueConverter.cs @@ -24,6 +24,8 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. using System; +using System.Threading; +using System.Threading.Tasks; using Mono.Debugging.Client; namespace MonoDevelop.Debugger @@ -34,6 +36,11 @@ namespace MonoDevelop.Debugger public abstract T GetValue (ObjectValue val); + public virtual Task<T> GetValueAsync (ObjectValue val, CancellationToken token = default (CancellationToken)) + { + return Task.FromResult (GetValue (val)); + } + public virtual bool CanSetValue (ObjectValue val) { return false; diff --git a/main/src/addins/MonoDevelop.Debugger/MonoDevelop.Debugger/DebuggingService.cs b/main/src/addins/MonoDevelop.Debugger/MonoDevelop.Debugger/DebuggingService.cs index e4a233e40a..b4182ffadc 100644 --- a/main/src/addins/MonoDevelop.Debugger/MonoDevelop.Debugger/DebuggingService.cs +++ b/main/src/addins/MonoDevelop.Debugger/MonoDevelop.Debugger/DebuggingService.cs @@ -830,7 +830,7 @@ namespace MonoDevelop.Debugger void UpdateDebugSessionCounter () { - var metadata = new Dictionary<string, string> (); + var metadata = new Dictionary<string, object> (); metadata ["Success"] = (!SessionError).ToString (); metadata ["DebuggerType"] = Engine.Id; @@ -843,7 +843,7 @@ namespace MonoDevelop.Debugger } } - Counters.DebugSession.Inc (metadata); + Counters.DebugSession.Inc (1, null, metadata); } void UpdateEvaluationStatsCounter () @@ -853,7 +853,7 @@ namespace MonoDevelop.Debugger return; } - var metadata = new Dictionary<string, string> (); + var metadata = new Dictionary<string, object> (); metadata ["DebuggerType"] = Engine.Id; metadata ["AverageDuration"] = Session.EvaluationStats.AverageTime.ToString (); metadata ["MaximumDuration"] = Session.EvaluationStats.MaxTime.ToString (); @@ -861,7 +861,7 @@ namespace MonoDevelop.Debugger metadata ["FailureCount"] = Session.EvaluationStats.FailureCount.ToString (); metadata ["SuccessCount"] = Session.EvaluationStats.TimingsCount.ToString (); - Counters.EvaluationStats.Inc (metadata); + Counters.EvaluationStats.Inc (1, null, metadata); } bool ExceptionHandler (Exception ex) diff --git a/main/src/addins/MonoDevelop.Debugger/MonoDevelop.Debugger/ExceptionCaughtDialog.cs b/main/src/addins/MonoDevelop.Debugger/MonoDevelop.Debugger/ExceptionCaughtDialog.cs index dc24c1e85b..456df5df01 100644 --- a/main/src/addins/MonoDevelop.Debugger/MonoDevelop.Debugger/ExceptionCaughtDialog.cs +++ b/main/src/addins/MonoDevelop.Debugger/MonoDevelop.Debugger/ExceptionCaughtDialog.cs @@ -41,6 +41,8 @@ using MonoDevelop.Ide.Gui.Content; using MonoDevelop.Ide.Editor.Extension; using MonoDevelop.Ide.Fonts; using System.Collections.Generic; +using System.Drawing; +using System.Text; namespace MonoDevelop.Debugger { @@ -765,7 +767,7 @@ widget ""*.exception_dialog_expander"" style ""exception-dialog-expander"" Context = ctx; } - string GetMethodMarkup (bool selected) + string GetMethodMarkup (bool selected, string foregroundColor) { if (Markup != null) return $"<span foreground='{Styles.ExceptionCaughtDialog.ExternalCodeTextColor.ToHexString (false)}'>{Markup}</span>"; @@ -777,30 +779,28 @@ widget ""*.exception_dialog_expander"" style ""exception-dialog-expander"" var markup = $"<b>{GLib.Markup.EscapeText (methodName)}</b> {GLib.Markup.EscapeText (parameters)}"; - if (selected) - markup = $"<span foreground='#FFFFFF'>{markup}</span>"; - else { - var textColor = IsUserCode ? Ide.Gui.Styles.BaseForegroundColor.ToHexString (false) : Styles.ExceptionCaughtDialog.ExternalCodeTextColor.ToHexString (false); - markup = $"<span foreground='{textColor}'>{markup}</span>"; + if (string.IsNullOrEmpty (foregroundColor)) { + return markup; } - - return markup; + return $"<span foreground='{foregroundColor}'>{markup}</span>"; } - string GetFileMarkup (bool selected) + string GetFileMarkup (bool selected, string foregroundColor) { if (Frame == null || string.IsNullOrEmpty (Frame.File)) { return ""; } - var markup = string.Format ("<span foreground='{0}'>{1}", selected ? "#FFFFFF" : Styles.ExceptionCaughtDialog.LineNumberTextColor.ToHexString (false), GLib.Markup.EscapeText (Path.GetFileName (Frame.File))); + var markup = GLib.Markup.EscapeText (Path.GetFileName (Frame.File)); if (Frame.Line > 0) { markup += ":" + Frame.Line; if (Frame.Column > 0) markup += "," + Frame.Column; } - markup += "</span>"; - return markup; + if (string.IsNullOrEmpty (foregroundColor)) { + return markup; + } + return $"<span foreground='{foregroundColor}'>{markup}</span>"; } public override void GetSize (Widget widget, ref Gdk.Rectangle cell_area, out int x_offset, out int y_offset, out int width, out int height) @@ -808,7 +808,11 @@ widget ""*.exception_dialog_expander"" style ""exception-dialog-expander"" using (var layout = new Pango.Layout (Context)) { Pango.Rectangle ink, logical; layout.FontDescription = font; - layout.SetMarkup (GetMethodMarkup (false)); + + var selected = false; + var foregroundColor = Styles.GetStackFrameForegroundHexColor (selected, IsUserCode); + + layout.SetMarkup (GetMethodMarkup (selected, foregroundColor)); layout.GetPixelExtents (out ink, out logical); height = logical.Height; @@ -821,10 +825,20 @@ widget ""*.exception_dialog_expander"" style ""exception-dialog-expander"" protected override void Render (Gdk.Drawable window, Widget widget, Gdk.Rectangle background_area, Gdk.Rectangle cell_area, Gdk.Rectangle expose_area, CellRendererState flags) { using (var cr = Gdk.CairoHelper.Create (window)) { + if (!widget.HasFocus) { + cr.Rectangle (background_area.ToCairoRect ()); + cr.SetSourceColor (Styles.ObjectValueTreeDisabledBackgroundColor); + cr.Fill (); + } + Pango.Rectangle ink, logical; using (var layout = new Pango.Layout (Context)) { layout.FontDescription = font; - layout.SetMarkup (GetFileMarkup ((flags & CellRendererState.Selected) != 0)); + + var selected = (flags & CellRendererState.Selected) != 0; + var foregroundColor = Styles.GetStackFrameForegroundHexColor (selected, IsUserCode); + + layout.SetMarkup (GetFileMarkup (selected, foregroundColor)); layout.GetPixelExtents (out ink, out logical); var width = widget.Allocation.Width; cr.Translate (width - logical.Width - 10, cell_area.Y); @@ -832,7 +846,7 @@ widget ""*.exception_dialog_expander"" style ""exception-dialog-expander"" cr.IdentityMatrix (); - layout.SetMarkup (GetMethodMarkup ((flags & CellRendererState.Selected) != 0)); + layout.SetMarkup (GetMethodMarkup (selected, foregroundColor)); layout.Width = (int)((width - logical.Width - 35) * Pango.Scale.PangoScale); layout.Ellipsize = Pango.EllipsizeMode.Middle; cr.Translate (cell_area.X + 10, cell_area.Y); @@ -1105,12 +1119,19 @@ widget ""*.exception_dialog_expander"" style ""exception-dialog-expander"" { public override bool KeyPress (KeyDescriptor descriptor) { - if (descriptor.SpecialKey == SpecialKey.Escape && DebuggingService.ExceptionCaughtMessage != null && + if (DebuggingService.ExceptionCaughtMessage != null && !DebuggingService.ExceptionCaughtMessage.IsMinimized && DebuggingService.ExceptionCaughtMessage.File.CanonicalPath == new FilePath (DocumentContext.Name).CanonicalPath) { - DebuggingService.ExceptionCaughtMessage.ShowMiniButton (); - return true; + if (descriptor.SpecialKey == SpecialKey.Escape) { + DebuggingService.ExceptionCaughtMessage.ShowMiniButton (); + return true; + } + + if (descriptor.SpecialKey == SpecialKey.Return) { + DebuggingService.ExceptionCaughtMessage.ShowDialog (); + return false; + } } return base.KeyPress (descriptor); diff --git a/main/src/addins/MonoDevelop.Debugger/MonoDevelop.Debugger/Extensions.cs b/main/src/addins/MonoDevelop.Debugger/MonoDevelop.Debugger/Extensions.cs index b1a2498285..1364ab3939 100644 --- a/main/src/addins/MonoDevelop.Debugger/MonoDevelop.Debugger/Extensions.cs +++ b/main/src/addins/MonoDevelop.Debugger/MonoDevelop.Debugger/Extensions.cs @@ -65,6 +65,10 @@ namespace MonoDevelop.Debugger public static AsyncOperation DebugApplication (this ProjectOperations opers, string executableFile, string args, string workingDir, IDictionary<string,string> envVars) { + if (!IdeApp.Workbench.RootWindow.Visible) { + IdeApp.Workbench.RootWindow.Show (); + } + var monitor = IdeApp.Workbench.ProgressMonitors.GetRunProgressMonitor (System.IO.Path.GetFileName (executableFile)); var oper = DebuggingService.Run (executableFile, args, workingDir, envVars, monitor.Console); @@ -79,6 +83,10 @@ namespace MonoDevelop.Debugger public static AsyncOperation AttachToProcess (this ProjectOperations opers, DebuggerEngine debugger, ProcessInfo proc) { + if (!IdeApp.Workbench.RootWindow.Visible) { + IdeApp.Workbench.RootWindow.Show (); + } + var oper = DebuggingService.AttachToProcess (debugger, proc); opers.AddRunOperation (oper); diff --git a/main/src/addins/MonoDevelop.Debugger/MonoDevelop.Debugger/ObjectValueTreeView.cs b/main/src/addins/MonoDevelop.Debugger/MonoDevelop.Debugger/ObjectValueTreeView.cs index c2998c9c8b..e08e49ff1c 100644 --- a/main/src/addins/MonoDevelop.Debugger/MonoDevelop.Debugger/ObjectValueTreeView.cs +++ b/main/src/addins/MonoDevelop.Debugger/MonoDevelop.Debugger/ObjectValueTreeView.cs @@ -930,7 +930,7 @@ namespace MonoDevelop.Debugger ExpandRow (store.GetPath (it), false); } } else { - RefreshRow (it); + RefreshRow (it, val); } } @@ -977,10 +977,11 @@ namespace MonoDevelop.Debugger enumerableLoading.Remove (value); }, cancellationTokenSource.Token, TaskContinuationOptions.NotOnCanceled, Xwt.Application.UITaskScheduler); } - - void RefreshRow (TreeIter iter) + + void RefreshRow (TreeIter iter, ObjectValue val) { - var val = (ObjectValue) store.GetValue (iter, ObjectColumn); + if (val == null) + return; UnregisterValue (val); RemoveChildren (iter); @@ -1013,8 +1014,7 @@ namespace MonoDevelop.Debugger while (store.IterChildren (out citer, iter)) { var val = (ObjectValue) store.GetValue (citer, ObjectColumn); - if (val != null) - UnregisterValue (val); + UnregisterValue (val); RemoveChildren (citer); store.Remove (ref citer); } @@ -1030,6 +1030,8 @@ namespace MonoDevelop.Debugger void UnregisterValue (ObjectValue val) { + if (val == null) + return; val.ValueChanged -= OnValueUpdated; nodes.Remove (val); } @@ -1904,7 +1906,7 @@ namespace MonoDevelop.Debugger var val = (ObjectValue)store.GetValue (it, ObjectColumn); if (DebuggingService.ShowValueVisualizer (val)) { UpdateParentValue (it); - RefreshRow (it); + RefreshRow (it, val); } } else if (cr == crtExp && !PreviewWindowManager.IsVisible && ValidObjectForPreviewIcon (it)) { var val = (ObjectValue)store.GetValue (it, ObjectColumn); diff --git a/main/src/addins/MonoDevelop.Debugger/MonoDevelop.Debugger/Styles.cs b/main/src/addins/MonoDevelop.Debugger/MonoDevelop.Debugger/Styles.cs index 411e8c5eda..e65c855147 100644 --- a/main/src/addins/MonoDevelop.Debugger/MonoDevelop.Debugger/Styles.cs +++ b/main/src/addins/MonoDevelop.Debugger/MonoDevelop.Debugger/Styles.cs @@ -26,6 +26,7 @@ using MonoDevelop.Ide; using Xwt.Drawing; +using MonoDevelop.Components; namespace MonoDevelop.Debugger { @@ -94,7 +95,13 @@ namespace MonoDevelop.Debugger ExceptionCaughtDialog.ValueTreeBackgroundColor = Color.FromName ("#525252"); } + //Disabled state + ObjectValueTreeDisabledBackgroundColor = new Cairo.Color (0.64f, 0.64f, 0.64f); + // Shared + ObjectValueTreeSelectedTextColor = Ide.Gui.Styles.BaseSelectionTextColor.ToHexString (false); + ObjectValueTreeForegroundTextColor = Ide.Gui.Styles.BaseSelectionTextColor.ToHexString (false); + ObjectValueTreeExternalCodeForegroundTextColor = ExceptionCaughtDialog.ExternalCodeTextColor.ToHexString (false); ObjectValueTreeValueErrorText = Ide.Gui.Styles.WarningForegroundColor; @@ -102,6 +109,20 @@ namespace MonoDevelop.Debugger PreviewVisualizerTextColor = Ide.Gui.Styles.PopoverWindow.DefaultTextColor; PreviewVisualizerHeaderTextColor = Ide.Gui.Styles.PopoverWindow.DefaultTextColor; } + + public static string ObjectValueTreeSelectedTextColor { get; private set; } + public static string ObjectValueTreeForegroundTextColor { get; private set; } + public static string ObjectValueTreeExternalCodeForegroundTextColor { get; private set; } + + public static Cairo.Color ObjectValueTreeDisabledBackgroundColor { get; private set; } + + internal static string GetStackFrameForegroundHexColor (bool selected, bool isUserCode) + { + if (selected) { + return ObjectValueTreeSelectedTextColor; + } + return isUserCode ? ObjectValueTreeForegroundTextColor : ObjectValueTreeExternalCodeForegroundTextColor; + } } } diff --git a/main/src/addins/MonoDevelop.Debugger/MonoDevelop.Debugger/TextEntryWithCodeCompletion.cs b/main/src/addins/MonoDevelop.Debugger/MonoDevelop.Debugger/TextEntryWithCodeCompletion.cs index ceb0633283..b68f8e5762 100644 --- a/main/src/addins/MonoDevelop.Debugger/MonoDevelop.Debugger/TextEntryWithCodeCompletion.cs +++ b/main/src/addins/MonoDevelop.Debugger/MonoDevelop.Debugger/TextEntryWithCodeCompletion.cs @@ -34,21 +34,18 @@ namespace MonoDevelop.Debugger class TextEntryWithCodeCompletion : TextEntry, ICompletionWidget { CodeCompletionContext ctx; - Gtk.Entry gtkEntry; - Gdk.ModifierType modifier; + Xwt.ModifierKeys modifier; bool keyHandled = false; uint keyValue; char keyChar; - Gdk.Key key; + Key key; public TextEntryWithCodeCompletion () { - gtkEntry = Xwt.Toolkit.CurrentEngine.GetNativeWidget (this) as Gtk.Entry; - if (gtkEntry == null) - throw new NotImplementedException (); - gtkEntry.KeyReleaseEvent += HandleKeyReleaseEvent; - gtkEntry.KeyPressEvent += HandleKeyPressEvent; + KeyPressed += HandleKeyPressEvent; + KeyReleased += HandleKeyReleaseEvent; + CompletionWindowManager.WindowClosed += HandleWindowClosed; } @@ -65,32 +62,40 @@ namespace MonoDevelop.Debugger CompletionContextChanged (this, EventArgs.Empty); } + char CharFromKey (Xwt.Key xwtKey) + { + if (xwtKey >= Key.Exclamation && xwtKey <= Key.Tilde) { + return (char)xwtKey; + } + + return '\0'; + } + [GLib.ConnectBeforeAttribute] - void HandleKeyPressEvent (object o, Gtk.KeyPressEventArgs args) + void HandleKeyPressEvent (object o, KeyEventArgs args) { keyHandled = false; - keyChar = (char)args.Event.Key; - keyValue = args.Event.KeyValue; - modifier = args.Event.State; - key = args.Event.Key; + keyChar = CharFromKey (args.Key); + modifier = args.Modifiers; + key = args.Key; - if ((args.Event.Key == Gdk.Key.Down || args.Event.Key == Gdk.Key.Up)) { + if ((args.Key == Key.Down || args.Key == Key.Up)) { keyChar = '\0'; } if (list != null) - args.RetVal = keyHandled = CompletionWindowManager.PreProcessKeyEvent (KeyDescriptor.FromGtk (key, keyChar, modifier)); + args.Handled = keyHandled = CompletionWindowManager.PreProcessKeyEvent (KeyDescriptor.FromXwt (key, keyChar, modifier)); } - void HandleKeyReleaseEvent (object o, Gtk.KeyReleaseEventArgs args) + void HandleKeyReleaseEvent (object o, KeyEventArgs args) { if (keyHandled) return; string text = ctx == null ? Text : Text.Substring (Math.Max (0, Math.Min (ctx.TriggerOffset, Text.Length))); CompletionWindowManager.UpdateWordSelection (text); - CompletionWindowManager.PostProcessKeyEvent (KeyDescriptor.FromGtk (key, keyChar, modifier)); + CompletionWindowManager.PostProcessKeyEvent (KeyDescriptor.FromXwt (key, keyChar, modifier)); PopupCompletion (); } @@ -121,7 +126,7 @@ namespace MonoDevelop.Debugger /// </summary> class NullDotKeyHandler : ICompletionKeyHandler { - #region ICompletionKeyHandler implementation +#region ICompletionKeyHandler implementation public bool PreProcessKey (CompletionListWindow listWindow, KeyDescriptor descriptor, out KeyActions keyAction) { @@ -141,10 +146,10 @@ namespace MonoDevelop.Debugger return false; } - #endregion +#endregion } - #region ICompletionWidget implementation +#region ICompletionWidget implementation public event EventHandler CompletionContextChanged; @@ -186,11 +191,11 @@ namespace MonoDevelop.Debugger public CodeCompletionContext CreateCodeCompletionContext (int triggerOffset) { - var height = gtkEntry.SizeRequest ().Height; + var height = Size.Height; var location = ConvertToScreenCoordinates (new Point (0, height)); return new CodeCompletionContext ( - (int)location.X, (int)location.Y, height, + (int)location.X, (int)location.Y, (int)height, triggerOffset, 0, triggerOffset, CaretOffset ); } @@ -203,13 +208,13 @@ namespace MonoDevelop.Debugger public void SetCompletionText (CodeCompletionContext ctx, string partial_word, string complete_word) { Text = complete_word; - gtkEntry.Position = complete_word.Length; + CursorPosition = complete_word.Length; } public void SetCompletionText (CodeCompletionContext ctx, string partial_word, string complete_word, int completeWordOffset) { Text = complete_word; - gtkEntry.Position = complete_word.Length; + CursorPosition = complete_word.Length; } public CodeCompletionContext CurrentCodeCompletionContext { @@ -220,10 +225,10 @@ namespace MonoDevelop.Debugger public int CaretOffset { get { - return gtkEntry.Position; + return CursorPosition; } set { - gtkEntry.Position = value; + CursorPosition = value; } } @@ -241,7 +246,7 @@ namespace MonoDevelop.Debugger public Gtk.Style GtkStyle { get { - return gtkEntry.Style; + return null; } } @@ -250,7 +255,7 @@ namespace MonoDevelop.Debugger return 1; } } - #endregion +#endregion } } diff --git a/main/src/addins/MonoDevelop.DesignerSupport/MonoDevelop.DesignerSupport.Toolbox/CodeTemplateToolboxProvider.cs b/main/src/addins/MonoDevelop.DesignerSupport/MonoDevelop.DesignerSupport.Toolbox/CodeTemplateToolboxProvider.cs index fded6a4a62..ee50ac79f5 100644 --- a/main/src/addins/MonoDevelop.DesignerSupport/MonoDevelop.DesignerSupport.Toolbox/CodeTemplateToolboxProvider.cs +++ b/main/src/addins/MonoDevelop.DesignerSupport/MonoDevelop.DesignerSupport.Toolbox/CodeTemplateToolboxProvider.cs @@ -41,7 +41,6 @@ namespace MonoDevelop.DesignerSupport.Toolbox { static string category = MonoDevelop.Core.GettextCatalog.GetString ("Text Snippets"); - public System.Collections.Generic.IEnumerable<ItemToolboxNode> GetDynamicItems (IToolboxConsumer consumer) { // TOTEST @@ -60,7 +59,7 @@ namespace MonoDevelop.DesignerSupport.Toolbox }; } } - + public event EventHandler ItemsChanged { add { CodeTemplateService.TemplatesChanged += value; } remove { CodeTemplateService.TemplatesChanged -= value; } diff --git a/main/src/addins/MonoDevelop.DesignerSupport/MonoDevelop.DesignerSupport.Toolbox/IToolboxProvider.cs b/main/src/addins/MonoDevelop.DesignerSupport/MonoDevelop.DesignerSupport.Toolbox/IToolboxProvider.cs index 3498dccd20..041c9cf194 100644 --- a/main/src/addins/MonoDevelop.DesignerSupport/MonoDevelop.DesignerSupport.Toolbox/IToolboxProvider.cs +++ b/main/src/addins/MonoDevelop.DesignerSupport/MonoDevelop.DesignerSupport.Toolbox/IToolboxProvider.cs @@ -35,7 +35,13 @@ using System.Collections.Generic; namespace MonoDevelop.DesignerSupport.Toolbox { - + public interface IToolboxDynamicProviderDeleteSupport + { + bool DeleteDynamicItem (ItemToolboxNode node); + + bool CanDeleteDynamicItem (ItemToolboxNode node); + } + //Used to fetch or generate the default toolbox items at the beginning of each MD session public interface IToolboxDefaultProvider { @@ -53,7 +59,7 @@ namespace MonoDevelop.DesignerSupport.Toolbox //This method will be called each time the consumer changes. Return null if not //returning any items for a specific consumer. IEnumerable<ItemToolboxNode> GetDynamicItems (IToolboxConsumer consumer); - + event EventHandler ItemsChanged; } } diff --git a/main/src/addins/MonoDevelop.DesignerSupport/MonoDevelop.DesignerSupport.Toolbox/IToolboxWidget.cs b/main/src/addins/MonoDevelop.DesignerSupport/MonoDevelop.DesignerSupport.Toolbox/IToolboxWidget.cs index 23045937ef..ae61656bc9 100644 --- a/main/src/addins/MonoDevelop.DesignerSupport/MonoDevelop.DesignerSupport.Toolbox/IToolboxWidget.cs +++ b/main/src/addins/MonoDevelop.DesignerSupport/MonoDevelop.DesignerSupport.Toolbox/IToolboxWidget.cs @@ -40,7 +40,6 @@ namespace MonoDevelop.DesignerSupport.Toolbox IEnumerable<ToolboxWidgetCategory> Categories { get; } IEnumerable<ToolboxWidgetItem> AllItems { get; } - event EventHandler SelectedItemChanged; event EventHandler ActivateSelectedItem; void AddCategory (ToolboxWidgetCategory category); diff --git a/main/src/addins/MonoDevelop.DesignerSupport/MonoDevelop.DesignerSupport.Toolbox/MacToolbox.cs b/main/src/addins/MonoDevelop.DesignerSupport/MonoDevelop.DesignerSupport.Toolbox/MacToolbox.cs index f96b00e16f..4a3633ed96 100644 --- a/main/src/addins/MonoDevelop.DesignerSupport/MonoDevelop.DesignerSupport.Toolbox/MacToolbox.cs +++ b/main/src/addins/MonoDevelop.DesignerSupport/MonoDevelop.DesignerSupport.Toolbox/MacToolbox.cs @@ -62,7 +62,7 @@ namespace MonoDevelop.DesignerSupport.Toolbox public event EventHandler<Gtk.TargetEntry []> DragSourceSet; public event EventHandler ContentFocused; - public ItemToolboxNode selectedNode; + public ItemToolboxNode SelectedNode => toolboxWidget.SelectedItem?.Node; NativeViews.ToggleButton catToggleButton; NativeViews.ToggleButton compactModeToggleButton; @@ -180,7 +180,6 @@ namespace MonoDevelop.DesignerSupport.Toolbox filterEntry.Changed += FilterEntry_Changed; - toolboxWidget.SelectedItemChanged += ToolboxWidget_SelectedItemChanged; toolboxWidget.DragBegin += ToolboxWidget_DragBegin; toolboxWidget.MouseDownActivated += ToolboxWidget_MouseDownActivated; toolboxWidget.ActivateSelectedItem += ToolboxWidget_ActivateSelectedItem; @@ -205,15 +204,13 @@ namespace MonoDevelop.DesignerSupport.Toolbox Refresh (); } - void ToolboxWidget_SelectedItemChanged (object sender, EventArgs e) - { - selectedNode = this.toolboxWidget.SelectedItem != null ? this.toolboxWidget.SelectedItem.Tag as ItemToolboxNode : null; - toolboxService.SelectItem (selectedNode); - } - void ToolboxWidget_ActivateSelectedItem (object sender, EventArgs e) { - toolboxService.UseSelectedItem (); + var selectedNode = SelectedNode; + if (selectedNode != null) { + DesignerSupport.Service.ToolboxService.SelectItem (selectedNode); + toolboxService.UseSelectedItem (); + } } void FilterEntry_Changed (object sender, EventArgs e) @@ -438,16 +435,20 @@ namespace MonoDevelop.DesignerSupport.Toolbox [CommandHandler (MonoDevelop.Ide.Commands.EditCommands.Delete)] internal void OnDeleteItem () { - if (MessageService.Confirm (GettextCatalog.GetString ("Are you sure you want to remove the selected Item?"), AlertButton.Delete)) - toolboxService.RemoveUserItem (selectedNode); + var selectedNode = SelectedNode; + if (selectedNode != null) { + if (MessageService.Confirm (GettextCatalog.GetString ("Are you sure you want to remove the selected Item?"), AlertButton.Delete)) + toolboxService.RemoveUserItem (SelectedNode); + } } [CommandUpdateHandler (MonoDevelop.Ide.Commands.EditCommands.Delete)] internal void OnUpdateDeleteItem (CommandInfo info) { + var selectedNode = SelectedNode; // Hack manually filter out gtk# widgets & container since they cannot be re added // because they're missing the toolbox attributes. - info.Enabled = selectedNode != null + info.Enabled = selectedNode != null && toolboxService.CanRemoveUserItem (selectedNode) && (selectedNode.ItemDomain != GtkWidgetDomain || (selectedNode.Category != "Widgets" && selectedNode.Category != "Container")); } @@ -540,7 +541,6 @@ namespace MonoDevelop.DesignerSupport.Toolbox toolboxAddButton.Activated -= ToolboxAddButton_Clicked; toolboxAddButton.Focused -= ToolboxAddButton_Focused; - toolboxWidget.SelectedItemChanged -= ToolboxWidget_SelectedItemChanged; toolboxWidget.ActivateSelectedItem -= ToolboxWidget_ActivateSelectedItem; toolboxWidget.MenuOpened -= ToolboxWidget_MenuOpened; toolboxWidget.MouseDownActivated -= ToolboxWidget_MouseDownActivated; @@ -560,12 +560,12 @@ namespace MonoDevelop.DesignerSupport.Toolbox object IPropertyPadProvider.GetActiveComponent () { - return selectedNode; + return SelectedNode; } object IPropertyPadProvider.GetProvider () { - return selectedNode; + return SelectedNode; } void IPropertyPadProvider.OnEndEditing (object obj) diff --git a/main/src/addins/MonoDevelop.DesignerSupport/MonoDevelop.DesignerSupport.Toolbox/MacToolboxWidget.cs b/main/src/addins/MonoDevelop.DesignerSupport/MonoDevelop.DesignerSupport.Toolbox/MacToolboxWidget.cs index 361798aca1..e52c52eb18 100644 --- a/main/src/addins/MonoDevelop.DesignerSupport/MonoDevelop.DesignerSupport.Toolbox/MacToolboxWidget.cs +++ b/main/src/addins/MonoDevelop.DesignerSupport/MonoDevelop.DesignerSupport.Toolbox/MacToolboxWidget.cs @@ -56,7 +56,6 @@ namespace MonoDevelop.DesignerSupport.Toolbox public event EventHandler Focused; public event EventHandler DragBegin; public event EventHandler<CGPoint> MenuOpened; - public event EventHandler SelectedItemChanged; public event EventHandler ActivateSelectedItem; public Action<NSEvent> MouseDownActivated { get; set; } @@ -74,8 +73,6 @@ namespace MonoDevelop.DesignerSupport.Toolbox internal void PerformActivateSelectedItem () => OnActivateSelectedItem (EventArgs.Empty); void OnActivateSelectedItem (EventArgs args) => ActivateSelectedItem?.Invoke (this, args); - - void OnSelectedItemChanged (EventArgs args) => SelectedItemChanged?.Invoke (this, args); NSIndexPath selectedIndexPath; public NSIndexPath SelectedIndexPath { @@ -85,7 +82,6 @@ namespace MonoDevelop.DesignerSupport.Toolbox set { if (selectedIndexPath != value) { selectedIndexPath = value; - OnSelectedItemChanged (EventArgs.Empty); } } } diff --git a/main/src/addins/MonoDevelop.DesignerSupport/MonoDevelop.DesignerSupport.Toolbox/ToolboxWidget.cs b/main/src/addins/MonoDevelop.DesignerSupport/MonoDevelop.DesignerSupport.Toolbox/ToolboxWidget.cs index 18d9ca7f3d..70a7f4462c 100644 --- a/main/src/addins/MonoDevelop.DesignerSupport/MonoDevelop.DesignerSupport.Toolbox/ToolboxWidget.cs +++ b/main/src/addins/MonoDevelop.DesignerSupport/MonoDevelop.DesignerSupport.Toolbox/ToolboxWidget.cs @@ -1149,8 +1149,7 @@ namespace MonoDevelop.DesignerSupport.Toolbox } } - [Obsolete("This class should never have been public")] - public class Item : IComparable<Item> + class Item : IComparable<Item> { ToolboxWidgetItem inner; diff --git a/main/src/addins/MonoDevelop.DesignerSupport/MonoDevelop.DesignerSupport/DesignerSupportService.cs b/main/src/addins/MonoDevelop.DesignerSupport/MonoDevelop.DesignerSupport/DesignerSupportService.cs index 5af30a213a..10f6e33171 100644 --- a/main/src/addins/MonoDevelop.DesignerSupport/MonoDevelop.DesignerSupport/DesignerSupportService.cs +++ b/main/src/addins/MonoDevelop.DesignerSupport/MonoDevelop.DesignerSupport/DesignerSupportService.cs @@ -36,12 +36,11 @@ using System; using System.Collections; using Mono.Addins; +using MonoDevelop.Core; using MonoDevelop.Ide; namespace MonoDevelop.DesignerSupport { - - public class DesignerSupportService { PropertyPad propertyPad = null; @@ -78,10 +77,17 @@ namespace MonoDevelop.DesignerSupport propertyPad.PropertyGrid.Changed += OnPropertyGridChanged; } else if (lastCustomProvider != null) { - propertyPad.UseCustomWidget (lastCustomProvider.GetCustomPropertyWidget ()); - var customizer = lastCustomProvider as IPropertyPadCustomizer; - if (customizer != null) - customizer.Customize (pad.PadWindow, null); + try { + var currentCustomWidget = lastCustomProvider.GetCustomPropertyWidget (); + if (currentCustomWidget != null) { + propertyPad.UseCustomWidget (currentCustomWidget); + if (lastCustomProvider is IPropertyPadCustomizer customizer) + customizer.Customize (pad.PadWindow, null); + } + } catch (Exception ex) { + LoggingService.LogInternalError ($"There was an error trying to GetCustomPropertyWidget from '{lastCustomProvider.GetType ()}' provider", ex); + ReSetPad (); + } } } } @@ -174,12 +180,24 @@ namespace MonoDevelop.DesignerSupport lastCustomProvider = provider; if (propertyPad != null) { - propertyPad.UseCustomWidget (provider.GetCustomPropertyWidget ()); - propertyPad.CommandRouteOrigin = commandRouteOrigin; - - var customizer = provider as IPropertyPadCustomizer; - if (customizer != null) - customizer.Customize (propertyPad.PadWindow, null); + try { + var customWidget = provider.GetCustomPropertyWidget (); + if (customWidget != null) { + propertyPad.UseCustomWidget (customWidget); + propertyPad.CommandRouteOrigin = commandRouteOrigin; + + var customizer = provider as IPropertyPadCustomizer; + if (customizer != null) + customizer.Customize (propertyPad.PadWindow, null); + } else { + propertyPad?.BlankPad (); + return; + } + } catch (Exception ex) { + LoggingService.LogInternalError ($"There was an error trying to GetCustomPropertyWidget from '{lastCustomProvider.GetType ()}' provider", ex); + propertyPad?.BlankPad (); + return; + } } } else { diff --git a/main/src/addins/MonoDevelop.DesignerSupport/MonoDevelop.DesignerSupport/ToolboxPad.cs b/main/src/addins/MonoDevelop.DesignerSupport/MonoDevelop.DesignerSupport/ToolboxPad.cs index 1be634b3a0..b69333c078 100644 --- a/main/src/addins/MonoDevelop.DesignerSupport/MonoDevelop.DesignerSupport/ToolboxPad.cs +++ b/main/src/addins/MonoDevelop.DesignerSupport/MonoDevelop.DesignerSupport/ToolboxPad.cs @@ -82,14 +82,17 @@ namespace MonoDevelop.DesignerSupport targets.AddTable (e); }; toolbox.DragBegin += (object sender, EventArgs e) => { - if (!isDragging) { + var selectedNode = toolbox.SelectedNode; + if (!isDragging && selectedNode != null) { + + DesignerSupport.Service.ToolboxService.SelectItem (selectedNode); Gtk.Drag.SourceUnset (widget); // Gtk.Application.CurrentEvent and other copied gdk_events seem to have a problem // when used as they use gdk_event_copy which seems to crash on de-allocating the private slice. IntPtr currentEvent = GtkWorkarounds.GetCurrentEventHandle (); - Gtk.Drag.Begin (widget, targets, Gdk.DragAction.Copy | Gdk.DragAction.Move, 1, new Gdk.Event (currentEvent)); + Gtk.Drag.Begin (widget, targets, Gdk.DragAction.Copy | Gdk.DragAction.Move, 1, new Gdk.Event (currentEvent, false)); // gtk_drag_begin does not store the event, so we're okay GtkWorkarounds.FreeEvent (currentEvent); diff --git a/main/src/addins/MonoDevelop.DesignerSupport/MonoDevelop.DesignerSupport/ToolboxService.cs b/main/src/addins/MonoDevelop.DesignerSupport/MonoDevelop.DesignerSupport/ToolboxService.cs index 2a53440dcd..a50f32043d 100644 --- a/main/src/addins/MonoDevelop.DesignerSupport/MonoDevelop.DesignerSupport/ToolboxService.cs +++ b/main/src/addins/MonoDevelop.DesignerSupport/MonoDevelop.DesignerSupport/ToolboxService.cs @@ -152,8 +152,16 @@ namespace MonoDevelop.DesignerSupport public void RemoveUserItem (ItemToolboxNode node) { - Configuration.ItemList.Remove (node); - SaveConfiguration (); + if (Configuration.ItemList.Remove (node)) { + SaveConfiguration (); + } else { + //we need check in the dynamic providers + foreach (var prov in dynamicProviders) { + if (prov is IToolboxDynamicProviderDeleteSupport provDelSupport && provDelSupport.DeleteDynamicItem (node)) { + break; + } + } + } OnToolboxContentsChanged (); } @@ -525,7 +533,22 @@ namespace MonoDevelop.DesignerSupport //we assume permitted, so only return false when blocked by a filter return true; } - + + internal bool CanRemoveUserItem (ItemToolboxNode node) + { + if (Configuration.ItemList.Contains (node)) { + return true; + } else { + //we need check in the dynamic providers + foreach (var prov in dynamicProviders) { + if (prov is IToolboxDynamicProviderDeleteSupport provDelSupport && provDelSupport.CanDeleteDynamicItem (node)) { + return true; + } + } + } + return false; + } + //evaluate a filter attribute against a list, and check whether permitted private bool FilterPermitted (ItemToolboxNode node, ToolboxItemFilterAttribute desFa, ICollection<ToolboxItemFilterAttribute> filterAgainst, IToolboxConsumer consumer) diff --git a/main/src/addins/MonoDevelop.DotNetCore/MonoDevelop.DotNetCore.Tests/MonoDevelop.DotNetCore.Tests/DotNetCoreMSBuildProjectTests.cs b/main/src/addins/MonoDevelop.DotNetCore/MonoDevelop.DotNetCore.Tests/MonoDevelop.DotNetCore.Tests/DotNetCoreMSBuildProjectTests.cs index 75efb748ab..bd7cf6e50d 100644 --- a/main/src/addins/MonoDevelop.DotNetCore/MonoDevelop.DotNetCore.Tests/MonoDevelop.DotNetCore.Tests/DotNetCoreMSBuildProjectTests.cs +++ b/main/src/addins/MonoDevelop.DotNetCore/MonoDevelop.DotNetCore.Tests/MonoDevelop.DotNetCore.Tests/DotNetCoreMSBuildProjectTests.cs @@ -88,11 +88,10 @@ namespace MonoDevelop.DotNetCore.Tests "</Project>"); ReadProject (); - project.Sdk = "Microsoft.NET.Sdk"; + project.HasSdk = true; Assert.AreEqual ("15.0", project.ToolsVersion); Assert.IsFalse (project.IsOutputTypeDefined); - Assert.AreEqual ("Microsoft.NET.Sdk", project.Sdk); Assert.IsTrue (project.HasSdk); } @@ -115,6 +114,24 @@ namespace MonoDevelop.DotNetCore.Tests } [Test] + public void ReadProject_ExplicityReferences () + { + CreateMSBuildProject ( + "<Project ToolsVersion=\"15.0\">\r\n" + + " <PropertyGroup>\r\n" + + " <OutputType>Exe</OutputType>\r\n" + + " <TargetFramework>netcoreapp1.0</TargetFramework>\r\n" + + " </PropertyGroup>\r\n" + + " <Import Sdk=\"Microsoft.NET.Sdk\" Project=\"Sdk.targets\" />" + + "</Project>"); + msbuildProject.Evaluate (); + + ReadProject (); + + Assert.That (msbuildProject.GetReferencedSDKs (), Is.Not.Empty); + } + + [Test] public void WriteProject_ProjectGuidAddedAndToolsVersionChanged_ProjectGuidIsRemovedAndToolsVersionReset () { CreateMSBuildProject ( @@ -313,7 +330,7 @@ namespace MonoDevelop.DotNetCore.Tests " </PropertyGroup>\r\n" + "</Project>"); ReadProject (); - project.Sdk = "Microsoft.NET.Sdk"; + project.HasSdk = true; msbuildProject.ToolsVersion = "4.0"; WriteProject (); @@ -332,7 +349,7 @@ namespace MonoDevelop.DotNetCore.Tests " </PropertyGroup>\r\n" + "</Project>"); ReadProject (); - project.Sdk = "Microsoft.NET.Sdk"; + project.HasSdk = true; var projectReferenceItem = msbuildProject.AddNewItem ("ProjectReference", @"Lib\Lib.csproj"); projectReferenceItem.Metadata.SetValue ("Name", "Lib"); projectReferenceItem.Metadata.SetValue ("Project", "{F109E7DF-F561-4CD6-A46E-CFB27A8B6F2C}"); @@ -359,7 +376,7 @@ namespace MonoDevelop.DotNetCore.Tests "</Project>"); msbuildProject.Evaluate (); ReadProject (); - project.Sdk = "Microsoft.NET.Sdk"; + project.HasSdk = true; WriteProject (".NETCoreApp,Version=v1.1"); @@ -380,7 +397,7 @@ namespace MonoDevelop.DotNetCore.Tests "</Project>"); msbuildProject.Evaluate (); ReadProject (); - project.Sdk = "Microsoft.NET.Sdk"; + project.HasSdk = true; WriteProject (".NETCoreApp,Version=v1.1"); WriteProject (".NETCoreApp,Version=v1.0"); @@ -402,7 +419,7 @@ namespace MonoDevelop.DotNetCore.Tests "</Project>"); msbuildProject.Evaluate (); ReadProject (".NET Standard,Version=v1.0"); - project.Sdk = "Microsoft.NET.Sdk"; + project.HasSdk = true; WriteProject (".NETStandard,Version=v1.6"); @@ -423,7 +440,7 @@ namespace MonoDevelop.DotNetCore.Tests "</Project>"); msbuildProject.Evaluate (); ReadProject (); - project.Sdk = "Microsoft.NET.Sdk"; + project.HasSdk = true; WriteProject (".NETFramework,Version=v4.6"); @@ -444,7 +461,7 @@ namespace MonoDevelop.DotNetCore.Tests "</Project>"); msbuildProject.Evaluate (); ReadProject (); - project.Sdk = "Microsoft.NET.Sdk"; + project.HasSdk = true; WriteProject (".NETCoreApp,Version=v1.1"); diff --git a/main/src/addins/MonoDevelop.DotNetCore/MonoDevelop.DotNetCore/DotNetCoreMSBuildProject.cs b/main/src/addins/MonoDevelop.DotNetCore/MonoDevelop.DotNetCore/DotNetCoreMSBuildProject.cs index da870db57f..0034e373fd 100644 --- a/main/src/addins/MonoDevelop.DotNetCore/MonoDevelop.DotNetCore/DotNetCoreMSBuildProject.cs +++ b/main/src/addins/MonoDevelop.DotNetCore/MonoDevelop.DotNetCore/DotNetCoreMSBuildProject.cs @@ -45,24 +45,14 @@ namespace MonoDevelop.DotNetCore public string ToolsVersion { get; private set; } public bool IsOutputTypeDefined { get; private set; } - public string Sdk { get; set; } - public IEnumerable<string> TargetFrameworks { - get { return targetFrameworks; } - } + public IEnumerable<string> TargetFrameworks => targetFrameworks; - public bool HasSdk { - get { return Sdk != null; } - } + public bool HasSdk { get; set; } - public bool HasToolsVersion () - { - return !string.IsNullOrEmpty (ToolsVersion); - } + public bool HasToolsVersion () => !string.IsNullOrEmpty (ToolsVersion); - public CompileTarget DefaultCompileTarget { - get { return defaultCompileTarget; } - } + public CompileTarget DefaultCompileTarget => defaultCompileTarget; /// <summary> /// Ensure MSBuildProject has ToolsVersion set to 15.0 so the correct diff --git a/main/src/addins/MonoDevelop.DotNetCore/MonoDevelop.DotNetCore/DotNetCoreProjectExtension.cs b/main/src/addins/MonoDevelop.DotNetCore/MonoDevelop.DotNetCore/DotNetCoreProjectExtension.cs index b70088877a..a005019c12 100644 --- a/main/src/addins/MonoDevelop.DotNetCore/MonoDevelop.DotNetCore/DotNetCoreProjectExtension.cs +++ b/main/src/addins/MonoDevelop.DotNetCore/MonoDevelop.DotNetCore/DotNetCoreProjectExtension.cs @@ -93,7 +93,7 @@ namespace MonoDevelop.DotNetCore /// </summary> bool IsSdkProject (DotNetProject project) { - return project.MSBuildProject.GetReferencedSDKs ().Any (); + return project.MSBuildProject.GetReferencedSDKs ().Length > 0; } protected override bool OnGetCanReferenceProject (DotNetProject targetProject, out string reason) @@ -409,7 +409,12 @@ namespace MonoDevelop.DotNetCore if (ProjectNeedsRestore ()) { return CreateNuGetRestoreRequiredBuildResult (); } - if ((HasSdk && !IsDotNetCoreSdkInstalled ()) || sdkPaths.IsUnsupportedSdkVersion) { + + if (!Project.TargetFramework.Id.IsNetStandardOrNetCoreApp ()) { + return null; + } + + if ((HasSdk && !IsDotNetCoreSdkInstalled ()) || (sdkPaths != null && sdkPaths.IsUnsupportedSdkVersion)) { return CreateDotNetCoreSdkRequiredBuildResult (); } return null; @@ -468,13 +473,10 @@ namespace MonoDevelop.DotNetCore protected override void OnBeginLoad () { - dotNetCoreMSBuildProject.Sdk = Project.MSBuildProject.Sdk; base.OnBeginLoad (); } - public bool HasSdk { - get { return dotNetCoreMSBuildProject.HasSdk; } - } + public bool HasSdk => Project.MSBuildProject.GetReferencedSDKs ().Length > 0; protected bool IsWebProject (DotNetProject project) { @@ -491,10 +493,11 @@ namespace MonoDevelop.DotNetCore if (!HasSdk) return; - - sdkPaths = DotNetCoreSdk.FindSdkPaths (dotNetCoreMSBuildProject.Sdk); - } + var referencedSdks = project.GetReferencedSDKs (); + sdkPaths = DotNetCoreSdk.FindSdkPaths (referencedSdks); + dotNetCoreMSBuildProject.HasSdk = referencedSdks.Length > 0; + } protected override async Task<ImmutableArray<ProjectFile>> OnGetSourceFiles (ProgressMonitor monitor, ConfigurationSelector configuration) { var sourceFiles = await base.OnGetSourceFiles (monitor, configuration); @@ -608,7 +611,14 @@ namespace MonoDevelop.DotNetCore bool IsFSharpSdkProject () { - return HasSdk && dotNetCoreMSBuildProject.Sdk.Contains ("FSharp"); + if (HasSdk) { + var sdks = Project.MSBuildProject.GetReferencedSDKs (); + for (var i = 0; i < sdks.Length; i++) { + if (sdks [i].Contains ("FSharp")) + return true; + } + } + return false; } /// <summary> diff --git a/main/src/addins/MonoDevelop.DotNetCore/MonoDevelop.DotNetCore/DotNetCoreProjectSupportedTargetFrameworks.cs b/main/src/addins/MonoDevelop.DotNetCore/MonoDevelop.DotNetCore/DotNetCoreProjectSupportedTargetFrameworks.cs index 97be6b2649..9863575fb1 100644 --- a/main/src/addins/MonoDevelop.DotNetCore/MonoDevelop.DotNetCore/DotNetCoreProjectSupportedTargetFrameworks.cs +++ b/main/src/addins/MonoDevelop.DotNetCore/MonoDevelop.DotNetCore/DotNetCoreProjectSupportedTargetFrameworks.cs @@ -94,7 +94,7 @@ namespace MonoDevelop.DotNetCore public static IEnumerable<TargetFramework> GetNetCoreAppTargetFrameworks () { foreach (Version runtimeVersion in GetMajorRuntimeVersions ()) { - if (runtimeVersion.Major == 3 && runtimeVersion.Minor > 0) { + if (runtimeVersion.Major > 3 || (runtimeVersion.Major == 3 && runtimeVersion.Minor > 0)) { // Skip versions > 3.0 since this is not currently supported. continue; } diff --git a/main/src/addins/MonoDevelop.DotNetCore/MonoDevelop.DotNetCore/DotNetCoreSdk.cs b/main/src/addins/MonoDevelop.DotNetCore/MonoDevelop.DotNetCore/DotNetCoreSdk.cs index df0ef2a820..534c810f88 100644 --- a/main/src/addins/MonoDevelop.DotNetCore/MonoDevelop.DotNetCore/DotNetCoreSdk.cs +++ b/main/src/addins/MonoDevelop.DotNetCore/MonoDevelop.DotNetCore/DotNetCoreSdk.cs @@ -36,11 +36,10 @@ namespace MonoDevelop.DotNetCore public static class DotNetCoreSdk { static readonly Version DotNetCoreVersion2_1 = new Version (2, 1, 0); - static readonly DotNetCoreSdkPaths sdkPaths; static DotNetCoreSdk () { - sdkPaths = new DotNetCoreSdkPaths (); + var sdkPaths = new DotNetCoreSdkPaths (); sdkPaths.ResolveSDK (); Update (sdkPaths); } @@ -82,9 +81,11 @@ namespace MonoDevelop.DotNetCore { } - internal static DotNetCoreSdkPaths FindSdkPaths (string sdk) + internal static DotNetCoreSdkPaths FindSdkPaths (string[] sdks) { - sdkPaths.FindSdkPaths (sdk); + var sdkPaths = new DotNetCoreSdkPaths (); + sdkPaths.ResolveSDK (); + sdkPaths.FindSdkPaths (sdks); return sdkPaths; } diff --git a/main/src/addins/MonoDevelop.DotNetCore/MonoDevelop.DotNetCore/DotNetCoreSdkPaths.cs b/main/src/addins/MonoDevelop.DotNetCore/MonoDevelop.DotNetCore/DotNetCoreSdkPaths.cs index 87122fbd1a..b41d895b9c 100644 --- a/main/src/addins/MonoDevelop.DotNetCore/MonoDevelop.DotNetCore/DotNetCoreSdkPaths.cs +++ b/main/src/addins/MonoDevelop.DotNetCore/MonoDevelop.DotNetCore/DotNetCoreSdkPaths.cs @@ -119,12 +119,12 @@ namespace MonoDevelop.DotNetCore IsUnsupportedSdkVersion = false; } - public void FindSdkPaths (string sdk) + public void FindSdkPaths (string [] sdks) { if (string.IsNullOrEmpty (MSBuildSDKsPath)) return; - Exist = CheckSdksExist (sdk); + Exist = CheckSdksExist (sdks); if (Exist) { IsUnsupportedSdkVersion = !CheckIsSupportedSdkVersion (SdksParentDirectory); @@ -149,18 +149,13 @@ namespace MonoDevelop.DotNetCore string SdksParentDirectory { get; set; } - static IEnumerable<string> SplitSdks (string sdk) => sdk.Split (new [] { ';' }, StringSplitOptions.RemoveEmptyEntries); - - bool CheckSdksExist (string sdk) + bool CheckSdksExist (string[] sdks) { - if (sdk.Contains (';')) { - foreach (string sdkItem in SplitSdks (sdk)) { - if (!SdkPathExists (sdkItem)) - return false; - } - return true; + foreach (string sdkItem in sdks) { + if (!SdkPathExists (sdkItem)) + return false; } - return SdkPathExists (sdk); + return true; } bool SdkPathExists (string sdk) @@ -238,10 +233,14 @@ namespace MonoDevelop.DotNetCore using (var r = new StreamReader (GlobalJsonPath)) { try { var token = JObject.Parse (r.ReadToEnd ()); - if (token == null) - return string.Empty; - var version = (string)token.SelectToken ("sdk").SelectToken ("version"); - return version; + + if (token != null && token.TryGetValue ("sdk", out var sdkToken)) { + var version = sdkToken ["version"]; + if (version != null) + return version.Value<string>(); + } + + return string.Empty; } catch (Exception e) { LoggingService.LogWarning ($"Unable to parse {GlobalJsonPath}.", e); return string.Empty; diff --git a/main/src/addins/MonoDevelop.DotNetCore/MonoDevelop.DotNetCore/MonoRuntimeInfoExtensions.cs b/main/src/addins/MonoDevelop.DotNetCore/MonoDevelop.DotNetCore/MonoRuntimeInfoExtensions.cs index f336abfb60..2e9a399908 100644 --- a/main/src/addins/MonoDevelop.DotNetCore/MonoDevelop.DotNetCore/MonoRuntimeInfoExtensions.cs +++ b/main/src/addins/MonoDevelop.DotNetCore/MonoDevelop.DotNetCore/MonoRuntimeInfoExtensions.cs @@ -34,7 +34,7 @@ namespace MonoDevelop.DotNetCore static readonly Version MonoVersion5_4 = new Version (5, 4, 0); static readonly Version DotNetCore2_1 = new Version (2, 1); - internal static Version CurrentRuntimeVersion { get; set; } = MonoRuntimeInfo.FromCurrentRuntime ().RuntimeVersion; + internal static Version CurrentRuntimeVersion { get; set; } = MonoRuntimeInfo.FromCurrentRuntime ()?.RuntimeVersion ?? new Version (); public static bool SupportsNetStandard20 (this Version monoVersion) { diff --git a/main/src/addins/MonoDevelop.GtkCore/MonoDevelop.GtkCore.GuiBuilder/ToolboxProvider.cs b/main/src/addins/MonoDevelop.GtkCore/MonoDevelop.GtkCore.GuiBuilder/ToolboxProvider.cs index 1616424340..2df3434088 100644 --- a/main/src/addins/MonoDevelop.GtkCore/MonoDevelop.GtkCore.GuiBuilder/ToolboxProvider.cs +++ b/main/src/addins/MonoDevelop.GtkCore/MonoDevelop.GtkCore.GuiBuilder/ToolboxProvider.cs @@ -14,7 +14,7 @@ using MonoDevelop.Components; namespace MonoDevelop.GtkCore.GuiBuilder { - public class ToolboxProvider: IToolboxDynamicProvider, IToolboxDefaultProvider + public class ToolboxProvider: IToolboxDynamicProvider, IToolboxDefaultProvider, IToolboxDynamicProviderDeleteSupport { internal static ToolboxProvider Instance; @@ -82,7 +82,11 @@ namespace MonoDevelop.GtkCore.GuiBuilder { yield return typeof(Stetic.Wrapper.Widget).Assembly.Location; } - + + public virtual bool DeleteDynamicItem (ItemToolboxNode node) => false; + + public virtual bool CanDeleteDynamicItem (ItemToolboxNode node) => false; + public event EventHandler ItemsChanged; } diff --git a/main/src/addins/MonoDevelop.PackageManagement/MonoDevelop.PackageManagement.Tests/MonoDevelop.PackageManagement.Tests.Helpers/LambdaMessageHandler.cs b/main/src/addins/MonoDevelop.PackageManagement/MonoDevelop.PackageManagement.Tests/MonoDevelop.PackageManagement.Tests.Helpers/LambdaMessageHandler.cs new file mode 100644 index 0000000000..fc9c82c06a --- /dev/null +++ b/main/src/addins/MonoDevelop.PackageManagement/MonoDevelop.PackageManagement.Tests/MonoDevelop.PackageManagement.Tests.Helpers/LambdaMessageHandler.cs @@ -0,0 +1,32 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +// +// From: https://github.com/NuGet/NuGet.Client +// test/NuGet.Core.Tests/NuGet.Protocol.Tests/HttpSource/LambdaMessageHandler.cs + +using System; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; + +namespace MonoDevelop.PackageManagement.Tests +{ + internal class LambdaMessageHandler : HttpMessageHandler + { + private readonly Func<HttpRequestMessage, HttpResponseMessage> _delegate; + + public LambdaMessageHandler (Func<HttpRequestMessage, HttpResponseMessage> @delegate) + { + if (@delegate == null) { + throw new ArgumentNullException (nameof (@delegate)); + } + + _delegate = @delegate; + } + + protected override Task<HttpResponseMessage> SendAsync (HttpRequestMessage request, CancellationToken cancellationToken) + { + return Task.FromResult (_delegate (request)); + } + } +}
\ No newline at end of file diff --git a/main/src/addins/MonoDevelop.PackageManagement/MonoDevelop.PackageManagement.Tests/MonoDevelop.PackageManagement.Tests.Helpers/RestoreTestBase.cs b/main/src/addins/MonoDevelop.PackageManagement/MonoDevelop.PackageManagement.Tests/MonoDevelop.PackageManagement.Tests.Helpers/RestoreTestBase.cs index 6500fb434b..1877922a3c 100644 --- a/main/src/addins/MonoDevelop.PackageManagement/MonoDevelop.PackageManagement.Tests/MonoDevelop.PackageManagement.Tests.Helpers/RestoreTestBase.cs +++ b/main/src/addins/MonoDevelop.PackageManagement/MonoDevelop.PackageManagement.Tests/MonoDevelop.PackageManagement.Tests.Helpers/RestoreTestBase.cs @@ -30,6 +30,7 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; using MonoDevelop.Core; +using MonoDevelop.Ide; using MonoDevelop.Projects; using NuGet.Configuration; using NuGet.PackageManagement; @@ -42,7 +43,7 @@ using UnitTests; namespace MonoDevelop.PackageManagement.Tests.Helpers { - public abstract class RestoreTestBase : TestBase + public abstract class RestoreTestBase : IdeTestBase { protected Solution solution; diff --git a/main/src/addins/MonoDevelop.PackageManagement/MonoDevelop.PackageManagement.Tests/MonoDevelop.PackageManagement.Tests.Helpers/TestProxy.cs b/main/src/addins/MonoDevelop.PackageManagement/MonoDevelop.PackageManagement.Tests/MonoDevelop.PackageManagement.Tests.Helpers/TestProxy.cs new file mode 100644 index 0000000000..9e0e626fde --- /dev/null +++ b/main/src/addins/MonoDevelop.PackageManagement/MonoDevelop.PackageManagement.Tests/MonoDevelop.PackageManagement.Tests.Helpers/TestProxy.cs @@ -0,0 +1,27 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +// +// From: https://github.com/NuGet/NuGet.Client +// test/NuGet.Core.Tests/NuGet.Protocol.Tests/HttpSource/TestProxy.cs + +using System; +using System.Net; + +namespace MonoDevelop.PackageManagement.Tests +{ + internal class TestProxy : IWebProxy + { + readonly Uri proxyAddress; + + public TestProxy (Uri proxyAddress) + { + this.proxyAddress = proxyAddress; + } + + public ICredentials Credentials { get; set; } + + public Uri GetProxy (Uri destination) => proxyAddress; + + public bool IsBypassed (Uri host) => false; + } +}
\ No newline at end of file diff --git a/main/src/addins/MonoDevelop.PackageManagement/MonoDevelop.PackageManagement.Tests/MonoDevelop.PackageManagement.Tests.csproj b/main/src/addins/MonoDevelop.PackageManagement/MonoDevelop.PackageManagement.Tests/MonoDevelop.PackageManagement.Tests.csproj index f3197f3c68..8e7ea6b49c 100644 --- a/main/src/addins/MonoDevelop.PackageManagement/MonoDevelop.PackageManagement.Tests/MonoDevelop.PackageManagement.Tests.csproj +++ b/main/src/addins/MonoDevelop.PackageManagement/MonoDevelop.PackageManagement.Tests/MonoDevelop.PackageManagement.Tests.csproj @@ -1,5 +1,6 @@ <Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003" DefaultTargets="Build"> <Import Project="..\..\..\..\MonoDevelop.props" /> + <Import Project="$(ReferencesGtk)" /> <PropertyGroup> <ProjectGuid>{2645C9F3-9ED5-4806-AB09-DAD9BE90C67B}</ProjectGuid> <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> @@ -70,6 +71,14 @@ <Reference Include="NuGet.Commands"> <HintPath>..\..\..\..\packages\NuGet.Commands.4.8.0\lib\net46\NuGet.Commands.dll</HintPath> </Reference> + <Reference Include="Castle.Core"> + <HintPath>..\..\..\..\packages\Castle.Core.4.2.1\lib\net45\Castle.Core.dll</HintPath> + </Reference> + <Reference Include="System.Configuration" /> + <Reference Include="Moq"> + <HintPath>..\..\..\..\packages\Moq.4.7.145\lib\net45\Moq.dll</HintPath> + </Reference> + <Reference Include="System.Net.Http" /> </ItemGroup> <ItemGroup> <Compile Include="Properties\AssemblyInfo.cs" /> @@ -183,6 +192,9 @@ <Compile Include="MonoDevelop.PackageManagement.Tests\NuGetPackageAssetSourceFilesTests.cs" /> <Compile Include="MonoDevelop.PackageManagement.Tests.Helpers\FakeNuGetAwareProject.cs" /> <Compile Include="MonoDevelop.PackageManagement.Tests\NuGetSdkResolverTests.cs" /> + <Compile Include="MonoDevelop.PackageManagement.Tests\NuGetHttpSourceAuthenticationHandlerTests.cs" /> + <Compile Include="MonoDevelop.PackageManagement.Tests.Helpers\LambdaMessageHandler.cs" /> + <Compile Include="MonoDevelop.PackageManagement.Tests.Helpers\TestProxy.cs" /> <Compile Include="MonoDevelop.PackageManagement.Tests\AssemblyReferenceWithConditionTests.cs" /> <Compile Include="MonoDevelop.PackageManagement.Tests\ProjectReferenceMaintainerTests.cs" /> <Compile Include="MonoDevelop.PackageManagement.Tests\FullyQualifiedReferencePathTests.cs" /> @@ -213,6 +225,15 @@ <Name>UnitTests</Name> <Private>False</Private> </ProjectReference> + <ProjectReference Include="..\..\..\..\external\xwt\Xwt\Xwt.csproj"> + <Project>{92494904-35FA-4DC9-BDE9-3A3E87AC49D3}</Project> + <Name>Xwt</Name> + <Private>False</Private> + </ProjectReference> + <ProjectReference Include="..\..\..\..\tests\IdeUnitTests\IdeUnitTests.csproj"> + <Project>{F7B2B155-7CF4-42C4-B5AF-63C0667D2E4F}</Project> + <Name>IdeUnitTests</Name> + </ProjectReference> </ItemGroup> <ItemGroup> <None Include="packages.config" /> diff --git a/main/src/addins/MonoDevelop.PackageManagement/MonoDevelop.PackageManagement.Tests/MonoDevelop.PackageManagement.Tests/DotNetCoreRestoreTests.cs b/main/src/addins/MonoDevelop.PackageManagement/MonoDevelop.PackageManagement.Tests/MonoDevelop.PackageManagement.Tests/DotNetCoreRestoreTests.cs index 13e91f8a09..b501bd500e 100644 --- a/main/src/addins/MonoDevelop.PackageManagement/MonoDevelop.PackageManagement.Tests/MonoDevelop.PackageManagement.Tests/DotNetCoreRestoreTests.cs +++ b/main/src/addins/MonoDevelop.PackageManagement/MonoDevelop.PackageManagement.Tests/MonoDevelop.PackageManagement.Tests/DotNetCoreRestoreTests.cs @@ -63,7 +63,7 @@ namespace MonoDevelop.PackageManagement.Tests } [Test] - public async Task OfflineRestore_NetCore22Project () + public async Task OfflineRestore_NetCore21Project () { FilePath solutionFileName = Util.GetSampleProject ("restore-netcore-offline", "dotnetcoreconsole.sln"); solution = (Solution)await Services.ProjectService.ReadWorkspaceItem (Util.GetMonitor (), solutionFileName); diff --git a/main/src/addins/MonoDevelop.PackageManagement/MonoDevelop.PackageManagement.Tests/MonoDevelop.PackageManagement.Tests/NuGetHttpSourceAuthenticationHandlerTests.cs b/main/src/addins/MonoDevelop.PackageManagement/MonoDevelop.PackageManagement.Tests/MonoDevelop.PackageManagement.Tests/NuGetHttpSourceAuthenticationHandlerTests.cs new file mode 100644 index 0000000000..61898383cb --- /dev/null +++ b/main/src/addins/MonoDevelop.PackageManagement/MonoDevelop.PackageManagement.Tests/MonoDevelop.PackageManagement.Tests/NuGetHttpSourceAuthenticationHandlerTests.cs @@ -0,0 +1,406 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +// +// From: https://github.com/NuGet/NuGet.Client +// Based on: NuGet.Core.Tests/NuGet.Protocol.Tests/HttpSource/HttpSourceAuthenticationHandlerTests.cs + +using System; +using System.Collections.Generic; +using System.Net; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; +using MonoDevelop.Core.Web; +using Moq; +using NuGet.Configuration; +using NuGet.Protocol; +using NUnit.Framework; + +namespace MonoDevelop.PackageManagement.Tests +{ + [TestFixture] + public class NuGetHttpSourceAuthenticationHandlerTests + { + [Test] + public void Constructor_WithSourceCredentials_InitializesClientHandler () + { + var packageSource = new PackageSource ("http://package.source.net", "source") { + Credentials = new PackageSourceCredential ("source", "user", "password", isPasswordClearText: true) + }; + var clientHandler = new TestHttpClientHandler (); + var credentialService = Mock.Of<ICredentialService> (); + + var handler = new NuGetHttpSourceAuthenticationHandler (packageSource, clientHandler, credentialService); + + Assert.NotNull (clientHandler.Credentials); + + var actualCredentials = clientHandler.Credentials.GetCredential (packageSource.SourceUri, "Basic"); + Assert.NotNull (actualCredentials); + Assert.AreEqual ("user", actualCredentials.UserName); + Assert.AreEqual ("password", actualCredentials.Password); + } + + [Test] + public async Task SendAsync_WithUnauthenticatedSource_PassesThru () + { + var packageSource = new PackageSource ("http://package.source.net"); + var clientHandler = new TestHttpClientHandler (); + + var credentialService = new Mock<ICredentialService> (MockBehavior.Strict); + credentialService.SetupGet (x => x.HandlesDefaultCredentials) + .Returns (false); + var handler = new NuGetHttpSourceAuthenticationHandler (packageSource, clientHandler, credentialService.Object) { + InnerHandler = GetLambdaMessageHandler (HttpStatusCode.OK) + }; + + var response = await SendAsync (handler); + + Assert.NotNull (response); + Assert.AreEqual (HttpStatusCode.OK, response.StatusCode); + } + + [Test] + public async Task SendAsync_WithAcquiredCredentialsOn401_RetriesRequest () + { + var packageSource = new PackageSource ("http://package.source.net"); + var clientHandler = new TestHttpClientHandler (); + + var credentialService = Mock.Of<ICredentialService> (); + Mock.Get (credentialService) + .Setup ( + x => x.GetCredentialsAsync ( + packageSource.SourceUri, + It.IsAny<IWebProxy> (), + CredentialRequestType.Unauthorized, + It.IsAny<string> (), + It.IsAny<CancellationToken> ())) + .Returns (() => Task.FromResult<ICredentials> (new NetworkCredential ())); + + var handler = new NuGetHttpSourceAuthenticationHandler (packageSource, clientHandler, credentialService) { + InnerHandler = GetLambdaMessageHandler ( + HttpStatusCode.Unauthorized, HttpStatusCode.OK) + }; + + var response = await SendAsync (handler); + + Assert.NotNull (response); + Assert.AreEqual (HttpStatusCode.OK, response.StatusCode); + + Mock.Get (credentialService) + .Verify ( + x => x.GetCredentialsAsync ( + packageSource.SourceUri, + It.IsAny<IWebProxy> (), + CredentialRequestType.Unauthorized, + It.IsAny<string> (), + It.IsAny<CancellationToken> ()), + Times.Once ()); + } + + [Test] + public async Task SendAsync_WithAcquiredCredentialsOn403_RetriesRequest () + { + // Arrange + var packageSource = new PackageSource ("http://package.source.net"); + var clientHandler = new TestHttpClientHandler (); + + var credentialService = Mock.Of<ICredentialService> (); + Mock.Get (credentialService) + .Setup ( + x => x.GetCredentialsAsync ( + packageSource.SourceUri, + It.IsAny<IWebProxy> (), + CredentialRequestType.Forbidden, + It.IsAny<string> (), + It.IsAny<CancellationToken> ())) + .Returns (() => Task.FromResult<ICredentials> (new NetworkCredential ())); + + var handler = new NuGetHttpSourceAuthenticationHandler (packageSource, clientHandler, credentialService) { + InnerHandler = GetLambdaMessageHandler ( + HttpStatusCode.Forbidden, HttpStatusCode.OK) + }; + + // Act + var response = await SendAsync (handler); + + // Assert + Assert.NotNull (response); + Assert.AreEqual (HttpStatusCode.OK, response.StatusCode); + + Mock.Get (credentialService) + .Verify ( + x => x.GetCredentialsAsync ( + packageSource.SourceUri, + It.IsAny<IWebProxy> (), + CredentialRequestType.Forbidden, + It.IsAny<string> (), + It.IsAny<CancellationToken> ()), + Times.Once ()); + } + + [Test] + public async Task SendAsync_WhenTaskCanceledExceptionThrownDuringAcquiringCredentials_Throws () + { + // Arrange + var packageSource = new PackageSource ("http://package.source.net"); + var clientHandler = new TestHttpClientHandler (); + + var credentialService = Mock.Of<ICredentialService> (); + Mock.Get (credentialService) + .Setup ( + x => x.GetCredentialsAsync ( + packageSource.SourceUri, + It.IsAny<IWebProxy> (), + CredentialRequestType.Unauthorized, + It.IsAny<string> (), + It.IsAny<CancellationToken> ())) + .ThrowsAsync (new TaskCanceledException ()); + + var handler = new NuGetHttpSourceAuthenticationHandler (packageSource, clientHandler, credentialService); + + int retryCount = 0; + var innerHandler = new LambdaMessageHandler ( + _ => { + retryCount++; + return new HttpResponseMessage (HttpStatusCode.Unauthorized); + }); + handler.InnerHandler = innerHandler; + + // Act & Assert + await AssertThrowsAsync<TaskCanceledException> ( + () => SendAsync (handler)); + + Assert.AreEqual (1, retryCount); + + Mock.Get (credentialService) + .Verify ( + x => x.GetCredentialsAsync ( + packageSource.SourceUri, + It.IsAny<IWebProxy> (), + CredentialRequestType.Unauthorized, + It.IsAny<string> (), + It.IsAny<CancellationToken> ()), + Times.Once); + } + + [Test] + public async Task SendAsync_WhenOperationCanceledExceptionThrownDuringAcquiringCredentials_Throws () + { + // Arrange + var packageSource = new PackageSource ("http://package.source.net"); + var clientHandler = new TestHttpClientHandler (); + + var cts = new CancellationTokenSource (); + + var credentialService = Mock.Of<ICredentialService> (); + Mock.Get (credentialService) + .Setup ( + x => x.GetCredentialsAsync ( + packageSource.SourceUri, + It.IsAny<IWebProxy> (), + CredentialRequestType.Unauthorized, + It.IsAny<string> (), + It.IsAny<CancellationToken> ())) + .ThrowsAsync (new OperationCanceledException ()) + .Callback (() => cts.Cancel ()); + + var handler = new NuGetHttpSourceAuthenticationHandler (packageSource, clientHandler, credentialService); + + int retryCount = 0; + var innerHandler = new LambdaMessageHandler ( + _ => { + retryCount++; + return new HttpResponseMessage (HttpStatusCode.Unauthorized); + }); + handler.InnerHandler = innerHandler; + + // Act & Assert + await AssertThrowsAsync<OperationCanceledException> ( + () => SendAsync (handler)); + + Assert.AreEqual (1, retryCount); + + Mock.Get (credentialService) + .Verify ( + x => x.GetCredentialsAsync ( + packageSource.SourceUri, + It.IsAny<IWebProxy> (), + CredentialRequestType.Unauthorized, + It.IsAny<string> (), + It.IsAny<CancellationToken> ()), + Times.Once); + } + + [Test] + public async Task SendAsync_WithWrongCredentials_StopsRetryingAfter3Times () + { + // Arrange + var packageSource = new PackageSource ("http://package.source.net"); + var clientHandler = new TestHttpClientHandler (); + + var credentialService = Mock.Of<ICredentialService> (); + Mock.Get (credentialService) + .Setup ( + x => x.GetCredentialsAsync ( + packageSource.SourceUri, + It.IsAny<IWebProxy> (), + CredentialRequestType.Unauthorized, + It.IsAny<string> (), + It.IsAny<CancellationToken> ())) + .Returns (() => Task.FromResult<ICredentials> (new NetworkCredential ())); + + var handler = new NuGetHttpSourceAuthenticationHandler (packageSource, clientHandler, credentialService); + + int retryCount = 0; + var innerHandler = new LambdaMessageHandler ( + _ => { + retryCount++; + return new HttpResponseMessage (HttpStatusCode.Unauthorized); + }); + handler.InnerHandler = innerHandler; + + // Act + var response = await SendAsync (handler); + + // Assert + Assert.NotNull (response); + Assert.AreEqual (HttpStatusCode.Unauthorized, response.StatusCode); + + Assert.AreEqual (NuGetHttpSourceAuthenticationHandler.MaxAuthRetries + 1, retryCount); + + Mock.Get (credentialService) + .Verify ( + x => x.GetCredentialsAsync ( + packageSource.SourceUri, + It.IsAny<IWebProxy> (), + CredentialRequestType.Unauthorized, + It.IsAny<string> (), + It.IsAny<CancellationToken> ()), + Times.Exactly (NuGetHttpSourceAuthenticationHandler.MaxAuthRetries)); + } + + [Test] + public async Task SendAsync_WithMissingCredentials_Returns401 () + { + // Arrange + var packageSource = new PackageSource ("http://package.source.net"); + var clientHandler = new TestHttpClientHandler (); + + var credentialService = Mock.Of<ICredentialService> (); + + var handler = new NuGetHttpSourceAuthenticationHandler (packageSource, clientHandler, credentialService); + + int retryCount = 0; + var innerHandler = new LambdaMessageHandler ( + _ => { + retryCount++; + return new HttpResponseMessage (HttpStatusCode.Unauthorized); + }); + handler.InnerHandler = innerHandler; + + // Act + var response = await SendAsync (handler); + + // Assert + Assert.NotNull (response); + Assert.AreEqual (HttpStatusCode.Unauthorized, response.StatusCode); + + Assert.AreEqual (1, retryCount); + + Mock.Get (credentialService) + .Verify ( + x => x.GetCredentialsAsync ( + packageSource.SourceUri, + It.IsAny<IWebProxy> (), + CredentialRequestType.Unauthorized, + It.IsAny<string> (), + It.IsAny<CancellationToken> ()), + Times.Once ()); + } + + [Test] + public async Task SendAsync_WhenCredentialServiceThrows_Returns401 () + { + // Arrange + var packageSource = new PackageSource ("http://package.source.net"); + var clientHandler = new TestHttpClientHandler (); + + var credentialService = Mock.Of<ICredentialService> (); + Mock.Get (credentialService) + .Setup ( + x => x.GetCredentialsAsync ( + packageSource.SourceUri, + It.IsAny<IWebProxy> (), + CredentialRequestType.Unauthorized, + It.IsAny<string> (), + It.IsAny<CancellationToken> ())) + .Throws (new InvalidOperationException ("Credential service failed acquring user credentials")); + + var handler = new NuGetHttpSourceAuthenticationHandler (packageSource, clientHandler, credentialService); + + int retryCount = 0; + var innerHandler = new LambdaMessageHandler ( + _ => { + retryCount++; + return new HttpResponseMessage (HttpStatusCode.Unauthorized); + }); + handler.InnerHandler = innerHandler; + + // Act + var response = await SendAsync (handler); + + // Assert + Assert.NotNull (response); + Assert.AreEqual (HttpStatusCode.Unauthorized, response.StatusCode); + + Assert.AreEqual (1, retryCount); + + Mock.Get (credentialService) + .Verify ( + x => x.GetCredentialsAsync ( + packageSource.SourceUri, + It.IsAny<IWebProxy> (), + CredentialRequestType.Unauthorized, + It.IsAny<string> (), + It.IsAny<CancellationToken> ()), + Times.Once ()); + } + + static LambdaMessageHandler GetLambdaMessageHandler (HttpStatusCode statusCode) + { + return new LambdaMessageHandler ( + _ => new HttpResponseMessage (statusCode)); + } + + static LambdaMessageHandler GetLambdaMessageHandler (params HttpStatusCode [] statusCodes) + { + var responses = new Queue<HttpStatusCode> (statusCodes); + return new LambdaMessageHandler ( + _ => new HttpResponseMessage (responses.Dequeue ())); + } + + static async Task<HttpResponseMessage> SendAsync ( + HttpMessageHandler handler, + HttpRequestMessage request = null, + CancellationToken cancellationToken = default (CancellationToken)) + { + using (var client = new HttpClient (handler)) { + return await client.SendAsync (request ?? new HttpRequestMessage (HttpMethod.Get, "http://foo"), cancellationToken); + } + } + + static async Task AssertThrowsAsync<T> (Func<Task> handler) + { + try { + await handler (); + Assert.Fail ("Exception not thrown"); + } catch (Exception ex) { + Assert.IsInstanceOf<T> (ex); + } + } + + class TestHttpClientHandler : HttpClientHandler, IHttpCredentialsHandler + { + } + } +}
\ No newline at end of file diff --git a/main/src/addins/MonoDevelop.PackageManagement/MonoDevelop.PackageManagement.Tests/MonoDevelop.PackageManagement.Tests/UpdateXamarinFormsBuildTest.cs b/main/src/addins/MonoDevelop.PackageManagement/MonoDevelop.PackageManagement.Tests/MonoDevelop.PackageManagement.Tests/UpdateXamarinFormsBuildTest.cs index 6b20e6a00c..1f333ad4cd 100644 --- a/main/src/addins/MonoDevelop.PackageManagement/MonoDevelop.PackageManagement.Tests/MonoDevelop.PackageManagement.Tests/UpdateXamarinFormsBuildTest.cs +++ b/main/src/addins/MonoDevelop.PackageManagement/MonoDevelop.PackageManagement.Tests/MonoDevelop.PackageManagement.Tests/UpdateXamarinFormsBuildTest.cs @@ -27,6 +27,7 @@ using System.Diagnostics;
using System.Linq; using System.Threading.Tasks; +using MonoDevelop.Core; using MonoDevelop.PackageManagement.Tests.Helpers; using MonoDevelop.Projects; using NuGet.Versioning; @@ -38,6 +39,23 @@ namespace MonoDevelop.PackageManagement.Tests [TestFixture] public class UpdateXamarinFormsBuildTest : RestoreTestBase { + static readonly string UseNSUrlSessionHandlerProperty = "MonoDevelop.MacIntegration.UseNSUrlSessionHandler"; + static bool originalSessionHandlerPropertyValue; + + [TestFixtureSetUp] + public void TestFixtureSetup () + { + // Disable use of Xamarin.Mac's NSUrlSessionHandler for tests. Xamarin.Mac is not initialized. + originalSessionHandlerPropertyValue = PropertyService.Get (UseNSUrlSessionHandlerProperty, true); + PropertyService.Set (UseNSUrlSessionHandlerProperty, false); + } + + [TestFixtureTearDown] + public void TestFixtureTearDown () + { + PropertyService.Set (UseNSUrlSessionHandlerProperty, originalSessionHandlerPropertyValue); + } + /// <summary> /// Tests that a build error due to a mismatched Xamarin.Forms NuGet package used /// in two projects can be fixed by updating the NuGet package to match without diff --git a/main/src/addins/MonoDevelop.PackageManagement/MonoDevelop.PackageManagement.Tests/packages.config b/main/src/addins/MonoDevelop.PackageManagement/MonoDevelop.PackageManagement.Tests/packages.config index ee51c23738..b6e3ec09f6 100644 --- a/main/src/addins/MonoDevelop.PackageManagement/MonoDevelop.PackageManagement.Tests/packages.config +++ b/main/src/addins/MonoDevelop.PackageManagement/MonoDevelop.PackageManagement.Tests/packages.config @@ -1,4 +1,6 @@ <?xml version="1.0" encoding="utf-8"?> <packages> + <package id="Castle.Core" version="4.2.1" targetFramework="net471" /> + <package id="Moq" version="4.7.145" targetFramework="net471" /> <package id="Newtonsoft.Json" version="10.0.3" targetFramework="net45" /> </packages>
\ No newline at end of file diff --git a/main/src/addins/MonoDevelop.PackageManagement/MonoDevelop.PackageManagement.csproj b/main/src/addins/MonoDevelop.PackageManagement/MonoDevelop.PackageManagement.csproj index fe47de2d96..f37a483a88 100644 --- a/main/src/addins/MonoDevelop.PackageManagement/MonoDevelop.PackageManagement.csproj +++ b/main/src/addins/MonoDevelop.PackageManagement/MonoDevelop.PackageManagement.csproj @@ -370,6 +370,10 @@ <Compile Include="MonoDevelop.PackageManagement\ItemTemplateNuGetPackageInstaller.cs" /> <Compile Include="Gui\MonoDevelop.PackageManagement.PackageManagementOptionsWidget.cs" /> <Compile Include="Gui\MonoDevelop.PackageManagement.PackageSourcesWidget.cs" /> + <Compile Include="MonoDevelop.PackageManagement\RepositoryProviderFactoryExtensions.cs" /> + <Compile Include="MonoDevelop.PackageManagement\MonoDevelopHttpHandlerResourceV3Provider.cs" /> + <Compile Include="MonoDevelop.PackageManagement\MonoDevelopServerWarningLogHandler.cs" /> + <Compile Include="MonoDevelop.PackageManagement\NuGetHttpSourceAuthenticationHandler.cs" /> <Compile Include="MonoDevelop.PackageManagement\ProjectReferenceMaintainer.cs" /> <Compile Include="MonoDevelop.PackageManagement\IProjectReferenceMaintainer.cs" /> <Compile Include="MonoDevelop.PackageManagement\IHasProjectReferenceMaintainer.cs" /> diff --git a/main/src/addins/MonoDevelop.PackageManagement/MonoDevelop.PackageManagement/HttpClientFactory.cs b/main/src/addins/MonoDevelop.PackageManagement/MonoDevelop.PackageManagement/HttpClientFactory.cs index 0222a6a372..d8d260abfc 100644 --- a/main/src/addins/MonoDevelop.PackageManagement/MonoDevelop.PackageManagement/HttpClientFactory.cs +++ b/main/src/addins/MonoDevelop.PackageManagement/MonoDevelop.PackageManagement/HttpClientFactory.cs @@ -25,14 +25,15 @@ // THE SOFTWARE. using System; +using System.Globalization; using System.Linq; using System.Net; using System.Net.Http; +using NuGet.Common; using NuGet.Configuration; +using NuGet.Credentials; using NuGet.Protocol; using NuGet.Protocol.Core.Types; -using NuGet.Common; -using NuGet.Credentials; namespace MonoDevelop.PackageManagement { @@ -47,37 +48,23 @@ namespace MonoDevelop.PackageManagement { var httpClient = new HttpClient (CreateHttpMessageHandler (packageSource, credentialService)); UserAgent.SetUserAgent (httpClient); + SetAcceptLanguage (httpClient); return httpClient; } - static HttpMessageHandler CreateHttpMessageHandler (PackageSource packageSource, ICredentialService credentialService) + static void SetAcceptLanguage (HttpClient httpClient) { - var proxy = ProxyCache.Instance.GetProxy (packageSource.SourceUri); - - var clientHandler = new HttpClientHandler { - Proxy = proxy, - AutomaticDecompression = (DecompressionMethods.GZip | DecompressionMethods.Deflate) - }; - - HttpMessageHandler messageHandler = clientHandler; - - if (proxy != null) { - messageHandler = new ProxyAuthenticationHandler (clientHandler, credentialService, ProxyCache.Instance); + string acceptLanguage = CultureInfo.CurrentUICulture.ToString (); + if (!string.IsNullOrEmpty (acceptLanguage)) { + httpClient.DefaultRequestHeaders.AcceptLanguage.ParseAdd (acceptLanguage); } + } - HttpMessageHandler innerHandler = messageHandler; - messageHandler = new StsAuthenticationHandler (packageSource, TokenStore.Instance) { - InnerHandler = messageHandler - }; - - innerHandler = messageHandler; - - messageHandler = new HttpSourceAuthenticationHandler (packageSource, clientHandler, credentialService) { - InnerHandler = innerHandler - }; - - return messageHandler; + static HttpMessageHandler CreateHttpMessageHandler (PackageSource packageSource, ICredentialService credentialService) + { + HttpHandlerResourceV3 resource = MonoDevelopHttpHandlerResourceV3Provider.CreateResource (packageSource, credentialService, nonInteractive: true); + return resource.MessageHandler; } public static ICredentialService CreateNonInteractiveCredentialService () diff --git a/main/src/addins/MonoDevelop.PackageManagement/MonoDevelop.PackageManagement/MonoDevelopHttpHandlerResourceV3Provider.cs b/main/src/addins/MonoDevelop.PackageManagement/MonoDevelop.PackageManagement/MonoDevelopHttpHandlerResourceV3Provider.cs new file mode 100644 index 0000000000..2776eac384 --- /dev/null +++ b/main/src/addins/MonoDevelop.PackageManagement/MonoDevelop.PackageManagement/MonoDevelopHttpHandlerResourceV3Provider.cs @@ -0,0 +1,118 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +// +// Based on HttpHandlerResourceV3Provider +// From: https://github.com/NuGet/NuGet.Client/ + +using System; +using System.Diagnostics; +using System.Net; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; +using MonoDevelop.Core.Web; +using NuGet.Configuration; +using NuGet.Protocol; +using NuGet.Protocol.Core.Types; + +namespace MonoDevelop.PackageManagement +{ + class MonoDevelopHttpHandlerResourceV3Provider : ResourceProvider + { + public MonoDevelopHttpHandlerResourceV3Provider () + : base (typeof (HttpHandlerResource), + nameof (MonoDevelopHttpHandlerResourceV3Provider), + NuGetResourceProviderPositions.Last) + { + } + + public override Task<Tuple<bool, INuGetResource>> TryCreate (SourceRepository source, CancellationToken token) + { + Debug.Assert (source.PackageSource.IsHttp, "HTTP handler requested for a non-http source."); + + HttpHandlerResourceV3 curResource = null; + + if (source.PackageSource.IsHttp) { + curResource = CreateResource (source.PackageSource); + } + + return Task.FromResult (new Tuple<bool, INuGetResource> (curResource != null, curResource)); + } + + static HttpHandlerResourceV3 CreateResource (PackageSource packageSource) + { + return CreateResource (packageSource, HttpHandlerResourceV3.CredentialService.Value); + } + + internal static HttpHandlerResourceV3 CreateResource (PackageSource packageSource, ICredentialService credentialService, bool nonInteractive = false) + { + var sourceUri = packageSource.SourceUri; + var settings = new HttpClientSettings { + AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate, + SourceAuthenticationRequired = false, + NonInteractive = nonInteractive + }; + var rootHandler = HttpClientProvider.CreateHttpMessageHandler (sourceUri, settings); + + // HTTP handler pipeline can be injected here, around the client handler + HttpMessageHandler messageHandler = new MonoDevelopServerWarningLogHandler (rootHandler); + + var innerHandler = messageHandler; + + messageHandler = new StsAuthenticationHandler (packageSource, TokenStore.Instance) { + InnerHandler = messageHandler + }; + + innerHandler = messageHandler; + var credentialsHandler = GetHttpCredentialsHandler (rootHandler); + + messageHandler = new NuGetHttpSourceAuthenticationHandler (packageSource, credentialsHandler, credentialService) { + InnerHandler = innerHandler + }; + + // Have to pass a dummy HttpClientProvider since it may not be used by a native implementation, such as on the Mac. + // It looks like the only place this is used in NuGet is with the DownloadResourcePluginProvider in order to pass the + // HttpClientHandler's Proxy to the GetCredentialsRequestHandler. There is no plugin support yet in MonoDevelop but + // this may be a problem in the future. Possibly a custom DownloadResourcePluginProvider would be required or possibly + // a HttpClientProvider created with just its Proxy property set based on the current proxy for that url. + var clientHandler = GetOrCreateHttpClientHandler (rootHandler); + + // Get the proxy from NuGet's ProxyCache which has support for proxy information define in the NuGet.Config file. + var proxy = ProxyCache.Instance.GetUserConfiguredProxy (); + if (proxy != null) { + clientHandler.Proxy = proxy; + } + var resource = new HttpHandlerResourceV3 (clientHandler, messageHandler); + + return resource; + } + + static IHttpCredentialsHandler GetHttpCredentialsHandler (HttpMessageHandler handler) + { + do { + if (handler is IHttpCredentialsHandler credentialsHandler) { + return credentialsHandler; + } + + var delegatingHandler = handler as DelegatingHandler; + handler = delegatingHandler?.InnerHandler; + } while (handler != null); + + return null; + } + + static HttpClientHandler GetOrCreateHttpClientHandler (HttpMessageHandler handler) + { + do { + if (handler is HttpClientHandler clientHandler) { + return clientHandler; + } + + var delegatingHandler = handler as DelegatingHandler; + handler = delegatingHandler?.InnerHandler; + } while (handler != null); + + return new HttpClientHandler (); + } + } +} diff --git a/main/src/addins/MonoDevelop.PackageManagement/MonoDevelop.PackageManagement/MonoDevelopServerWarningLogHandler.cs b/main/src/addins/MonoDevelop.PackageManagement/MonoDevelop.PackageManagement/MonoDevelopServerWarningLogHandler.cs new file mode 100644 index 0000000000..98a7d5629f --- /dev/null +++ b/main/src/addins/MonoDevelop.PackageManagement/MonoDevelop.PackageManagement/MonoDevelopServerWarningLogHandler.cs @@ -0,0 +1,32 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +// +// Based on ServerWarningLogHandler. +// From: https://github.com/NuGet/NuGet.Client/From: https://github.com/NuGet/NuGet.Client/ + +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; +using NuGet.Protocol; + +namespace MonoDevelop.PackageManagement +{ + class MonoDevelopServerWarningLogHandler : DelegatingHandler + { + public MonoDevelopServerWarningLogHandler (HttpMessageHandler innerHandler) + : base (innerHandler) + { + } + + protected override async Task<HttpResponseMessage> SendAsync (HttpRequestMessage request, CancellationToken cancellationToken) + { + var configuration = request.GetOrCreateConfiguration (); + + var response = await base.SendAsync (request, cancellationToken).ConfigureAwait (false); + + response.LogServerWarning (configuration.Logger); + + return response; + } + } +} diff --git a/main/src/addins/MonoDevelop.PackageManagement/MonoDevelop.PackageManagement/NuGetHttpSourceAuthenticationHandler.cs b/main/src/addins/MonoDevelop.PackageManagement/MonoDevelop.PackageManagement/NuGetHttpSourceAuthenticationHandler.cs new file mode 100644 index 0000000000..88b0b8fa24 --- /dev/null +++ b/main/src/addins/MonoDevelop.PackageManagement/MonoDevelop.PackageManagement/NuGetHttpSourceAuthenticationHandler.cs @@ -0,0 +1,230 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +// +// Based on HttpSourceAuthenticationHandler +// From: https://github.com/NuGet/NuGet.Client/ + +using System; +using System.Collections.Generic; +using System.Net; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; +using MonoDevelop.Core.Web; +using NuGet.Common; +using NuGet.Configuration; + +namespace NuGet.Protocol +{ + class NuGetHttpSourceAuthenticationHandler : DelegatingHandler + { + public static readonly int MaxAuthRetries = 4; + + // Only one source may prompt at a time + readonly static SemaphoreSlim credentialPromptLock = new SemaphoreSlim (1, 1); + + readonly PackageSource packageSource; + readonly IHttpCredentialsHandler credentialsHandler; + readonly ICredentialService credentialService; + + readonly SemaphoreSlim httpClientLock = new SemaphoreSlim (1, 1); + Dictionary<string, AmbientAuthenticationState> authStates = new Dictionary<string, AmbientAuthenticationState> (); + HttpSourceCredentials credentials; + + public NuGetHttpSourceAuthenticationHandler ( + PackageSource packageSource, + IHttpCredentialsHandler credentialsHandler, + ICredentialService credentialService) + { + if (packageSource == null) { + throw new ArgumentNullException (nameof (packageSource)); + } + + this.packageSource = packageSource; + + if (credentialsHandler == null) { + throw new ArgumentNullException (nameof (credentialsHandler)); + } + + this.credentialsHandler = credentialsHandler; + + // credential service is optional as credentials may be attached to a package source + this.credentialService = credentialService; + + // Create a new wrapper for ICredentials that can be modified + + if (credentialService == null || !credentialService.HandlesDefaultCredentials) { + // This is used to match the value of HttpClientHandler.UseDefaultCredentials = true + credentials = new HttpSourceCredentials (CredentialCache.DefaultNetworkCredentials); + } else { + credentials = new HttpSourceCredentials (); + } + + if (packageSource.Credentials != null && + packageSource.Credentials.IsValid ()) { + var sourceCredentials = new NetworkCredential (packageSource.Credentials.Username, packageSource.Credentials.Password); + credentials.Credentials = sourceCredentials; + } + + this.credentialsHandler.Credentials = credentials; + // Always take the credentials from the helper. + this.credentialsHandler.UseDefaultCredentials = false; + } + + protected override async Task<HttpResponseMessage> SendAsync (HttpRequestMessage request, CancellationToken cancellationToken) + { + HttpResponseMessage response = null; + ICredentials promptCredentials = null; + + var configuration = request.GetOrCreateConfiguration (); + + // Authorizing may take multiple attempts + while (true) { + // Clean up any previous responses + if (response != null) { + response.Dispose (); + } + + // store the auth state before sending the request + var beforeLockVersion = credentials.Version; + + response = await base.SendAsync (request, cancellationToken).ConfigureAwait (false); + + if (credentialService == null) { + return response; + } + + if (response.StatusCode == HttpStatusCode.Unauthorized || + (configuration.PromptOn403 && response.StatusCode == HttpStatusCode.Forbidden)) { + promptCredentials = await AcquireCredentialsAsync ( + response.StatusCode, + beforeLockVersion, + configuration.Logger, + cancellationToken); + + if (promptCredentials == null) { + return response; + } + + continue; + } + + if (promptCredentials != null) { + CredentialsSuccessfullyUsed (packageSource.SourceUri, promptCredentials); + } + + return response; + } + } + + private async Task<ICredentials> AcquireCredentialsAsync (HttpStatusCode statusCode, Guid credentialsVersion, ILogger log, CancellationToken cancellationToken) + { + try { + // Only one request may prompt and attempt to auth at a time + await httpClientLock.WaitAsync (); + + cancellationToken.ThrowIfCancellationRequested (); + + // Auth may have happened on another thread, if so just continue + if (credentialsVersion != credentials.Version) { + return credentials.Credentials; + } + + var authState = GetAuthenticationState (); + + if (authState.IsBlocked) { + cancellationToken.ThrowIfCancellationRequested (); + + return null; + } + + // Construct a reasonable message for the prompt to use. + CredentialRequestType type; + if (statusCode == HttpStatusCode.Unauthorized) { + type = CredentialRequestType.Unauthorized; + } else { + type = CredentialRequestType.Forbidden; + } + + var promptCredentials = await PromptForCredentialsAsync ( + type, + authState, + log, + cancellationToken); + + if (promptCredentials == null) { + return null; + } + + credentials.Credentials = promptCredentials; + + return promptCredentials; + } finally { + httpClientLock.Release (); + } + } + + AmbientAuthenticationState GetAuthenticationState () + { + var correlationId = ActivityCorrelationId.Current; + + AmbientAuthenticationState authState; + if (!authStates.TryGetValue (correlationId, out authState)) { + authState = new AmbientAuthenticationState (); + authStates [correlationId] = authState; + } + + return authState; + } + + async Task<ICredentials> PromptForCredentialsAsync ( + CredentialRequestType type, + AmbientAuthenticationState authState, + ILogger log, + CancellationToken token) + { + ICredentials promptCredentials; + + try { + // Only one prompt may display at a time. + await credentialPromptLock.WaitAsync (); + + // Get the proxy for this URI so we can pass it to the credentialService methods + // this lets them use the proxy if they have to hit the network. + var proxyCache = ProxyCache.Instance; + var proxy = proxyCache?.GetProxy (packageSource.SourceUri); + + promptCredentials = await credentialService + .GetCredentialsAsync (packageSource.SourceUri, proxy, type, string.Empty, token); + + if (promptCredentials == null) { + // If this is the case, this means none of the credential providers were able to + // handle the credential request or no credentials were available for the + // endpoint. + authState.Block (); + } else { + authState.Increment (); + } + } catch (OperationCanceledException) { + // This indicates a non-human cancellation. + throw; + } catch (Exception e) { + // If this is the case, this means there was a fatal exception when interacting + // with the credential service (or its underlying credential providers). Either way, + // block asking for credentials for the live of this operation. + log.LogError (ExceptionUtilities.DisplayMessage (e)); + promptCredentials = null; + authState.Block (); + } finally { + credentialPromptLock.Release (); + } + + return promptCredentials; + } + + void CredentialsSuccessfullyUsed (Uri uri, ICredentials usedCredentials) + { + HttpHandlerResourceV3.CredentialsSuccessfullyUsed?.Invoke (uri, usedCredentials); + } + } +}
\ No newline at end of file diff --git a/main/src/addins/MonoDevelop.PackageManagement/MonoDevelop.PackageManagement/RepositoryProviderFactoryExtensions.cs b/main/src/addins/MonoDevelop.PackageManagement/MonoDevelop.PackageManagement/RepositoryProviderFactoryExtensions.cs new file mode 100644 index 0000000000..c106bcf839 --- /dev/null +++ b/main/src/addins/MonoDevelop.PackageManagement/MonoDevelop.PackageManagement/RepositoryProviderFactoryExtensions.cs @@ -0,0 +1,74 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +// +// Based on FactoryExtensionsV3 and FactoryExtensionsVS +// From: https://github.com/NuGet/NuGet.Client/ + +using System; +using System.Collections.Generic; +using NuGet.Protocol; +using NuGet.Protocol.Core.Types; +using NuGet.Protocol.LocalRepositories; + +namespace MonoDevelop.PackageManagement +{ + static class RepositoryProviderFactoryExtensions + { + public static IEnumerable<Lazy<INuGetResourceProvider>> GetMonoDevelop (this Repository.ProviderFactory factory) + { + foreach (Lazy<INuGetResourceProvider> item in Repository.Provider.GetMonoDevelopCoreV3 ()) { + yield return item; + } + } + + /// <summary> + /// Includes a custom HttpHandlerResourceV3Provider which can use native HttpMessageHandlers defined by MonoDevelop. + /// </summary> + public static IEnumerable<Lazy<INuGetResourceProvider>> GetMonoDevelopCoreV3 (this Repository.ProviderFactory factory) + { + yield return new Lazy<INuGetResourceProvider> (() => new FeedTypeResourceProvider ()); + yield return new Lazy<INuGetResourceProvider> (() => new DependencyInfoResourceV3Provider ()); + yield return new Lazy<INuGetResourceProvider> (() => new DownloadResourcePluginProvider ()); + yield return new Lazy<INuGetResourceProvider> (() => new DownloadResourceV3Provider ()); + yield return new Lazy<INuGetResourceProvider> (() => new MetadataResourceV3Provider ()); + yield return new Lazy<INuGetResourceProvider> (() => new RawSearchResourceV3Provider ()); + yield return new Lazy<INuGetResourceProvider> (() => new RegistrationResourceV3Provider ()); + yield return new Lazy<INuGetResourceProvider> (() => new ReportAbuseResourceV3Provider ()); + yield return new Lazy<INuGetResourceProvider> (() => new ServiceIndexResourceV3Provider ()); + yield return new Lazy<INuGetResourceProvider> (() => new ODataServiceDocumentResourceV2Provider ()); + yield return new Lazy<INuGetResourceProvider> (() => new MonoDevelopHttpHandlerResourceV3Provider ()); + yield return new Lazy<INuGetResourceProvider> (() => new HttpSourceResourceProvider ()); + yield return new Lazy<INuGetResourceProvider> (() => new PluginFindPackageByIdResourceProvider ()); + yield return new Lazy<INuGetResourceProvider> (() => new HttpFileSystemBasedFindPackageByIdResourceProvider ()); + yield return new Lazy<INuGetResourceProvider> (() => new RemoteV3FindPackageByIdResourceProvider ()); + yield return new Lazy<INuGetResourceProvider> (() => new RemoteV2FindPackageByIdResourceProvider ()); + yield return new Lazy<INuGetResourceProvider> (() => new LocalV3FindPackageByIdResourceProvider ()); + yield return new Lazy<INuGetResourceProvider> (() => new LocalV2FindPackageByIdResourceProvider ()); + yield return new Lazy<INuGetResourceProvider> (() => new PackageUpdateResourceV2Provider ()); + yield return new Lazy<INuGetResourceProvider> (() => new PackageUpdateResourceV3Provider ()); + yield return new Lazy<INuGetResourceProvider> (() => new DependencyInfoResourceV2FeedProvider ()); + yield return new Lazy<INuGetResourceProvider> (() => new DownloadResourceV2FeedProvider ()); + yield return new Lazy<INuGetResourceProvider> (() => new MetadataResourceV2FeedProvider ()); + yield return new Lazy<INuGetResourceProvider> (() => new V3FeedListResourceProvider ()); + yield return new Lazy<INuGetResourceProvider> (() => new V2FeedListResourceProvider ()); + yield return new Lazy<INuGetResourceProvider> (() => new LocalPackageListResourceProvider ()); + yield return new Lazy<INuGetResourceProvider> (() => new PackageSearchResourceV2FeedProvider ()); + yield return new Lazy<INuGetResourceProvider> (() => new PackageSearchResourceV3Provider ()); + yield return new Lazy<INuGetResourceProvider> (() => new PackageMetadataResourceV2FeedProvider ()); + yield return new Lazy<INuGetResourceProvider> (() => new PackageMetadataResourceV3Provider ()); + yield return new Lazy<INuGetResourceProvider> (() => new AutoCompleteResourceV2FeedProvider ()); + yield return new Lazy<INuGetResourceProvider> (() => new AutoCompleteResourceV3Provider ()); + yield return new Lazy<INuGetResourceProvider> (() => new PluginResourceProvider ()); + yield return new Lazy<INuGetResourceProvider> (() => new FindLocalPackagesResourceUnzippedProvider ()); + yield return new Lazy<INuGetResourceProvider> (() => new FindLocalPackagesResourceV2Provider ()); + yield return new Lazy<INuGetResourceProvider> (() => new FindLocalPackagesResourceV3Provider ()); + yield return new Lazy<INuGetResourceProvider> (() => new FindLocalPackagesResourcePackagesConfigProvider ()); + yield return new Lazy<INuGetResourceProvider> (() => new LocalAutoCompleteResourceProvider ()); + yield return new Lazy<INuGetResourceProvider> (() => new LocalDependencyInfoResourceProvider ()); + yield return new Lazy<INuGetResourceProvider> (() => new LocalDownloadResourceProvider ()); + yield return new Lazy<INuGetResourceProvider> (() => new LocalMetadataResourceProvider ()); + yield return new Lazy<INuGetResourceProvider> (() => new LocalPackageMetadataResourceProvider ()); + yield return new Lazy<INuGetResourceProvider> (() => new LocalPackageSearchResourceProvider ()); + } + } +} diff --git a/main/src/addins/MonoDevelop.PackageManagement/MonoDevelop.PackageManagement/SourceRepositoryProvider.cs b/main/src/addins/MonoDevelop.PackageManagement/MonoDevelop.PackageManagement/SourceRepositoryProvider.cs index c1264c3bb8..3bc25eef1a 100644 --- a/main/src/addins/MonoDevelop.PackageManagement/MonoDevelop.PackageManagement/SourceRepositoryProvider.cs +++ b/main/src/addins/MonoDevelop.PackageManagement/MonoDevelop.PackageManagement/SourceRepositoryProvider.cs @@ -26,7 +26,6 @@ using NuGet.Configuration;
using NuGet.Protocol.Core.Types;
-using NuGet.Protocol.VisualStudio;
namespace MonoDevelop.PackageManagement
{
@@ -41,7 +40,7 @@ namespace MonoDevelop.PackageManagement public static ISourceRepositoryProvider CreateSourceRepositoryProvider (ISettings settings)
{
var packageSourceProvider = new MonoDevelopPackageSourceProvider (settings);
- return new SourceRepositoryProvider (packageSourceProvider, Repository.Provider.GetVisualStudio ());
+ return new SourceRepositoryProvider (packageSourceProvider, Repository.Provider.GetMonoDevelop ());
}
}
}
diff --git a/main/src/addins/MonoDevelop.Packaging/Gui/MonoDevelop.Packaging.Gui.GtkNuGetPackageMetadataOptionsPanelWidget.cs b/main/src/addins/MonoDevelop.Packaging/Gui/MonoDevelop.Packaging.Gui.GtkNuGetPackageMetadataOptionsPanelWidget.cs index 49a3bd30d5..7af75f47b5 100644 --- a/main/src/addins/MonoDevelop.Packaging/Gui/MonoDevelop.Packaging.Gui.GtkNuGetPackageMetadataOptionsPanelWidget.cs +++ b/main/src/addins/MonoDevelop.Packaging/Gui/MonoDevelop.Packaging.Gui.GtkNuGetPackageMetadataOptionsPanelWidget.cs @@ -48,7 +48,7 @@ namespace MonoDevelop.Packaging.Gui private global::Gtk.HBox packageLanguageHBox; - private global::Gtk.ComboBoxEntry packageLanguageComboBox; + private global::Gtk.ComboBox packageLanguageComboBox; private global::Gtk.Label packageLanguageLabel; @@ -321,7 +321,7 @@ namespace MonoDevelop.Packaging.Gui this.packageLanguageHBox.Name = "packageLanguageHBox"; this.packageLanguageHBox.Spacing = 6; // Container child packageLanguageHBox.Gtk.Box+BoxChild - this.packageLanguageComboBox = global::Gtk.ComboBoxEntry.NewText(); + this.packageLanguageComboBox = global::Gtk.ComboBox.NewText(); this.packageLanguageComboBox.Name = "packageLanguageComboBox"; this.packageLanguageHBox.Add(this.packageLanguageComboBox); global::Gtk.Box.BoxChild w19 = ((global::Gtk.Box.BoxChild)(this.packageLanguageHBox[this.packageLanguageComboBox])); diff --git a/main/src/addins/MonoDevelop.Packaging/MonoDevelop.Packaging.Gui/GtkNuGetPackageMetadataOptionsPanelWidget.cs b/main/src/addins/MonoDevelop.Packaging/MonoDevelop.Packaging.Gui/GtkNuGetPackageMetadataOptionsPanelWidget.cs index 3f6f091de5..8e2ab47e58 100644 --- a/main/src/addins/MonoDevelop.Packaging/MonoDevelop.Packaging.Gui/GtkNuGetPackageMetadataOptionsPanelWidget.cs +++ b/main/src/addins/MonoDevelop.Packaging/MonoDevelop.Packaging.Gui/GtkNuGetPackageMetadataOptionsPanelWidget.cs @@ -42,6 +42,8 @@ namespace MonoDevelop.Packaging.Gui NuGetPackageMetadata metadata; bool projectOriginallyHadMetadata; bool hasPackageId; + List<CultureInfo> languages; + ListStore languagesListStore; public GtkNuGetPackageMetadataOptionsPanelWidget () { @@ -58,85 +60,68 @@ namespace MonoDevelop.Packaging.Gui packageReleaseNotesPaddingLabel.Accessible.Role = Atk.Role.Filler; packageIdTextBox.SetCommonAccessibilityAttributes ("NuGetMetadata.ID", - GettextCatalog.GetString ("ID"), - GettextCatalog.GetString ("Enter the ID of the NuGet package")); - packageIdTextBox.SetAccessibilityLabelRelationship (packageIdLabel); + packageIdLabel, + GettextCatalog.GetString ("Enter the ID of the NuGet package")); packageVersionTextBox.SetCommonAccessibilityAttributes ("NuGetMetadata.Version", - GettextCatalog.GetString ("Version"), - GettextCatalog.GetString ("Enter the version of the NuGet package")); - packageVersionTextBox.SetAccessibilityLabelRelationship (packageVersionLabel); + packageVersionLabel, + GettextCatalog.GetString ("Enter the version of the NuGet package")); packageAuthorsTextBox.SetCommonAccessibilityAttributes ("NuGetMetadata.Authors", - GettextCatalog.GetString ("Authors"), - GettextCatalog.GetString ("Enter the authors of the NuGet package")); - packageAuthorsTextBox.SetAccessibilityLabelRelationship (packageAuthorsLabel); + packageAuthorsLabel, + GettextCatalog.GetString ("Enter the authors of the NuGet package")); packageDescriptionTextView.SetCommonAccessibilityAttributes ("NuGetMetadata.Description", - GettextCatalog.GetString ("Description"), - GettextCatalog.GetString ("Enter the description of the NuGet package")); - packageDescriptionTextView.SetAccessibilityLabelRelationship (packageDescriptionLabel); + packageDescriptionLabel, + GettextCatalog.GetString ("Enter the description of the NuGet package")); packageOwnersTextBox.SetCommonAccessibilityAttributes ("NuGetMetadata.Owners", - GettextCatalog.GetString ("Owners"), - GettextCatalog.GetString ("Enter the owners of the NuGet package")); - packageOwnersTextBox.SetAccessibilityLabelRelationship (packageOwnersLabel); + packageOwnersLabel, + GettextCatalog.GetString ("Enter the owners of the NuGet package")); packageCopyrightTextBox.SetCommonAccessibilityAttributes ("NuGetMetadata.Copyright", - GettextCatalog.GetString ("Copyright"), - GettextCatalog.GetString ("Enter the copyright statement for the NuGet package")); - packageCopyrightTextBox.SetAccessibilityLabelRelationship (packageCopyrightLabel); + packageCopyrightLabel, + GettextCatalog.GetString ("Enter the copyright statement for the NuGet package")); packageTitleTextBox.SetCommonAccessibilityAttributes ("NuGetMetadata.Title", - GettextCatalog.GetString ("Title"), - GettextCatalog.GetString ("Enter the title of the NuGet package")); - packageTitleTextBox.SetAccessibilityLabelRelationship (packageTitleLabel); + packageTitleLabel, + GettextCatalog.GetString ("Enter the title of the NuGet package")); packageSummaryTextBox.SetCommonAccessibilityAttributes ("NuGetMetadata.Summary", - GettextCatalog.GetString ("Summary"), - GettextCatalog.GetString ("Enter the summary for the NuGet package")); - packageSummaryTextBox.SetAccessibilityLabelRelationship (packageSummaryLabel); + packageSummaryLabel, + GettextCatalog.GetString ("Enter the summary for the NuGet package")); packageProjectUrlTextBox.SetCommonAccessibilityAttributes ("NuGetMetadata.URL", - GettextCatalog.GetString ("Project URL"), - GettextCatalog.GetString ("Enter the project URL for the NuGet package")); - packageProjectUrlTextBox.SetAccessibilityLabelRelationship (packageProjectUrlLabel); + packageProjectUrlLabel, + GettextCatalog.GetString ("Enter the project URL for the NuGet package")); packageIconUrlTextBox.SetCommonAccessibilityAttributes ("NuGetMetadata.Icon", - GettextCatalog.GetString ("Icon URL"), - GettextCatalog.GetString ("Enter the URL for the NuGet package's icon")); - packageIconUrlTextBox.SetAccessibilityLabelRelationship (packageIconUrlLabel); + packageIconUrlLabel, + GettextCatalog.GetString ("Enter the URL for the NuGet package's icon")); packageLicenseUrlTextBox.SetCommonAccessibilityAttributes ("NuGetMetadata.licence", - GettextCatalog.GetString ("License URL"), - GettextCatalog.GetString ("Enter the URL for the NuGet package's license")); - packageLicenseUrlTextBox.SetAccessibilityLabelRelationship (packageLicenseUrlLabel); + packageLicenseUrlLabel, + GettextCatalog.GetString ("Enter the URL for the NuGet package's license")); packageRequireLicenseAcceptanceCheckBox.SetCommonAccessibilityAttributes ("NuGetMetadata.Acceptance", - GettextCatalog.GetString ("Require License Acceptance"), - GettextCatalog.GetString ("Check to require the user to accept the NuGet package's license")); - packageRequireLicenseAcceptanceCheckBox.SetAccessibilityLabelRelationship (packageRequireLicenseAcceptanceLabel); + packageRequireLicenseAcceptanceLabel, + GettextCatalog.GetString ("Check to require the user to accept the NuGet package's license")); packageDevelopmentDependencyCheckBox.SetCommonAccessibilityAttributes ("NuGetMetadata.Development", - GettextCatalog.GetString ("Development Dependency"), - GettextCatalog.GetString ("Check to indicate that this is a development dependency")); - packageDevelopmentDependencyCheckBox.SetAccessibilityLabelRelationship (packageDevelopmentDependencyLabel); + packageDevelopmentDependencyLabel, + GettextCatalog.GetString ("Check to indicate that this is a development dependency")); packageTagsTextBox.SetCommonAccessibilityAttributes ("NuGetMetadata.Tags", - GettextCatalog.GetString ("Tags"), - GettextCatalog.GetString ("Enter the tags for this NuGet package")); - packageTagsTextBox.SetAccessibilityLabelRelationship (packageTagsLabel); + packageTagsLabel, + GettextCatalog.GetString ("Enter the tags for this NuGet package")); packageLanguageComboBox.SetCommonAccessibilityAttributes ("NuGetMetadata.Language", - GettextCatalog.GetString ("Language"), - GettextCatalog.GetString ("Select the language for this NuGet package")); - packageLanguageComboBox.SetAccessibilityLabelRelationship (packageLanguageLabel); + packageLanguageLabel, + GettextCatalog.GetString ("Select the language for this NuGet package")); packageReleaseNotesTextView.SetCommonAccessibilityAttributes ("NuGetMetadata.ReleaseNotes", - GettextCatalog.GetString ("Release Notes"), - GettextCatalog.GetString ("Enter the release notes for this NuGet package")); - packageReleaseNotesTextView.SetAccessibilityLabelRelationship (packageReleaseNotesLabel); - + packageReleaseNotesLabel, + GettextCatalog.GetString ("Enter the release notes for this NuGet package")); } internal static System.Action<bool> OnProjectHasMetadataChanged; @@ -168,7 +153,7 @@ namespace MonoDevelop.Packaging.Gui packageCopyrightTextBox.Text = GetTextBoxText (metadata.Copyright); packageDevelopmentDependencyCheckBox.Active = metadata.DevelopmentDependency; packageIconUrlTextBox.Text = GetTextBoxText (metadata.IconUrl); - packageLanguageComboBox.Entry.Text = GetTextBoxText (metadata.Language); + LoadLanguage (metadata.Language); packageLicenseUrlTextBox.Text = GetTextBoxText (metadata.LicenseUrl); packageOwnersTextBox.Text = GetTextBoxText (metadata.Owners); packageProjectUrlTextBox.Text = GetTextBoxText (metadata.ProjectUrl); @@ -184,6 +169,35 @@ namespace MonoDevelop.Packaging.Gui return text ?? string.Empty; } + void LoadLanguage (string language) + { + if (string.IsNullOrEmpty (language)) { + packageLanguageComboBox.Active = 0; + return; + } + + int index = GetLanguageIndex (language); + if (index >= 0) { + packageLanguageComboBox.Active = index + 1; + return; + } + + // Language does not match so we need to add it to the combo box. + TreeIter iter = languagesListStore.AppendValues (language); + packageLanguageComboBox.SetActiveIter (iter); + } + + int GetLanguageIndex (string language) + { + for (int i = 0; i < languages.Count; ++i) { + CultureInfo culture = languages [i]; + if (string.Equals (culture.Name, language, StringComparison.OrdinalIgnoreCase)) { + return i; + } + } + return -1; + } + internal void Save (PackagingProject project) { UpdateMetadata (); @@ -220,7 +234,7 @@ namespace MonoDevelop.Packaging.Gui metadata.Copyright = packageCopyrightTextBox.Text; metadata.DevelopmentDependency = packageDevelopmentDependencyCheckBox.Active; metadata.IconUrl = packageIconUrlTextBox.Text; - metadata.Language = packageLanguageComboBox.Entry.Text; + metadata.Language = GetSelectedLanguage (); metadata.LicenseUrl = packageLicenseUrlTextBox.Text; metadata.Owners = packageOwnersTextBox.Text; metadata.ProjectUrl = packageProjectUrlTextBox.Text; @@ -231,20 +245,49 @@ namespace MonoDevelop.Packaging.Gui metadata.Title = packageTitleTextBox.Text; } + string GetSelectedLanguage () + { + if (packageLanguageComboBox.Active == 0) { + // 'None' selected. + return string.Empty; + } + + int languageIndex = packageLanguageComboBox.Active - 1; + if (languageIndex < languages.Count) { + return languages [languageIndex].Name; + } + + // No match for language so just return the combo box text. + return packageLanguageComboBox.ActiveText; + } + void PopulateLanguages () { - var languagesListStore = new ListStore (typeof (string)); + languagesListStore = new ListStore (typeof (string)); packageLanguageComboBox.Model = languagesListStore; - List<string> languages = CultureInfo.GetCultures(CultureTypes.AllCultures) - .Select (c => c.Name) - .ToList (); + languages = new List<CultureInfo> (); - languages.Sort (); + foreach (CultureInfo culture in CultureInfo.GetCultures (CultureTypes.AllCultures)) { + if (!string.IsNullOrEmpty (culture.Name)) { + languages.Add (culture); + } + } - foreach (string language in languages) { - languagesListStore.AppendValues (language); + languages.Sort (CompareLanguages); + + languagesListStore.AppendValues (GettextCatalog.GetString ("None")); + + foreach (CultureInfo language in languages) { + languagesListStore.AppendValues (language.DisplayName); } + + packageLanguageComboBox.Active = 0; + } + + static int CompareLanguages (CultureInfo x, CultureInfo y) + { + return string.Compare (x.DisplayName, y.DisplayName, StringComparison.CurrentCulture); } bool ProjectHasMetadata () diff --git a/main/src/addins/MonoDevelop.Packaging/MonoDevelop.Packaging.Gui/GtkProjectNuGetBuildOptionsPanelWidget.cs b/main/src/addins/MonoDevelop.Packaging/MonoDevelop.Packaging.Gui/GtkProjectNuGetBuildOptionsPanelWidget.cs index 3645bd12ac..38468f2f14 100644 --- a/main/src/addins/MonoDevelop.Packaging/MonoDevelop.Packaging.Gui/GtkProjectNuGetBuildOptionsPanelWidget.cs +++ b/main/src/addins/MonoDevelop.Packaging/MonoDevelop.Packaging.Gui/GtkProjectNuGetBuildOptionsPanelWidget.cs @@ -44,9 +44,13 @@ namespace MonoDevelop.Packaging.Gui SetupAccessibility (); } - void SetupAccessibility () + void SetupAccessibility (bool includeMissingMetadataLabelText = false) { - packOnBuildButton.SetCommonAccessibilityAttributes ("NugetBuildOptionsPanel.PackOnBuild", "", + string accessibilityLabel = packOnBuildButton.Label; + if (includeMissingMetadataLabelText) { + accessibilityLabel += " " + missingMetadataLabel.Text; + } + packOnBuildButton.SetCommonAccessibilityAttributes ("NugetBuildOptionsPanel.PackOnBuild", accessibilityLabel, GettextCatalog.GetString ("Check to create a NuGet package when building")); } @@ -73,10 +77,15 @@ namespace MonoDevelop.Packaging.Gui void UpdateMissingMetadataLabelVisibility () { - if (packOnBuildButton.Active) { - missingMetadataLabel.Visible = !ProjectHasMetadata; + bool visible = packOnBuildButton.Active && !ProjectHasMetadata; + missingMetadataLabel.Visible = visible; + + // Refresh accessibility information so missing metadata label text is available to Voice Over + // when the check box is selected. + if (visible) { + SetupAccessibility (includeMissingMetadataLabelText: true); } else { - missingMetadataLabel.Visible = false; + SetupAccessibility (); } } diff --git a/main/src/addins/MonoDevelop.Refactoring/MonoDevelop.AnalysisCore/Gui/ResultsEditorExtension.cs b/main/src/addins/MonoDevelop.Refactoring/MonoDevelop.AnalysisCore/Gui/ResultsEditorExtension.cs index 1a8be1f307..b2768902cb 100644 --- a/main/src/addins/MonoDevelop.Refactoring/MonoDevelop.AnalysisCore/Gui/ResultsEditorExtension.cs +++ b/main/src/addins/MonoDevelop.Refactoring/MonoDevelop.AnalysisCore/Gui/ResultsEditorExtension.cs @@ -90,19 +90,27 @@ namespace MonoDevelop.AnalysisCore.Gui return; enabled = false; diagService.DiagnosticsUpdated -= OnDiagnosticsUpdated; + diagService = null; CancelUpdateTimout (); AnalysisOptions.AnalysisEnabled.Changed -= AnalysisOptionsChanged; - foreach (var queue in markers) { - foreach (var marker in queue.Value) - Editor.RemoveMarker (marker); - queue.Value.Clear (); - } + RemoveAllMarkers (); disposed = true; base.Dispose (); } + + void RemoveAllMarkers () + { + foreach (var markerQueue in markers) { + foreach (var marker in markerQueue.Value) { + Editor.RemoveMarker (marker); + } + PutBackCachedList (markerQueue.Value); + } + markers.Clear (); + } bool enabled; - + public bool Enabled { get { return enabled; } set { @@ -248,40 +256,39 @@ namespace MonoDevelop.AnalysisCore.Gui } } + const int MaxCacheSize = 10; + Queue<List<IGenericTextSegmentMarker>> listCache = new Queue<List<IGenericTextSegmentMarker>> (); - class ResultsUpdater + List<IGenericTextSegmentMarker> GetCachedList () + { + if (listCache.Count == 0) + return new List<IGenericTextSegmentMarker> (); + return listCache.Dequeue (); + } + + void PutBackCachedList (List<IGenericTextSegmentMarker> list) + { + list.Clear (); + if (listCache.Count < MaxCacheSize) + listCache.Enqueue (list); + } + + class ResultsUpdater { readonly ResultsEditorExtension ext; readonly CancellationToken cancellationToken; - + //the number of markers at the head of the queue that need tp be removed int oldMarkerIndex; - readonly List<IGenericTextSegmentMarker> oldMarkers; + List<IGenericTextSegmentMarker> oldMarkers; int curResult = 0; IReadOnlyList<Result> results; - private List<IGenericTextSegmentMarker> newMarkers; + List<IGenericTextSegmentMarker> newMarkers; ImmutableArray<QuickTask>.Builder builder; object id; - const int MaxCacheSize = 200; - readonly static Queue<List<IGenericTextSegmentMarker>> listCache = new Queue<List<IGenericTextSegmentMarker>> (); - - static List<IGenericTextSegmentMarker> GetCachedList () - { - if (listCache.Count == 0) - return new List<IGenericTextSegmentMarker> (); - return listCache.Dequeue (); - } - - static void PutBackCachedList (List<IGenericTextSegmentMarker> list) - { - list.Clear (); - if (listCache.Count < MaxCacheSize) - listCache.Enqueue (list); - } - public ResultsUpdater (ResultsEditorExtension ext, IReadOnlyList<Result> results, object resultsId, CancellationToken cancellationToken) { if (ext == null) @@ -293,13 +300,13 @@ namespace MonoDevelop.AnalysisCore.Gui this.cancellationToken = cancellationToken; if (resultsId != null) { - if (!ext.markers.TryGetValue (id, out oldMarkers)) - ext.markers [id] = oldMarkers = GetCachedList (); + ext.markers.TryGetValue (id, out oldMarkers); } builder = ImmutableArray<QuickTask>.Empty.ToBuilder (); this.results = results; - newMarkers = GetCachedList (); + newMarkers = ext.GetCachedList (); + Debug.Assert (newMarkers != null); } public void Update () @@ -342,91 +349,95 @@ namespace MonoDevelop.AnalysisCore.Gui //in order to to block the GUI thread, we batch them in UPDATE_COUNT bool IdleHandler () { - var editor = ext.Editor; - if (editor == null) - return false; - if (id == null) { - foreach (var markerQueue in ext.markers) { - foreach (var marker in markerQueue.Value) { - editor.RemoveMarker (marker); - } - PutBackCachedList (markerQueue.Value); + try { + var editor = ext.Editor; + if (editor == null) + return false; + if (id == null) { + ext.RemoveAllMarkers (); + lock (ext.tasks) + ext.tasks.Clear (); + ext.OnTasksUpdated (EventArgs.Empty); + return false; } - ext.markers.Clear (); - lock (ext.tasks) - ext.tasks.Clear (); - ext.OnTasksUpdated (EventArgs.Empty); - return false; - } - if (cancellationToken.IsCancellationRequested) { - FinishUpdateRun (); - return false; - } + if (cancellationToken.IsCancellationRequested) { + FinishUpdateRun (); + return false; + } - //clear the old results out at the same rate we add in the new ones - for (int i = 0; oldMarkerIndex < oldMarkers.Count && i < UPDATE_COUNT; i++) { - var oldMarker = oldMarkers [oldMarkerIndex++]; - - var oldResult = (Result)oldMarker.Tag; - if (curResult < results.Count) { - Result currentResult = results [curResult]; - if (currentResult.Equals (oldResult, oldMarker.Offset)) { - oldMarker.Tag = currentResult; - newMarkers.Add (oldMarker); - if (oldResult.QuickTask != null) { - currentResult.QuickTask = oldResult.QuickTask; - builder.Add (currentResult.QuickTask); + //clear the old results out at the same rate we add in the new ones + if (oldMarkers != null) { + for (int i = 0; oldMarkerIndex < oldMarkers.Count && i < UPDATE_COUNT; i++) { + var oldMarker = oldMarkers [oldMarkerIndex++]; + + var oldResult = (Result)oldMarker.Tag; + if (oldResult != null && curResult < results.Count) { + Result currentResult = results [curResult]; + if (currentResult.Equals (oldResult, oldMarker.Offset)) { + oldMarker.Tag = currentResult; + newMarkers.Add (oldMarker); + if (oldResult.QuickTask != null) { + currentResult.QuickTask = oldResult.QuickTask; + builder.Add (currentResult.QuickTask); + } + curResult++; + continue; + } } - curResult++; - continue; + editor.RemoveMarker (oldMarker); } } - editor.RemoveMarker (oldMarker); - } - //add in the new markers - for (int i = 0; i < UPDATE_COUNT; i++) { - if (curResult >= results.Count) { - FinishUpdateRun (); - return false; - } - var currentResult = results [curResult++]; - if (currentResult.InspectionMark != IssueMarker.None) { - int start = currentResult.Region.Start; - int end = currentResult.Region.End; - if (start > end) - continue; - - // In case a diagnostic has a 0 length span, force it to 1. - if (start == end) - end = end + 1; - - var marker = TextMarkerFactory.CreateGenericTextSegmentMarker (editor, GetSegmentMarkerEffect (currentResult.InspectionMark), TextSegment.FromBounds (start, end)); - marker.Tag = currentResult; - marker.IsVisible = currentResult.Underline; - - if (currentResult.InspectionMark != IssueMarker.GrayOut) { - marker.Color = GetColor (editor, currentResult); - marker.IsVisible &= currentResult.Level != DiagnosticSeverity.Hidden; + //add in the new markers + for (int i = 0; i < UPDATE_COUNT; i++) { + if (curResult >= results.Count) { + FinishUpdateRun (); + return false; + } + var currentResult = results [curResult++]; + if (currentResult.InspectionMark != IssueMarker.None) { + int start = currentResult.Region.Start; + int end = currentResult.Region.End; + if (start > end) + continue; + + // In case a diagnostic has a 0 length span, force it to 1. + if (start == end) + end = end + 1; + + var marker = TextMarkerFactory.CreateGenericTextSegmentMarker (editor, GetSegmentMarkerEffect (currentResult.InspectionMark), TextSegment.FromBounds (start, end)); + marker.Tag = currentResult; + marker.IsVisible = currentResult.Underline; + + if (currentResult.InspectionMark != IssueMarker.GrayOut) { + marker.Color = GetColor (editor, currentResult); + marker.IsVisible &= currentResult.Level != DiagnosticSeverity.Hidden; + } + editor.AddMarker (marker); + newMarkers.Add (marker); } - editor.AddMarker (marker); - newMarkers.Add (marker); + builder.Add (currentResult.QuickTask = new QuickTask (currentResult.Message, currentResult.Region.Start, currentResult.Level)); } - builder.Add (currentResult.QuickTask = new QuickTask (currentResult.Message, currentResult.Region.Start, currentResult.Level)); + return true; + } catch (Exception ex) { + LoggingService.LogInternalError ("Error while ResutsUpdater.IdleHandler", ex); + return false; } - return true; } void FinishUpdateRun () { var editor = ext.Editor; // remove remaining old markers - while (oldMarkerIndex < oldMarkers.Count) { - editor.RemoveMarker (oldMarkers [oldMarkerIndex]); - oldMarkerIndex++; + if (oldMarkers != null) { + while (oldMarkerIndex < oldMarkers.Count) { + editor.RemoveMarker (oldMarkers [oldMarkerIndex]); + oldMarkerIndex++; + } + ext.PutBackCachedList (oldMarkers); + oldMarkers = null; } - PutBackCachedList (oldMarkers); ext.markers [id] = newMarkers; lock (ext.tasks) ext.tasks [id] = builder.ToImmutable (); diff --git a/main/src/addins/MonoDevelop.Refactoring/MonoDevelop.CodeActions/CodeFixMenuService.cs b/main/src/addins/MonoDevelop.Refactoring/MonoDevelop.CodeActions/CodeFixMenuService.cs index f309fa2a76..bb43e7b75f 100644 --- a/main/src/addins/MonoDevelop.Refactoring/MonoDevelop.CodeActions/CodeFixMenuService.cs +++ b/main/src/addins/MonoDevelop.Refactoring/MonoDevelop.CodeActions/CodeFixMenuService.cs @@ -186,11 +186,11 @@ namespace MonoDevelop.CodeActions var item = new CodeFixMenuEntry (label, async delegate { // Task.Run here so we don't end up binding the whole document on popping the menu, also there is no cancellation token support - var fix = await Task.Run (() => { - var context = fixState.CreateFixAllContext (new RoslynProgressTracker (), token); - return provider.GetFixAsync (context); - }); + Microsoft.CodeAnalysis.Text.TextChange[] result = await Task.Run (async () => { + var context = fixState.CreateFixAllContext (new RoslynProgressTracker (), token); + var fix = await provider.GetFixAsync (context); + var previewOperations = await fix.GetPreviewOperationsAsync (token); return await Runtime.RunInMainThread (() => { var engine = Xwt.Toolkit.CurrentEngine; // NativeEngine diff --git a/main/src/addins/MonoDevelop.Refactoring/MonoDevelop.Refactoring/RefactoringPreviewTooltipWindow.cs b/main/src/addins/MonoDevelop.Refactoring/MonoDevelop.Refactoring/RefactoringPreviewTooltipWindow.cs index 69270698f2..705bb142ae 100644 --- a/main/src/addins/MonoDevelop.Refactoring/MonoDevelop.Refactoring/RefactoringPreviewTooltipWindow.cs +++ b/main/src/addins/MonoDevelop.Refactoring/MonoDevelop.Refactoring/RefactoringPreviewTooltipWindow.cs @@ -135,6 +135,7 @@ namespace MonoDevelop.Refactoring if (temp.LineKind == LineKind.Normal && temp.TextMarkup == null) { var newText = editor.GetMarkup (temp.Offset, temp.Length, new MarkupOptions (MarkupFormat.Pango, false)); diff.LineResults [i] = new LineResult (newText, temp.Offset, temp.Length, temp.LineKind, temp.XNeedsMeasure); + QueueResize (); } } @@ -353,7 +354,7 @@ namespace MonoDevelop.Refactoring return; } - drawingLayout.SetMarkup (lineResult.TextMarkup); + drawingLayout.SetMarkup (lineResult.TextMarkup ?? ""); drawingLayout.GetPixelSize (out int w, out int h); x = Math.Max (x, w); y += lineHeight; @@ -398,7 +399,7 @@ namespace MonoDevelop.Refactoring { using (var drawingLayout = new Pango.Layout (PangoContext)) { drawingLayout.FontDescription = fontDescription; - drawingLayout.SetMarkup (lineResult.TextMarkup); + drawingLayout.SetMarkup (lineResult.TextMarkup ?? ""); g.Save (); g.Translate (textBorder, y); diff --git a/main/src/addins/MonoDevelop.SourceEditor2/Mono.TextEditor/Gui/IconMargin.cs b/main/src/addins/MonoDevelop.SourceEditor2/Mono.TextEditor/Gui/IconMargin.cs index a0db793606..cfdd1730b0 100644 --- a/main/src/addins/MonoDevelop.SourceEditor2/Mono.TextEditor/Gui/IconMargin.cs +++ b/main/src/addins/MonoDevelop.SourceEditor2/Mono.TextEditor/Gui/IconMargin.cs @@ -43,16 +43,21 @@ namespace Mono.TextEditor public IconMargin (MonoTextEditor editor) { - this.editor = editor;
-
- editor.Document.MarkerAdded += OnMarkerAdded;
- editor.Document.MarkerRemoved += OnMarkerRemoved; + this.editor = editor; + + if (IdeTheme.AccessibilityEnabled) { + editor.Document.MarkerAdded += OnMarkerAdded;
+ editor.Document.MarkerRemoved += OnMarkerRemoved; + markerToAccessible = new Dictionary<TextLineMarker, AccessibilityMarkerProxy> (); + } } public override void Dispose () {
- editor.Document.MarkerAdded -= OnMarkerAdded;
- editor.Document.MarkerRemoved -= OnMarkerRemoved;
+ if (IdeTheme.AccessibilityEnabled) { + editor.Document.MarkerAdded -= OnMarkerAdded; + editor.Document.MarkerRemoved -= OnMarkerRemoved; + }
if (markerToAccessible != null) {
foreach (var proxy in markerToAccessible.Values) {
@@ -187,44 +192,30 @@ namespace Mono.TextEditor } } - Dictionary<TextLineMarker, AccessibilityMarkerProxy> markerToAccessible = null;
+ Dictionary<TextLineMarker, AccessibilityMarkerProxy> markerToAccessible; +
void OnMarkerAdded (object sender, TextMarkerEvent e)
{ - if (!IdeTheme.AccessibilityEnabled) { - return; + lock (markerToAccessible) { + var proxy = new AccessibilityMarkerProxy (e.TextMarker, editor, this); + Accessible.AddAccessibleChild (proxy.Accessible); + markerToAccessible [e.TextMarker] = proxy; } - if (markerToAccessible == null) {
- markerToAccessible = new Dictionary<TextLineMarker, AccessibilityMarkerProxy> ();
- }
-
- var proxy = new AccessibilityMarkerProxy (e.TextMarker, editor, this);
- Accessible.AddAccessibleChild (proxy.Accessible);
-
- markerToAccessible [e.TextMarker] = proxy; - if (focusMarkers != null) { UpdateMarkers (); - }
+ } }
void OnMarkerRemoved (object sender, TextMarkerEvent e)
{ - if (!IdeTheme.AccessibilityEnabled) { - return; + lock (markerToAccessible) { + if (!markerToAccessible.TryGetValue (e.TextMarker, out var proxy)) { + return; + } + Accessible.RemoveAccessibleChild (proxy.Accessible); + markerToAccessible.Remove (e.TextMarker); } -
- if (markerToAccessible == null) {
- return;
- }
-
- var proxy = markerToAccessible [e.TextMarker];
- if (proxy == null) {
- throw new Exception ("No accessible found for marker");
- }
-
- Accessible.RemoveAccessibleChild (proxy.Accessible);
- markerToAccessible.Remove (e.TextMarker); if (focusMarkers != null) { UpdateMarkers (); diff --git a/main/src/addins/MonoDevelop.SourceEditor2/Mono.TextEditor/Gui/MdTextViewLineCollection.MdTextViewLine.cs b/main/src/addins/MonoDevelop.SourceEditor2/Mono.TextEditor/Gui/MdTextViewLineCollection.MdTextViewLine.cs index aa4875782a..df50904421 100644 --- a/main/src/addins/MonoDevelop.SourceEditor2/Mono.TextEditor/Gui/MdTextViewLineCollection.MdTextViewLine.cs +++ b/main/src/addins/MonoDevelop.SourceEditor2/Mono.TextEditor/Gui/MdTextViewLineCollection.MdTextViewLine.cs @@ -50,6 +50,7 @@ namespace Mono.TextEditor /// 1-based /// </summary> public int LineNumber { get; private set; } + public bool HasDrawn { get; set; } public MdTextViewLine(MdTextViewLineCollection collection, MonoTextEditor textEditor, DocumentLine line, int lineNumber, TextViewMargin.LayoutWrapper layoutWrapper) { diff --git a/main/src/addins/MonoDevelop.SourceEditor2/Mono.TextEditor/Gui/TextArea.cs b/main/src/addins/MonoDevelop.SourceEditor2/Mono.TextEditor/Gui/TextArea.cs index 874a18c389..f6dafc25f0 100644 --- a/main/src/addins/MonoDevelop.SourceEditor2/Mono.TextEditor/Gui/TextArea.cs +++ b/main/src/addins/MonoDevelop.SourceEditor2/Mono.TextEditor/Gui/TextArea.cs @@ -522,18 +522,26 @@ namespace Mono.TextEditor preeditOffset = Caret.Offset; preeditLine = Caret.Line; } - if (UpdatePreeditLineHeight ()) + if (UpdatePreeditLineHeight ()) { QueueDraw (); + } else { + this.textViewMargin.ForceInvalidateLine (preeditLine); + this.textEditorData.Document.CommitLineUpdate (preeditLine); + } } else { + if (preeditOffset < 0) + return; preeditOffset = -1; preeditString = null; preeditAttrs = null; preeditCursorCharIndex = 0; - if (UpdatePreeditLineHeight ()) + if (UpdatePreeditLineHeight ()) { QueueDraw (); + } else { + this.textViewMargin.ForceInvalidateLine (preeditLine); + this.textEditorData.Document.CommitLineUpdate (preeditLine); + } } - this.textViewMargin.ForceInvalidateLine (preeditLine); - this.textEditorData.Document.CommitLineUpdate (preeditLine); } internal bool UpdatePreeditLineHeight () @@ -1117,8 +1125,6 @@ namespace Mono.TextEditor if (isDisposed || logicalLine > LineCount || logicalLine < DocumentLocation.MinLine) return; - textViewMargin.RemoveCachedLine(logicalLine); - double y = LineToY (logicalLine) - this.textEditorData.VAdjustment.Value; double h = GetLineHeight (logicalLine); @@ -1414,8 +1420,13 @@ namespace Mono.TextEditor return true; } } - if (margin != null) - margin.MousePressed (new MarginMouseEventArgs (textEditorData.Parent, e, e.Button, e.X - startPos, e.Y, e.State)); + if (margin != null) { + try { + margin.MousePressed (new MarginMouseEventArgs (textEditorData.Parent, e, e.Button, e.X - startPos, e.Y, e.State)); + } catch (Exception ex) { + LoggingService.LogInternalError ("Exception while margin mouse press.", ex); + } + } } return result; } diff --git a/main/src/addins/MonoDevelop.SourceEditor2/Mono.TextEditor/Gui/TextViewMargin.cs b/main/src/addins/MonoDevelop.SourceEditor2/Mono.TextEditor/Gui/TextViewMargin.cs index 20f8c18b96..6cb5586a22 100644 --- a/main/src/addins/MonoDevelop.SourceEditor2/Mono.TextEditor/Gui/TextViewMargin.cs +++ b/main/src/addins/MonoDevelop.SourceEditor2/Mono.TextEditor/Gui/TextViewMargin.cs @@ -670,18 +670,23 @@ namespace Mono.TextEditor #if MAC try { lineHeight = System.Math.Ceiling (0.5 + OSXEditor.GetLineHeight(font.ToString ())); + if (lineHeight < 0) + lineHeight = GetLineHeight (metrics); } catch (Exception e) { LoggingService.LogError ("Error while getting the macOS font metrics for " + font, e); - lineHeight = System.Math.Ceiling (0.5 + (metrics.Ascent + metrics.Descent) / Pango.Scale.PangoScale); + lineHeight = GetLineHeight (metrics); } #else - lineHeight = System.Math.Ceiling(0.5 + (metrics.Ascent + metrics.Descent) / Pango.Scale.PangoScale); + lineHeight = GetLineHeight (metrics); #endif underlinePosition = metrics.UnderlinePosition; underLineThickness = metrics.UnderlineThickness; charWidth = metrics.ApproximateCharWidth / Pango.Scale.PangoScale; } } + + static double GetLineHeight (Pango.FontMetrics metrics) => System.Math.Ceiling (0.5 + (metrics.Ascent + metrics.Descent) / Pango.Scale.PangoScale); + public override void Dispose () { CancelCodeSegmentTooltip (); diff --git a/main/src/addins/MonoDevelop.SourceEditor2/MonoDevelop.SourceEditor/ClipboardRingService.cs b/main/src/addins/MonoDevelop.SourceEditor2/MonoDevelop.SourceEditor/ClipboardRingService.cs index e5cbe4f179..3e1b8a9248 100644 --- a/main/src/addins/MonoDevelop.SourceEditor2/MonoDevelop.SourceEditor/ClipboardRingService.cs +++ b/main/src/addins/MonoDevelop.SourceEditor2/MonoDevelop.SourceEditor/ClipboardRingService.cs @@ -31,6 +31,7 @@ using MonoDevelop.Core; using MonoDevelop.DesignerSupport.Toolbox; using MonoDevelop.Ide; using MonoDevelop.Ide.Gui; +using System.Linq; namespace MonoDevelop.SourceEditor { @@ -102,6 +103,8 @@ namespace MonoDevelop.SourceEditor return clipboardRing; } + internal static bool DeleteItem (ItemToolboxNode node) => clipboardRing.Remove (node as ClipboardToolboxNode); + class ClipboardToolboxNode : ItemToolboxNode, ITextToolboxNode, ICustomTooltipToolboxNode { static readonly ToolboxItemFilterAttribute filterAtt = new ToolboxItemFilterAttribute ("text/plain", ToolboxItemFilterType.Allow); diff --git a/main/src/addins/MonoDevelop.SourceEditor2/MonoDevelop.SourceEditor/SourceEditorView.cs b/main/src/addins/MonoDevelop.SourceEditor2/MonoDevelop.SourceEditor/SourceEditorView.cs index cb7b112912..75478307b3 100644 --- a/main/src/addins/MonoDevelop.SourceEditor2/MonoDevelop.SourceEditor/SourceEditorView.cs +++ b/main/src/addins/MonoDevelop.SourceEditor2/MonoDevelop.SourceEditor/SourceEditorView.cs @@ -1,4 +1,4 @@ -// SourceEditorView.cs +// SourceEditorView.cs // // Author: // Mike Krüger <mkrueger@novell.com> @@ -68,7 +68,7 @@ using MonoDevelop.Ide.Gui.Documents; namespace MonoDevelop.SourceEditor { partial class SourceEditorView : IBookmarkBuffer, IClipboardHandler, ITextFile, - ICompletionWidget2, ISplittable, IFoldable, IToolboxDynamicProvider, + ICompletionWidget2, ISplittable, IFoldable, IToolboxDynamicProvider, IToolboxDynamicProviderDeleteSupport, ICustomFilteringToolboxConsumer, IZoomable, ITextEditorResolver, ITextEditorDataProvider, ICodeTemplateHandler, ICodeTemplateContextProvider, IPrintable, ITextEditorImpl, ITextMarkerFactory, IUndoHandler @@ -3111,14 +3111,20 @@ namespace MonoDevelop.SourceEditor string ITextEditorImpl.GetMarkup (int offset, int length, MarkupOptions options) { + return GetMarkupAsync (offset, length, options, default).WaitAndGetResult (default); + } + + public async Task<string> GetMarkupAsync (int offset, int length, MarkupOptions options, CancellationToken cancellationToken = default) + { + Runtime.AssertMainThread (); var data = TextEditor.GetTextEditorData (); switch (options.MarkupFormat) { case MarkupFormat.Pango: - return data.GetMarkup (offset, length, false, replaceTabs: false, fitIdeStyle: options.FitIdeStyle); + return await data.GetMarkupAsync (offset, length, false, replaceTabs: false, fitIdeStyle: options.FitIdeStyle, cancellationToken: cancellationToken); case MarkupFormat.Html: - return HtmlWriter.GenerateHtml (ClipboardColoredText.GetChunks (data, new TextSegment (offset, length)).WaitAndGetResult (default (System.Threading.CancellationToken)), data.ColorStyle, data.Options); + return HtmlWriter.GenerateHtml (await ClipboardColoredText.GetChunks (data, new TextSegment (offset, length), cancellationToken), data.ColorStyle, data.Options); case MarkupFormat.RichText: - return RtfWriter.GenerateRtf (ClipboardColoredText.GetChunks (data, new TextSegment (offset, length)).WaitAndGetResult (default (System.Threading.CancellationToken)), data.ColorStyle, data.Options); + return RtfWriter.GenerateRtf (await ClipboardColoredText.GetChunks (data, new TextSegment (offset, length), cancellationToken), data.ColorStyle, data.Options); default: throw new ArgumentOutOfRangeException (); } @@ -3413,6 +3419,10 @@ namespace MonoDevelop.SourceEditor return TextEditor.GetLineHeight (line); } + public bool DeleteDynamicItem (ItemToolboxNode node) => ClipboardRingService.DeleteItem (node); + + public bool CanDeleteDynamicItem (ItemToolboxNode node) => ClipboardRingService.GetToolboxItems ().Contains (node); + public bool HasFocus { get { return this.TextEditor.HasFocus; diff --git a/main/src/addins/MonoDevelop.SourceEditor2/VSEditor/TagBasedSyntaxHighlighting.cs b/main/src/addins/MonoDevelop.SourceEditor2/VSEditor/TagBasedSyntaxHighlighting.cs index 9ef7217efa..bcb9df0d6b 100644 --- a/main/src/addins/MonoDevelop.SourceEditor2/VSEditor/TagBasedSyntaxHighlighting.cs +++ b/main/src/addins/MonoDevelop.SourceEditor2/VSEditor/TagBasedSyntaxHighlighting.cs @@ -198,19 +198,19 @@ namespace Microsoft.VisualStudio.Platform }
}
- private void OnClassificationChanged(object sender, ClassificationChangedEventArgs args)
- {
- var handler = _highlightingStateChanged;
- if (handler != null)
- {
- foreach (Mono.TextEditor.MdTextViewLineCollection.MdTextViewLine line in textView.TextViewLines)
- {
- if (line.Start.Position > args.ChangeSpan.End.Position || line.End.Position < args.ChangeSpan.Start)
- continue;
- handler(this, new LineEventArgs(line.line));
- }
- }
- }
+ private void OnClassificationChanged (object sender, ClassificationChangedEventArgs args)
+ {
+ var handler = _highlightingStateChanged;
+ if (handler != null) {
+ foreach (Mono.TextEditor.MdTextViewLineCollection.MdTextViewLine line in textView.TextViewLines) {
+ if (!line.HasDrawn) { + line.HasDrawn = true; + handler(this, new LineEventArgs(line.line)); + continue; + } + }
+ }
+ }
private ScopeStack GetScopeStackFromClassificationType (IClassificationType classificationType)
{
diff --git a/main/src/addins/MonoDevelop.UnitTesting.NUnit/MonoDevelop.UnitTesting.NUnit/ExternalTestRunner.cs b/main/src/addins/MonoDevelop.UnitTesting.NUnit/MonoDevelop.UnitTesting.NUnit/ExternalTestRunner.cs index 51fa5342d2..2cbf788fe0 100644 --- a/main/src/addins/MonoDevelop.UnitTesting.NUnit/MonoDevelop.UnitTesting.NUnit/ExternalTestRunner.cs +++ b/main/src/addins/MonoDevelop.UnitTesting.NUnit/MonoDevelop.UnitTesting.NUnit/ExternalTestRunner.cs @@ -48,6 +48,8 @@ namespace MonoDevelop.UnitTesting.NUnit.External IRemoteEventListener listener; readonly string assemblyDirectory; + public ProcessExecutionArchitecture ProcessExecutionArchitecture { get; set; } + public ExternalTestRunner () { } @@ -61,6 +63,7 @@ namespace MonoDevelop.UnitTesting.NUnit.External { var exePath = Path.Combine (Path.GetDirectoryName (GetType ().Assembly.Location), version.ToString (), "NUnitRunner.exe"); connection = new RemoteProcessConnection (exePath, assemblyDirectory, executionHandler, console, Runtime.MainSynchronizationContext); + connection.ProcessExecutionArchitecture = ProcessExecutionArchitecture; connection.AddListener (this); return connection.Connect (); } diff --git a/main/src/addins/MonoDevelop.UnitTesting.NUnit/MonoDevelop.UnitTesting.NUnit/NUnitAssemblyTestSuite.cs b/main/src/addins/MonoDevelop.UnitTesting.NUnit/MonoDevelop.UnitTesting.NUnit/NUnitAssemblyTestSuite.cs index b75a993743..5b3e57bbed 100644 --- a/main/src/addins/MonoDevelop.UnitTesting.NUnit/MonoDevelop.UnitTesting.NUnit/NUnitAssemblyTestSuite.cs +++ b/main/src/addins/MonoDevelop.UnitTesting.NUnit/MonoDevelop.UnitTesting.NUnit/NUnitAssemblyTestSuite.cs @@ -44,6 +44,7 @@ using System.Xml.Linq; using System.Linq; using System.Globalization; using System.Threading.Tasks; +using MonoDevelop.Core.Assemblies; namespace MonoDevelop.UnitTesting.NUnit { @@ -333,6 +334,7 @@ namespace MonoDevelop.UnitTesting.NUnit try { if (File.Exists (ld.Path)) { runner = new ExternalTestRunner (Path.GetDirectoryName (ld.Path)); + runner.ProcessExecutionArchitecture = AssemblyUtilities.GetProcessExecutionArchitectureForAssembly (ld.Path); runner.Connect (ld.NUnitVersion).Wait (); var supportAssemblies = new List<string> (ld.SupportAssemblies.Result); ld.Info = runner.GetTestInfo (ld.Path, supportAssemblies).Result; @@ -398,6 +400,7 @@ namespace MonoDevelop.UnitTesting.NUnit OperationConsoleFactory.CreateConsoleOptions.Default.WithTitle (GettextCatalog.GetString ("Unit Tests"))); ExternalTestRunner runner = new ExternalTestRunner (Path.GetDirectoryName (AssemblyPath)); + runner.ProcessExecutionArchitecture = AssemblyUtilities.GetProcessExecutionArchitectureForAssembly (AssemblyPath); runner.Connect (NUnitVersion, testContext.ExecutionContext.ExecutionHandler, console).Wait (); LocalTestMonitor localMonitor = new LocalTestMonitor (testContext, test, suiteName, testName != null); diff --git a/main/src/addins/MonoDevelop.UnitTesting/Commands/UnitTestCommands.cs b/main/src/addins/MonoDevelop.UnitTesting/Commands/UnitTestCommands.cs index 5f776ccc60..6d0a84150d 100644 --- a/main/src/addins/MonoDevelop.UnitTesting/Commands/UnitTestCommands.cs +++ b/main/src/addins/MonoDevelop.UnitTesting/Commands/UnitTestCommands.cs @@ -44,7 +44,7 @@ namespace MonoDevelop.UnitTesting.Commands GoToFailure, RerunTest, } - + public enum TestChartCommands { ShowResults, @@ -55,13 +55,20 @@ namespace MonoDevelop.UnitTesting.Commands ShowFailedTests, ShowIgnoredTests } - + public enum NUnitProjectCommands { AddAssembly } - - class RunAllTestsHandler: CommandHandler + + public enum TextEditorCommands + { + RunTestAtCaret, + DebugTestAtCaret, + SelectTestAtCaret + } + + class RunAllTestsHandler : CommandHandler { protected override void Run () { diff --git a/main/src/addins/MonoDevelop.UnitTesting/MonoDevelop.UnitTesting.addin.xml b/main/src/addins/MonoDevelop.UnitTesting/MonoDevelop.UnitTesting.addin.xml index c2158343b2..4fd591a678 100644 --- a/main/src/addins/MonoDevelop.UnitTesting/MonoDevelop.UnitTesting.addin.xml +++ b/main/src/addins/MonoDevelop.UnitTesting/MonoDevelop.UnitTesting.addin.xml @@ -75,6 +75,9 @@ <Command id = "MonoDevelop.UnitTesting.Commands.TestChartCommands.ShowSuccessfulTests" _label = "Show successful tests" type="check"/> <Command id = "MonoDevelop.UnitTesting.Commands.TestChartCommands.ShowFailedTests" _label = "Show failed tests" type="check"/> <Command id = "MonoDevelop.UnitTesting.Commands.TestChartCommands.ShowIgnoredTests" _label = "Show ignored tests" type="check"/> + <Command id = "MonoDevelop.UnitTesting.Commands.TextEditorCommands.RunTestAtCaret" _label = "Run Test at Caret"/> + <Command id = "MonoDevelop.UnitTesting.Commands.TextEditorCommands.DebugTestAtCaret" _label = "Debug Test at Caret"/> + <Command id = "MonoDevelop.UnitTesting.Commands.TextEditorCommands.SelectTestAtCaret" _label = "Select Test at Caret"/> </Category> </Extension> diff --git a/main/src/addins/MonoDevelop.UnitTesting/Services/AbstractUnitTestEditorExtension.cs b/main/src/addins/MonoDevelop.UnitTesting/Services/AbstractUnitTestEditorExtension.cs index 58449a06a8..27464ad5b2 100644 --- a/main/src/addins/MonoDevelop.UnitTesting/Services/AbstractUnitTestEditorExtension.cs +++ b/main/src/addins/MonoDevelop.UnitTesting/Services/AbstractUnitTestEditorExtension.cs @@ -37,6 +37,8 @@ using MonoDevelop.Ide.Editor.Extension; using MonoDevelop.Ide.Editor; using MonoDevelop.Projects; using Mono.Addins; +using MonoDevelop.Components.Commands; +using MonoDevelop.UnitTesting.Commands; namespace MonoDevelop.UnitTesting { @@ -137,6 +139,36 @@ namespace MonoDevelop.UnitTesting List<IUnitTestMarker> currentMarker = new List<IUnitTestMarker>(); + TestRunner GetTestRunnerAtCaret (bool debug) + { + var line = Editor.GetLine (Editor.CaretLine); + if (line == null) + return null; + foreach (var marker in Editor.GetLineMarkers (line)) { + if (marker is IUnitTestMarker result) + return new TestRunner (result.UnitTest.UnitTestIdentifier, DocumentContext.Project, debug); + } + return null; + } + + [CommandHandler (TextEditorCommands.RunTestAtCaret)] + protected void OnRunTestAtCaret () + { + GetTestRunnerAtCaret (false)?.Run (this, EventArgs.Empty); + } + + [CommandHandler (TextEditorCommands.DebugTestAtCaret)] + protected void OnDebugTestAtCaret () + { + GetTestRunnerAtCaret (true)?.Run (this, EventArgs.Empty); + } + + [CommandHandler (TextEditorCommands.SelectTestAtCaret)] + protected void OnSelectTestAtCaret () + { + GetTestRunnerAtCaret (false)?.Select (this, EventArgs.Empty); + } + class UnitTestMarkerHostImpl : UnitTestMarkerHost { readonly AbstractUnitTestTextEditorExtension ext; @@ -277,82 +309,70 @@ namespace MonoDevelop.UnitTesting #endregion - class TestRunner - { - readonly string testCase; - readonly bool debug; - IBuildTarget project; - - public TestRunner (string testCase, IBuildTarget project, bool debug) - { - this.testCase = testCase; - this.debug = debug; - this.project = project; - } - - bool TimeoutHandler () - { - var test = UnitTestService.SearchTestByDocumentId (testCase); - if (test != null) { - RunTest (test); - timeoutHandler = 0; - } else { - return true; - } - return false; - } + } + class TestRunner + { + readonly string testCase; + readonly bool debug; + IBuildTarget project; - internal async void Run (object sender, EventArgs e) - { - if (IdeApp.ProjectOperations.IsBuilding (IdeApp.ProjectOperations.CurrentSelectedSolution) || - IdeApp.ProjectOperations.IsRunning (IdeApp.ProjectOperations.CurrentSelectedSolution)) - return; + public TestRunner (string testCase, IBuildTarget project, bool debug) + { + this.testCase = testCase; + this.debug = debug; + this.project = project; + } - var foundTest = UnitTestService.SearchTestByDocumentId (testCase); - if (foundTest != null) { - RunTest (foundTest); - return; - } + internal async void Run (object sender, EventArgs e) + { + if (IdeApp.ProjectOperations.IsBuilding (IdeApp.ProjectOperations.CurrentSelectedSolution) || + IdeApp.ProjectOperations.IsRunning (IdeApp.ProjectOperations.CurrentSelectedSolution)) + return; - bool buildBeforeExecuting = IdeApp.Preferences.BuildBeforeRunningTests; + var foundTest = UnitTestService.SearchTestByDocumentId (testCase); + if (foundTest != null) { + RunTest (foundTest); + return; + } - if (buildBeforeExecuting) { - await IdeApp.ProjectOperations.Build (project).Task; - await UnitTestService.RefreshTests (CancellationToken.None); - } + bool buildBeforeExecuting = IdeApp.Preferences.BuildBeforeRunningTests; - foundTest = UnitTestService.SearchTestByDocumentId (testCase); - if (foundTest != null) - RunTest (foundTest); - else - UnitTestService.ReportExecutionError (GettextCatalog.GetString ("Unit test '{0}' could not be loaded.", testCase)); + if (buildBeforeExecuting) { + await IdeApp.ProjectOperations.Build (project).Task; + await UnitTestService.RefreshTests (CancellationToken.None); } - internal void Select (object sender, EventArgs e) - { - var test = UnitTestService.SearchTestByDocumentId (testCase); - if (test == null) - return; - UnitTestService.CurrentSelectedTest = test; - } + foundTest = UnitTestService.SearchTestByDocumentId (testCase); + if (foundTest != null) + RunTest (foundTest); + else + UnitTestService.ReportExecutionError (GettextCatalog.GetString ("Unit test '{0}' could not be loaded.", testCase)); + } - void RunTest (UnitTest test) - { - var debugModeSet = Runtime.ProcessService.GetDebugExecutionMode (); - Core.Execution.IExecutionHandler ctx = null; - if (debug && debugModeSet != null) { - foreach (var executionMode in debugModeSet.ExecutionModes) { - if (test.CanRun (executionMode.ExecutionHandler)) { - ctx = executionMode.ExecutionHandler; - break; - } + internal void Select (object sender, EventArgs e) + { + var test = UnitTestService.SearchTestByDocumentId (testCase); + if (test == null) + return; + UnitTestService.CurrentSelectedTest = test; + } + + void RunTest (UnitTest test) + { + var debugModeSet = Runtime.ProcessService.GetDebugExecutionMode (); + Core.Execution.IExecutionHandler ctx = null; + if (debug && debugModeSet != null) { + foreach (var executionMode in debugModeSet.ExecutionModes) { + if (test.CanRun (executionMode.ExecutionHandler)) { + ctx = executionMode.ExecutionHandler; + break; } } - // NUnitService.Instance.RunTest (test, ctx); - var pad = IdeApp.Workbench.GetPad<TestPad> (); - var content = (TestPad)pad.Content; - content.RunTest (test, ctx); } + // NUnitService.Instance.RunTest (test, ctx); + var pad = IdeApp.Workbench.GetPad<TestPad> (); + var content = (TestPad)pad.Content; + content.RunTest (test, ctx); } } } diff --git a/main/src/addins/MonoDevelop.UnitTesting/Services/SolutionFolderTestGroup.cs b/main/src/addins/MonoDevelop.UnitTesting/Services/SolutionFolderTestGroup.cs index 5346301540..bcf3ebbe39 100644 --- a/main/src/addins/MonoDevelop.UnitTesting/Services/SolutionFolderTestGroup.cs +++ b/main/src/addins/MonoDevelop.UnitTesting/Services/SolutionFolderTestGroup.cs @@ -64,7 +64,7 @@ namespace MonoDevelop.UnitTesting public override void Dispose () { folder.NameChanged -= OnCombineRenamed; - if (folder.IsRoot) { + if (folder.IsRoot && folder.ParentSolution != null) { folder.ParentSolution.SolutionItemAdded -= OnEntryChanged; folder.ParentSolution.SolutionItemRemoved -= OnEntryChanged; } diff --git a/main/src/addins/MonoDevelop.WebReferences/MonoDevelop.WebReferences.Dialogs/WebReferenceDialog.cs b/main/src/addins/MonoDevelop.WebReferences/MonoDevelop.WebReferences.Dialogs/WebReferenceDialog.cs index 234deb1883..b05fc834f8 100644 --- a/main/src/addins/MonoDevelop.WebReferences/MonoDevelop.WebReferences.Dialogs/WebReferenceDialog.cs +++ b/main/src/addins/MonoDevelop.WebReferences/MonoDevelop.WebReferences.Dialogs/WebReferenceDialog.cs @@ -150,7 +150,7 @@ namespace MonoDevelop.WebReferences.Dialogs #endregion #region Member Variables - const string homeUrl = "https://www.w3schools.com/xml/tempconvert.asmx?WSDL"; + const string homeUrl = "https://www.w3schools.com/xml/tempconvert.asmx"; WebServiceDiscoveryResult selectedService; // protected Gtk.Alignment frmBrowserAlign; #endregion @@ -639,50 +639,4 @@ namespace MonoDevelop.WebReferences.Dialogs base.OnDestroyed (); } } - - class AskCredentials: GuiSyncObject, ICredentials - { - static readonly Dictionary<string,NetworkCredential> credentials = new Dictionary<string, NetworkCredential> (); - - readonly Dictionary<string,NetworkCredential> tempCredentials = new Dictionary<string, NetworkCredential> (); - - public bool Canceled; - - public void Reset () - { - tempCredentials.Clear (); - } - - public void Store () - { - foreach (var creds in tempCredentials) - credentials [creds.Key] = creds.Value; - } - - public NetworkCredential GetCredential (Uri uri, string authType) - { - NetworkCredential nc; - if (tempCredentials.TryGetValue (uri.Host + uri.AbsolutePath, out nc)) - return nc; // Exact match - - var dlg = new UserPasswordDialog (uri.Host); - if (tempCredentials.TryGetValue (uri.Host, out nc) || credentials.TryGetValue (uri.Host, out nc)) { - dlg.User = nc.UserName; - dlg.Password = nc.Password; - } - try { - if (MessageService.RunCustomDialog (dlg) == (int)ResponseType.Ok) { - nc = new NetworkCredential (dlg.User, dlg.Password); - tempCredentials [uri.Host + uri.AbsolutePath] = nc; - tempCredentials [uri.Host] = nc; - return nc; - } - Canceled = true; - return null; - } finally { - dlg.Destroy (); - dlg.Dispose (); - } - } - } } diff --git a/main/src/addins/MonoDevelop.WebReferences/MonoDevelop.WebReferences.csproj b/main/src/addins/MonoDevelop.WebReferences/MonoDevelop.WebReferences.csproj index fcc52a16fa..dd917ce6e6 100644 --- a/main/src/addins/MonoDevelop.WebReferences/MonoDevelop.WebReferences.csproj +++ b/main/src/addins/MonoDevelop.WebReferences/MonoDevelop.WebReferences.csproj @@ -86,6 +86,7 @@ <Compile Include="Gui\MonoDevelop.WebReferences.Dialogs.WCFConfigWidget.cs" /> <Compile Include="Gui\MonoDevelop.WebReferences.Dialogs.WebReferenceDialog.cs" /> <Compile Include="MonoDevelop.WebReferences\WebReferencesProjectExtension.cs" /> + <Compile Include="MonoDevelop.WebReferences\MonoDevelopDiscoveryClientProtocol.cs" /> </ItemGroup> <ItemGroup> <EmbeddedResource Include="MonoDevelop.WebReferences.addin.xml" /> diff --git a/main/src/addins/MonoDevelop.WebReferences/MonoDevelop.WebReferences/Library.cs b/main/src/addins/MonoDevelop.WebReferences/MonoDevelop.WebReferences/Library.cs index 14c9492527..4ed5a6f709 100644 --- a/main/src/addins/MonoDevelop.WebReferences/MonoDevelop.WebReferences/Library.cs +++ b/main/src/addins/MonoDevelop.WebReferences/MonoDevelop.WebReferences/Library.cs @@ -18,14 +18,14 @@ namespace MonoDevelop.WebReferences /// <summary>Read the service description for a specified uri.</summary> /// <param name="uri">A string containing the unique reference identifier for the service.</param> /// <returns>A ServiceDescription for the specified uri.</returns> + [Obsolete] public static ServiceDescription ReadServiceDescription(string uri) { var desc = new ServiceDescription(); try { - var request = (HttpWebRequest)WebRequest.Create(uri); - WebResponse response = request.GetResponse(); - + WebResponse response = WebRequestHelper.GetResponse (() => (HttpWebRequest)WebRequest.Create (uri)); + desc = ServiceDescription.Read(response.GetResponseStream()); response.Close(); desc.RetrievalUrl = uri; diff --git a/main/src/addins/MonoDevelop.WebReferences/MonoDevelop.WebReferences/MonoDevelopDiscoveryClientProtocol.cs b/main/src/addins/MonoDevelop.WebReferences/MonoDevelop.WebReferences/MonoDevelopDiscoveryClientProtocol.cs new file mode 100644 index 0000000000..0688c67394 --- /dev/null +++ b/main/src/addins/MonoDevelop.WebReferences/MonoDevelop.WebReferences/MonoDevelopDiscoveryClientProtocol.cs @@ -0,0 +1,65 @@ +// +// MonoDevelopDiscoveryClientProtocol.cs +// +// Author: +// Matt Ward <matt.ward@microsoft.com> +// +// Copyright (c) 2018 Microsoft +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +using System; +using System.Net; +using System.Web.Services.Discovery; +using MonoDevelop.Core; + +namespace MonoDevelop.WebReferences +{ + /// <summary> + /// Integrates with the MonoDevelop credential provider. + /// </summary> + class MonoDevelopDiscoveryClientProtocol : DiscoveryClientProtocol + { + protected override WebResponse GetWebResponse (WebRequest request) + { +#pragma warning disable CS0618 // Type or member is obsolete. Have to use WebRequest with the DiscoveryClientProtocol + return WebRequestHelper.GetResponse (() => CreateWebRequest (request.RequestUri)); +#pragma warning restore CS0618 // Type or member is obsolete. + } + + /// <summary> + /// Need to create a new web request just in case the request is not authorized and the + /// WebRequestHelper is retrying. Otherwise the second attempt will fail since the request + /// has already been started and will throw an System.InvalidOperationException: request started + /// when properties such as the Proxy are changed by the WebRequestHelper. The only method used + /// is the DiscoveryClientProtocol's Download method which creates the web request and then gets + /// the response immediately afterwards. + /// </summary> + HttpWebRequest CreateWebRequest (Uri uri) + { + var request = (HttpWebRequest)base.GetWebRequest (uri); + // Method is set in base.Download so set it here. + request.Method = "GET"; + // Allow redirects. This allows the Web Reference dialog to show information about the + // the https://www.w3schools.com/xml/tempconvert.asmx web service. + request.AllowAutoRedirect = true; + return request; + } + } +} diff --git a/main/src/addins/MonoDevelop.WebReferences/MonoDevelop.WebReferences/WebServiceEngine.cs b/main/src/addins/MonoDevelop.WebReferences/MonoDevelop.WebReferences/WebServiceEngine.cs index 62ced2eb08..edc362d24f 100644 --- a/main/src/addins/MonoDevelop.WebReferences/MonoDevelop.WebReferences/WebServiceEngine.cs +++ b/main/src/addins/MonoDevelop.WebReferences/MonoDevelop.WebReferences/WebServiceEngine.cs @@ -27,8 +27,6 @@ using MonoDevelop.Projects; using System.Collections.Generic; using System.Web.Services.Discovery; -using MonoDevelop.WebReferences.Dialogs; -using System.Net; using MonoDevelop.Core; namespace MonoDevelop.WebReferences @@ -50,36 +48,14 @@ namespace MonoDevelop.WebReferences protected DiscoveryClientProtocol DiscoResolve (string url) { // Checks the availablity of any services - var protocol = new DiscoveryClientProtocol (); - var creds = new AskCredentials (); - protocol.Credentials = creds; - bool unauthorized; - - do { - unauthorized = false; - creds.Reset (); - - try { - protocol.DiscoverAny (url); - } catch (WebException wex) { - var wr = wex.Response as HttpWebResponse; - if (!creds.Canceled && wr != null && wr.StatusCode == HttpStatusCode.Unauthorized) { - unauthorized = true; - continue; - } - throw; - } - } while (unauthorized); - - if (protocol != null) { - creds.Store (); - if (protocol.References.Count == 0) - return null; - } + var protocol = new MonoDevelopDiscoveryClientProtocol (); + protocol.DiscoverAny (url); + + if (protocol.References.Count == 0) + return null; + return protocol; } } - - } diff --git a/main/src/addins/PerformanceDiagnostics/PerformanceDiagnostics/UIThreadMonitor.cs b/main/src/addins/PerformanceDiagnostics/PerformanceDiagnostics/UIThreadMonitor.cs index e383d6192b..cfa5c6c497 100644 --- a/main/src/addins/PerformanceDiagnostics/PerformanceDiagnostics/UIThreadMonitor.cs +++ b/main/src/addins/PerformanceDiagnostics/PerformanceDiagnostics/UIThreadMonitor.cs @@ -212,7 +212,9 @@ namespace PerformanceDiagnosticsAddIn { var rx = new Regex (@"\?\?\? \(in <unknown binary>\) \[0x([0-9a-f]+)\]", RegexOptions.Compiled); if (File.Exists (fileName) && new FileInfo (fileName).Length > 0) { + Directory.CreateDirectory (Options.OutputPath); var outputFilename = Path.Combine (Options.OutputPath, $"{BrandingService.ApplicationName}_{profilingType}_{DateTime.Now:yyyy-MM-dd__HH-mm-ss}.txt"); + using (var sr = new StreamReader (fileName)) using (var sw = new StreamWriter (outputFilename)) { string line; diff --git a/main/src/addins/VersionControl/MonoDevelop.VersionControl.Git/MonoDevelop.VersionControl.Git/EditRemoteDialog.cs b/main/src/addins/VersionControl/MonoDevelop.VersionControl.Git/MonoDevelop.VersionControl.Git/EditRemoteDialog.cs index 5e05821a6e..591007a30f 100644 --- a/main/src/addins/VersionControl/MonoDevelop.VersionControl.Git/MonoDevelop.VersionControl.Git/EditRemoteDialog.cs +++ b/main/src/addins/VersionControl/MonoDevelop.VersionControl.Git/MonoDevelop.VersionControl.Git/EditRemoteDialog.cs @@ -24,6 +24,8 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. +using System;
+using System.Linq; using LibGit2Sharp; using MonoDevelop.Components; @@ -31,18 +33,22 @@ namespace MonoDevelop.VersionControl.Git { partial class EditRemoteDialog : Gtk.Dialog { + GitRepository repo; + // TODO: Add user possibility to choose refspecs. - public EditRemoteDialog () : this (null) + public EditRemoteDialog () : this (null, null) { } bool sameUrls; - public EditRemoteDialog (Remote remote) + public EditRemoteDialog (GitRepository repository, Remote remote) { this.Build (); this.UseNativeContextMenus (); + repo = repository; + if (remote != null) { entryName.Text = remote.Name; entryUrl.Text = remote.Url ?? ""; @@ -80,7 +86,7 @@ namespace MonoDevelop.VersionControl.Git void UpdateButtons () { - buttonOk.Sensitive = entryName.Text.Length > 0 && entryUrl.Text.Length > 0 && entryPushUrl.Text.Length > 0; + buttonOk.Sensitive = entryName.Text.Length > 0 && IsValidUrl (entryUrl.Text) && IsValidUrl(entryPushUrl.Text); } protected virtual void OnEntryNameChanged (object sender, System.EventArgs e) @@ -107,5 +113,12 @@ namespace MonoDevelop.VersionControl.Git UpdateButtons (); } + + bool IsValidUrl (string url) + { + if (repo == null) + return url.Length > 0; + return repo.IsUrlValid (url); + } } } diff --git a/main/src/addins/VersionControl/MonoDevelop.VersionControl.Git/MonoDevelop.VersionControl.Git/GitCommitDialogExtension.cs b/main/src/addins/VersionControl/MonoDevelop.VersionControl.Git/MonoDevelop.VersionControl.Git/GitCommitDialogExtension.cs index 2c0cff0a13..55278dbfa6 100644 --- a/main/src/addins/VersionControl/MonoDevelop.VersionControl.Git/MonoDevelop.VersionControl.Git/GitCommitDialogExtension.cs +++ b/main/src/addins/VersionControl/MonoDevelop.VersionControl.Git/MonoDevelop.VersionControl.Git/GitCommitDialogExtension.cs @@ -30,11 +30,14 @@ using System; using MonoDevelop.Core; using MonoDevelop.Projects; using MonoDevelop.Ide; +using System.Text.RegularExpressions; namespace MonoDevelop.VersionControl.Git { sealed class GitCommitDialogExtension: CommitDialogExtension { + public static readonly Regex MailRegex = new Regex (@"[\w\d._%+-]+@[\w\d.-]+\.\w+", RegexOptions.Compiled); + GitCommitDialogExtensionWidget widget; Gtk.TextView textView; @@ -49,7 +52,8 @@ namespace MonoDevelop.VersionControl.Git widget.Show (); Show (); widget.Changed += delegate { - AllowCommit = widget.CommitterIsAuthor || widget.AuthorName.Length > 0; + AllowCommit = widget.CommitterIsAuthor || + (widget.AuthorName.Length > 0 && IsValidMail(widget.AuthorMail)); }; return true; } @@ -202,5 +206,15 @@ namespace MonoDevelop.VersionControl.Git textView.HasTooltip = false; } } + + bool IsValidMail (string mail) + { + if (string.IsNullOrEmpty (mail)) + return false; + + var match = MailRegex.Match (mail); + + return match.Success; + } } } diff --git a/main/src/addins/VersionControl/MonoDevelop.VersionControl.Git/MonoDevelop.VersionControl.Git/GitConfigurationDialog.cs b/main/src/addins/VersionControl/MonoDevelop.VersionControl.Git/MonoDevelop.VersionControl.Git/GitConfigurationDialog.cs index 66c265820b..3df491f6eb 100644 --- a/main/src/addins/VersionControl/MonoDevelop.VersionControl.Git/MonoDevelop.VersionControl.Git/GitConfigurationDialog.cs +++ b/main/src/addins/VersionControl/MonoDevelop.VersionControl.Git/MonoDevelop.VersionControl.Git/GitConfigurationDialog.cs @@ -236,7 +236,7 @@ namespace MonoDevelop.VersionControl.Git protected virtual void OnButtonAddRemoteClicked (object sender, EventArgs e) { - var dlg = new EditRemoteDialog (); + var dlg = new EditRemoteDialog (repo, null); try { if (MessageService.RunCustomDialog (dlg) == (int) ResponseType.Ok) { repo.AddRemote (dlg.RemoteName, dlg.RemoteUrl, dlg.ImportTags); @@ -258,7 +258,7 @@ namespace MonoDevelop.VersionControl.Git if (remote == null) return; - var dlg = new EditRemoteDialog (remote); + var dlg = new EditRemoteDialog (repo, remote); try { if (MessageService.RunCustomDialog (dlg) == (int) ResponseType.Ok) { if (remote.Url != dlg.RemoteUrl) @@ -364,12 +364,20 @@ namespace MonoDevelop.VersionControl.Git protected async void OnButtonFetchClicked (object sender, EventArgs e) { - TreeIter it; - if (!treeRemotes.Selection.GetSelected (out it)) + if (!treeRemotes.Selection.GetSelected (out var it)) return; - string remoteName = (string) storeRemotes.GetValue (it, 4); - if (remoteName == null) + bool toplevel = !storeRemotes.IterParent (out var parent, it); + + string remoteName = string.Empty; + + if (toplevel) { + remoteName = (string)storeRemotes.GetValue (it, 4); + } else { + remoteName = (string)storeRemotes.GetValue (parent, 4); + } + + if (string.IsNullOrEmpty(remoteName)) return; await System.Threading.Tasks.Task.Run (() => repo.Fetch (VersionControlService.GetProgressMonitor (GettextCatalog.GetString ("Fetching remote...")), remoteName)); diff --git a/main/src/addins/VersionControl/MonoDevelop.VersionControl.Git/MonoDevelop.VersionControl.Git/GitRepository.cs b/main/src/addins/VersionControl/MonoDevelop.VersionControl.Git/MonoDevelop.VersionControl.Git/GitRepository.cs index f209d7046b..71981d13a4 100644 --- a/main/src/addins/VersionControl/MonoDevelop.VersionControl.Git/MonoDevelop.VersionControl.Git/GitRepository.cs +++ b/main/src/addins/VersionControl/MonoDevelop.VersionControl.Git/MonoDevelop.VersionControl.Git/GitRepository.cs @@ -478,9 +478,12 @@ namespace MonoDevelop.VersionControl.Git static void GetFilesVersionInfoCore (LibGit2Sharp.Repository repo, GitRevision rev, List<FilePath> localPaths, List<VersionInfo> versions) { - foreach (var file in repo.ToGitPath (localPaths)) { - var status = repo.RetrieveStatus (file); - AddStatus (repo, rev, file, versions, status, null); + foreach (var localPath in localPaths) { + if (!localPath.IsDirectory) { + var file = repo.ToGitPath (localPath); + var status = repo.RetrieveStatus (file); + AddStatus (repo, rev, file, versions, status, null); + } } } @@ -518,6 +521,7 @@ namespace MonoDevelop.VersionControl.Git { var relativePath = repo.ToGitPath (directory); var status = repo.RetrieveStatus (new StatusOptions { + DisablePathSpecMatch = true, PathSpec = relativePath != "." ? new [] { relativePath } : null, IncludeUnaltered = true, }); @@ -664,11 +668,13 @@ namespace MonoDevelop.VersionControl.Git { monitor.BeginTask (GettextCatalog.GetString ("Fetching"), 1); monitor.Log.WriteLine (GettextCatalog.GetString ("Fetching from '{0}'", remote)); + int progress = 0; RetryUntilSuccess (monitor, credType => RootRepository.Fetch (remote, new FetchOptions { CredentialsProvider = (url, userFromUrl, types) => GitCredentials.TryGet (url, userFromUrl, types, credType), OnTransferProgress = tp => OnTransferProgress (tp, monitor, ref progress), })); + monitor.Step (1); monitor.EndTask (); } diff --git a/main/src/addins/VersionControl/MonoDevelop.VersionControl/MonoDevelop.VersionControl.Dialogs/SelectRepositoryDialog.cs b/main/src/addins/VersionControl/MonoDevelop.VersionControl/MonoDevelop.VersionControl.Dialogs/SelectRepositoryDialog.cs index 8ad5a5e087..9df102723f 100644 --- a/main/src/addins/VersionControl/MonoDevelop.VersionControl/MonoDevelop.VersionControl.Dialogs/SelectRepositoryDialog.cs +++ b/main/src/addins/VersionControl/MonoDevelop.VersionControl/MonoDevelop.VersionControl.Dialogs/SelectRepositoryDialog.cs @@ -69,6 +69,7 @@ namespace MonoDevelop.VersionControl.Dialogs defaultPath = VersionControlDefaultPath; entryFolder.Text = defaultPath; buttonOk.Label = GettextCatalog.GetString ("_Checkout"); + UpdateCheckoutButton (); } else { labelTargetDir.Visible = false; boxFolder.Visible = false; @@ -111,6 +112,16 @@ namespace MonoDevelop.VersionControl.Dialogs } } + protected override void OnDestroyed () + { + UrlBasedRepositoryEditor edit = currentEditor as UrlBasedRepositoryEditor; + if (edit != null) { + edit.UrlChanged -= OnEditUrlChanged; + edit.PathChanged -= OnPathChanged; + } + base.OnDestroyed (); + } + protected virtual void OnRepComboChanged(object sender, System.EventArgs e) { if (repoContainer.Child != null) @@ -125,11 +136,19 @@ namespace MonoDevelop.VersionControl.Dialogs repoContainer.Add (currentEditor.Widget); currentEditor.Show (); UrlBasedRepositoryEditor edit = currentEditor as UrlBasedRepositoryEditor; - if (edit != null) + if (edit != null) { + edit.UrlChanged += OnEditUrlChanged; edit.PathChanged += OnPathChanged; + } UpdateRepoDescription (); } - + + protected virtual void OnRepositoryServerEntryChanged (object sender, System.EventArgs e) + { + if (mode == SelectRepositoryMode.Checkout) + buttonOk.Sensitive = entryFolder.Text.Length > 0; + } + public void LoadRepositories ()
{ store.Clear (); @@ -348,6 +367,21 @@ namespace MonoDevelop.VersionControl.Dialogs Respond (ResponseType.Ok); } + protected virtual void OnEditUrlChanged (object sender, EventArgs e) + { + if (mode == SelectRepositoryMode.Checkout) { + UpdateCheckoutButton (); + } + } + + void UpdateCheckoutButton() + { + UrlBasedRepositoryEditor edit = currentEditor as UrlBasedRepositoryEditor; + if (edit == null) + return; + buttonOk.Sensitive = !string.IsNullOrWhiteSpace (edit.RepositoryServer); + } + protected virtual void OnPathChanged (object sender, EventArgs e) { AppendRelativePath (); diff --git a/main/src/addins/VersionControl/MonoDevelop.VersionControl/MonoDevelop.VersionControl.Views/BlameView.cs b/main/src/addins/VersionControl/MonoDevelop.VersionControl/MonoDevelop.VersionControl.Views/BlameView.cs index 559c24fa3c..695ebdbb7d 100644 --- a/main/src/addins/VersionControl/MonoDevelop.VersionControl/MonoDevelop.VersionControl.Views/BlameView.cs +++ b/main/src/addins/VersionControl/MonoDevelop.VersionControl/MonoDevelop.VersionControl.Views/BlameView.cs @@ -28,6 +28,8 @@ using MonoDevelop.Core; using MonoDevelop.Ide.Gui.Content; using Mono.TextEditor; using MonoDevelop.Ide.Gui.Documents; +using System; +using System.Linq; namespace MonoDevelop.VersionControl.Views { @@ -60,19 +62,30 @@ namespace MonoDevelop.VersionControl.Views var buffer = info.Controller.GetContent<MonoDevelop.Ide.Editor.TextEditor> (); if (buffer != null) { - var loc = buffer.CaretLocation; - int line = loc.Line < 1 ? 1 : loc.Line; - int column = loc.Column < 1 ? 1 : loc.Column; - widget.Editor.SetCaretTo (line, column, highlight: false, centerCaret: false); + if (!(buffer.TextView is MonoTextEditor)) { + //compatibility for other not MonoTextEditor editors + var loc = buffer.CaretLocation; + int line = loc.Line < 1 ? 1 : loc.Line; + int column = loc.Column < 1 ? 1 : loc.Column; + widget.Editor.SetCaretTo (line, column, highlight: false, centerCaret: false); + } } } protected override void OnDeselected () { - var buffer = info.Controller.GetContent<MonoDevelop.Ide.Editor.TextEditor> (); + var buffer = info.Controller.GetContent<MonoDevelop.Ide.Editor.TextEditor> () ; if (buffer != null) { - buffer.SetCaretLocation (widget.Editor.Caret.Line, widget.Editor.Caret.Column, usePulseAnimation: false, centerCaret: false); - buffer.ScrollTo (new Ide.Editor.DocumentLocation (widget.Editor.YToLine (widget.Editor.VAdjustment.Value), 1)); + if (buffer.TextView is MonoTextEditor exEditor) { + if (widget.Revision == null) + exEditor.Document.UpdateFoldSegments (widget.Editor.Document.FoldSegments.Select (f => new Mono.TextEditor.FoldSegment (f))); + exEditor.SetCaretTo (widget.Editor.Caret.Line, widget.Editor.Caret.Column); + exEditor.VAdjustment.Value = widget.Editor.VAdjustment.Value; + } else { + //compatibility for other not MonoTextEditor editors + buffer.ScrollTo (new Ide.Editor.DocumentLocation (widget.Editor.YToLine (widget.Editor.VAdjustment.Value), 1)); + buffer.SetCaretLocation (widget.Editor.Caret.Line, widget.Editor.Caret.Column, usePulseAnimation: false, centerCaret: false); + } } } diff --git a/main/src/addins/VersionControl/MonoDevelop.VersionControl/MonoDevelop.VersionControl.Views/BlameWidget.cs b/main/src/addins/VersionControl/MonoDevelop.VersionControl/MonoDevelop.VersionControl.Views/BlameWidget.cs index 162abcb171..e3d5623ed8 100644 --- a/main/src/addins/VersionControl/MonoDevelop.VersionControl/MonoDevelop.VersionControl.Views/BlameWidget.cs +++ b/main/src/addins/VersionControl/MonoDevelop.VersionControl/MonoDevelop.VersionControl.Views/BlameWidget.cs @@ -36,6 +36,7 @@ using MonoDevelop.Core; using MonoDevelop.Components; using MonoDevelop.Components.Commands; using MonoDevelop.Ide.Fonts; +using MonoDevelop.Ide.Editor; namespace MonoDevelop.VersionControl.Views { @@ -49,6 +50,13 @@ namespace MonoDevelop.VersionControl.Views class BlameWidget : Bin { Revision revision; + + public Revision Revision { + get { + return revision; + } + } + Adjustment vAdjustment; Gtk.VScrollbar vScrollBar; @@ -131,7 +139,11 @@ namespace MonoDevelop.VersionControl.Views IsReadOnly = true, MimeType = sourceEditor.TextEditor.Document.MimeType, }; - editor = new MonoTextEditor (doc, sourceEditor.TextEditor.Options); + var options = new CustomEditorOptions (DefaultSourceEditorOptions.Instance); + options.TabsToSpaces = false; + + editor = new MonoTextEditor (doc, new SourceEditor.StyledSourceEditorOptions (options)); + AddChild (editor); editor.SetScrollAdjustments (hAdjustment, vAdjustment); @@ -412,17 +424,14 @@ namespace MonoDevelop.VersionControl.Views WidthRequest = newWidthRequest; QueueResize (); } - int startLine = widget.Editor.YToLine (widget.Editor.VAdjustment.Value + evnt.Y); - var ann = startLine > 0 && startLine <= annotations.Count ? annotations[startLine - 1] : null; - if (ann != null) + + GetAnnotationFromY (evnt.Y, out var annotation, out var startLine); + + if (annotation != null) TooltipText = GetCommitMessage (startLine - 1, true); - highlightPositon = evnt.Y; - if (highlightAnnotation != ann) { - highlightAnnotation = ann; - widget.QueueDraw (); - } - + SetHighlight (annotation, startLine, evnt.Y, highlightAnnotation != annotation); + return base.OnMotionNotifyEvent (evnt); } @@ -438,8 +447,8 @@ namespace MonoDevelop.VersionControl.Views protected override bool OnButtonPressEvent (EventButton evnt) { if (evnt.TriggersContextMenu ()) { - int startLine = widget.Editor.YToLine (widget.Editor.VAdjustment.Value + evnt.Y); - menuAnnotation = startLine > 0 && startLine <= annotations.Count ? annotations[startLine - 1] : null; + + GetAnnotationFromY (evnt.Y, out menuAnnotation, out var startLine); CommandEntrySet opset = new CommandEntrySet (); opset.AddItem (BlameCommands.ShowDiff); @@ -556,6 +565,11 @@ namespace MonoDevelop.VersionControl.Views document.Text = widget.VersionControlItem.Repository.GetTextAtRevision (widget.Document.FileName, widget.revision); } else { document.Text = widget.Document.Editor.Text; + if (widget.Document.Editor.TextView is MonoTextEditor exEditor) { + document.UpdateFoldSegments (exEditor.Document.FoldSegments.Select (f => new Mono.TextEditor.FoldSegment (f))); + widget.Editor.SetCaretTo (exEditor.Caret.Line, exEditor.Caret.Column); + widget.Editor.VAdjustment.Value = exEditor.VAdjustment.Value; + } } widget.editor.Caret.Location = location; widget.editor.VAdjustment.Value = adj; @@ -773,7 +787,26 @@ namespace MonoDevelop.VersionControl.Views } return true; } - + + int YToStartLine (double y) => widget.Editor.YToLine (widget.Editor.VAdjustment.Value + y); + + void GetAnnotationFromY (double y, out Annotation annotation, out int startLine) + { + startLine = YToStartLine (y); + annotation = GetAnnotationFromLine (startLine); + } + + internal Annotation GetAnnotationFromLine (int startLine) => + startLine > 0 && startLine <= annotations.Count ? annotations [startLine - 1] : null; + + internal void SetHighlight (Annotation annotation, int line, double y, bool needsRedraw) + { + highlightPositon = y; + highlightAnnotation = annotation; + if (needsRedraw) { + widget.QueueDraw (); + } + } } } } diff --git a/main/src/addins/VersionControl/MonoDevelop.VersionControl/MonoDevelop.VersionControl.Views/DiffView.cs b/main/src/addins/VersionControl/MonoDevelop.VersionControl/MonoDevelop.VersionControl.Views/DiffView.cs index d9d4321c50..c8de96214b 100644 --- a/main/src/addins/VersionControl/MonoDevelop.VersionControl/MonoDevelop.VersionControl.Views/DiffView.cs +++ b/main/src/addins/VersionControl/MonoDevelop.VersionControl/MonoDevelop.VersionControl.Views/DiffView.cs @@ -30,6 +30,7 @@ using MonoDevelop.Components; using MonoDevelop.Core; using MonoDevelop.Ide.Gui.Content; using MonoDevelop.Ide.Gui.Documents; +using MonoDevelop.Ide; namespace MonoDevelop.VersionControl.Views { @@ -46,7 +47,11 @@ namespace MonoDevelop.VersionControl.Views if (widget == null) { widget = new DiffWidget (info); - ComparisonWidget.DiffEditor.Document.Text = info.Item.Repository.GetBaseText (info.Item.Path); + try { + ComparisonWidget.DiffEditor.Document.Text = info.Item.Repository.GetBaseText (info.Item.Path); + } catch (Exception ex) { + LoggingService.LogInternalError ("Error fetching text from repository ", ex); + } ComparisonWidget.SetLocal (ComparisonWidget.OriginalEditor.GetTextEditorData ()); widget.ShowAll (); } diff --git a/main/src/addins/VersionControl/MonoDevelop.VersionControl/MonoDevelop.VersionControl.Views/LogView.cs b/main/src/addins/VersionControl/MonoDevelop.VersionControl/MonoDevelop.VersionControl.Views/LogView.cs index 5033015745..c1fa66ad2e 100644 --- a/main/src/addins/VersionControl/MonoDevelop.VersionControl/MonoDevelop.VersionControl.Views/LogView.cs +++ b/main/src/addins/VersionControl/MonoDevelop.VersionControl/MonoDevelop.VersionControl.Views/LogView.cs @@ -87,9 +87,10 @@ namespace MonoDevelop.VersionControl.Views [CommandHandler (MonoDevelop.Ide.Commands.EditCommands.Copy)] protected void OnCopy () { - string data = widget.DiffText; - if (data == null) + string data = widget.GetSelectedText (); + if (data == null) { return; + } var clipboard = Clipboard.Get (Gdk.Atom.Intern ("CLIPBOARD", false)); clipboard.Text = data; diff --git a/main/src/addins/VersionControl/MonoDevelop.VersionControl/MonoDevelop.VersionControl.Views/LogWidget.cs b/main/src/addins/VersionControl/MonoDevelop.VersionControl/MonoDevelop.VersionControl.Views/LogWidget.cs index ea3cbf2c67..c318989981 100644 --- a/main/src/addins/VersionControl/MonoDevelop.VersionControl/MonoDevelop.VersionControl.Views/LogWidget.cs +++ b/main/src/addins/VersionControl/MonoDevelop.VersionControl/MonoDevelop.VersionControl.Views/LogWidget.cs @@ -36,6 +36,7 @@ using Mono.TextEditor; using System.Linq; using MonoDevelop.Ide.Editor; using MonoDevelop.Ide.Fonts; +using Humanizer; namespace MonoDevelop.VersionControl.Views { @@ -368,7 +369,9 @@ namespace MonoDevelop.VersionControl.Views doc?.GetContent<VersionControlDocumentController> ()?.ShowDiffView (SelectedRevision.GetPrevious (), SelectedRevision, line); } + const int colFile = 3; const int colOperation = 4; + const int colOperationText = 1; const int colPath = 5; const int colDiff = 6; @@ -497,21 +500,15 @@ namespace MonoDevelop.VersionControl.Views static void DateFunc (Gtk.TreeViewColumn tree_column, Gtk.CellRenderer cell, Gtk.TreeModel model, Gtk.TreeIter iter) { - CellRendererText renderer = (CellRendererText)cell; - var rev = (Revision)model.GetValue (iter, 0); - string day; - + var renderer = (CellRendererText)cell; + var revision = (Revision)model.GetValue (iter, 0); // Grab today's day and the start of tomorrow's day to make Today/Yesterday calculations. var now = DateTime.Now; - var age = new DateTime (now.Year, now.Month, now.Day).AddDays(1) - rev.Time; - if (age.Days >= 0 && age.Days < 1) { // Check whether it's a commit that's less than a day away. Also discard future commits. - day = GettextCatalog.GetString ("Today"); - } else if (age.Days < 2) { // Check whether it's a commit from yesterday. - day = GettextCatalog.GetString ("Yesterday"); - } else { - day = rev.Time.ToShortDateString (); - } - renderer.Text = string.Format ("{0} {1:HH:mm}", day, rev.Time); + var age = new DateTime (now.Year, now.Month, now.Day).AddDays (1) - revision.Time; + + renderer.Text = age.Days >= 2 ? + revision.Time.ToShortDateString () : + revision.Time.Humanize (utcDate: false, dateToCompareAgainst: now); } static void GraphFunc (Gtk.TreeViewColumn tree_column, Gtk.CellRenderer cell, Gtk.TreeModel model, Gtk.TreeIter iter) @@ -777,16 +774,60 @@ namespace MonoDevelop.VersionControl.Views } } - internal string DiffText { - get { - TreeIter iter; - if (treeviewFiles.Selection.GetSelected (out iter)) { - string [] items = changedpathstore.GetValue (iter, colDiff) as string []; - if (items != null) - return String.Join (Environment.NewLine, items); + internal string GetSelectedText () + { + if (treeviewFiles.HasFocus ) { + if (treeviewFiles.Selection.GetSelected (out var iter)) { + if (changedpathstore.GetValue (iter, colDiff) is string [] items) { + return string.Join (Environment.NewLine, items); + } + if (changedpathstore.GetValue (iter, colFile) is string file) { + var path = changedpathstore.GetValue (iter, colPath) as string; + var operation = changedpathstore.GetValue (iter, colOperationText) as string; + return string.Format ("{0}, {1}, {2}", file, operation, path); + } + } + } + + if (treeviewLog.HasFocus) { + if (treeviewLog.Selection.GetSelected (out var iter)) { + if (logstore.GetValue (iter, 0) is Revision revision) { + return string.Format ("{0}, {1}, {2}", revision.ShortMessage, revision.Time, revision.Author); + } + } + } + + if (textviewDetails.HasFocus) { + textviewDetails.Buffer.GetSelectionBounds (out var A, out var B); + var result = textviewDetails.Buffer.GetText (A, B, true); + if (!string.IsNullOrEmpty (result)) { + return result; + } + } + + int start, end; + if (labelDate.HasFocus) { + labelDate.GetSelectionBounds (out start, out end); + if (start != end) { + return labelDate.Text.Substring (start, end - start); } - return null; } + + if (labelAuthor.HasFocus) { + labelAuthor.GetSelectionBounds (out start, out end); + if (start != end) { + return labelAuthor.Text.Substring (start, end - start); + } + } + + if (labelRevision.HasFocus) { + labelRevision.GetSelectionBounds (out start, out end); + if (start != end) { + return labelRevision.Text.Substring (start, end - start); + } + } + + return null; } } } diff --git a/main/src/addins/VersionControl/MonoDevelop.VersionControl/MonoDevelop.VersionControl.Views/StatusView.cs b/main/src/addins/VersionControl/MonoDevelop.VersionControl/MonoDevelop.VersionControl.Views/StatusView.cs index 762880365f..a662180f1e 100644 --- a/main/src/addins/VersionControl/MonoDevelop.VersionControl/MonoDevelop.VersionControl.Views/StatusView.cs +++ b/main/src/addins/VersionControl/MonoDevelop.VersionControl/MonoDevelop.VersionControl.Views/StatusView.cs @@ -83,7 +83,7 @@ namespace MonoDevelop.VersionControl.Views bool updatingComment; ChangeSet changeSet; bool firstLoad = true; - VersionControlItemList fileList; + volatile VersionControlItemList fileList; const int ColIcon = 0; const int ColStatus = 1; @@ -428,6 +428,7 @@ namespace MonoDevelop.VersionControl.Views SetupToolbar (view.GetToolbar ()); return widget; } + object updateLock = new object (); void StartUpdate () { @@ -444,25 +445,27 @@ namespace MonoDevelop.VersionControl.Views buttonCommit.Sensitive = false; ThreadPool.QueueUserWorkItem (delegate { - if (fileList != null) { - var group = fileList.GroupBy (v => v.IsDirectory || v.WorkspaceObject is SolutionFolderItem); - foreach (var item in group) { - // Is directory. - if (item.Key) { - foreach (var directory in item) - changeSet.AddFiles (vc.GetDirectoryVersionInfo (directory.Path, remoteStatus, true)); - } else - changeSet.AddFiles (item.Select (v => v.VersionInfo).ToArray ()); + lock (updateLock) { + if (fileList != null) { + var group = fileList.GroupBy (v => v.IsDirectory || v.WorkspaceObject is SolutionFolderItem); + foreach (var item in group) { + // Is directory. + if (item.Key) { + foreach (var directory in item) + changeSet.AddFiles (vc.GetDirectoryVersionInfo (directory.Path, remoteStatus, true)); + } else + changeSet.AddFiles (item.Select (v => v.VersionInfo).ToArray ()); + } + changeSet.AddFiles (fileList.Where (v => !v.IsDirectory).Select (v => v.VersionInfo).ToArray ()); + fileList = null; } - changeSet.AddFiles (fileList.Where (v => !v.IsDirectory).Select (v => v.VersionInfo).ToArray ()); - fileList = null; + List<VersionInfo> newList = new List<VersionInfo> (); + newList.AddRange (vc.GetDirectoryVersionInfo (filepath, remoteStatus, true)); + Runtime.RunInMainThread (delegate { + if (!disposed) + LoadStatus (newList); + }); } - List<VersionInfo> newList = new List<VersionInfo> (); - newList.AddRange (vc.GetDirectoryVersionInfo (filepath, remoteStatus, true)); - Runtime.RunInMainThread (delegate { - if (!disposed) - LoadStatus (newList); - }); }); } diff --git a/main/src/addins/VersionControl/MonoDevelop.VersionControl/MonoDevelop.VersionControl.csproj b/main/src/addins/VersionControl/MonoDevelop.VersionControl/MonoDevelop.VersionControl.csproj index 1094a11cf4..e9317f97b6 100644 --- a/main/src/addins/VersionControl/MonoDevelop.VersionControl/MonoDevelop.VersionControl.csproj +++ b/main/src/addins/VersionControl/MonoDevelop.VersionControl/MonoDevelop.VersionControl.csproj @@ -78,6 +78,10 @@ <HintPath>..\..\..\..\build\bin\Microsoft.VisualStudio.Text.UI.dll</HintPath> <Private>False</Private> </Reference> + <Reference Include="Humanizer"> + <HintPath>..\..\..\..\packages\Humanizer.Core.2.2.0\lib\netstandard1.0\Humanizer.dll</HintPath> + <Private>False</Private> + </Reference> </ItemGroup> <ItemGroup> <EmbeddedResource Include="icons\added-overlay-16.png" /> @@ -516,10 +520,14 @@ <Compile Include="Gui\MonoDevelop.VersionControl.Views.DiffWidget.cs" /> <Compile Include="Gui\MonoDevelop.VersionControl.Views.LogWidget.cs" /> <Compile Include="MonoDevelop.VersionControl\VersionControlDocumentController.cs" /> + <Compile Include="MonoDevelop.VersionControl\Instrumentation.cs" /> </ItemGroup> <ItemGroup> <InternalsVisibleTo Include="MonoDevelop.VersionControl.Git.Tests" /> <InternalsVisibleTo Include="MonoDevelop.MacDev" /> </ItemGroup> + <ItemGroup> + <None Include="packages.config" /> + </ItemGroup> <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" /> </Project> diff --git a/main/src/addins/VersionControl/MonoDevelop.VersionControl/MonoDevelop.VersionControl/Instrumentation.cs b/main/src/addins/VersionControl/MonoDevelop.VersionControl/MonoDevelop.VersionControl/Instrumentation.cs new file mode 100644 index 0000000000..402ec0183a --- /dev/null +++ b/main/src/addins/VersionControl/MonoDevelop.VersionControl/MonoDevelop.VersionControl/Instrumentation.cs @@ -0,0 +1,163 @@ +// +// Counters.cs +// +// Author: +// Vsevolod Kukol <sevoku@microsoft.com> +// +// Copyright (c) 2019 (c) Microsoft Corporation +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +using System; +using MonoDevelop.Core.Instrumentation; +using System.Threading; +using MonoDevelop.Core; + +namespace MonoDevelop.VersionControl +{ + static class Instrumentation + { + const string Category = "Version Control"; + const string Identifier = "VersionControl"; + + public static readonly Counter<RepositoryMetadata> Repositories = InstrumentationService.CreateCounter<RepositoryMetadata> ("VersionControl.RepositoryOpened", Category, id: $"{Identifier}.RepositoryOpened"); + public static readonly TimerCounter<MultipathOperationMetadata> CheckoutCounter = InstrumentationService.CreateTimerCounter<MultipathOperationMetadata> ("Checkout", Category, id: $"{Identifier}.Checkout"); + public static readonly TimerCounter<MultipathOperationMetadata> CommitCounter = InstrumentationService.CreateTimerCounter<MultipathOperationMetadata> ("Commit", Category, id: $"{Identifier}.Commit"); + public static readonly TimerCounter<RevertMetadata> RevertCounter = InstrumentationService.CreateTimerCounter<RevertMetadata> ("Revert", Category, id: $"{Identifier}.Revert"); + public static readonly TimerCounter<PublishMetadata> PublishCounter = InstrumentationService.CreateTimerCounter<PublishMetadata> ("Publish", Category, id: $"{Identifier}.Publish"); + public static readonly TimerCounter<MultipathOperationMetadata> UpdateCounter = InstrumentationService.CreateTimerCounter<MultipathOperationMetadata> ("Update", Category, id: $"{Identifier}.Update"); + public static readonly TimerCounter<MultipathOperationMetadata> AddCounter = InstrumentationService.CreateTimerCounter<MultipathOperationMetadata> ("Add", Category, id: $"{Identifier}.Add"); + public static readonly TimerCounter<MoveMetadata> MoveCounter = InstrumentationService.CreateTimerCounter<MoveMetadata> ("Move", Category, id: $"{Identifier}.Move"); + public static readonly TimerCounter<DeleteMetadata> DeleteCounter = InstrumentationService.CreateTimerCounter<DeleteMetadata> ("Delete", Category, id: $"{Identifier}.Delete"); + public static readonly TimerCounter<MultipathOperationMetadata> LockCounter = InstrumentationService.CreateTimerCounter<MultipathOperationMetadata> ("Lock", Category, id: $"{Identifier}.Lock"); + public static readonly TimerCounter<MultipathOperationMetadata> UnlockCounter = InstrumentationService.CreateTimerCounter<MultipathOperationMetadata> ("Unlock", Category, id: $"{Identifier}.Unlock"); + public static readonly TimerCounter<MultipathOperationMetadata> IgnoreCounter = InstrumentationService.CreateTimerCounter<MultipathOperationMetadata> ("Ignore", Category, id: $"{Identifier}.Ignore"); + public static readonly TimerCounter<MultipathOperationMetadata> UnignoreCounter = InstrumentationService.CreateTimerCounter<MultipathOperationMetadata> ("Unignore", Category, id: $"{Identifier}.Unignore"); + + public static readonly TimerCounter<RepositoryMetadata> GetRevisionChangesCounter = InstrumentationService.CreateTimerCounter<RepositoryMetadata> ("Get Revision Changes", Category, id: $"{Identifier}.GetRevisionChanges"); + public static readonly TimerCounter<RepositoryMetadata> GetHistoryCounter = InstrumentationService.CreateTimerCounter<RepositoryMetadata> ("Get History", Category, id: $"{Identifier}.GetHistory"); + } + + class MultipathOperationMetadata : RepositoryMetadata + { + public MultipathOperationMetadata () + { + } + + public MultipathOperationMetadata (VersionControlSystem versionControl) : base (versionControl) + { + } + + public int PathsCount { + get => GetProperty<int> (); + set => SetProperty (value); + } + + public bool Recursive { + get => GetProperty<bool> (); + set => SetProperty (value); + } + } + + class RevertMetadata : MultipathOperationMetadata + { + public enum RevertType + { + LocalChanges, + ToRevision, + SpecificRevision + }; + + public RevertMetadata () + { + } + + public RevertMetadata (VersionControlSystem versionControl) : base (versionControl) + { + } + + public RevertType OperationType { + get => GetProperty<RevertType> (); + set => SetProperty (value); + } + } + + class PublishMetadata : MultipathOperationMetadata + { + public PublishMetadata () + { + } + + public PublishMetadata (VersionControlSystem versionControl) : base (versionControl) + { + } + + public bool SubFolder { + get => GetProperty<bool> (); + set => SetProperty (value); + } + } + + class DeleteMetadata : MultipathOperationMetadata + { + public DeleteMetadata () + { + } + + public DeleteMetadata (VersionControlSystem versionControl) : base (versionControl) + { + } + + public bool Force { + get => GetProperty<bool> (); + set => SetProperty (value); + } + + public bool KeepLocal { + get => GetProperty<bool> (); + set => SetProperty (value); + } + } + + class MoveMetadata : RepositoryMetadata + { + public enum MoveType + { + File, + Directory + } + + public MoveMetadata () + { + } + + public MoveMetadata (VersionControlSystem versionControl) : base (versionControl) + { + } + + public bool Force { + get => GetProperty<bool> (); + set => SetProperty (value); + } + + public MoveType OperationType { + get => GetProperty<MoveType> (); + set => SetProperty (value); + } + } +} diff --git a/main/src/addins/VersionControl/MonoDevelop.VersionControl/MonoDevelop.VersionControl/Repository.cs b/main/src/addins/VersionControl/MonoDevelop.VersionControl/MonoDevelop.VersionControl/Repository.cs index 8359d9a992..8cea552dcb 100644 --- a/main/src/addins/VersionControl/MonoDevelop.VersionControl/MonoDevelop.VersionControl/Repository.cs +++ b/main/src/addins/VersionControl/MonoDevelop.VersionControl/MonoDevelop.VersionControl/Repository.cs @@ -468,7 +468,14 @@ namespace MonoDevelop.VersionControl /// </param> public RevisionPath[] GetRevisionChanges (Revision revision) { - return OnGetRevisionChanges (revision); + using (var tracker = Instrumentation.GetRevisionChangesCounter.BeginTiming (new RepositoryMetadata (VersionControlSystem))) { + try { + return OnGetRevisionChanges (revision); + } catch { + tracker.Metadata.SetFailure (); + throw; + } + } } @@ -478,7 +485,14 @@ namespace MonoDevelop.VersionControl // Returns the revision history of a file public Revision[] GetHistory (FilePath localFile, Revision since) { - return OnGetHistory (localFile, since); + using (var tracker = Instrumentation.GetHistoryCounter.BeginTiming (new RepositoryMetadata (VersionControlSystem))) { + try { + return OnGetHistory (localFile, since); + } catch { + tracker.Metadata.SetFailure (); + throw; + } + } } protected abstract Revision[] OnGetHistory (FilePath localFile, Revision since); @@ -505,9 +519,17 @@ namespace MonoDevelop.VersionControl // repository directory (must use absolute local paths). public Repository Publish (string serverPath, FilePath localPath, FilePath[] files, string message, ProgressMonitor monitor) { - var res = OnPublish (serverPath, localPath, files, message, monitor); - ClearCachedVersionInfo (localPath); - return res; + var metadata = new PublishMetadata (VersionControlSystem) { PathsCount = files.Length, SubFolder = localPath.IsChildPathOf (RootPath) }; + using (var tracker = Instrumentation.PublishCounter.BeginTiming (metadata, monitor.CancellationToken)) { + try { + var res = OnPublish (serverPath, localPath, files, message, monitor); + ClearCachedVersionInfo (localPath); + return res; + } catch { + metadata.SetFailure (); + throw; + } + } } protected abstract Repository OnPublish (string serverPath, FilePath localPath, FilePath[] files, string message, ProgressMonitor monitor); @@ -521,8 +543,16 @@ namespace MonoDevelop.VersionControl public void Update (FilePath[] localPaths, bool recurse, ProgressMonitor monitor) { - OnUpdate (localPaths, recurse, monitor); - ClearCachedVersionInfo (localPaths); + var metadata = new MultipathOperationMetadata (VersionControlSystem) { PathsCount = localPaths.Length, Recursive = recurse }; + using (var tracker = Instrumentation.UpdateCounter.BeginTiming (metadata, monitor.CancellationToken)) { + try { + OnUpdate (localPaths, recurse, monitor); + ClearCachedVersionInfo (localPaths); + } catch { + metadata.SetFailure (); + throw; + } + } } protected abstract void OnUpdate (FilePath[] localPaths, bool recurse, ProgressMonitor monitor); @@ -553,8 +583,16 @@ namespace MonoDevelop.VersionControl // Commits changes in a set of files or directories into the repository public void Commit (ChangeSet changeSet, ProgressMonitor monitor) { - ClearCachedVersionInfo (changeSet.BaseLocalPath); - OnCommit (changeSet, monitor); + var metadata = new MultipathOperationMetadata (VersionControlSystem) { PathsCount = changeSet.Count }; + using (var tracker = Instrumentation.CommitCounter.BeginTiming (metadata, monitor.CancellationToken)) { + try { + ClearCachedVersionInfo (changeSet.BaseLocalPath); + OnCommit (changeSet, monitor); + } catch { + metadata.SetFailure (); + throw; + } + } } protected abstract void OnCommit (ChangeSet changeSet, ProgressMonitor monitor); @@ -567,16 +605,37 @@ namespace MonoDevelop.VersionControl public void Checkout (FilePath targetLocalPath, Revision rev, bool recurse, ProgressMonitor monitor) { - ClearCachedVersionInfo (targetLocalPath); - OnCheckout (targetLocalPath, rev, recurse, monitor); + var metadata = new MultipathOperationMetadata (VersionControlSystem) { Recursive = recurse }; + using (var tracker = Instrumentation.CheckoutCounter.BeginTiming (metadata, monitor.CancellationToken)) { + try { + ClearCachedVersionInfo (targetLocalPath); + OnCheckout (targetLocalPath, rev, recurse, monitor); + + if (!Directory.Exists (targetLocalPath)) + metadata.SetFailure (); + else + metadata.SetSuccess (); + } catch { + metadata.SetFailure (); + throw; + } + } } protected abstract void OnCheckout (FilePath targetLocalPath, Revision rev, bool recurse, ProgressMonitor monitor); public void Revert (FilePath[] localPaths, bool recurse, ProgressMonitor monitor) { - ClearCachedVersionInfo (localPaths); - OnRevert (localPaths, recurse, monitor); + var metadata = new RevertMetadata (VersionControlSystem) { PathsCount = localPaths.Length, Recursive = recurse, OperationType = RevertMetadata.RevertType.LocalChanges }; + using (var tracker = Instrumentation.RevertCounter.BeginTiming (metadata, monitor.CancellationToken)) { + try { + ClearCachedVersionInfo (localPaths); + OnRevert (localPaths, recurse, monitor); + } catch { + metadata.SetFailure (); + throw; + } + } } public void Revert (FilePath localPath, bool recurse, ProgressMonitor monitor) @@ -588,16 +647,32 @@ namespace MonoDevelop.VersionControl public void RevertRevision (FilePath localPath, Revision revision, ProgressMonitor monitor) { - ClearCachedVersionInfo (localPath); - OnRevertRevision (localPath, revision, monitor); + var metadata = new RevertMetadata (VersionControlSystem) { PathsCount = 1, Recursive = true, OperationType = RevertMetadata.RevertType.SpecificRevision }; + using (var tracker = Instrumentation.RevertCounter.BeginTiming (metadata, monitor.CancellationToken)) { + try { + ClearCachedVersionInfo (localPath); + OnRevertRevision (localPath, revision, monitor); + } catch { + metadata.SetFailure (); + throw; + } + } } protected abstract void OnRevertRevision (FilePath localPath, Revision revision, ProgressMonitor monitor); public void RevertToRevision (FilePath localPath, Revision revision, ProgressMonitor monitor) { - ClearCachedVersionInfo (localPath); - OnRevertToRevision (localPath, revision, monitor); + var metadata = new RevertMetadata (VersionControlSystem) { PathsCount = 1, Recursive = true, OperationType = RevertMetadata.RevertType.ToRevision }; + using (var tracker = Instrumentation.RevertCounter.BeginTiming (metadata, monitor.CancellationToken)) { + try { + ClearCachedVersionInfo (localPath); + OnRevertToRevision (localPath, revision, monitor); + } catch { + metadata.SetFailure (); + throw; + } + } } protected abstract void OnRevertToRevision (FilePath localPath, Revision revision, ProgressMonitor monitor); @@ -610,12 +685,17 @@ namespace MonoDevelop.VersionControl public void Add (FilePath[] localPaths, bool recurse, ProgressMonitor monitor) { - try { - OnAdd (localPaths, recurse, monitor); - } catch (Exception e) { - LoggingService.LogError ("Failed to add file", e); + var metadata = new MultipathOperationMetadata (VersionControlSystem) { PathsCount = localPaths.Length, Recursive = recurse }; + using (var tracker = Instrumentation.AddCounter.BeginTiming (metadata, monitor.CancellationToken)) { + try { + OnAdd (localPaths, recurse, monitor); + } catch (Exception e) { + LoggingService.LogError ("Failed to add file", e); + metadata.SetFailure (); + } finally { + ClearCachedVersionInfo (localPaths); + } } - ClearCachedVersionInfo (localPaths); } protected abstract void OnAdd (FilePath[] localPaths, bool recurse, ProgressMonitor monitor); @@ -635,11 +715,15 @@ namespace MonoDevelop.VersionControl public void MoveFile (FilePath localSrcPath, FilePath localDestPath, bool force, ProgressMonitor monitor) { ClearCachedVersionInfo (localSrcPath, localDestPath); - try { - OnMoveFile (localSrcPath, localDestPath, force, monitor); - } catch (Exception e) { - LoggingService.LogError ("Failed to move file", e); - File.Move (localSrcPath, localDestPath); + var metadata = new MoveMetadata (VersionControlSystem) { Force = force, OperationType = MoveMetadata.MoveType.File }; + using (var tracker = Instrumentation.MoveCounter.BeginTiming (metadata, monitor.CancellationToken)) { + try { + OnMoveFile (localSrcPath, localDestPath, force, monitor); + } catch (Exception e) { + LoggingService.LogError ("Failed to move file", e); + metadata.SetFailure (); + File.Move (localSrcPath, localDestPath); + } } } @@ -653,11 +737,15 @@ namespace MonoDevelop.VersionControl public void MoveDirectory (FilePath localSrcPath, FilePath localDestPath, bool force, ProgressMonitor monitor) { ClearCachedVersionInfo (localSrcPath, localDestPath); - try { - OnMoveDirectory (localSrcPath, localDestPath, force, monitor); - } catch (Exception e) { - LoggingService.LogError ("Failed to move directory", e); - FileService.SystemDirectoryRename (localSrcPath, localDestPath); + var metadata = new MoveMetadata (VersionControlSystem) { Force = force, OperationType = MoveMetadata.MoveType.Directory }; + using (var tracker = Instrumentation.MoveCounter.BeginTiming (metadata, monitor.CancellationToken)) { + try { + OnMoveDirectory (localSrcPath, localDestPath, force, monitor); + } catch (Exception e) { + LoggingService.LogError ("Failed to move directory", e); + metadata.SetFailure (); + FileService.SystemDirectoryRename (localSrcPath, localDestPath); + } } } @@ -675,13 +763,17 @@ namespace MonoDevelop.VersionControl public void DeleteFiles (FilePath[] localPaths, bool force, ProgressMonitor monitor, bool keepLocal = true) { - try { - OnDeleteFiles (localPaths, force, monitor, keepLocal); - } catch (Exception e) { - LoggingService.LogError ("Failed to delete file", e); - if (!keepLocal) - foreach (var path in localPaths) - File.Delete (path); + var metadata = new DeleteMetadata (VersionControlSystem) { PathsCount = localPaths.Length, Force = force, KeepLocal = keepLocal }; + using (var tracker = Instrumentation.DeleteCounter.BeginTiming (metadata, monitor.CancellationToken)) { + try { + OnDeleteFiles (localPaths, force, monitor, keepLocal); + } catch (Exception e) { + LoggingService.LogError ("Failed to delete file", e); + metadata.SetFailure (); + if (!keepLocal) + foreach (var path in localPaths) + File.Delete (path); + } } ClearCachedVersionInfo (localPaths); } @@ -695,13 +787,17 @@ namespace MonoDevelop.VersionControl public void DeleteDirectories (FilePath[] localPaths, bool force, ProgressMonitor monitor, bool keepLocal = true) { - try { - OnDeleteDirectories (localPaths, force, monitor, keepLocal); - } catch (Exception e) { - LoggingService.LogError ("Failed to delete directory", e); - if (!keepLocal) - foreach (var path in localPaths) - Directory.Delete (path, true); + var metadata = new DeleteMetadata (VersionControlSystem) { PathsCount = localPaths.Length, Force = force, KeepLocal = keepLocal }; + using (var tracker = Instrumentation.DeleteCounter.BeginTiming (metadata, monitor.CancellationToken)) { + try { + OnDeleteDirectories (localPaths, force, monitor, keepLocal); + } catch (Exception e) { + LoggingService.LogError ("Failed to delete directory", e); + metadata.SetFailure (); + if (!keepLocal) + foreach (var path in localPaths) + Directory.Delete (path, true); + } } ClearCachedVersionInfo (localPaths); } @@ -733,8 +829,16 @@ namespace MonoDevelop.VersionControl // Locks a file in the repository so no other users can change it public void Lock (ProgressMonitor monitor, params FilePath[] localPaths) { - ClearCachedVersionInfo (localPaths); - OnLock (monitor, localPaths); + var metadata = new MultipathOperationMetadata (VersionControlSystem) { PathsCount = localPaths.Length }; + using (var tracker = Instrumentation.LockCounter.BeginTiming (metadata, monitor.CancellationToken)) { + try { + ClearCachedVersionInfo (localPaths); + OnLock (monitor, localPaths); + } catch { + metadata.SetFailure (); + throw; + } + } } // Locks a file in the repository so no other users can change it @@ -746,8 +850,16 @@ namespace MonoDevelop.VersionControl // Unlocks a file in the repository so other users can change it public void Unlock (ProgressMonitor monitor, params FilePath[] localPaths) { - ClearCachedVersionInfo (localPaths); - OnUnlock (monitor, localPaths); + var metadata = new MultipathOperationMetadata (VersionControlSystem) { PathsCount = localPaths.Length }; + using (var tracker = Instrumentation.UnlockCounter.BeginTiming (metadata, monitor.CancellationToken)) { + try { + ClearCachedVersionInfo (localPaths); + OnUnlock (monitor, localPaths); + } catch { + metadata.SetFailure (); + throw; + } + } } protected virtual void OnUnlock (ProgressMonitor monitor, params FilePath[] localPaths) @@ -882,8 +994,16 @@ namespace MonoDevelop.VersionControl // Ignores a file for version control operations. public void Ignore (FilePath[] localPath) { - ClearCachedVersionInfo (localPath); - OnIgnore (localPath); + var metadata = new MultipathOperationMetadata (VersionControlSystem) { PathsCount = localPath.Length }; + using (var tracker = Instrumentation.IgnoreCounter.BeginTiming (metadata)) { + try { + ClearCachedVersionInfo (localPath); + OnIgnore (localPath); + } catch { + metadata.SetFailure (); + throw; + } + } } protected abstract void OnIgnore (FilePath[] localPath); @@ -891,8 +1011,16 @@ namespace MonoDevelop.VersionControl // Unignores a file for version control operations. public void Unignore (FilePath[] localPath) { - ClearCachedVersionInfo (localPath); - OnUnignore (localPath); + var metadata = new MultipathOperationMetadata (VersionControlSystem) { PathsCount = localPath.Length }; + using (var tracker = Instrumentation.UnignoreCounter.BeginTiming (metadata)) { + try { + ClearCachedVersionInfo (localPath); + OnUnignore (localPath); + } catch { + metadata.SetFailure (); + throw; + } + } } protected abstract void OnUnignore (FilePath[] localPath); diff --git a/main/src/addins/VersionControl/MonoDevelop.VersionControl/MonoDevelop.VersionControl/UrlBasedRepositoryEditor.cs b/main/src/addins/VersionControl/MonoDevelop.VersionControl/MonoDevelop.VersionControl/UrlBasedRepositoryEditor.cs index 45cefe96fa..33c295cfc0 100644 --- a/main/src/addins/VersionControl/MonoDevelop.VersionControl/MonoDevelop.VersionControl/UrlBasedRepositoryEditor.cs +++ b/main/src/addins/VersionControl/MonoDevelop.VersionControl/MonoDevelop.VersionControl/UrlBasedRepositoryEditor.cs @@ -11,6 +11,7 @@ namespace MonoDevelop.VersionControl { UrlBasedRepository repo; public event EventHandler<EventArgs> PathChanged; + public event EventHandler<EventArgs> UrlChanged; bool updating; List<string> protocols = new List<string> (); @@ -53,6 +54,10 @@ namespace MonoDevelop.VersionControl get { return repositoryPathEntry.Text; } } + public string RepositoryServer { + get { return repositoryServerEntry.Text; } + } + bool ParseSSHUrl (string url) { if (!url.Contains (':')) @@ -81,6 +86,7 @@ namespace MonoDevelop.VersionControl comboProtocol.Active = protocols.IndexOf ("ssh"); comboProtocol.Sensitive = false; PathChanged?.Invoke (this, EventArgs.Empty); + UrlChanged?.Invoke (this, EventArgs.Empty); return true; } @@ -133,6 +139,7 @@ namespace MonoDevelop.VersionControl repo.Name = repo.Uri.Host; } updating = false; + UrlChanged?.Invoke (this, EventArgs.Empty); } void UpdateControls () diff --git a/main/src/addins/VersionControl/MonoDevelop.VersionControl/MonoDevelop.VersionControl/VersionControlService.cs b/main/src/addins/VersionControl/MonoDevelop.VersionControl/MonoDevelop.VersionControl/VersionControlService.cs index 6f43ccae81..aa25a066f2 100644 --- a/main/src/addins/VersionControl/MonoDevelop.VersionControl/MonoDevelop.VersionControl/VersionControlService.cs +++ b/main/src/addins/VersionControl/MonoDevelop.VersionControl/MonoDevelop.VersionControl/VersionControlService.cs @@ -226,7 +226,6 @@ namespace MonoDevelop.VersionControl return repo; } - readonly static Counter<RepositoryMetadata> Repositories = InstrumentationService.CreateCounter<RepositoryMetadata> ("VersionControl.RepositoryOpened", "Version Control", id:"VersionControl.RepositoryOpened"); internal static readonly Dictionary<FilePath,Repository> repositoryCache = new Dictionary<FilePath,Repository> (); public static Repository GetRepositoryReference (string path, string id) { @@ -257,10 +256,7 @@ namespace MonoDevelop.VersionControl var repo = detectedVCS?.GetRepositoryReference (bestMatch, id); if (repo != null) { repositoryCache.Add (bestMatch, repo); - Repositories.Inc (new RepositoryMetadata { - Type = detectedVCS.Name, - Version = detectedVCS.Version, - }); + Instrumentation.Repositories.Inc (new RepositoryMetadata (detectedVCS)); } return repo; } catch (Exception e) { @@ -841,10 +837,17 @@ namespace MonoDevelop.VersionControl Other } - public class RepositoryMetadata : CounterMetadata + class RepositoryMetadata : CounterMetadata { public RepositoryMetadata () { + throw new InvalidOperationException (); + } + + public RepositoryMetadata (VersionControlSystem versionControl) + { + Type = versionControl?.Name; + Version = versionControl?.Version; } public string Type { diff --git a/main/src/addins/VersionControl/MonoDevelop.VersionControl/packages.config b/main/src/addins/VersionControl/MonoDevelop.VersionControl/packages.config new file mode 100644 index 0000000000..f3aeebbbc7 --- /dev/null +++ b/main/src/addins/VersionControl/MonoDevelop.VersionControl/packages.config @@ -0,0 +1,4 @@ +<?xml version="1.0" encoding="utf-8"?> +<packages> + <package id="Humanizer.Core" version="2.2.0" targetFramework="net471" /> +</packages>
\ No newline at end of file diff --git a/main/src/addins/Xml/Completion/XmlSchemaCompletionData.cs b/main/src/addins/Xml/Completion/XmlSchemaCompletionData.cs index 0ecc94b5a9..27562c546d 100644 --- a/main/src/addins/Xml/Completion/XmlSchemaCompletionData.cs +++ b/main/src/addins/Xml/Completion/XmlSchemaCompletionData.cs @@ -66,17 +66,6 @@ namespace MonoDevelop.Xml.Completion }
/// <summary>
- /// Creates completion data from the schema passed in
- /// via the reader object.
- /// </summary> - [Obsolete ("Please pass in a TextReader instead")]
- public XmlSchemaCompletionData(XmlTextReader reader)
- {
- reader.XmlResolver = new LocalOnlyXmlResolver ();
- ReadSchema(reader);
- }
-
- /// <summary>
/// Creates the completion data from the specified schema file.
/// </summary>
public XmlSchemaCompletionData (string fileName) : this (String.Empty, fileName)
diff --git a/main/src/core/Mono.TextEditor.Shared/Mono.TextEditor.Utils/ClipboardColoredText.cs b/main/src/core/Mono.TextEditor.Shared/Mono.TextEditor.Utils/ClipboardColoredText.cs index 4ecfda0278..1115115d51 100644 --- a/main/src/core/Mono.TextEditor.Shared/Mono.TextEditor.Utils/ClipboardColoredText.cs +++ b/main/src/core/Mono.TextEditor.Shared/Mono.TextEditor.Utils/ClipboardColoredText.cs @@ -34,6 +34,7 @@ using MonoDevelop.Core.Text; using MonoDevelop.Ide.Editor.Highlighting; using System.Collections.Immutable; using System.Threading.Tasks; +using System.Threading; namespace Mono.TextEditor.Utils { @@ -48,7 +49,7 @@ namespace Mono.TextEditor.Utils this.Text = doc.GetTextAt (chunk); } - public static async Task<List<List<ClipboardColoredText>>> GetChunks (TextEditorData data, ISegment selectedSegment) + public static async Task<List<List<ClipboardColoredText>>> GetChunks (TextEditorData data, ISegment selectedSegment, CancellationToken cancellationToken = default) { int startLineNumber = data.OffsetToLineNumber (selectedSegment.Offset); int endLineNumber = data.OffsetToLineNumber (selectedSegment.EndOffset); @@ -59,7 +60,8 @@ namespace Mono.TextEditor.Utils var chunks = await data.GetChunks ( line, offset, - length + length, + cancellationToken ); copiedColoredChunks.Add ( chunks diff --git a/main/src/core/Mono.TextEditor.Shared/Mono.TextEditor/CaretImpl.ITextCaret.cs b/main/src/core/Mono.TextEditor.Shared/Mono.TextEditor/CaretImpl.ITextCaret.cs index 6f121254c2..113f6992ac 100644 --- a/main/src/core/Mono.TextEditor.Shared/Mono.TextEditor/CaretImpl.ITextCaret.cs +++ b/main/src/core/Mono.TextEditor.Shared/Mono.TextEditor/CaretImpl.ITextCaret.cs @@ -29,6 +29,7 @@ using Microsoft.VisualStudio.Text; using Microsoft.VisualStudio.Text.Editor; using Microsoft.VisualStudio.Text.Formatting; using MonoDevelop.Ide.Editor; +using MonoDevelop.Core; namespace Mono.TextEditor { @@ -116,8 +117,14 @@ namespace Mono.TextEditor insertionPoint = vsp; if (args.CaretChangeReason == CaretChangeReason.Movement) { oldCaretLocation = args.Location; - var oldOffset = TextEditor.LocationToOffset (args.Location); - var snapshotPoint = new SnapshotPoint (TextEditor.TextSnapshot, oldOffset); + var snapShot = args.Snapshot; + var snapshotLine = snapShot.GetLineFromLineNumber (args.Location.Line - 1); + if (snapshotLine == null) { + LoggingService.LogError ("PositionChanged_ITextCaret line number : " + args.Location.Line + " is out of range."); + return; + } + var oldOffset = snapshotLine.Start.Position + Math.Min (snapshotLine.Length, args.Location.Column - 1); + var snapshotPoint = new SnapshotPoint (snapShot, oldOffset); var mappingPoint = TextEditor.BufferGraph.CreateMappingPoint (snapshotPoint, PointTrackingMode.Positive); var oldCaretPosition = new CaretPosition (vsp, mappingPoint, _caretAffinity); var eventArgs = new CaretPositionChangedEventArgs (TextEditor, oldCaretPosition, ((ITextCaret)this).Position); diff --git a/main/src/core/Mono.TextEditor.Shared/Mono.TextEditor/CaretImpl.cs b/main/src/core/Mono.TextEditor.Shared/Mono.TextEditor/CaretImpl.cs index 9822b5a151..7ad29ede3d 100644 --- a/main/src/core/Mono.TextEditor.Shared/Mono.TextEditor/CaretImpl.cs +++ b/main/src/core/Mono.TextEditor.Shared/Mono.TextEditor/CaretImpl.cs @@ -39,7 +39,9 @@ namespace Mono.TextEditor bool autoScrollToCaret = true; CaretMode mode; - + + ITextSnapshot currentBuffer; + int line = DocumentLocation.MinLine; public override int Line { get { @@ -54,7 +56,7 @@ namespace Mono.TextEditor CheckLine (); SetColumn (); UpdateCaretOffset (); - OnPositionChanged (new CaretLocationEventArgs (old, CaretChangeReason.Movement)); + OnPositionChanged (new CaretLocationEventArgs (old, currentBuffer ?? TextEditorData.Document.TextBuffer.CurrentSnapshot, CaretChangeReason.Movement)); } } } @@ -73,7 +75,7 @@ namespace Mono.TextEditor CheckColumn (); SetDesiredColumn (); UpdateCaretOffset (); - OnPositionChanged (new CaretLocationEventArgs (old, CaretChangeReason.Movement)); + OnPositionChanged (new CaretLocationEventArgs (old, currentBuffer ?? TextEditorData.Document.TextBuffer.CurrentSnapshot, CaretChangeReason.Movement)); } } } @@ -93,7 +95,7 @@ namespace Mono.TextEditor CheckColumn (); SetDesiredColumn (); UpdateCaretOffset (); - OnPositionChanged (new CaretLocationEventArgs (old, CaretChangeReason.Movement)); + OnPositionChanged (new CaretLocationEventArgs (old, currentBuffer ?? TextEditorData.Document.TextBuffer.CurrentSnapshot, CaretChangeReason.Movement)); } } } @@ -119,7 +121,7 @@ namespace Mono.TextEditor CheckLine (); CheckColumn (); SetDesiredColumn (); - OnPositionChanged (new CaretLocationEventArgs (old, CaretChangeReason.Movement)); + OnPositionChanged (new CaretLocationEventArgs (old, currentBuffer ?? TextEditorData.Document.TextBuffer.CurrentSnapshot, CaretChangeReason.Movement)); } } @@ -270,7 +272,7 @@ namespace Mono.TextEditor } UpdateCaretOffset (); - OnPositionChanged (new CaretLocationEventArgs (old, CaretChangeReason.Movement)); + OnPositionChanged (new CaretLocationEventArgs (old, currentBuffer ?? TextEditorData.Document.TextBuffer.CurrentSnapshot, CaretChangeReason.Movement)); } void SetDesiredColumn () @@ -301,7 +303,7 @@ namespace Mono.TextEditor var old = Location; DesiredColumn = desiredColumn; SetColumn (); - OnPositionChanged (new CaretLocationEventArgs (old, CaretChangeReason.Movement)); + OnPositionChanged (new CaretLocationEventArgs (old, currentBuffer ?? TextEditorData.Document.TextBuffer.CurrentSnapshot, CaretChangeReason.Movement)); } public override string ToString () @@ -330,6 +332,7 @@ namespace Mono.TextEditor TextEditorData.Document.EnsureOffsetIsUnfolded (Offset); base.OnPositionChanged (args); PositionChanged_ITextCaret (args); + currentBuffer = TextEditorData.Document.TextBuffer.CurrentSnapshot; } protected virtual void OnModeChanged () @@ -363,8 +366,9 @@ namespace Mono.TextEditor //} var curVersion = TextEditorData.Version; var newOffset = e.GetNewOffset (caretOffset); - if (newOffset == caretOffset || !AutoUpdatePosition) + if (!AutoUpdatePosition) return; + DocumentLocation old = Location; var newLocation = TextEditorData.OffsetToLocation (newOffset); int newColumn = newLocation.Column; @@ -385,7 +389,7 @@ namespace Mono.TextEditor SetDesiredColumn (); UpdateCaretOffset (); - OnPositionChanged (new CaretLocationEventArgs (old, CaretChangeReason.BufferChange)); + OnPositionChanged (new CaretLocationEventArgs (old, currentBuffer ?? TextEditorData.Document.TextBuffer.CurrentSnapshot, CaretChangeReason.BufferChange)); } public void SetDocument (TextDocument doc) diff --git a/main/src/core/Mono.TextEditor.Shared/Mono.TextEditor/Document/TextDocument.cs b/main/src/core/Mono.TextEditor.Shared/Mono.TextEditor/Document/TextDocument.cs index 2b4946c069..c801947809 100644 --- a/main/src/core/Mono.TextEditor.Shared/Mono.TextEditor/Document/TextDocument.cs +++ b/main/src/core/Mono.TextEditor.Shared/Mono.TextEditor/Document/TextDocument.cs @@ -1,4 +1,4 @@ -// +// // TextDocument.cs // // Author: @@ -282,7 +282,6 @@ namespace Mono.TextEditor }
var textChange = new TextChangeEventArgs(changes); - InterruptFoldWorker(); TextChanging?.Invoke(this, textChange); // After TextChanging notification has been sent, we can update the cached snapshot
this.currentSnapshot = args.After; @@ -1371,10 +1370,6 @@ namespace Mono.TextEditor void RemoveFolding (FoldSegment folding) { folding.isAttached = false; - if (folding.isFolded) { - foldedSegments.Remove (folding); - CommitUpdateAll (); - } foldSegmentTree.Remove (folding); } @@ -1386,7 +1381,7 @@ namespace Mono.TextEditor { var oldSegments = new List<FoldSegment> (FoldSegments); int oldIndex = 0; - bool foldedSegmentAdded = false; + bool foldedSegmentAdded = false, foldedFoldingRemoved = false; var newSegments = segments.ToList (); newSegments.Sort (); var newFoldedSegments = new HashSet<FoldSegment> (); @@ -1399,6 +1394,7 @@ namespace Mono.TextEditor int offset = newFoldSegment.Offset; while (oldIndex < oldSegments.Count && offset > oldSegments [oldIndex].Offset) { RemoveFolding (oldSegments [oldIndex]); + foldedFoldingRemoved |= oldSegments [oldIndex].IsCollapsed; oldIndex++; }
if (oldIndex < oldSegments.Count && offset == oldSegments [oldIndex].Offset) { @@ -1432,33 +1428,19 @@ namespace Mono.TextEditor return null; } RemoveFolding (oldSegments [oldIndex]); + foldedFoldingRemoved |= oldSegments [oldIndex].IsCollapsed; oldIndex++; } bool countChanged = foldedSegments.Count != newFoldedSegments.Count; - update = foldedSegmentAdded || countChanged; + update = foldedSegmentAdded || countChanged || foldedFoldingRemoved; return newFoldedSegments; } - public void WaitForFoldUpdateFinished () - { - if (foldSegmentTask != null) { - try { - foldSegmentTask.Wait (5000); - } catch (AggregateException e) { - e.Flatten ().Handle (x => x is OperationCanceledException); - } catch (OperationCanceledException) { - - } - foldSegmentTask = null; - } - } - internal void InterruptFoldWorker () { if (foldSegmentSrc == null) return; foldSegmentSrc.Cancel (); - WaitForFoldUpdateFinished (); foldSegmentSrc = null; } @@ -1742,6 +1724,7 @@ namespace Mono.TextEditor OnHeightChanged (EventArgs.Empty); } } + marker.parent = null; if (updateLine) this.CommitLineUpdate (line); } diff --git a/main/src/core/Mono.TextEditor.Shared/Mono.TextEditor/Styles.cs b/main/src/core/Mono.TextEditor.Shared/Mono.TextEditor/Styles.cs index 6fe1fc0ac6..7de9bbeea7 100644 --- a/main/src/core/Mono.TextEditor.Shared/Mono.TextEditor/Styles.cs +++ b/main/src/core/Mono.TextEditor.Shared/Mono.TextEditor/Styles.cs @@ -55,7 +55,7 @@ namespace Mono.TextEditor.PopupWindow public static void LoadStyles () { - var bgColor = Platform.IsMac ? Color.FromName ("#5189ed") : Color.FromName ("#cce8ff"); + var bgColor = Platform.IsMac ? Color.FromName ("#2862d9") : Color.FromName ("#cce8ff"); var fgColor = Platform.IsMac ? Color.FromName ("#ffffff") : Color.FromName ("#000000"); ModeHelpWindowTokenOutlineColor = fgColor; diff --git a/main/src/core/Mono.TextEditor.Shared/Mono.TextEditor/TextEditorData.cs b/main/src/core/Mono.TextEditor.Shared/Mono.TextEditor/TextEditorData.cs index e669554bee..2accdab270 100644 --- a/main/src/core/Mono.TextEditor.Shared/Mono.TextEditor/TextEditorData.cs +++ b/main/src/core/Mono.TextEditor.Shared/Mono.TextEditor/TextEditorData.cs @@ -1,4 +1,4 @@ -// TextEditorData.cs +// TextEditorData.cs // // Author: // Mike Krüger <mkrueger@novell.com> @@ -413,6 +413,11 @@ namespace Mono.TextEditor public string GetMarkup (int offset, int length, bool removeIndent, bool useColors = true, bool replaceTabs = true, bool fitIdeStyle = false) { + return GetMarkupAsync (offset, length, removeIndent, useColors, replaceTabs, fitIdeStyle).WaitAndGetResult (default (CancellationToken)); + } + + public async Task<string> GetMarkupAsync (int offset, int length, bool removeIndent, bool useColors = true, bool replaceTabs = true, bool fitIdeStyle = false, CancellationToken cancellationToken = default) + { var doc = Document; var mode = doc.SyntaxMode; var style = fitIdeStyle ? SyntaxHighlightingService.GetEditorTheme (Parent.GetIdeColorStyleName ()) : ColorStyle; @@ -441,7 +446,7 @@ namespace Mono.TextEditor } } - foreach (var chunk in GetChunks (line, curOffset, toOffset - curOffset).WaitAndGetResult (default (CancellationToken))) { + foreach (var chunk in await GetChunks (line, curOffset, toOffset - curOffset, cancellationToken)) { if (chunk.Length == 0) continue; var chunkStyle = style.GetChunkStyle (chunk.ScopeStack); @@ -486,14 +491,30 @@ namespace Mono.TextEditor return StringBuilderCache.ReturnAndFree (result); } - internal async Task<IEnumerable<MonoDevelop.Ide.Editor.Highlighting.ColoredSegment>> GetChunks (DocumentLine line, int offset, int length) + internal async Task<IEnumerable<MonoDevelop.Ide.Editor.Highlighting.ColoredSegment>> GetChunks (DocumentLine line, int offset, int length, CancellationToken cancellationToken = default) { - var lineOffset = line.Offset; - return TextViewMargin.TrimChunks ( - (await document.SyntaxMode.GetHighlightedLineAsync (line, CancellationToken.None).ConfigureAwait(false)) - .Segments - .Select (c => c.WithOffset (c.Offset + lineOffset)) - .ToList (), offset, length); + if (line == null) + throw new ArgumentNullException (nameof (line)); + if (document == null) + throw new InvalidOperationException ("TextEditorData was disposed."); + Runtime.AssertMainThread (); + try { + var lineOffset = line.Offset; + var syntaxMode = document.SyntaxMode; + var highlightedLine = await syntaxMode.GetHighlightedLineAsync (line, cancellationToken).ConfigureAwait (false); + var result = new List<MonoDevelop.Ide.Editor.Highlighting.ColoredSegment> (highlightedLine.Segments.Count); + foreach (var segment in highlightedLine.Segments) { + if (segment == null) { + LoggingService.LogInternalError (new InvalidOperationException ("Segment== null insede highlighed line " + highlightedLine)); + continue; + } + result.Add (segment.WithOffset (segment.Offset + lineOffset)); + } + return TextViewMargin.TrimChunks (result, offset, length); + } catch (Exception e) { + LoggingService.LogInternalError ("Error while getting chunks.", e); + return new [] { new MonoDevelop.Ide.Editor.Highlighting.ColoredSegment (offset, length, ScopeStack.Empty) }; + } } public int Insert (int offset, string value) diff --git a/main/src/core/MonoDevelop.Core/MonoDevelop.Core.Assemblies/AssemblyUtilities.cs b/main/src/core/MonoDevelop.Core/MonoDevelop.Core.Assemblies/AssemblyUtilities.cs new file mode 100644 index 0000000000..bcb83e52a9 --- /dev/null +++ b/main/src/core/MonoDevelop.Core/MonoDevelop.Core.Assemblies/AssemblyUtilities.cs @@ -0,0 +1,103 @@ +// +// AssemblyUtilities.cs +// +// Author: +// Mike Krüger <mikkrg@microsoft.com> +// +// Copyright (c) 2019 Microsoft Corporation. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +using System; +using System.Threading; +using System.IO; +using System.Xml; +using System.Collections; +using System.Collections.Generic; +using System.Diagnostics; +using System.Reflection; +using MonoDevelop.Core; +using MonoDevelop.Core.Execution; +using MonoDevelop.Core.AddIns; +using MonoDevelop.Core.Serialization; +using Mono.Addins; +using Mono.PkgConfig; +using System.Reflection.PortableExecutable; +using System.Reflection.Metadata; + +namespace MonoDevelop.Core.Assemblies +{ + public static class AssemblyUtilities + { + static bool Is64BitPE (Machine machine) + { + return machine == Machine.Amd64 || machine == Machine.Arm64 || machine == Machine.IA64 || machine == Machine.Alpha64; + } + + static bool TryReadPEHeaders (string assemblyPath, out PortableExecutableKinds peKind, out Machine machine) + { + peKind = default; + machine = default; + + try { + if (!File.Exists (assemblyPath)) + return false; + + using (var reader = new PEReader (File.OpenRead (assemblyPath))) { + var peHeaders = reader.PEHeaders; + + var corFlags = peHeaders.CorHeader.Flags; + if ((corFlags & CorFlags.ILOnly) != 0) + peKind |= PortableExecutableKinds.ILOnly; + + if ((corFlags & CorFlags.Prefers32Bit) != 0) + peKind |= PortableExecutableKinds.Preferred32Bit; + else if ((corFlags & CorFlags.Requires32Bit) != 0) + peKind |= PortableExecutableKinds.Required32Bit; + + if (peHeaders.PEHeader.Magic == PEMagic.PE32Plus) + peKind |= PortableExecutableKinds.PE32Plus; + + machine = peHeaders.CoffHeader.Machine; + } + + return true; + } catch (Exception e) { + LoggingService.LogError ("Error while determining 64/32 bit assembly.", e); + return false; + } + } + + public static ProcessExecutionArchitecture GetProcessExecutionArchitectureForAssembly (string assemblyPath) + { + if (string.IsNullOrEmpty (assemblyPath)) + throw new ArgumentNullException (nameof (assemblyPath)); + + if (TryReadPEHeaders (assemblyPath, out var peKind, out var machine)) { + if ((peKind & (PortableExecutableKinds.Preferred32Bit | PortableExecutableKinds.Required32Bit)) != 0) + return ProcessExecutionArchitecture.X86; + + if ((peKind & PortableExecutableKinds.PE32Plus) != 0 || Is64BitPE (machine)) + return ProcessExecutionArchitecture.X64; + } + + return ProcessExecutionArchitecture.Unspecified; + } + } +} diff --git a/main/src/core/MonoDevelop.Core/MonoDevelop.Core.Assemblies/MonoTargetRuntime.cs b/main/src/core/MonoDevelop.Core/MonoDevelop.Core.Assemblies/MonoTargetRuntime.cs index 57c6ab4c57..67c332cc6d 100644 --- a/main/src/core/MonoDevelop.Core/MonoDevelop.Core.Assemblies/MonoTargetRuntime.cs +++ b/main/src/core/MonoDevelop.Core/MonoDevelop.Core.Assemblies/MonoTargetRuntime.cs @@ -294,13 +294,6 @@ namespace MonoDevelop.Core.Assemblies return new ExecutionEnvironment (EnvironmentVariables); } - static bool Is64BitPE (Mono.Cecil.TargetArchitecture machine) - { - return machine == Mono.Cecil.TargetArchitecture.AMD64 || - machine == Mono.Cecil.TargetArchitecture.IA64 || - machine == Mono.Cecil.TargetArchitecture.ARM64; - } - /// <summary> /// Get the Mono executable best matching the assembly architecture flags. /// </summary> @@ -309,35 +302,30 @@ namespace MonoDevelop.Core.Assemblies /// <param name="assemblyPath">Assembly path.</param> public string GetMonoExecutableForAssembly (string assemblyPath) { - Mono.Cecil.ModuleAttributes peKind; - Mono.Cecil.TargetArchitecture machine; - - try { - using (var adef = Mono.Cecil.AssemblyDefinition.ReadAssembly (assemblyPath)) { - peKind = adef.MainModule.Attributes; - machine = adef.MainModule.Architecture; - } - } catch { - peKind = Mono.Cecil.ModuleAttributes.ILOnly; - machine = Mono.Cecil.TargetArchitecture.I386; - } + return GetMonoExecutable (AssemblyUtilities.GetProcessExecutionArchitectureForAssembly (assemblyPath)); + } + internal string GetMonoExecutable (ProcessExecutionArchitecture use64Bit) + { string monoPath; - - if ((peKind & (Mono.Cecil.ModuleAttributes.Required32Bit | Mono.Cecil.ModuleAttributes.Preferred32Bit)) != 0) { - monoPath = Path.Combine (MonoRuntimeInfo.Prefix, "bin", "mono32"); + switch (use64Bit) { + case ProcessExecutionArchitecture.X64: + monoPath = Path.Combine (MonoRuntimeInfo.Prefix, "bin", "mono64"); if (File.Exists (monoPath)) return monoPath; - } else if (Is64BitPE (machine)) { - monoPath = Path.Combine (MonoRuntimeInfo.Prefix, "bin", "mono64"); + break; + case ProcessExecutionArchitecture.X86: + monoPath = Path.Combine (MonoRuntimeInfo.Prefix, "bin", "mono32"); if (File.Exists (monoPath)) return monoPath; + break; } return monoPath = Path.Combine (MonoRuntimeInfo.Prefix, "bin", "mono"); } + } - + class PcFileCacheContext: Mono.PkgConfig.IPcFileCacheContext<LibraryPackageInfo> { public void ReportError (string message, System.Exception ex) diff --git a/main/src/core/MonoDevelop.Core/MonoDevelop.Core.Assemblies/SystemAssemblyService.cs b/main/src/core/MonoDevelop.Core/MonoDevelop.Core.Assemblies/SystemAssemblyService.cs index 4ec62572f9..3d42b374db 100644 --- a/main/src/core/MonoDevelop.Core/MonoDevelop.Core.Assemblies/SystemAssemblyService.cs +++ b/main/src/core/MonoDevelop.Core/MonoDevelop.Core.Assemblies/SystemAssemblyService.cs @@ -30,12 +30,14 @@ using System; using System.Collections.Generic; +using System.Collections.Immutable; using System.IO; using System.Linq; using System.Reflection; +using System.Reflection.Metadata; +using System.Reflection.PortableExecutable; using System.Threading; using Mono.Addins; -using Mono.Cecil; using MonoDevelop.Core.AddIns; namespace MonoDevelop.Core.Assemblies @@ -369,29 +371,54 @@ namespace MonoDevelop.Core.Assemblies { if (!File.Exists (file)) return TargetFrameworkMoniker.UNKNOWN; - AssemblyDefinition assembly = null; + try { - assembly = AssemblyDefinition.ReadAssembly (file); - var att = assembly.CustomAttributes.FirstOrDefault (a => - a.AttributeType.FullName == "System.Runtime.Versioning.TargetFrameworkAttribute" - ); - if (att != null) { - if (att.ConstructorArguments.Count == 1) { - var v = att.ConstructorArguments[0].Value as string; - TargetFrameworkMoniker m; - if (v != null && TargetFrameworkMoniker.TryParse (v, out m)) { + using (var reader = new PEReader (File.OpenRead (file))) { + var mr = reader.GetMetadataReader (); + + foreach (var customAttributeHandle in mr.GetAssemblyDefinition ().GetCustomAttributes ()) { + var customAttribute = mr.GetCustomAttribute (customAttributeHandle); + + var ctorHandle = customAttribute.Constructor; + if (ctorHandle.Kind != HandleKind.MemberReference) + continue; + + var ctor = mr.GetMemberReference ((MemberReferenceHandle)ctorHandle); + var attrType = mr.GetTypeReference ((TypeReferenceHandle)ctor.Parent); + + var ns = mr.GetString (attrType.Namespace); + if (ns != "System.Runtime.Versioning") + continue; + + var typeName = mr.GetString (attrType.Name); + if (typeName != "TargetFrameworkAttribute") + continue; + + var provider = new StringParameterValueTypeProvider (mr, customAttribute.Value); + var signature = ctor.DecodeMethodSignature (provider, null); + var parameterTypes = signature.ParameterTypes; + if (parameterTypes.Length != 1) + continue; + + var value = parameterTypes [0]; + if (value != null && TargetFrameworkMoniker.TryParse (value, out var m)) { return m; } + LoggingService.LogError ("Invalid TargetFrameworkAttribute in assembly {0} - {1}", file, value); } - LoggingService.LogError ("Invalid TargetFrameworkAttribute in assembly {0}", file); - } - if (tr != null) { - foreach (var r in assembly.MainModule.AssemblyReferences) { - if (r.Name == "mscorlib") { + + if (tr != null) { + foreach (var assemblyReferenceHandle in mr.AssemblyReferences) { + var assemblyReference = mr.GetAssemblyReference (assemblyReferenceHandle); + + var name = mr.GetString (assemblyReference.Name); + if (name != "mscorlib") + continue; + TargetFramework compatibleFramework = null; // If there are several frameworks that can run the file, pick one that is installed foreach (TargetFramework tf in GetKnownFrameworks ()) { - if (tf.GetCorlibVersion () == r.Version.ToString ()) { + if (tf.GetCorlibVersion () == assemblyReference.Version.ToString ()) { compatibleFramework = tf; if (tr.IsInstalled (tf)) return tf.Id; @@ -405,125 +432,66 @@ namespace MonoDevelop.Core.Assemblies } } catch (Exception ex) { LoggingService.LogError ("Error determining target framework for assembly {0}: {1}", file, ex); - return TargetFrameworkMoniker.UNKNOWN; - } finally { - assembly?.Dispose (); } - LoggingService.LogError ("Failed to determine target framework for assembly {0}", file); return TargetFrameworkMoniker.UNKNOWN; } /// <summary> /// Simply get all assembly reference names from an assembly given it's file name. /// </summary> - public static IEnumerable<string> GetAssemblyReferences (string fileName) - {
- AssemblyDefinition assembly = null;
- try {
- try {
- assembly = Mono.Cecil.AssemblyDefinition.ReadAssembly (fileName);
- } catch {
- return Enumerable.Empty<string> ();
- }
- return assembly.MainModule.AssemblyReferences.Select (x => x.Name);
- } finally {
- assembly?.Dispose ();
- }
- } + public static ImmutableArray<string> GetAssemblyReferences (string fileName) + { + try { + using (var reader = new PEReader (File.OpenRead (fileName))) { + var mr = reader.GetMetadataReader (); + var assemblyReferences = mr.AssemblyReferences; - static Dictionary<string, bool> referenceDict = new Dictionary<string, bool> (); + var builder = ImmutableArray.CreateBuilder<string> (assemblyReferences.Count); - static bool ContainsReferenceToSystemRuntimeInternal (string fileName) - { - bool result;
- if (referenceDict.TryGetValue (fileName, out result))
- return result;
-
- //const int cacheLimit = 4096;
- //if (referenceDict.Count > cacheLimit)
- // referenceDict = ImmutableDictionary<string, bool>.Empty
-
- AssemblyDefinition assembly = null;
- try {
- try {
- assembly = Mono.Cecil.AssemblyDefinition.ReadAssembly (fileName);
- } catch {
- return false;
- }
- foreach (var r in assembly.MainModule.AssemblyReferences) {
- // Don't compare the version number since it may change depending on the version of .net standard
- if (r.Name.Equals ("System.Runtime")) {
- referenceDict [fileName] = true; ;
- return true;
- }
- }
- } finally {
- assembly?.Dispose ();
- }
- referenceDict [fileName] = false;
- return false; + foreach (var assemblyReferenceHandle in assemblyReferences) { + var assemblyReference = mr.GetAssemblyReference (assemblyReferenceHandle); + builder.Add (mr.GetString (assemblyReference.Name)); + } + + return builder.MoveToImmutable(); + } + } catch { + return ImmutableArray<string>.Empty; + } } static Dictionary<string, bool> facadeReferenceDict = new Dictionary<string, bool> (); static bool RequiresFacadeAssembliesInternal (string fileName) { - bool result; - if (facadeReferenceDict.TryGetValue (fileName, out result)) + if (facadeReferenceDict.TryGetValue (fileName, out var result)) return result; - AssemblyDefinition assembly = null; try { - try { - assembly = Mono.Cecil.AssemblyDefinition.ReadAssembly (fileName); - } catch { - return false; - } - foreach (var r in assembly.MainModule.AssemblyReferences) { - // Don't compare the version number since it may change depending on the version of .net standard - if (r.Name.Equals ("System.Runtime") || r.Name.Equals ("netstandard")) { - facadeReferenceDict [fileName] = true; ; - return true; + using (var reader = new PEReader (File.OpenRead (fileName))) { + var mr = reader.GetMetadataReader (); + + foreach (var assemblyReferenceHandle in mr.AssemblyReferences) { + var assemblyReference = mr.GetAssemblyReference (assemblyReferenceHandle); + var name = mr.GetString (assemblyReference.Name); + + // Don't compare the version number since it may change depending on the version of .net standard + if (name.Equals ("System.Runtime") || name.Equals ("netstandard")) { + facadeReferenceDict [fileName] = true; + return true; + } } } - } finally { - assembly?.Dispose (); + } catch { + return false; } + facadeReferenceDict [fileName] = false; return false; } - static object referenceLock = new object (); - - [Obsolete ("Use RequiresFacadeAssemblies (string fileName)")] - public static bool ContainsReferenceToSystemRuntime (string fileName) - { - lock (referenceLock) { - return ContainsReferenceToSystemRuntimeInternal (fileName); - } - } - - static SemaphoreSlim referenceLockAsync = new SemaphoreSlim (1, 1); - - [Obsolete ("Use RequiresFacadeAssembliesAsync (string fileName)")] - public static async System.Threading.Tasks.Task<bool> ContainsReferenceToSystemRuntimeAsync (string filename) - { - try { - await referenceLockAsync.WaitAsync ().ConfigureAwait (false); - return ContainsReferenceToSystemRuntimeInternal (filename); - } finally { - referenceLockAsync.Release (); - } - } - - internal static bool RequiresFacadeAssemblies (string fileName) - { - lock (referenceLock) { - return RequiresFacadeAssembliesInternal (fileName); - } - } - - internal static async System.Threading.Tasks.Task<bool> RequiresFacadeAssembliesAsync (string filename) + static readonly SemaphoreSlim referenceLockAsync = new SemaphoreSlim (1, 1); + public static async System.Threading.Tasks.Task<bool> RequiresFacadeAssembliesAsync (string filename) { try { await referenceLockAsync.WaitAsync ().ConfigureAwait (false); @@ -556,25 +524,51 @@ namespace MonoDevelop.Core.Assemblies /// Simply get all assembly manifest resources from an assembly given it's file name. /// </summary> public static IEnumerable<ManifestResource> GetAssemblyManifestResources (string fileName) - {
- AssemblyDefinition assembly = null;
- try {
- try {
- assembly = Mono.Cecil.AssemblyDefinition.ReadAssembly (fileName);
- } catch {
- yield break;
- }
- foreach (var r in assembly.MainModule.Resources) {
- if (r.ResourceType == ResourceType.Embedded) {
- var er = (EmbeddedResource)r;
-
- // Explicitly create a capture and query it here so the stream isn't queried after the module is disposed.
- var rs = er.GetResourceStream ();
- yield return new ManifestResource (er.Name, () => rs);
- }
- }
- } finally {
- assembly?.Dispose ();
+ { + using (var reader = new PEReader (File.OpenRead (fileName))) { + var mr = reader.GetMetadataReader (); + + var headers = reader.PEHeaders; + var resources = headers.CorHeader.ResourcesDirectory; + var sectionData = reader.GetSectionData (resources.RelativeVirtualAddress); + if (sectionData.Length == 0) + return Array.Empty<ManifestResource> (); // RVA could not be found in any section + + var sectionReader = sectionData.GetReader (); + var manifestResources = mr.ManifestResources; + var result = new List<ManifestResource> (manifestResources.Count); + + foreach (var manifestResourceHandle in manifestResources) { + var manifestResource = mr.GetManifestResource (manifestResourceHandle); + + // This means the type is Embedded. + var isEmbeddedResource = manifestResource.Implementation.IsNil; + if (!isEmbeddedResource) + continue; + + int offset = (int)manifestResource.Offset; + sectionReader.Offset += offset; + try { + int length = sectionReader.ReadInt32 (); + if ((uint)length > sectionReader.RemainingBytes) { + LoggingService.LogError ("Resource stream invalid length {0}", length.ToString ()); + continue; + } + + var name = mr.GetString (manifestResource.Name); + unsafe { + using (var unmanagedStream = new UnmanagedMemoryStream (sectionReader.CurrentPointer, length, length, FileAccess.Read)) { + var memoryStream = new MemoryStream (length); + unmanagedStream.CopyTo (memoryStream); + memoryStream.Position = 0; + result.Add (new ManifestResource (name, () => memoryStream)); + } + } + } finally { + sectionReader.Offset -= offset; + } + } + return result; } } @@ -583,5 +577,37 @@ namespace MonoDevelop.Core.Assemblies { return Runtime.LoadAssemblyFrom (asmPath); } + + sealed class StringParameterValueTypeProvider : ISignatureTypeProvider<string, object> + { + readonly BlobReader valueReader; + + public StringParameterValueTypeProvider (MetadataReader reader, BlobHandle value) + { + valueReader = reader.GetBlobReader (value); + + var prolog = valueReader.ReadUInt16 (); + if (prolog != 1) + throw new BadImageFormatException ("Invalid custom attribute prolog."); + } + + public string GetPrimitiveType (PrimitiveTypeCode typeCode) => typeCode != PrimitiveTypeCode.String ? "" : valueReader.ReadSerializedString (); + public string GetArrayType (string elementType, ArrayShape shape) => ""; + public string GetByReferenceType (string elementType) => ""; + public string GetFunctionPointerType (MethodSignature<string> signature) => ""; + public string GetGenericInstance (string genericType, ImmutableArray<string> typestrings) => ""; + public string GetGenericInstantiation (string genericType, ImmutableArray<string> typeArguments) { throw new NotImplementedException (); } + public string GetGenericMethodParameter (int index) => ""; + public string GetGenericMethodParameter (object genericContext, int index) { throw new NotImplementedException (); } + public string GetGenericTypeParameter (int index) => ""; + public string GetGenericTypeParameter (object genericContext, int index) { throw new NotImplementedException (); } + public string GetModifiedType (string modifier, string unmodifiedType, bool isRequired) => ""; + public string GetPinnedType (string elementType) => ""; + public string GetPointerType (string elementType) => ""; + public string GetSZArrayType (string elementType) => ""; + public string GetTypeFromDefinition (MetadataReader reader, TypeDefinitionHandle handle, byte rawTypeKind) => ""; + public string GetTypeFromReference (MetadataReader reader, TypeReferenceHandle handle, byte rawTypeKind) => ""; + public string GetTypeFromSpecification (MetadataReader reader, object genericContext, TypeSpecificationHandle handle, byte rawTypeKind) => ""; + } } } diff --git a/main/src/core/MonoDevelop.Core/MonoDevelop.Core.Execution/MonoPlatformExecutionHandler.cs b/main/src/core/MonoDevelop.Core/MonoDevelop.Core.Execution/MonoPlatformExecutionHandler.cs index 845ffd7727..6e832f7296 100644 --- a/main/src/core/MonoDevelop.Core/MonoDevelop.Core.Execution/MonoPlatformExecutionHandler.cs +++ b/main/src/core/MonoDevelop.Core/MonoDevelop.Core.Execution/MonoPlatformExecutionHandler.cs @@ -45,18 +45,24 @@ namespace MonoDevelop.Core.Execution public override ProcessAsyncOperation Execute (ExecutionCommand command, OperationConsole console) { - var dotcmd = (DotNetExecutionCommand) command; - - string runtimeArgs = string.IsNullOrEmpty (dotcmd.RuntimeArguments) ? "--debug" : dotcmd.RuntimeArguments; + var dotcmd = (DotNetExecutionCommand)command; - var monoRunner = runtime.GetMonoExecutableForAssembly (dotcmd.Command); - + string runtimeArgs = string.IsNullOrEmpty (dotcmd.RuntimeArguments) ? "--debug" : dotcmd.RuntimeArguments; + var monoRunner = GetExecutionRunner (command, dotcmd); string args = string.Format ("{2} \"{0}\" {1}", dotcmd.Command, dotcmd.Arguments, runtimeArgs); NativeExecutionCommand cmd = new NativeExecutionCommand (monoRunner, args, dotcmd.WorkingDirectory, dotcmd.EnvironmentVariables); - + return base.Execute (cmd, console); } - + + private string GetExecutionRunner (ExecutionCommand command, DotNetExecutionCommand dotcmd) + { + if (command is ProcessExecutionCommand processExecutionCommand && processExecutionCommand.ProcessExecutionArchitecture != ProcessExecutionArchitecture.Unspecified) { + return runtime.GetMonoExecutable (processExecutionCommand.ProcessExecutionArchitecture); + } + return runtime.GetMonoExecutableForAssembly (dotcmd.Command); + } + public override bool CanExecute (ExecutionCommand command) { return command is DotNetExecutionCommand; diff --git a/main/src/core/MonoDevelop.Core/MonoDevelop.Core.Execution/ProcessExecutionCommand.cs b/main/src/core/MonoDevelop.Core/MonoDevelop.Core.Execution/ProcessExecutionCommand.cs index 76b3af1915..01af607c01 100644 --- a/main/src/core/MonoDevelop.Core/MonoDevelop.Core.Execution/ProcessExecutionCommand.cs +++ b/main/src/core/MonoDevelop.Core/MonoDevelop.Core.Execution/ProcessExecutionCommand.cs @@ -29,6 +29,13 @@ using System.Collections.Generic; namespace MonoDevelop.Core.Execution { + public enum ProcessExecutionArchitecture + { + Unspecified, + X86, + X64 + } + public class ProcessExecutionCommand: ExecutionCommand { IDictionary<string, string> environmentVariables; @@ -69,7 +76,9 @@ namespace MonoDevelop.Core.Execution } public string WorkingDirectory { get; set; } - + + public ProcessExecutionArchitecture ProcessExecutionArchitecture { get; set; } + public IDictionary<string, string> EnvironmentVariables { get { if (environmentVariables == null) diff --git a/main/src/core/MonoDevelop.Core/MonoDevelop.Core.Execution/RemoteProcessConnection.cs b/main/src/core/MonoDevelop.Core/MonoDevelop.Core.Execution/RemoteProcessConnection.cs index 74c89622dd..2d804cf738 100644 --- a/main/src/core/MonoDevelop.Core/MonoDevelop.Core.Execution/RemoteProcessConnection.cs +++ b/main/src/core/MonoDevelop.Core/MonoDevelop.Core.Execution/RemoteProcessConnection.cs @@ -156,15 +156,6 @@ namespace MonoDevelop.Core.Execution } } - [Obsolete ("Use Disconnect()")] - public void Disconnect (bool waitUntilDone) - { - if (waitUntilDone) - Disconnect ().Wait (TimeSpan.FromSeconds (7)); - else - Disconnect ().Ignore (); - } - public async Task Disconnect () { StopPinger (); @@ -285,6 +276,8 @@ namespace MonoDevelop.Core.Execution SetStatus (ConnectionStatus.ConnectionFailed, ex.Message, ex); } + public ProcessExecutionArchitecture ProcessExecutionArchitecture { get; set; } + Task StartRemoteProcess () { return Task.Run (() => { @@ -296,7 +289,7 @@ namespace MonoDevelop.Core.Execution // Explicitly propagate the PATH var to the process. It ensures that tools required // to run XS are also in the PATH for remote processes. cmd.EnvironmentVariables ["PATH"] = Environment.GetEnvironmentVariable ("PATH"); - + cmd.ProcessExecutionArchitecture = ProcessExecutionArchitecture; process = executionHandler.Execute (cmd, console); process.Task.ContinueWith (t => ProcessExited ()); }); diff --git a/main/src/core/MonoDevelop.Core/MonoDevelop.Core.Text/BacktrackingStringMatcher.cs b/main/src/core/MonoDevelop.Core/MonoDevelop.Core.Text/BacktrackingStringMatcher.cs index ada6e0c8c6..f0fa0c4205 100644 --- a/main/src/core/MonoDevelop.Core/MonoDevelop.Core.Text/BacktrackingStringMatcher.cs +++ b/main/src/core/MonoDevelop.Core/MonoDevelop.Core.Text/BacktrackingStringMatcher.cs @@ -250,6 +250,11 @@ namespace MonoDevelop.Core.Text // clear cache return result; } + + public override string ToString () + { + return string.Format ("[BacktrackingStringMatcher: filterText={0}]", filterText); + } } } diff --git a/main/src/core/MonoDevelop.Core/MonoDevelop.Core.Web/AmbientAuthenticationState.cs b/main/src/core/MonoDevelop.Core/MonoDevelop.Core.Web/AmbientAuthenticationState.cs new file mode 100644 index 0000000000..43165df3e8 --- /dev/null +++ b/main/src/core/MonoDevelop.Core/MonoDevelop.Core.Web/AmbientAuthenticationState.cs @@ -0,0 +1,33 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +// +// From: https://github.com/NuGet/NuGet.Client + +namespace MonoDevelop.Core.Web +{ + /// <summary> + /// Represents source authentication status per active operation + /// </summary> + class AmbientAuthenticationState + { + internal const int MaxAuthRetries = 4; + + public bool IsBlocked { get; private set; } = false; + public int AuthenticationRetriesCount { get; private set; } = 0; + + public void Block () + { + IsBlocked = true; + } + + public void Increment () + { + AuthenticationRetriesCount++; + + if (AuthenticationRetriesCount >= MaxAuthRetries) { + // Block future attempts. + Block (); + } + } + } +}
\ No newline at end of file diff --git a/main/src/core/MonoDevelop.Core/MonoDevelop.Core.Web/CredentialResponse.cs b/main/src/core/MonoDevelop.Core/MonoDevelop.Core.Web/CredentialResponse.cs new file mode 100644 index 0000000000..1ab0b0dfcf --- /dev/null +++ b/main/src/core/MonoDevelop.Core/MonoDevelop.Core.Web/CredentialResponse.cs @@ -0,0 +1,48 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +// +// From: https://github.com/NuGet/NuGet.Client + +using System.Net; +using System; + +namespace MonoDevelop.Core.Web +{ + class CredentialResponse + { + /// <summary> + /// Creates a credential response object without giving credentials. This constructor is used only if the + /// credential provider was not able to get credentials. The <paramref name="status"/> indicates why the + /// provider was not able to get credentials. + /// </summary> + /// <param name="status">The status of why the credential provider was not able to get credentials.</param> + public CredentialResponse (CredentialStatus status) + : this (null, status) + { + } + + /// <summary> + /// Creates a credential response object with a given set of credentials. This constuctor is used only if the + /// credential provider was able to get credentials. + /// </summary> + /// <param name="credentials">The credentials fetched by the credential provider.</param> + public CredentialResponse (ICredentials credentials) + : this (credentials, CredentialStatus.Success) + { + } + + CredentialResponse (ICredentials credentials, CredentialStatus status) + { + if ((credentials != null && status != CredentialStatus.Success) || + (credentials == null && status == CredentialStatus.Success)) { + throw new InvalidOperationException ("Could not create credential response object because the response was invalid."); + } + + Credentials = credentials; + Status = status; + } + + public ICredentials Credentials { get; } + public CredentialStatus Status { get; } + } +}
\ No newline at end of file diff --git a/main/src/core/MonoDevelop.Core/MonoDevelop.Core.Web/CredentialService.cs b/main/src/core/MonoDevelop.Core/MonoDevelop.Core.Web/CredentialService.cs new file mode 100644 index 0000000000..2d073a3e5a --- /dev/null +++ b/main/src/core/MonoDevelop.Core/MonoDevelop.Core.Web/CredentialService.cs @@ -0,0 +1,73 @@ +// +// CredentialService.cs +// +// Author: +// Matt Ward <matt.ward@microsoft.com> +// +// Copyright (c) 2018 Microsoft +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +using System; +using System.Net; +using System.Threading; +using System.Threading.Tasks; + +namespace MonoDevelop.Core.Web +{ + /// <summary> + /// Task based wrapper around the synchronous ICredentialProvider + /// </summary> + class CredentialService : ICredentialService + { + /// <summary> + /// Provides credentials for http requests. + /// </summary> + /// <param name="uri"> + /// The URI of a web resource for which credentials are needed. + /// </param> + /// <param name="proxy"> + /// The currently configured proxy. It may be necessary for CredentialProviders + /// to use this proxy in order to acquire credentials from their authentication source. + /// </param> + /// <param name="type"> + /// The type of credential request that is being made. + /// </param> + /// <param name="cancellationToken">A cancellation token.</param> + /// <returns>A credential object, or null if no credentials could be acquired.</returns> + public Task<ICredentials> GetCredentialsAsync ( + Uri uri, + IWebProxy proxy, + CredentialType type, + bool isRetry, + CancellationToken cancellationToken) + { + if (uri == null) + throw new ArgumentNullException (nameof (uri)); + + var cp = WebRequestHelper.CredentialProvider; + if (cp == null) + return null; + + return Task.Run (() => { + return cp.GetCredentials (uri, proxy, type, isRetry); + }); + } + } +} diff --git a/main/src/core/MonoDevelop.Core/MonoDevelop.Core.Web/CredentialStatus.cs b/main/src/core/MonoDevelop.Core/MonoDevelop.Core.Web/CredentialStatus.cs new file mode 100644 index 0000000000..da88fd9e66 --- /dev/null +++ b/main/src/core/MonoDevelop.Core/MonoDevelop.Core.Web/CredentialStatus.cs @@ -0,0 +1,18 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +// +// From: https://github.com/NuGet/NuGet.Client + +namespace MonoDevelop.Core.Web +{ + /// <summary> + /// Result of an attempt to acquire credentials. + /// Keep in sync with NuGet.VisualStudio.VsCredentialStatus + /// </summary> + enum CredentialStatus + { + Success, + ProviderNotApplicable, + UserCanceled + } +}
\ No newline at end of file diff --git a/main/src/core/MonoDevelop.Core/MonoDevelop.Core.Web/DefaultHttpClientHandler.cs b/main/src/core/MonoDevelop.Core/MonoDevelop.Core.Web/DefaultHttpClientHandler.cs new file mode 100644 index 0000000000..47ad8092b6 --- /dev/null +++ b/main/src/core/MonoDevelop.Core/MonoDevelop.Core.Web/DefaultHttpClientHandler.cs @@ -0,0 +1,42 @@ +// +// DefaultHttpClientHandler.cs +// +// Author: +// Matt Ward <matt.ward@microsoft.com> +// +// Copyright (c) 2018 Microsoft +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +using System.Net; +using System.Net.Http; + +namespace MonoDevelop.Core.Web +{ + class DefaultHttpClientHandler : HttpClientHandler, IHttpCredentialsHandler + { + public DefaultHttpClientHandler (IWebProxy proxy, HttpClientSettings settings) + { + AllowAutoRedirect = settings.AllowAutoRedirect; + AutomaticDecompression = settings.AutomaticDecompression; + PreAuthenticate = settings.PreAuthenticate; + Proxy = proxy; + } + } +} diff --git a/main/src/core/MonoDevelop.Core/MonoDevelop.Core.Web/DefaultHttpMessageHandlerProvider.cs b/main/src/core/MonoDevelop.Core/MonoDevelop.Core.Web/DefaultHttpMessageHandlerProvider.cs new file mode 100644 index 0000000000..ecf8844f11 --- /dev/null +++ b/main/src/core/MonoDevelop.Core/MonoDevelop.Core.Web/DefaultHttpMessageHandlerProvider.cs @@ -0,0 +1,38 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +// +// Based on: src/NuGet.Core/NuGet.Protocol/HttpSource/HttpHandlerResourceV3Provider.cs +// From: https://github.com/NuGet/NuGet.Client + +using System; +using System.Net.Http; + +namespace MonoDevelop.Core.Web +{ + public class DefaultHttpMessageHandlerProvider : HttpMessageHandlerProvider + { + public override HttpMessageHandler CreateHttpMessageHandler (Uri uri, HttpClientSettings settings) + { + var proxy = WebRequestHelper.ProxyCache.GetProxy (uri); + var clientHandler = new DefaultHttpClientHandler (proxy, settings); + + HttpMessageHandler messageHandler = clientHandler; + + if (proxy != null) { + messageHandler = new ProxyAuthenticationHandler (clientHandler, HttpClientProvider.CredentialService, WebRequestHelper.ProxyCache) { + NonInteractive = settings.NonInteractive + }; + } + + if (settings.SourceAuthenticationRequired) { + var innerHandler = messageHandler; + + messageHandler = new HttpSourceAuthenticationHandler (uri, clientHandler, HttpClientProvider.CredentialService) { + InnerHandler = innerHandler + }; + } + + return messageHandler; + } + } +} diff --git a/main/src/core/MonoDevelop.Core/MonoDevelop.Core.Web/HttpClientProvider.cs b/main/src/core/MonoDevelop.Core/MonoDevelop.Core.Web/HttpClientProvider.cs new file mode 100644 index 0000000000..9b11bbb68e --- /dev/null +++ b/main/src/core/MonoDevelop.Core/MonoDevelop.Core.Web/HttpClientProvider.cs @@ -0,0 +1,81 @@ +// +// HttpClientProvider.cs +// +// Author: +// Matt Ward <matt.ward@microsoft.com> +// +// Copyright (c) 2018 Microsoft +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +using System; +using System.Linq; +using System.Net.Http; +using Mono.Addins; + +namespace MonoDevelop.Core.Web +{ + public static class HttpClientProvider + { + const string ProvidersPath = "/MonoDevelop/Core/HttpMessageHandlerProviders"; + + static HttpMessageHandlerProvider httpMessageHandlerProvider; + static CredentialService credentialService; + static readonly HttpClientSettings defaultHttpClientSettings = new HttpClientSettings (); + + internal static void Initialize () + { + httpMessageHandlerProvider = AddinManager.GetExtensionObjects<HttpMessageHandlerProvider> (ProvidersPath).FirstOrDefault (); + + if (httpMessageHandlerProvider == null) { + httpMessageHandlerProvider = new DefaultHttpMessageHandlerProvider (); + } + + credentialService = new CredentialService (); + } + + public static HttpClient CreateHttpClient (string uri) + { + return CreateHttpClient (new Uri (uri)); + } + + public static HttpClient CreateHttpClient (Uri uri) + { + return CreateHttpClient (uri, defaultHttpClientSettings); + } + + public static HttpClient CreateHttpClient (string uri, HttpClientSettings settings) + { + return CreateHttpClient (new Uri (uri), settings); + } + + public static HttpClient CreateHttpClient (Uri uri, HttpClientSettings settings) + { + var handler = CreateHttpMessageHandler (uri, settings); + return new HttpClient (handler); + } + + public static HttpMessageHandler CreateHttpMessageHandler (Uri uri, HttpClientSettings settings) + { + return httpMessageHandlerProvider.CreateHttpMessageHandler (uri, settings ?? defaultHttpClientSettings); + } + + static internal ICredentialService CredentialService => credentialService; + } +} diff --git a/main/src/core/MonoDevelop.Core/MonoDevelop.Core.Web/HttpClientSettings.cs b/main/src/core/MonoDevelop.Core/MonoDevelop.Core.Web/HttpClientSettings.cs new file mode 100644 index 0000000000..2e59d2e09c --- /dev/null +++ b/main/src/core/MonoDevelop.Core/MonoDevelop.Core.Web/HttpClientSettings.cs @@ -0,0 +1,39 @@ +// +// HttpClientSettings.cs +// +// Author: +// Matt Ward <matt.ward@microsoft.com> +// +// Copyright (c) 2018 Microsoft +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +using System.Net; + +namespace MonoDevelop.Core.Web +{ + public class HttpClientSettings + { + public bool AllowAutoRedirect { get; set; } = true; + public DecompressionMethods AutomaticDecompression { get; set; } = DecompressionMethods.Deflate | DecompressionMethods.GZip; + public bool PreAuthenticate { get; set; } + public bool SourceAuthenticationRequired { get; set; } = true; + public bool NonInteractive { get; set; } + } +} diff --git a/main/src/core/MonoDevelop.Core/MonoDevelop.Core.Web/HttpMessageHandlerProvider.cs b/main/src/core/MonoDevelop.Core/MonoDevelop.Core.Web/HttpMessageHandlerProvider.cs new file mode 100644 index 0000000000..2521854aa1 --- /dev/null +++ b/main/src/core/MonoDevelop.Core/MonoDevelop.Core.Web/HttpMessageHandlerProvider.cs @@ -0,0 +1,36 @@ +// +// HttpMessageHandlerProvider.cs +// +// Author: +// Matt Ward <matt.ward@microsoft.com> +// +// Copyright (c) 2018 Microsoft +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +using System; +using System.Net.Http; + +namespace MonoDevelop.Core.Web +{ + public abstract class HttpMessageHandlerProvider + { + public abstract HttpMessageHandler CreateHttpMessageHandler (Uri uri, HttpClientSettings settings); + } +} diff --git a/main/src/core/MonoDevelop.Core/MonoDevelop.Core.Web/HttpSourceAuthenticationHandler.cs b/main/src/core/MonoDevelop.Core/MonoDevelop.Core.Web/HttpSourceAuthenticationHandler.cs new file mode 100644 index 0000000000..4ff35e00e4 --- /dev/null +++ b/main/src/core/MonoDevelop.Core/MonoDevelop.Core.Web/HttpSourceAuthenticationHandler.cs @@ -0,0 +1,180 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +// +// From: https://github.com/NuGet/NuGet.Client + +using System; +using System.Net; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; + +namespace MonoDevelop.Core.Web +{ + public class HttpSourceAuthenticationHandler : DelegatingHandler + { + public static readonly int MaxAuthRetries = AmbientAuthenticationState.MaxAuthRetries; + + // Only one source may prompt at a time + readonly static SemaphoreSlim credentialPromptLock = new SemaphoreSlim (1, 1); + + readonly Uri source; + readonly IHttpCredentialsHandler credentialsHandler; + readonly ICredentialService credentialService; + + readonly SemaphoreSlim httpClientLock = new SemaphoreSlim (1, 1); + HttpSourceCredentials credentials; + + internal HttpSourceAuthenticationHandler ( + Uri source, + DefaultHttpClientHandler clientHandler, + ICredentialService credentialService) + : this (source, clientHandler, clientHandler, credentialService) + { + } + + HttpSourceAuthenticationHandler ( + Uri source, + IHttpCredentialsHandler credentialsHandler, + HttpMessageHandler innerHandler, + ICredentialService credentialService) + : base (innerHandler) + { + this.source = source ?? throw new ArgumentNullException (nameof (source)); + credentialsHandler = credentialsHandler ?? throw new ArgumentNullException (nameof (credentialsHandler)); + + this.credentialService = credentialService; + + // Create a new wrapper for ICredentials that can be modified + // This is used to match the value of HttpClientHandler.UseDefaultCredentials = true + credentials = new HttpSourceCredentials (CredentialCache.DefaultNetworkCredentials); + + credentialsHandler.Credentials = credentials; + // Always take the credentials from the helper. + credentialsHandler.UseDefaultCredentials = false; + } + + public HttpSourceAuthenticationHandler (Uri source, IHttpCredentialsHandler credentialsHandler, HttpMessageHandler innerHandler) + : this (source, credentialsHandler, innerHandler, HttpClientProvider.CredentialService) + { + } + + protected override async Task<HttpResponseMessage> SendAsync (HttpRequestMessage request, CancellationToken cancellationToken) + { + HttpResponseMessage response = null; + ICredentials promptCredentials = null; + var authState = new AmbientAuthenticationState (); + + // Authorizing may take multiple attempts + while (true) { + // Clean up any previous responses + if (response != null) { + response.Dispose (); + } + + // store the auth state before sending the request + var beforeLockVersion = credentials.Version; + + response = await base.SendAsync (request, cancellationToken).ConfigureAwait (false); + + if (credentialService == null) { + return response; + } + + if (response.StatusCode == HttpStatusCode.Unauthorized || + response.StatusCode == HttpStatusCode.Forbidden) { + promptCredentials = await AcquireCredentialsAsync ( + authState, + beforeLockVersion, + cancellationToken); + + if (promptCredentials == null) { + return response; + } + + continue; + } + + return response; + } + } + + async Task<ICredentials> AcquireCredentialsAsync (AmbientAuthenticationState authState, Guid credentialsVersion, CancellationToken cancellationToken) + { + try { + // Only one request may prompt and attempt to auth at a time + await httpClientLock.WaitAsync (); + + cancellationToken.ThrowIfCancellationRequested (); + + // Auth may have happened on another thread, if so just continue + if (credentialsVersion != credentials.Version) { + return credentials.Credentials; + } + + if (authState.IsBlocked) { + cancellationToken.ThrowIfCancellationRequested (); + return null; + } + + var promptCredentials = await PromptForCredentialsAsync ( + CredentialType.RequestCredentials, + authState, + cancellationToken); + + if (promptCredentials == null) { + return null; + } + + credentials.Credentials = promptCredentials; + + return promptCredentials; + } finally { + httpClientLock.Release (); + } + } + + async Task<ICredentials> PromptForCredentialsAsync ( + CredentialType type, + AmbientAuthenticationState authState, + CancellationToken token) + { + ICredentials promptCredentials; + + try { + // Only one prompt may display at a time. + await credentialPromptLock.WaitAsync (); + + // Get the proxy for this URI so we can pass it to the credentialService methods + // this lets them use the proxy if they have to hit the network. + var proxyCache = WebRequestHelper.ProxyCache; + var proxy = proxyCache?.GetProxy (source); + + bool isRetry = authState.AuthenticationRetriesCount > 0; + promptCredentials = await credentialService.GetCredentialsAsync (source, proxy, type, isRetry, token); + + if (promptCredentials == null) { + // If this is the case, this means none of the credential providers were able to + // handle the credential request or no credentials were available for the + // endpoint. + authState.Block (); + } else { + authState.Increment (); + } + } catch (OperationCanceledException) { + // This indicates a non-human cancellation. + throw; + } catch (Exception) { + // If this is the case, this means there was a fatal exception when interacting + // with the credential service (or its underlying credential providers). Either way, + // block asking for credentials for the live of this operation. + promptCredentials = null; + authState.Block (); + } finally { + credentialPromptLock.Release (); + } + + return promptCredentials; + } + } +}
\ No newline at end of file diff --git a/main/src/core/MonoDevelop.Core/MonoDevelop.Core.Web/HttpSourceCredentials.cs b/main/src/core/MonoDevelop.Core/MonoDevelop.Core.Web/HttpSourceCredentials.cs new file mode 100644 index 0000000000..cffcbccf22 --- /dev/null +++ b/main/src/core/MonoDevelop.Core/MonoDevelop.Core.Web/HttpSourceCredentials.cs @@ -0,0 +1,88 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +// +// From: https://github.com/NuGet/NuGet.Client + +using System; +using System.Net; + +namespace MonoDevelop.Core.Web +{ + /// <summary> + /// A mutable CredentialCache wrapper. This allows the underlying ICredentials to + /// be changed to work around HttpClientHandler not allowing Credentials to change. + /// This class intentionally inherits from CredentialCache to support authentication on redirects. + /// According to System.Net implementation any other ICredentials implementation is dropped for security reasons. + /// </summary> + class HttpSourceCredentials : CredentialCache, ICredentials + { + public HttpSourceCredentials () + { + Credentials = null; + } + + /// <summary> + /// Credentials can be changed by other threads, for this reason volatile + /// is added below so that the value is not cached anywhere. + /// </summary> + volatile VersionedCredentials credentials; + + /// <summary> + /// The latest credentials to be used. + /// </summary> + public ICredentials Credentials { + get { + return credentials.Credentials; + } + + set { + // We must update the credentials and it's associated version GUID atomically. This + // can be achieved with a reference assignment. It is important that credentials and + // version always match. In other words, if the credentials are updated, it should + // at no instant be possible to get old version GUID and the new credentials. + credentials = new VersionedCredentials (value); + } + } + + /// <summary> + /// The latest version ID of the <see cref="Credentials"/>. + /// </summary> + public Guid Version { + get { + return credentials.Version; + } + } + + /// <summary> + /// Initializes a new instance of the <see cref="HttpSourceCredentials"/> class + /// </summary> + /// <param name="credentials"> + /// Optional initial credentials. May be null. + /// </param> + public HttpSourceCredentials (ICredentials credentials = null) + { + Credentials = credentials; + } + + /// <summary> + /// Used by the HttpClientHandler to retrieve the current credentials. + /// </summary> + NetworkCredential ICredentials.GetCredential (Uri uri, string authType) + { + // Get credentials from the current credential store, if any + return Credentials?.GetCredential (uri, authType); + } + + class VersionedCredentials + { + public VersionedCredentials (ICredentials credentials) + { + Version = Guid.NewGuid (); + Credentials = credentials; + } + + public ICredentials Credentials { get; } + public Guid Version { get; } + } + } +}
\ No newline at end of file diff --git a/main/src/core/MonoDevelop.Core/MonoDevelop.Core.Web/IAsyncCredentialProvider.cs b/main/src/core/MonoDevelop.Core/MonoDevelop.Core.Web/IAsyncCredentialProvider.cs new file mode 100644 index 0000000000..bc6ef8d2d2 --- /dev/null +++ b/main/src/core/MonoDevelop.Core/MonoDevelop.Core.Web/IAsyncCredentialProvider.cs @@ -0,0 +1,25 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +// +// Based on ICredentialProvider +// From: https://github.com/NuGet/NuGet.Client + +using System; +using System.Net; +using System.Threading; +using System.Threading.Tasks; + +namespace MonoDevelop.Core.Web +{ + interface IAsyncCredentialProvider + { + string Id { get; } + + Task<CredentialResponse> GetAsync( + Uri uri, + IWebProxy proxy, + CredentialType type, + bool isRetry, + CancellationToken cancellationToken); + } +} diff --git a/main/src/core/MonoDevelop.Core/MonoDevelop.Core.Web/ICredentialService.cs b/main/src/core/MonoDevelop.Core/MonoDevelop.Core.Web/ICredentialService.cs new file mode 100644 index 0000000000..fd0750ffaa --- /dev/null +++ b/main/src/core/MonoDevelop.Core/MonoDevelop.Core.Web/ICredentialService.cs @@ -0,0 +1,35 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +// +// From: https://github.com/NuGet/NuGet.Client + +using System; +using System.Net; +using System.Threading; +using System.Threading.Tasks; + +namespace MonoDevelop.Core.Web +{ + interface ICredentialService + { + /// <summary> + /// Asynchronously gets credentials. + /// </summary> + /// <param name="uri">The URI for which credentials should be retrieved.</param> + /// <param name="proxy">A web proxy.</param> + /// <param name="type">The credential request type.</param> + /// <param name="isRetry">The request is being retried.</param> + /// <param name="cancellationToken">A cancellation token.</param> + /// <returns>A task that represents the asynchronous operation. + /// The task result (<see cref="Task{TResult}.Result" />) returns a <see cref="ICredentials" />.</returns> + /// <exception cref="ArgumentNullException">Thrown if <paramref name="uri" /> is <c>null</c>.</exception> + /// <exception cref="OperationCanceledException">Thrown if <paramref name="cancellationToken" /> + /// is cancelled.</exception> + Task<ICredentials> GetCredentialsAsync ( + Uri uri, + IWebProxy proxy, + CredentialType type, + bool isRetry, + CancellationToken cancellationToken); + } +} diff --git a/main/src/core/MonoDevelop.Core/MonoDevelop.Core.Web/IHttpCredentialsHandler.cs b/main/src/core/MonoDevelop.Core/MonoDevelop.Core.Web/IHttpCredentialsHandler.cs new file mode 100644 index 0000000000..5ce9fe5e70 --- /dev/null +++ b/main/src/core/MonoDevelop.Core/MonoDevelop.Core.Web/IHttpCredentialsHandler.cs @@ -0,0 +1,39 @@ +// +// IHttpCredentialsHandler.cs +// +// Author: +// Matt Ward <matt.ward@microsoft.com> +// +// Copyright (c) 2018 Microsoft +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +using System.Net; + +namespace MonoDevelop.Core.Web +{ + /// <summary> + /// Must be implemented by one HttpMessageHandler in order for credentials to be configured for requests. + /// </summary> + public interface IHttpCredentialsHandler + { + ICredentials Credentials { get; set; } + bool UseDefaultCredentials { get; set; } + } +} diff --git a/main/src/core/MonoDevelop.Core/MonoDevelop.Core.Web/IProxyCredentialCache.cs b/main/src/core/MonoDevelop.Core/MonoDevelop.Core.Web/IProxyCredentialCache.cs new file mode 100644 index 0000000000..78bfc4c970 --- /dev/null +++ b/main/src/core/MonoDevelop.Core/MonoDevelop.Core.Web/IProxyCredentialCache.cs @@ -0,0 +1,28 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +// +// From: https://github.com/NuGet/NuGet.Client + +using System; +using System.Net; + +namespace MonoDevelop.Core.Web +{ + /// <summary> + /// <see cref="CredentialCache"/>-like interface with Update credential semantics rather than Add/Remove + /// </summary> + interface IProxyCredentialCache : ICredentials + { + /// <summary> + /// Tracks the cache version. Changes every time proxy credential is updated. + /// </summary> + Guid Version { get; } + + /// <summary> + /// Add or update proxy credential + /// </summary> + /// <param name="proxyAddress">Proxy network address</param> + /// <param name="credentials">New credential object</param> + void UpdateCredential (Uri proxyAddress, NetworkCredential credentials); + } +} diff --git a/main/src/core/MonoDevelop.Core/MonoDevelop.Core.Web/ProxyAuthenticationHandler.cs b/main/src/core/MonoDevelop.Core/MonoDevelop.Core.Web/ProxyAuthenticationHandler.cs new file mode 100644 index 0000000000..7c20165326 --- /dev/null +++ b/main/src/core/MonoDevelop.Core/MonoDevelop.Core.Web/ProxyAuthenticationHandler.cs @@ -0,0 +1,172 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +// +// From: https://github.com/NuGet/NuGet.Client + +using System; +using System.Globalization; +using System.Net; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; + +namespace MonoDevelop.Core.Web +{ + /// <summary> + /// A message handler responsible for retrying request for authenticated proxies + /// with missing credentials. + /// </summary> + class ProxyAuthenticationHandler : DelegatingHandler + { + public static readonly int MaxAuthRetries = 3; + const string BasicAuthenticationType = "Basic"; + + // Only one source may prompt at a time + static readonly SemaphoreSlim credentialPromptLock = new SemaphoreSlim (1, 1); + + readonly HttpClientHandler clientHandler; + readonly ICredentialService credentialService; + readonly IProxyCredentialCache credentialCache; + + int authRetries; + + public ProxyAuthenticationHandler ( + HttpClientHandler clientHandler, + ICredentialService credentialService, + IProxyCredentialCache credentialCache) + : base (clientHandler) + { + if (clientHandler == null) { + throw new ArgumentNullException (nameof (clientHandler)); + } + + this.clientHandler = clientHandler; + + // credential service is optional + this.credentialService = credentialService; + + if (credentialCache == null) { + throw new ArgumentNullException (nameof (credentialCache)); + } + + this.credentialCache = credentialCache; + } + + public bool NonInteractive { get; set; } + + protected override async Task<HttpResponseMessage> SendAsync ( + HttpRequestMessage request, + CancellationToken cancellationToken) + { + while (true) { + // Store the auth start before sending the request + var cacheVersion = credentialCache.Version; + + try { + var response = await base.SendAsync (request, cancellationToken).ConfigureAwait (false); + + if (response.StatusCode != HttpStatusCode.ProxyAuthenticationRequired) { + return response; + } + + if (clientHandler.Proxy == null) { + return response; + } + + if (credentialService == null) { + return response; + } + + if (!await AcquireCredentialsAsync (request.RequestUri, cacheVersion, cancellationToken)) { + return response; + } + } catch (Exception ex) + when (ProxyAuthenticationRequired (ex) && clientHandler.Proxy != null && credentialService != null) { + if (!await AcquireCredentialsAsync (request.RequestUri, cacheVersion, cancellationToken)) { + throw; + } + } + } + } + + // Returns true if the cause of the exception is proxy authentication failure + static bool ProxyAuthenticationRequired (Exception ex) + { + var response = ExtractResponse (ex); + return response?.StatusCode == HttpStatusCode.ProxyAuthenticationRequired; + } + + static HttpWebResponse ExtractResponse (Exception ex) + { + var webException = ex.InnerException as WebException; + var response = webException?.Response as HttpWebResponse; + return response; + } + + async Task<bool> AcquireCredentialsAsync (Uri requestUri, Guid cacheVersion, CancellationToken cancellationToken) + { + if (NonInteractive) + return false; + + try { + await credentialPromptLock.WaitAsync (); + + cancellationToken.ThrowIfCancellationRequested (); + + // Check if the credentials have already changed + if (cacheVersion != credentialCache.Version) { + // retry the request with updated credentials + return true; + } + + // Limit the number of retries + authRetries++; + if (authRetries >= MaxAuthRetries) { + // user prompting no more + return false; + } + + var proxyAddress = clientHandler.Proxy.GetProxy (requestUri); + + // prompt user for proxy credentials. + var credentials = await PromptForProxyCredentialsAsync (proxyAddress, clientHandler.Proxy, cancellationToken); + + cancellationToken.ThrowIfCancellationRequested (); + + if (credentials == null) { + // user cancelled or error occured + return false; + } + + credentialCache.UpdateCredential (proxyAddress, credentials); + + // use the user provided credential to send the request again. + return true; + } finally { + credentialPromptLock.Release (); + } + } + + async Task<NetworkCredential> PromptForProxyCredentialsAsync (Uri proxyAddress, IWebProxy proxy, CancellationToken cancellationToken) + { + ICredentials promptCredentials; + + try { + promptCredentials = await credentialService.GetCredentialsAsync ( + proxyAddress, + proxy, + type: CredentialType.ProxyCredentials, + authRetries > 1, + cancellationToken: cancellationToken); + } catch (OperationCanceledException) { + throw; // pass-thru + } catch (Exception ex) { + // Fatal credential service failure + LoggingService.LogError ("PromptForProxyCredentialsAsync error", ex); + promptCredentials = null; + } + + return promptCredentials?.GetCredential (proxyAddress, BasicAuthenticationType); + } + } +} diff --git a/main/src/core/MonoDevelop.Core/MonoDevelop.Core.Web/ProxyCache.cs b/main/src/core/MonoDevelop.Core/MonoDevelop.Core.Web/ProxyCache.cs index ddba33042a..2b4c6e1f00 100644 --- a/main/src/core/MonoDevelop.Core/MonoDevelop.Core.Web/ProxyCache.cs +++ b/main/src/core/MonoDevelop.Core/MonoDevelop.Core.Web/ProxyCache.cs @@ -21,7 +21,7 @@ using System.Net; namespace MonoDevelop.Core.Web { - class ProxyCache : IProxyCache + class ProxyCache : IProxyCache, IProxyCredentialCache { /// <summary> /// Capture the default System Proxy so that it can be re-used by the IProxyFinder @@ -30,25 +30,70 @@ namespace MonoDevelop.Core.Web /// </summary> static readonly IWebProxy originalSystemProxy = WebRequest.GetSystemWebProxy (); - readonly ConcurrentDictionary<Uri, WebProxy> cache = new ConcurrentDictionary<Uri, WebProxy> (); + readonly ConcurrentDictionary<Uri, ICredentials> cache = new ConcurrentDictionary<Uri, ICredentials> (); + + public Guid Version { get; private set; } = Guid.NewGuid (); public IWebProxy GetProxy (Uri uri) { if (!IsSystemProxySet (uri)) return null; - WebProxy systemProxy = GetSystemProxy (uri), effectiveProxy; + var systemProxy = GetSystemProxy (uri); + TryAddProxyCredentialsToCache (systemProxy); + systemProxy.Credentials = this; + return systemProxy; + } + + // Adds new proxy credentials to cache if there's not any in there yet + bool TryAddProxyCredentialsToCache (WebProxy configuredProxy) + { + // If a proxy was cached, it means the stored credentials are incorrect. Use the cached one in this case. + var proxyCredentials = configuredProxy.Credentials ?? CredentialCache.DefaultCredentials; + return cache.TryAdd (configuredProxy.Address, proxyCredentials); + } + + public void UpdateCredential (Uri proxyAddress, NetworkCredential credentials) + { + if (credentials == null) + throw new ArgumentNullException (nameof (credentials)); + + Version = Guid.NewGuid (); + cache [proxyAddress] = credentials; + } + + public NetworkCredential GetCredential (Uri proxyAddress, string authType) + { + var credential = GetCredentialInternal (proxyAddress, authType); + if (credential != null) + return credential; + + // Add workaround for Mono using the request url not the proxy address for non-secure http requests + // when getting the proxy credentials. + // https://github.com/mono/mono/issues/10622 + var correctedProxyAddress = originalSystemProxy.GetProxy (proxyAddress); + if (!string.Equals (correctedProxyAddress.AbsoluteUri, proxyAddress.AbsoluteUri)) + return GetCredentialInternal (correctedProxyAddress, authType); - // See if we have a proxy instance cached for this proxy address - return cache.TryGetValue (systemProxy.Address, out effectiveProxy) ? effectiveProxy : systemProxy; + return null; + } + + NetworkCredential GetCredentialInternal (Uri proxyAddress, string authType) + { + ICredentials cachedCredentials; + if (cache.TryGetValue (proxyAddress, out cachedCredentials)) { + return cachedCredentials.GetCredential (proxyAddress, authType); + } + return null; } + [Obsolete ("Retained for backcompat only. Use UpdateCredential instead")] public void Add (IWebProxy proxy) { var webProxy = proxy as WebProxy; if (webProxy != null) - cache.TryAdd (webProxy.Address, webProxy); + cache.TryAdd (webProxy.Address, webProxy.Credentials); } static WebProxy GetSystemProxy (Uri uri) @@ -81,7 +126,6 @@ namespace MonoDevelop.Core.Web return false; if (proxy.IsBypassed (uri)) return false; - proxy = new WebProxy (proxyAddress); } return proxy != null; diff --git a/main/src/core/MonoDevelop.Core/MonoDevelop.Core.addin.xml b/main/src/core/MonoDevelop.Core/MonoDevelop.Core.addin.xml index 0797a0af98..2448b77e25 100644 --- a/main/src/core/MonoDevelop.Core/MonoDevelop.Core.addin.xml +++ b/main/src/core/MonoDevelop.Core/MonoDevelop.Core.addin.xml @@ -43,6 +43,11 @@ <Description>Platform-specific services used for obtaining proxy credentials.</Description> <ExtensionNode name="Class" objectType="MonoDevelop.Core.Web.ICredentialProvider" /> </ExtensionPoint> + + <ExtensionPoint path = "/MonoDevelop/Core/HttpMessageHandlerProviders" name = "HttpMessageHandler providers"> + <Description>Platform-specific services used to create HttpMessageHandlers used by the HttpClient.</Description> + <ExtensionNode name="Class" objectType="MonoDevelop.Core.Web.HttpMessageHandlerProvider" /> + </ExtensionPoint> <ExtensionPoint path = "/MonoDevelop/Core/FileSystemExtensions" name = "File system extensions"> <Description>File system extensions which can provide specific behavior when handling files. Specified classes must implement MonoDevelop.Core.FileSystem.FileSystemExtension.</Description> diff --git a/main/src/core/MonoDevelop.Core/MonoDevelop.Core.csproj b/main/src/core/MonoDevelop.Core/MonoDevelop.Core.csproj index 64c64568bf..187756f0e5 100644 --- a/main/src/core/MonoDevelop.Core/MonoDevelop.Core.csproj +++ b/main/src/core/MonoDevelop.Core/MonoDevelop.Core.csproj @@ -1,4 +1,4 @@ -<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="4.0"> +<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="4.0"> <Import Project="..\..\..\packages\Microsoft.Build.Locator.1.1.2\build\Microsoft.Build.Locator.props" Condition="Exists('..\..\..\packages\Microsoft.Build.Locator.1.1.2\build\Microsoft.Build.Locator.props')" /> <Import Project="..\..\..\MonoDevelop.props" /> <Import Project="$(ReferencesGtk)" /> @@ -75,6 +75,7 @@ <Reference Include="System.Xml" /> <Reference Include="System.Configuration" /> <Reference Include="System.Core" /> + <Reference Include="System.Net.Http" /> <Reference Include="monodoc, Version=1.0.0.0, Culture=neutral, PublicKeyToken=0738eb9f132ed756" /> <Reference Include="System.Runtime.InteropServices.RuntimeInformation" /> <Reference Include="System.Xml.Linq" /> @@ -706,26 +707,6 @@ <Compile Include="MonoDevelop.Projects.MSBuild\FileUtilities.cs" /> <Compile Include="MonoDevelop.Projects\ProgressEvent.cs" /> <Compile Include="MonoDevelop.Core.Setup\UpdateChannel.cs" /> - <Compile Include="MonoDevelop.FSW\OSX\FileSystemWatcher.cs" /> - <Compile Include="MonoDevelop.FSW\OSX\FileSystemWatcher.OSX.cs" /> - <Compile Include="MonoDevelop.FSW\OSX\Interop.CoreFoundation.cs" /> - <Compile Include="MonoDevelop.FSW\OSX\Interop.Error.cs" /> - <Compile Include="MonoDevelop.FSW\OSX\Interop.EventStream.cs" /> - <Compile Include="MonoDevelop.FSW\OSX\Interop.IOError.cs" /> - <Compile Include="MonoDevelop.FSW\OSX\Interop.Libraries.cs" /> - <Compile Include="MonoDevelop.FSW\OSX\Interop.PathConf.cs" /> - <Compile Include="MonoDevelop.FSW\OSX\Interop.RealPath.cs" /> - <Compile Include="MonoDevelop.FSW\OSX\Interop.RunLoop.cs" /> - <Compile Include="MonoDevelop.FSW\OSX\Interop.Sync.cs" /> - <Compile Include="MonoDevelop.FSW\OSX\PathInternal.CaseSensitivity.cs" /> - <Compile Include="MonoDevelop.FSW\OSX\PathInternal.cs" /> - <Compile Include="MonoDevelop.FSW\OSX\PathInternal.Unix.cs" /> - <Compile Include="MonoDevelop.FSW\OSX\PatternMatcher.cs" /> - <Compile Include="MonoDevelop.FSW\OSX\SafeCreateHandle.OSX.cs" /> - <Compile Include="MonoDevelop.FSW\OSX\SafeEventStreamHandle.OSX.cs" /> - <Compile Include="MonoDevelop.FSW\OSX\SR.cs" /> - <Compile Include="MonoDevelop.FSW\FileSystemWatcher.cs" /> - <Compile Include="MonoDevelop.FSW\Mono\FileSystemWatcher.cs" /> <Compile Include="MonoDevelop.Core\StringBuilderCache.cs" /> <Compile Include="MonoDevelop.Core\ObjectPool.cs" /> <Compile Include="MonoDevelop.Core\SharedPools.cs" /> @@ -742,6 +723,22 @@ <Compile Include="MonoDevelop.Projects.MSBuild\SdkResolverManifest.cs" /> <Compile Include="MonoDevelop.Core.FeatureConfiguration\FeatureSwitchService.cs" /> <Compile Include="MonoDevelop.Core.FeatureConfiguration\FeatureSwitchCondition.cs" /> + <Compile Include="MonoDevelop.Core.Web\HttpMessageHandlerProvider.cs" /> + <Compile Include="MonoDevelop.Core.Web\HttpClientProvider.cs" /> + <Compile Include="MonoDevelop.Core.Web\DefaultHttpMessageHandlerProvider.cs" /> + <Compile Include="MonoDevelop.Core.Web\ProxyAuthenticationHandler.cs" /> + <Compile Include="MonoDevelop.Core.Web\IProxyCredentialCache.cs" /> + <Compile Include="MonoDevelop.Core.Web\ICredentialService.cs" /> + <Compile Include="MonoDevelop.Core.Web\CredentialService.cs" /> + <Compile Include="MonoDevelop.Core.Web\IAsyncCredentialProvider.cs" /> + <Compile Include="MonoDevelop.Core.Web\CredentialResponse.cs" /> + <Compile Include="MonoDevelop.Core.Web\CredentialStatus.cs" /> + <Compile Include="MonoDevelop.Core.Web\HttpSourceAuthenticationHandler.cs" /> + <Compile Include="MonoDevelop.Core.Web\AmbientAuthenticationState.cs" /> + <Compile Include="MonoDevelop.Core.Web\HttpSourceCredentials.cs" /> + <Compile Include="MonoDevelop.Core.Web\HttpClientSettings.cs" /> + <Compile Include="MonoDevelop.Core.Web\IHttpCredentialsHandler.cs" /> + <Compile Include="MonoDevelop.Core.Web\DefaultHttpClientHandler.cs" /> <Compile Include="MonoDevelop.Projects.MSBuild\MSBuildProcessService.cs" /> <Compile Include="MonoDevelop.Core.FeatureConfiguration\IFeatureSwitchController.cs" /> <Compile Include="MonoDevelop.Core\Service.cs" /> @@ -750,6 +747,7 @@ <Compile Include="MonoDevelop.Core\BasicServiceProvider.cs" /> <Compile Include="MonoDevelop.Core.AddIns\PlatformConditionAttribute.cs" /> <Compile Include="MonoDevelop.Core\CoreServices.cs" /> + <Compile Include="MonoDevelop.Core.Assemblies\AssemblyUtilities.cs" /> </ItemGroup> <ItemGroup> <None Include="BuildVariables.cs.in" /> @@ -786,6 +784,7 @@ <ItemGroup> <InternalsVisibleTo Include="MonoDevelop.Core.Tests" /> <InternalsVisibleTo Include="MonoDevelop.Ide.Tests" /> + <InternalsVisibleTo Include="DynamicProxyGenAssembly2" /> </ItemGroup> <ItemGroup> <Folder Include="MonoDevelop.Utilities\" /> @@ -804,7 +803,7 @@ <Exec Command=""$(Git)" rev-parse HEAD > $(VcRevision)" WorkingDirectory="$(MSBuildProjectDirectory)" IgnoreExitCode="True" /> <RemoveDir Directories="$(FullBuildInfo)" /> </Target> - <Import Project="..\..\..\packages\Microsoft.VisualStudio.Threading.Analyzers.15.6.46\build\Microsoft.VisualStudio.Threading.Analyzers.targets" Condition="Exists('..\..\..\packages\Microsoft.VisualStudio.Threading.Analyzers.15.6.46\build\Microsoft.VisualStudio.Threading.Analyzers.targets')" /> + <Import Project="..\..\..\packages\Microsoft.VisualStudio.Threading.Analyzers.15.8.209\build\Microsoft.VisualStudio.Threading.Analyzers.targets" Condition="Exists('..\..\..\packages\Microsoft.VisualStudio.Threading.Analyzers.15.8.209\build\Microsoft.VisualStudio.Threading.Analyzers.targets')" /> <Import Project="..\..\..\packages\SQLitePCLRaw.lib.e_sqlite3.linux.1.1.12\build\net35\SQLitePCLRaw.lib.e_sqlite3.linux.targets" Condition="Exists('..\..\..\packages\SQLitePCLRaw.lib.e_sqlite3.linux.1.1.12\build\net35\SQLitePCLRaw.lib.e_sqlite3.linux.targets')" /> <Import Project="..\..\..\packages\SQLitePCLRaw.lib.e_sqlite3.osx.1.1.12\build\net35\SQLitePCLRaw.lib.e_sqlite3.osx.targets" Condition="Exists('..\..\..\packages\SQLitePCLRaw.lib.e_sqlite3.osx.1.1.12\build\net35\SQLitePCLRaw.lib.e_sqlite3.osx.targets')" /> <Import Project="..\..\..\packages\SQLitePCLRaw.lib.e_sqlite3.v110_xp.1.1.12\build\net35\SQLitePCLRaw.lib.e_sqlite3.v110_xp.targets" Condition="Exists('..\..\..\packages\SQLitePCLRaw.lib.e_sqlite3.v110_xp.1.1.12\build\net35\SQLitePCLRaw.lib.e_sqlite3.v110_xp.targets')" /> diff --git a/main/src/core/MonoDevelop.Core/MonoDevelop.Core/FileService.cs b/main/src/core/MonoDevelop.Core/MonoDevelop.Core/FileService.cs index c4f105defb..48d285fd69 100644 --- a/main/src/core/MonoDevelop.Core/MonoDevelop.Core/FileService.cs +++ b/main/src/core/MonoDevelop.Core/MonoDevelop.Core/FileService.cs @@ -35,11 +35,13 @@ using System.Text; using Mono.Addins; using Mono.Unix.Native; using MonoDevelop.Core.FileSystem; +using MonoDevelop.Core.Web; using System.Collections.Generic; using System.Threading; using System.Linq; using System.Threading.Tasks; using System.Net; +using System.Net.Http; namespace MonoDevelop.Core { @@ -808,24 +810,27 @@ namespace MonoDevelop.Core { bool deleteTempFile = true; var tempFile = cacheFile + ".temp"; + HttpClient client = null; try { - var response = await WebRequestHelper.GetResponseAsync ( - () => (HttpWebRequest)WebRequest.Create (url), - r => {
- //check to see if the online file has been modified since it was last downloaded
- var localNewsXml = new FileInfo (cacheFile); - if (localNewsXml.Exists) - r.IfModifiedSince = localNewsXml.LastWriteTime; - }, - ct - ).ConfigureAwait (false); + client = HttpClientProvider.CreateHttpClient (url); + //check to see if the online file has been modified since it was last downloaded + var localNewsXml = new FileInfo (cacheFile); + if (localNewsXml.Exists) + client.DefaultRequestHeaders.IfModifiedSince = localNewsXml.LastWriteTime; + using (var response = await client.GetAsync (url, HttpCompletionOption.ResponseHeadersRead, ct).ConfigureAwait (false)) { - ct.ThrowIfCancellationRequested (); + ct.ThrowIfCancellationRequested (); - //TODO: limit this size in case open wifi hotspots provide junk data
- if (response.StatusCode == HttpStatusCode.OK) { - using (var fs = File.Create (tempFile))
- response.GetResponseStream ().CopyTo (fs, 2048); + //TODO: limit this size in case open wifi hotspots provide junk data
+ if (response.StatusCode == HttpStatusCode.OK) { + using (var fs = File.Create (tempFile)) + await response.Content.CopyToAsync (fs); + } else if (response.StatusCode == HttpStatusCode.NotModified) { + return false; + } else { + LoggingService.LogWarning ("FileService.UpdateDownloadedCacheFile. Unexpected status code {0}", response.StatusCode); + return false; + } }
//check the document is valid, might get bad ones from wifi hotspots etc
@@ -850,13 +855,8 @@ namespace MonoDevelop.Core SystemRename (tempFile, cacheFile); deleteTempFile = false; return true;
- } catch (Exception ex) {
- if (ex.FlattenAggregate ().InnerException is WebException wex) {
- if (wex.Response is HttpWebResponse resp && resp.StatusCode == HttpStatusCode.NotModified)
- return false;
- }
- throw;
- } finally {
+ } finally { + client?.Dispose ();
if (deleteTempFile) {
try {
File.Delete (tempFile);
diff --git a/main/src/core/MonoDevelop.Core/MonoDevelop.Core/Runtime.cs b/main/src/core/MonoDevelop.Core/MonoDevelop.Core/Runtime.cs index 8887c89a41..ad91bdca38 100644 --- a/main/src/core/MonoDevelop.Core/MonoDevelop.Core/Runtime.cs +++ b/main/src/core/MonoDevelop.Core/MonoDevelop.Core/Runtime.cs @@ -44,6 +44,7 @@ using MonoDevelop.Core.Assemblies; using MonoDevelop.Core.Execution; using MonoDevelop.Core.Instrumentation; using MonoDevelop.Core.Setup; +using MonoDevelop.Core.Web; namespace MonoDevelop.Core { @@ -128,8 +129,9 @@ namespace MonoDevelop.Core PropertyService.Initialize (); WebRequestHelper.Initialize (); - Mono.Addins.Setup.WebRequestHelper.SetRequestHandler (WebRequestHelper.GetResponse); - + Web.HttpClientProvider.Initialize (); + Mono.Addins.Setup.HttpClientProvider.SetHttpClientFactory (Web.HttpClientProvider.CreateHttpClient); + //have to do this after the addin service and property service have initialized if (UserDataMigrationService.HasSource) { Counters.RuntimeInitialization.Trace ("Migrating User Data from MD " + UserDataMigrationService.SourceVersion); @@ -217,7 +219,7 @@ namespace MonoDevelop.Core static void OnLoad (object s, AddinEventArgs args) { - Counters.AddinsLoaded.Inc ("Add-in loaded: " + args.AddinId, new Dictionary<string, string> { + Counters.AddinsLoaded.Inc (1, "Add-in loaded: " + args.AddinId, new Dictionary<string, object> { { "AddinId", args.AddinId }, { "LoadTrace", Environment.StackTrace }, }); @@ -251,7 +253,6 @@ namespace MonoDevelop.Core ShuttingDown (null, EventArgs.Empty); PropertyService.SaveProperties (); - FSW.OSX.FileSystemWatcher.DisposeAll (); if (processService != null) { processService.Dispose (); diff --git a/main/src/core/MonoDevelop.Core/MonoDevelop.Core/WebRequestHelper.cs b/main/src/core/MonoDevelop.Core/MonoDevelop.Core/WebRequestHelper.cs index 504b07cd64..b6cbdbbe88 100644 --- a/main/src/core/MonoDevelop.Core/MonoDevelop.Core/WebRequestHelper.cs +++ b/main/src/core/MonoDevelop.Core/MonoDevelop.Core/WebRequestHelper.cs @@ -61,6 +61,8 @@ namespace MonoDevelop.Core [Obsolete] public static IProxyAuthenticationHandler ProxyAuthenticationHandler { get; internal set; } + static internal ProxyCache ProxyCache => proxyCache; + /// <summary> /// Gets the web response, using the <see cref="ProxyAuthenticationHandler"/> to handle proxy authentication /// if necessary. @@ -73,6 +75,7 @@ namespace MonoDevelop.Core /// Keeps sending requests until a response code that doesn't require authentication happens or if the request /// requires authentication and the user has stopped trying to enter them (i.e. they hit cancel when they are prompted). /// </remarks> + [Obsolete ("Use HttpClientProvider.CreateHttpClient")] public static async Task<HttpWebResponse> GetResponseAsync ( Func<HttpWebRequest> createRequest, Action<HttpWebRequest> prepareRequest = null, @@ -109,6 +112,7 @@ namespace MonoDevelop.Core /// Keeps sending requests until a response code that doesn't require authentication happens or if the request /// requires authentication and the user has stopped trying to enter them (i.e. they hit cancel when they are prompted). /// </remarks> + [Obsolete ("Use HttpClientProvider.CreateHttpClient")] public static HttpWebResponse GetResponse ( Func<HttpWebRequest> createRequest, Action<HttpWebRequest> prepareRequest = null, diff --git a/main/src/core/MonoDevelop.Core/MonoDevelop.FSW/FileSystemWatcher.cs b/main/src/core/MonoDevelop.Core/MonoDevelop.FSW/FileSystemWatcher.cs deleted file mode 100644 index c10ca2df28..0000000000 --- a/main/src/core/MonoDevelop.Core/MonoDevelop.FSW/FileSystemWatcher.cs +++ /dev/null @@ -1,580 +0,0 @@ -// -// FileSystemWatcher.cs -// -// Author: -// ludovic <ludovic.henry@xamarin.com> -// -// Copyright (c) 2017 ludovic -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. - -using System; -using System.Runtime.InteropServices; - -namespace MonoDevelop.FSW -{ - internal class FileSystemWatcher : System.ComponentModel.Component, System.ComponentModel.ISupportInitialize - { - static Platform _platform; - - enum Platform - { - OSX, - Mono, - } - - [DllImport (OSX.Interop.Libraries.SystemNative, EntryPoint = "SystemNative_HasOSXSupport")] - private static extern bool HasOSXSupport (); - - static FileSystemWatcher () - { - try { - if (Core.Platform.IsMac && HasOSXSupport ()) { - _platform = Platform.OSX; - return; - } - } catch (EntryPointNotFoundException) { - } catch (DllNotFoundException) { - } - - _platform = Platform.Mono; - } - - OSX.FileSystemWatcher _osxFsw; - Mono.FileSystemWatcher _monoFsw; - - public FileSystemWatcher () - { - switch (_platform) { - case Platform.Mono: - _monoFsw = new Mono.FileSystemWatcher (); - break; - case Platform.OSX: - _osxFsw = new OSX.FileSystemWatcher (); - break; - default: - throw new NotImplementedException (); - } - } - - public FileSystemWatcher (string path) - { - switch (_platform) { - case Platform.Mono: - _monoFsw = new Mono.FileSystemWatcher (path); - break; - case Platform.OSX: - _osxFsw = new OSX.FileSystemWatcher (path); - break; - default: - throw new NotImplementedException (); - } - } - - public FileSystemWatcher (string path, string filter) - { - switch (_platform) { - case Platform.Mono: - _monoFsw = new Mono.FileSystemWatcher (path, filter); - break; - case Platform.OSX: - _osxFsw = new OSX.FileSystemWatcher (path, filter); - break; - default: throw new NotImplementedException (); - } - } - - /// <summary> - /// Used by unit tests to verify the native file watcher is being used. - /// </summary> - internal static bool IsMac { - get { return _platform == Platform.OSX; } - } - - public bool EnableRaisingEvents { - get { - switch (_platform) { - case Platform.Mono: - return _monoFsw.EnableRaisingEvents; - case Platform.OSX: - return _osxFsw.EnableRaisingEvents; - default: - throw new NotImplementedException (); - } - } - set { - switch (_platform) { - case Platform.Mono: - _monoFsw.EnableRaisingEvents = value; - break; - case Platform.OSX: - _osxFsw.EnableRaisingEvents = value; - break; - default: - throw new NotImplementedException (); - } - } - } - public string Filter { - get { - switch (_platform) { - case Platform.Mono: - return _monoFsw.Filter; - case Platform.OSX: - return _osxFsw.Filter; - default: - throw new NotImplementedException (); - } - } - set { - switch (_platform) { - case Platform.Mono: - _monoFsw.Filter = value; - break; - case Platform.OSX: - _osxFsw.Filter = value; - break; - default: - throw new NotImplementedException (); - } - } - } - - public bool IncludeSubdirectories { - get { - switch (_platform) { - case Platform.Mono: - return _monoFsw.IncludeSubdirectories; - case Platform.OSX: - return _osxFsw.IncludeSubdirectories; - default: - throw new NotImplementedException (); - } - } - set { - switch (_platform) { - case Platform.Mono: - _monoFsw.IncludeSubdirectories = value; - break; - case Platform.OSX: - _osxFsw.IncludeSubdirectories = value; - break; - default: - throw new NotImplementedException (); - } - } - } - - public int InternalBufferSize { - get { - switch (_platform) { - case Platform.Mono: - return _monoFsw.InternalBufferSize; - case Platform.OSX: - return _osxFsw.InternalBufferSize; - default: - throw new NotImplementedException (); - } - } - set { - switch (_platform) { - case Platform.Mono: - _monoFsw.InternalBufferSize = value; - break; - case Platform.OSX: - _osxFsw.InternalBufferSize = value; - break; - default: - throw new NotImplementedException (); - } - } - } - - public System.IO.NotifyFilters NotifyFilter { - get { - switch (_platform) { - case Platform.Mono: - return _monoFsw.NotifyFilter; - case Platform.OSX: - return _osxFsw.NotifyFilter; - default: - throw new NotImplementedException (); - } - } - set { - switch (_platform) { - case Platform.Mono: - _monoFsw.NotifyFilter = value; - break; - case Platform.OSX: - _osxFsw.NotifyFilter = value; - break; - default: - throw new NotImplementedException (); - } - } - } - - public string Path { - get { - switch (_platform) { - case Platform.Mono: - return _monoFsw.Path; - case Platform.OSX: - return _osxFsw.Path; - default: - throw new NotImplementedException (); - } - } - set { - switch (_platform) { - case Platform.Mono: - _monoFsw.Path = value; - break; - case Platform.OSX: - _osxFsw.Path = value; - break; - default: - throw new NotImplementedException (); - } - } - } - - public event System.IO.FileSystemEventHandler Changed { - add { - switch (_platform) { - case Platform.Mono: - _monoFsw.Changed += value; - break; - case Platform.OSX: - _osxFsw.Changed += value; - break; - default: - throw new NotImplementedException (); - } - } - remove { - switch (_platform) { - case Platform.Mono: - _monoFsw.Changed -= value; - break; - case Platform.OSX: - _osxFsw.Changed -= value; - break; - default: - throw new NotImplementedException (); - } - } - } - - public event System.IO.FileSystemEventHandler Created { - add { - switch (_platform) { - case Platform.Mono: - _monoFsw.Created += value; - break; - case Platform.OSX: - _osxFsw.Created += value; - break; - default: - throw new NotImplementedException (); - } - } - remove { - switch (_platform) { - case Platform.Mono: - _monoFsw.Created -= value; - break; - case Platform.OSX: - _osxFsw.Created -= value; - break; - default: - throw new NotImplementedException (); - } - } - } - - public event System.IO.FileSystemEventHandler Deleted { - add { - switch (_platform) { - case Platform.Mono: - _monoFsw.Deleted += value; - break; - case Platform.OSX: - _osxFsw.Deleted += value; - break; - default: - throw new NotImplementedException (); - } - } - remove { - switch (_platform) { - case Platform.Mono: - _monoFsw.Deleted -= value; - break; - case Platform.OSX: - _osxFsw.Deleted -= value; - break; - default: - throw new NotImplementedException (); - } - } - } - - public event System.IO.ErrorEventHandler Error { - add { - switch (_platform) { - case Platform.Mono: - _monoFsw.Error += value; - break; - case Platform.OSX: - _osxFsw.Error += value; - break; - default: - throw new NotImplementedException (); - } - } - remove { - switch (_platform) { - case Platform.Mono: - _monoFsw.Error -= value; - break; - case Platform.OSX: - _osxFsw.Error -= value; - break; - default: - throw new NotImplementedException (); - } - } - } - - public event System.IO.RenamedEventHandler Renamed { - add { - switch (_platform) { - case Platform.Mono: - _monoFsw.Renamed += value; - break; - case Platform.OSX: - _osxFsw.Renamed += value; - break; - default: - throw new NotImplementedException (); - } - } - remove { - switch (_platform) { - case Platform.Mono: - _monoFsw.Renamed -= value; - break; - case Platform.OSX: - _osxFsw.Renamed -= value; - break; - default: - throw new NotImplementedException (); - } - } - } - - protected internal void OnChanged (System.IO.FileSystemEventArgs e) - { - switch (_platform) { - case Platform.Mono: - _monoFsw.OnChanged (e); - break; - case Platform.OSX: - _osxFsw.OnChanged (e); - break; - default: - throw new NotImplementedException (); - } - } - - protected internal void OnCreated (System.IO.FileSystemEventArgs e) - { - switch (_platform) { - case Platform.Mono: - _monoFsw.OnChanged (e); - break; - case Platform.OSX: - _osxFsw.OnChanged (e); - break; - default: - throw new NotImplementedException (); - } - } - - protected internal void OnDeleted (System.IO.FileSystemEventArgs e) - { - switch (_platform) { - case Platform.Mono: - _monoFsw.OnDeleted (e); - break; - case Platform.OSX: - _osxFsw.OnDeleted (e); - break; - default: - throw new NotImplementedException (); - } - } - - protected internal void OnError (System.IO.ErrorEventArgs e) - { - switch (_platform) { - case Platform.Mono: - _monoFsw.OnError (e); - break; - case Platform.OSX: - _osxFsw.OnError (e); - break; - default: - throw new NotImplementedException (); - } - } - - protected internal void OnRenamed (System.IO.RenamedEventArgs e) - { - switch (_platform) { - case Platform.Mono: - _monoFsw.OnRenamed (e); - break; - case Platform.OSX: - _osxFsw.OnRenamed (e); - break; - default: - throw new NotImplementedException (); - } - } - - public System.IO.WaitForChangedResult WaitForChanged (System.IO.WatcherChangeTypes changeType) - { - switch (_platform) { - case Platform.Mono: - return _monoFsw.WaitForChanged (changeType); - case Platform.OSX: - return _osxFsw.WaitForChanged (changeType); - default: - throw new NotImplementedException (); - } - } - - public System.IO.WaitForChangedResult WaitForChanged (System.IO.WatcherChangeTypes changeType, int timeout) - { - switch (_platform) { - case Platform.Mono: - return _monoFsw.WaitForChanged (changeType, timeout); - case Platform.OSX: - return _osxFsw.WaitForChanged (changeType, timeout); - default: - throw new NotImplementedException (); - } - } - - public override System.ComponentModel.ISite Site { - get { - switch (_platform) { - case Platform.Mono: - return _monoFsw.Site; - case Platform.OSX: - return _osxFsw.Site; - default: - throw new NotImplementedException (); - } - } - set { - switch (_platform) { - case Platform.Mono: - _monoFsw.Site = value; - break; - case Platform.OSX: - _osxFsw.Site = value; - break; - default: - throw new NotImplementedException (); - } - } - } - - public System.ComponentModel.ISynchronizeInvoke SynchronizingObject { - get { - switch (_platform) { - case Platform.Mono: - return _monoFsw.SynchronizingObject; - case Platform.OSX: - return _osxFsw.SynchronizingObject; - default: - throw new NotImplementedException (); - } - } - set { - switch (_platform) { - case Platform.Mono: - _monoFsw.SynchronizingObject = value; - break; - case Platform.OSX: - _osxFsw.SynchronizingObject = value; - break; - default: - throw new NotImplementedException (); - } - } - } - - public void BeginInit () - { - switch (_platform) { - case Platform.Mono: - _monoFsw.BeginInit (); - break; - case Platform.OSX: - _osxFsw.BeginInit (); - break; - default: - throw new NotImplementedException (); - } - } - - protected override void Dispose (bool disposing) - { - switch (_platform) { - case Platform.Mono: - _monoFsw.Dispose (disposing); - break; - case Platform.OSX: - _osxFsw.Dispose (disposing); - break; - default: - throw new NotImplementedException (); - } - } - - public void EndInit () - { - switch (_platform) { - case Platform.Mono: - _monoFsw.EndInit (); - break; - case Platform.OSX: - _osxFsw.EndInit (); - break; - default: - throw new NotImplementedException (); - } - } - } -} diff --git a/main/src/core/MonoDevelop.Core/MonoDevelop.FSW/OSX/FileSystemWatcher.OSX.cs b/main/src/core/MonoDevelop.Core/MonoDevelop.FSW/OSX/FileSystemWatcher.OSX.cs deleted file mode 100644 index cab4ca7a64..0000000000 --- a/main/src/core/MonoDevelop.Core/MonoDevelop.FSW/OSX/FileSystemWatcher.OSX.cs +++ /dev/null @@ -1,484 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System.Collections.Generic; -using System.Diagnostics; -using System.Runtime.InteropServices; -using System.Threading; -using System.IO; - -using CFStringRef = System.IntPtr; -using FSEventStreamRef = System.IntPtr; -using size_t = System.IntPtr; -using FSEventStreamEventId = System.UInt64; -using CFRunLoopRef = System.IntPtr; -using Microsoft.Win32.SafeHandles; -using System; - -namespace MonoDevelop.FSW.OSX -{ - internal partial class FileSystemWatcher - { - /// <summary>Called when FileSystemWatcher is finalized.</summary> - private void FinalizeDispose () - { - // Make sure we cleanup - StopRaisingEvents (); - } - - private void StartRaisingEvents () - { - // If we're called when "Initializing" is true, set enabled to true - if (IsSuspended ()) { - _enabled = true; - return; - } - - // Don't start another instance if one is already runnings - if (_cancellation != null) { - return; - } - - try { - CancellationTokenSource cancellation = new CancellationTokenSource (); - RunningInstance instance = new RunningInstance (this, _directory, _includeSubdirectories, TranslateFlags (_notifyFilters), cancellation.Token); - _enabled = true; - _cancellation = cancellation; - instance.Start (); - - lock (fileSystemWatchers) - fileSystemWatchers.Add (this); - } catch { - _enabled = false; - _cancellation = null; - throw; - } - } - - private void StopRaisingEvents () - { - _enabled = false; - - if (IsSuspended ()) - return; - - lock (fileSystemWatchers) - fileSystemWatchers.Remove (this); - - CancellationTokenSource token = _cancellation; - if (token != null) { - _cancellation = null; - token.Cancel (); - } - } - - // ----------------------------- - // ---- PAL layer ends here ---- - // ----------------------------- - - private CancellationTokenSource _cancellation; - - private static Interop.EventStream.FSEventStreamEventFlags TranslateFlags (NotifyFilters flagsToTranslate) - { - Interop.EventStream.FSEventStreamEventFlags flags = 0; - - // Always re-create the filter flags when start is called since they could have changed - if ((flagsToTranslate & (NotifyFilters.Attributes | NotifyFilters.CreationTime | NotifyFilters.LastAccess | NotifyFilters.LastWrite | NotifyFilters.Size)) != 0) { - flags = Interop.EventStream.FSEventStreamEventFlags.kFSEventStreamEventFlagItemInodeMetaMod | - Interop.EventStream.FSEventStreamEventFlags.kFSEventStreamEventFlagItemFinderInfoMod | - Interop.EventStream.FSEventStreamEventFlags.kFSEventStreamEventFlagItemModified | - Interop.EventStream.FSEventStreamEventFlags.kFSEventStreamEventFlagItemChangeOwner; - } - if ((flagsToTranslate & NotifyFilters.Security) != 0) { - flags |= Interop.EventStream.FSEventStreamEventFlags.kFSEventStreamEventFlagItemChangeOwner | - Interop.EventStream.FSEventStreamEventFlags.kFSEventStreamEventFlagItemXattrMod; - } - if ((flagsToTranslate & NotifyFilters.DirectoryName) != 0) { - flags |= Interop.EventStream.FSEventStreamEventFlags.kFSEventStreamEventFlagItemIsDir | - Interop.EventStream.FSEventStreamEventFlags.kFSEventStreamEventFlagItemIsSymlink | - Interop.EventStream.FSEventStreamEventFlags.kFSEventStreamEventFlagItemCreated | - Interop.EventStream.FSEventStreamEventFlags.kFSEventStreamEventFlagItemRemoved | - Interop.EventStream.FSEventStreamEventFlags.kFSEventStreamEventFlagItemRenamed; - } - if ((flagsToTranslate & NotifyFilters.FileName) != 0) { - flags |= Interop.EventStream.FSEventStreamEventFlags.kFSEventStreamEventFlagItemIsFile | - Interop.EventStream.FSEventStreamEventFlags.kFSEventStreamEventFlagItemIsSymlink | - Interop.EventStream.FSEventStreamEventFlags.kFSEventStreamEventFlagItemCreated | - Interop.EventStream.FSEventStreamEventFlags.kFSEventStreamEventFlagItemRemoved | - Interop.EventStream.FSEventStreamEventFlags.kFSEventStreamEventFlagItemRenamed; - } - - return flags; - } - - private sealed class RunningInstance - { - // Flags used to create the event stream - private const Interop.EventStream.FSEventStreamCreateFlags EventStreamFlags = (Interop.EventStream.FSEventStreamCreateFlags.kFSEventStreamCreateFlagFileEvents | - Interop.EventStream.FSEventStreamCreateFlags.kFSEventStreamCreateFlagNoDefer | - Interop.EventStream.FSEventStreamCreateFlags.kFSEventStreamCreateFlagWatchRoot); - - // Weak reference to the associated watcher. A weak reference is used so that the FileSystemWatcher may be collected and finalized, - // causing an active operation to be torn down. - private readonly WeakReference<FileSystemWatcher> _weakWatcher; - - // The user can input relative paths, which can muck with our path comparisons. Save off the - // actual full path so we can use it for comparing - private string _fullDirectory; - - // Boolean if we allow events from nested folders - private bool _includeChildren; - - // The bitmask of events that we want to send to the user - private Interop.EventStream.FSEventStreamEventFlags _filterFlags; - - // The EventStream to listen for events on - private SafeEventStreamHandle _eventStream; - - // A reference to the RunLoop that we can use to start or stop a Watcher - private CFRunLoopRef _watcherRunLoop; - - // Callback delegate for the EventStream events - private Interop.EventStream.FSEventStreamCallback _callback; - - // Token to monitor for cancellation requests, upon which processing is stopped and all - // state is cleaned up. - private readonly CancellationToken _cancellationToken; - - // Calling RunLoopStop multiple times SegFaults so protect the call to it - private bool _stopping; - private object StopLock => this; - - internal RunningInstance ( - FileSystemWatcher watcher, - string directory, - bool includeChildren, - Interop.EventStream.FSEventStreamEventFlags filter, - CancellationToken cancelToken) - { - Debug.Assert (string.IsNullOrEmpty (directory) == false); - Debug.Assert (!cancelToken.IsCancellationRequested); - - _weakWatcher = new WeakReference<FileSystemWatcher> (watcher); - _watcherRunLoop = IntPtr.Zero; - _fullDirectory = System.IO.Path.GetFullPath (directory); - _includeChildren = includeChildren; - _filterFlags = filter; - _cancellationToken = cancelToken; - _cancellationToken.Register (obj => ((RunningInstance)obj).CancellationCallback (), this); - _stopping = false; - } - - private void CancellationCallback () - { - lock (StopLock) { - if (!_stopping && _watcherRunLoop != IntPtr.Zero) { - _stopping = true; - - // Stop the FS event message pump - Interop.RunLoop.CFRunLoopStop (_watcherRunLoop); - } - } - } - - internal void Start () - { - // Make sure _fullPath doesn't contain a link or alias - // since the OS will give back the actual, non link'd or alias'd paths - _fullDirectory = Interop.Sys.RealPath (_fullDirectory); - if (_fullDirectory == null) { - throw Interop.GetExceptionForIoErrno (Interop.Sys.GetLastErrorInfo (), _fullDirectory, true); - } - - Debug.Assert (string.IsNullOrEmpty (_fullDirectory) == false, "Watch directory is null or empty"); - - // Normalize the _fullDirectory path to have a trailing slash - if (_fullDirectory [_fullDirectory.Length - 1] != '/') { - _fullDirectory += "/"; - } - - // Get the path to watch and verify we created the CFStringRef - SafeCreateHandle path = Interop.CoreFoundation.CFStringCreateWithCString (_fullDirectory); - if (path.IsInvalid) { - throw Interop.GetExceptionForIoErrno (Interop.Sys.GetLastErrorInfo (), _fullDirectory, true); - } - - // Take the CFStringRef and put it into an array to pass to the EventStream - SafeCreateHandle arrPaths = Interop.CoreFoundation.CFArrayCreate (new CFStringRef [1] { path.DangerousGetHandle () }, (UIntPtr)1); - if (arrPaths.IsInvalid) { - path.Dispose (); - throw Interop.GetExceptionForIoErrno (Interop.Sys.GetLastErrorInfo (), _fullDirectory, true); - } - - // Create the callback for the EventStream if it wasn't previously created for this instance. - if (_callback == null) { - _callback = new Interop.EventStream.FSEventStreamCallback (FileSystemEventCallback); - } - - // Make sure the OS file buffer(s) are fully flushed so we don't get events from cached I/O - Interop.Sys.Sync (); - - // Create the event stream for the path and tell the stream to watch for file system events. - _eventStream = Interop.EventStream.FSEventStreamCreate ( - _callback, - arrPaths, - Interop.EventStream.kFSEventStreamEventIdSinceNow, - 0.0f, - EventStreamFlags); - if (_eventStream.IsInvalid) { - arrPaths.Dispose (); - path.Dispose (); - throw Interop.GetExceptionForIoErrno (Interop.Sys.GetLastErrorInfo (), _fullDirectory, true); - } - - // Create and start our watcher thread then wait for the thread to initialize and start - // the RunLoop. We wait for that to prevent this function from returning before the RunLoop - // has a chance to start so that any callers won't race with the background thread's initialization - // and calling Stop, which would attempt to stop a RunLoop that hasn't started yet. - var runLoopStarted = new ManualResetEventSlim (); - new Thread (WatchForFileSystemEventsThreadStart) { IsBackground = true }.Start (runLoopStarted); - runLoopStarted.Wait (); - } - - private void WatchForFileSystemEventsThreadStart (object arg) - { - var runLoopStarted = (ManualResetEventSlim)arg; - - // Get this thread's RunLoop - _watcherRunLoop = Interop.RunLoop.CFRunLoopGetCurrent (); - Debug.Assert (_watcherRunLoop != IntPtr.Zero); - - // Retain the RunLoop so that it doesn't get moved or cleaned up before we're done with it. - IntPtr retainResult = Interop.CoreFoundation.CFRetain (_watcherRunLoop); - Debug.Assert (retainResult == _watcherRunLoop, "CFRetain is supposed to return the input value"); - - // Schedule the EventStream to run on the thread's RunLoop - Interop.EventStream.FSEventStreamScheduleWithRunLoop (_eventStream, _watcherRunLoop, Interop.RunLoop.kCFRunLoopDefaultMode); - - try { - bool started = Interop.EventStream.FSEventStreamStart (_eventStream); - - // Notify the StartRaisingEvents call that we are initialized and about to start - // so that it can return and avoid a race-condition around multiple threads calling Stop and Start - runLoopStarted.Set (); - - if (started) { - // Start the OS X RunLoop (a blocking call) that will pump file system changes into the callback function - Interop.RunLoop.CFRunLoopRun (); - - // When we get here, we've requested to stop so cleanup the EventStream and unschedule from the RunLoop - Interop.EventStream.FSEventStreamStop (_eventStream); - } else { - // Try to get the Watcher to raise the error event; if we can't do that, just silently exist since the watcher is gone anyway - FileSystemWatcher watcher; - if (_weakWatcher.TryGetTarget (out watcher)) { - // An error occurred while trying to start the run loop so fail out - watcher.OnError (new ErrorEventArgs (new IOException (SR.EventStream_FailedToStart, Marshal.GetLastWin32Error ()))); - } - } - } finally { - // Always unschedule the RunLoop before cleaning up - Interop.EventStream.FSEventStreamUnscheduleFromRunLoop (_eventStream, _watcherRunLoop, Interop.RunLoop.kCFRunLoopDefaultMode); - - // Release the WatcherLoop Core Foundation object. - lock (StopLock) { - Interop.CoreFoundation.CFRelease (_watcherRunLoop); - _watcherRunLoop = IntPtr.Zero; - } - } - } - - private void FileSystemEventCallback ( - FSEventStreamRef streamRef, - IntPtr clientCallBackInfo, - size_t numEvents, - String [] eventPaths, - [MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 2)] - Interop.EventStream.FSEventStreamEventFlags[] eventFlags, - [MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 2)] - FSEventStreamEventId[] eventIds) - { - Debug.Assert ((numEvents.ToInt32 () == eventPaths.Length) && (numEvents.ToInt32 () == eventFlags.Length) && (numEvents.ToInt32 () == eventIds.Length)); - - // Try to get the actual watcher from our weak reference. We maintain a weak reference most of the time - // so as to avoid a rooted cycle that would prevent our processing loop from ever ending - // if the watcher is dropped by the user without being disposed. If we can't get the watcher, - // there's nothing more to do (we can't raise events), so bail. - FileSystemWatcher watcher; - if (!_weakWatcher.TryGetTarget (out watcher)) { - CancellationCallback (); - return; - } - - // Since renames come in pairs, when we find the first we need to search for the next one. Once we find it, we'll add it to this - // list so when the for-loop comes across it, we'll skip it since it's already been processed as part of the original of the pair. - List<FSEventStreamEventId> handledRenameEvents = null; - - for (long i = 0; i < numEvents.ToInt32 (); i++) { - Debug.Assert (eventPaths [i].Length > 0, "Empty events are not supported"); - Debug.Assert (eventPaths [i] [eventPaths [i].Length - 1] != '/', "Trailing slashes on events is not supported"); - - // Match Windows and don't notify us about changes to the Root folder - string path = eventPaths [i]; - if (string.Compare (path, 0, _fullDirectory, 0, path.Length, StringComparison.OrdinalIgnoreCase) == 0) { - continue; - } - - WatcherChangeTypes eventType = 0; - // First, we should check if this event should kick off a re-scan since we can't really rely on anything after this point if that is true - if (ShouldRescanOccur (eventFlags [i])) { - watcher.OnError (new ErrorEventArgs (new IOException (SR.FSW_BufferOverflow, (int)eventFlags [i]))); - break; - } else if ((handledRenameEvents != null) && (handledRenameEvents.Contains (eventIds [i]))) { - // If this event is the second in a rename pair then skip it - continue; - } else if (CheckIfPathIsNested (path) && ((eventType = FilterEvents (eventFlags [i], path)) != 0)) { - // The base FileSystemWatcher does a match check against the relative path before combining with - // the root dir; however, null is special cased to signify the root dir, so check if we should use that. - string relativePath = null; - if (path.Equals (_fullDirectory, StringComparison.OrdinalIgnoreCase) == false) { - // Remove the root directory to get the relative path - relativePath = path.Remove (0, _fullDirectory.Length); - } - - // Raise a notification for the event - if (((eventType & WatcherChangeTypes.Changed) > 0)) { - watcher.NotifyFileSystemEventArgs (WatcherChangeTypes.Changed, relativePath); - } - if (((eventType & WatcherChangeTypes.Created) > 0)) { - watcher.NotifyFileSystemEventArgs (WatcherChangeTypes.Created, relativePath); - } - if (((eventType & WatcherChangeTypes.Deleted) > 0)) { - watcher.NotifyFileSystemEventArgs (WatcherChangeTypes.Deleted, relativePath); - } - if (((eventType & WatcherChangeTypes.Renamed) > 0)) { - // Find the rename that is paired to this rename, which should be the next rename in the list - long pairedId = FindRenameChangePairedChange (i, eventPaths, eventFlags, eventIds); - if (pairedId == long.MinValue) { - // Getting here means we have a rename without a pair, meaning it should be a create for the - // move from unwatched folder to watcher folder scenario or a move from the watcher folder out. - // Check if the item exists on disk to check which it is - // Don't send a new notification if we already sent one for this event. - if (DoesItemExist (path, IsFlagSet (eventFlags [i], Interop.EventStream.FSEventStreamEventFlags.kFSEventStreamEventFlagItemIsFile))) { - if ((eventType & WatcherChangeTypes.Created) == 0) { - watcher.NotifyFileSystemEventArgs (WatcherChangeTypes.Created, relativePath); - } - } else if ((eventType & WatcherChangeTypes.Deleted) == 0) { - watcher.NotifyFileSystemEventArgs (WatcherChangeTypes.Deleted, relativePath); - } - } else { - // Remove the base directory prefix and add the paired event to the list of - // events to skip and notify the user of the rename - string newPathRelativeName = eventPaths [pairedId].Remove (0, _fullDirectory.Length); - watcher.NotifyRenameEventArgs (WatcherChangeTypes.Renamed, newPathRelativeName, relativePath); - - // Create a new list, if necessary, and add the event - if (handledRenameEvents == null) { - handledRenameEvents = new List<FSEventStreamEventId> (); - } - handledRenameEvents.Add (eventIds [pairedId]); - } - } - } - } - } - - /// <summary> - /// Compares the given event flags to the filter flags and returns which event (if any) corresponds - /// to those flags. - /// </summary> - private WatcherChangeTypes FilterEvents (Interop.EventStream.FSEventStreamEventFlags eventFlags, string fullPath) - { - const Interop.EventStream.FSEventStreamEventFlags changedFlags = Interop.EventStream.FSEventStreamEventFlags.kFSEventStreamEventFlagItemInodeMetaMod | - Interop.EventStream.FSEventStreamEventFlags.kFSEventStreamEventFlagItemFinderInfoMod | - Interop.EventStream.FSEventStreamEventFlags.kFSEventStreamEventFlagItemModified | - Interop.EventStream.FSEventStreamEventFlags.kFSEventStreamEventFlagItemChangeOwner | - Interop.EventStream.FSEventStreamEventFlags.kFSEventStreamEventFlagItemXattrMod; - WatcherChangeTypes eventType = 0; - // If any of the Changed flags are set in both Filter and Event then a Changed event has occurred. - if (((_filterFlags & changedFlags) & (eventFlags & changedFlags)) > 0) { - eventType |= WatcherChangeTypes.Changed; - } - - // Notify created/deleted/renamed events if they pass through the filters - bool allowDirs = (_filterFlags & Interop.EventStream.FSEventStreamEventFlags.kFSEventStreamEventFlagItemIsDir) > 0; - bool allowFiles = (_filterFlags & Interop.EventStream.FSEventStreamEventFlags.kFSEventStreamEventFlagItemIsFile) > 0; - bool isDir = (eventFlags & Interop.EventStream.FSEventStreamEventFlags.kFSEventStreamEventFlagItemIsDir) > 0; - bool isFile = (eventFlags & Interop.EventStream.FSEventStreamEventFlags.kFSEventStreamEventFlagItemIsFile) > 0; - bool eventIsCorrectType = (isDir && allowDirs) || (isFile && allowFiles); - bool eventIsLink = (eventFlags & (Interop.EventStream.FSEventStreamEventFlags.kFSEventStreamEventFlagItemIsHardlink | Interop.EventStream.FSEventStreamEventFlags.kFSEventStreamEventFlagItemIsSymlink | Interop.EventStream.FSEventStreamEventFlags.kFSEventStreamEventFlagItemIsLastHardlink)) > 0; - - if (eventIsCorrectType || ((allowDirs || allowFiles) && (eventIsLink))) { - // Notify Created/Deleted/Renamed events. - if (IsFlagSet (eventFlags, Interop.EventStream.FSEventStreamEventFlags.kFSEventStreamEventFlagItemRenamed)) { - eventType |= WatcherChangeTypes.Renamed; - } - if (IsFlagSet (eventFlags, Interop.EventStream.FSEventStreamEventFlags.kFSEventStreamEventFlagItemCreated)) { - eventType |= WatcherChangeTypes.Created; - } - if (IsFlagSet (eventFlags, Interop.EventStream.FSEventStreamEventFlags.kFSEventStreamEventFlagItemRemoved)) { - eventType |= WatcherChangeTypes.Deleted; - } - } - return eventType; - } - - private bool ShouldRescanOccur (Interop.EventStream.FSEventStreamEventFlags flags) - { - // Check if any bit is set that signals that the caller should rescan - return (IsFlagSet (flags, Interop.EventStream.FSEventStreamEventFlags.kFSEventStreamEventFlagMustScanSubDirs) || - IsFlagSet (flags, Interop.EventStream.FSEventStreamEventFlags.kFSEventStreamEventFlagUserDropped) || - IsFlagSet (flags, Interop.EventStream.FSEventStreamEventFlags.kFSEventStreamEventFlagKernelDropped) || - IsFlagSet (flags, Interop.EventStream.FSEventStreamEventFlags.kFSEventStreamEventFlagRootChanged) || - IsFlagSet (flags, Interop.EventStream.FSEventStreamEventFlags.kFSEventStreamEventFlagMount) || - IsFlagSet (flags, Interop.EventStream.FSEventStreamEventFlags.kFSEventStreamEventFlagUnmount)); - } - - private bool CheckIfPathIsNested (string eventPath) - { - bool doesPathPass = true; - - // If we shouldn't include subdirectories, check if this path's parent is the watch directory - if (_includeChildren == false) { - // Check if the parent is the root. If so, then we'll continue processing based on the name. - // If it isn't, then this will be set to false and we'll skip the name processing since it's irrelevant. - string parent = System.IO.Path.GetDirectoryName (eventPath); - doesPathPass = (string.Compare (parent, 0, _fullDirectory, 0, parent.Length, StringComparison.OrdinalIgnoreCase) == 0); - } - - return doesPathPass; - } - - private long FindRenameChangePairedChange ( - long currentIndex, - String [] eventPaths, - Interop.EventStream.FSEventStreamEventFlags [] eventFlags, - FSEventStreamEventId [] eventIds) - { - // Start at one past the current index and try to find the next Rename item, which should be the old path. - for (long i = currentIndex + 1; i < eventPaths.Length; i++) { - if (IsFlagSet (eventFlags [i], Interop.EventStream.FSEventStreamEventFlags.kFSEventStreamEventFlagItemRenamed)) { - // We found match, stop looking - return i; - } - } - - return long.MinValue; - } - - private static bool IsFlagSet (Interop.EventStream.FSEventStreamEventFlags flags, Interop.EventStream.FSEventStreamEventFlags value) - { - return (value & flags) == value; - } - - private static bool DoesItemExist (string path, bool isFile) - { - if (isFile) - return File.Exists (path); - else - return Directory.Exists (path); - } - } - } -} diff --git a/main/src/core/MonoDevelop.Core/MonoDevelop.FSW/OSX/FileSystemWatcher.cs b/main/src/core/MonoDevelop.Core/MonoDevelop.FSW/OSX/FileSystemWatcher.cs deleted file mode 100644 index f34689ae9e..0000000000 --- a/main/src/core/MonoDevelop.Core/MonoDevelop.FSW/OSX/FileSystemWatcher.cs +++ /dev/null @@ -1,603 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System.ComponentModel; -using System.Diagnostics; -using System.Diagnostics.CodeAnalysis; -using System.Threading; -using System.Threading.Tasks; -using System.IO; -using System; -using System.Collections.Generic; - -namespace MonoDevelop.FSW.OSX -{ - /// <devdoc> - /// Listens to the system directory change notifications and - /// raises events when a directory or file within a directory changes. - /// </devdoc> - - internal partial class FileSystemWatcher : Component, ISupportInitialize - { - /// <devdoc> - /// Private instance variables - /// </devdoc> - // Directory being monitored - private string _directory; - - // Filter for name matching - private string _filter; - - // The watch filter for the API call. - private const NotifyFilters c_defaultNotifyFilters = NotifyFilters.LastWrite | NotifyFilters.FileName | NotifyFilters.DirectoryName; - private NotifyFilters _notifyFilters = c_defaultNotifyFilters; - - // Flag to watch subtree of this directory - private bool _includeSubdirectories = false; - - // Flag to note whether we are attached to the thread pool and responding to changes - private bool _enabled = false; - - // Are we in init? - private bool _initializing = false; - - // Buffer size - private int _internalBufferSize = 8192; - - // Used for synchronization - private bool _disposed; - private ISynchronizeInvoke _synchronizingObject; - - // Event handlers - private FileSystemEventHandler _onChangedHandler = null; - private FileSystemEventHandler _onCreatedHandler = null; - private FileSystemEventHandler _onDeletedHandler = null; - private RenamedEventHandler _onRenamedHandler = null; - private ErrorEventHandler _onErrorHandler = null; - - // To validate the input for "path" - private static readonly char [] s_wildcards = new char [] { '?', '*' }; - - private const int c_notifyFiltersValidMask = (int)(NotifyFilters.Attributes | - NotifyFilters.CreationTime | - NotifyFilters.DirectoryName | - NotifyFilters.FileName | - NotifyFilters.LastAccess | - NotifyFilters.LastWrite | - NotifyFilters.Security | - NotifyFilters.Size); - -#if DEBUG - static FileSystemWatcher () - { - int s_notifyFiltersValidMask = 0; - foreach (int enumValue in Enum.GetValues (typeof (NotifyFilters))) - s_notifyFiltersValidMask |= enumValue; - Debug.Assert (c_notifyFiltersValidMask == s_notifyFiltersValidMask, "The NotifyFilters enum has changed. The c_notifyFiltersValidMask must be updated to reflect the values of the NotifyFilters enum."); - } -#endif - static HashSet<FileSystemWatcher> fileSystemWatchers = new HashSet<FileSystemWatcher> (); - public static void DisposeAll() - { - lock (fileSystemWatchers) { - foreach (var fsw in fileSystemWatchers) { - fsw.Dispose (); - } - fileSystemWatchers.Clear (); - } - } - - /// <devdoc> - /// Initializes a new instance of the <see cref='System.IO.FileSystemWatcher'/> class. - /// </devdoc> - public FileSystemWatcher () - { - _directory = string.Empty; - _filter = "*.*"; - } - - /// <devdoc> - /// Initializes a new instance of the <see cref='System.IO.FileSystemWatcher'/> class, - /// given the specified directory to monitor. - /// </devdoc> - public FileSystemWatcher (string path) : this (path, "*.*") - { - } - - /// <devdoc> - /// Initializes a new instance of the <see cref='System.IO.FileSystemWatcher'/> class, - /// given the specified directory and type of files to monitor. - /// </devdoc> - public FileSystemWatcher (string path, string filter) - { - if (path == null) - throw new ArgumentNullException (nameof (path)); - - if (filter == null) - throw new ArgumentNullException (nameof (filter)); - - // Early check for directory parameter so that an exception can be thrown as early as possible. - if (path.Length == 0 || !Directory.Exists (path)) - throw new ArgumentException (SR.Format (SR.InvalidDirName, path), nameof (path)); - - _directory = path; - _filter = filter; - } - - /// <devdoc> - /// Gets or sets the type of changes to watch for. - /// </devdoc> - public NotifyFilters NotifyFilter { - get { - return _notifyFilters; - } - set { - if (((int)value & ~c_notifyFiltersValidMask) != 0) - throw new ArgumentException (SR.Format (SR.InvalidEnumArgument, nameof (value), (int)value, nameof (NotifyFilters))); - - if (_notifyFilters != value) { - _notifyFilters = value; - - Restart (); - } - } - } - - /// <devdoc> - /// Gets or sets a value indicating whether the component is enabled. - /// </devdoc> - public bool EnableRaisingEvents { - get { - return _enabled; - } - set { - if (_enabled == value) { - return; - } - - if (IsSuspended ()) { - _enabled = value; // Alert the Component to start watching for events when EndInit is called. - } else { - if (value) { - StartRaisingEventsIfNotDisposed (); // will set _enabled to true once successfully started - } else { - StopRaisingEvents (); // will set _enabled to false - } - } - } - } - - /// <devdoc> - /// Gets or sets the filter string, used to determine what files are monitored in a directory. - /// </devdoc> - public string Filter { - get { - return _filter; - } - set { - if (string.IsNullOrEmpty (value)) { - // Skip the string compare for "*.*" since it has no case-insensitive representation that differs from - // the case-sensitive representation. - _filter = "*.*"; - } else if (!string.Equals (_filter, value, PathInternal.StringComparison)) { - _filter = value; - } - } - } - - /// <devdoc> - /// Gets or sets a value indicating whether subdirectories within the specified path should be monitored. - /// </devdoc> - public bool IncludeSubdirectories { - get { - return _includeSubdirectories; - } - set { - if (_includeSubdirectories != value) { - _includeSubdirectories = value; - - Restart (); - } - } - } - - /// <devdoc> - /// Gets or sets the size of the internal buffer. - /// </devdoc> - public int InternalBufferSize { - get { - return _internalBufferSize; - } - set { - if (_internalBufferSize != value) { - if (value < 4096) { - _internalBufferSize = 4096; - } else { - _internalBufferSize = value; - } - - Restart (); - } - } - } - - /// <summary>Allocates a buffer of the requested internal buffer size.</summary> - /// <returns>The allocated buffer.</returns> - private byte [] AllocateBuffer () - { - try { - return new byte [_internalBufferSize]; - } catch (OutOfMemoryException) { - throw new OutOfMemoryException (SR.Format (SR.BufferSizeTooLarge, _internalBufferSize)); - } - } - - /// <devdoc> - /// Gets or sets the path of the directory to watch. - /// </devdoc> - public string Path { - get { - return _directory; - } - set { - value = (value == null) ? string.Empty : value; - if (!string.Equals (_directory, value, PathInternal.StringComparison)) { - if (!Directory.Exists (value)) { - throw new ArgumentException (SR.Format (SR.InvalidDirName, value)); - } - - _directory = value; - Restart (); - } - } - } - - /// <devdoc> - /// Occurs when a file or directory in the specified <see cref='System.IO.FileSystemWatcher.Path'/> is changed. - /// </devdoc> - public event FileSystemEventHandler Changed { - add { - _onChangedHandler += value; - } - remove { - _onChangedHandler -= value; - } - } - - /// <devdoc> - /// Occurs when a file or directory in the specified <see cref='System.IO.FileSystemWatcher.Path'/> is created. - /// </devdoc> - public event FileSystemEventHandler Created { - add { - _onCreatedHandler += value; - } - remove { - _onCreatedHandler -= value; - } - } - - /// <devdoc> - /// Occurs when a file or directory in the specified <see cref='System.IO.FileSystemWatcher.Path'/> is deleted. - /// </devdoc> - public event FileSystemEventHandler Deleted { - add { - _onDeletedHandler += value; - } - remove { - _onDeletedHandler -= value; - } - } - - /// <devdoc> - /// Occurs when the internal buffer overflows. - /// </devdoc> - public event ErrorEventHandler Error { - add { - _onErrorHandler += value; - } - remove { - _onErrorHandler -= value; - } - } - - /// <devdoc> - /// Occurs when a file or directory in the specified <see cref='System.IO.FileSystemWatcher.Path'/> - /// is renamed. - /// </devdoc> - public event RenamedEventHandler Renamed { - add { - _onRenamedHandler += value; - } - remove { - _onRenamedHandler -= value; - } - } - - /// <devdoc> - /// </devdoc> - protected internal new void Dispose (bool disposing) - { - try { - if (disposing) { - //Stop raising events cleans up managed and - //unmanaged resources. - StopRaisingEvents (); - - // Clean up managed resources - _onChangedHandler = null; - _onCreatedHandler = null; - _onDeletedHandler = null; - _onRenamedHandler = null; - _onErrorHandler = null; - } else { - FinalizeDispose (); - } - } finally { - _disposed = true; - base.Dispose (disposing); - } - } - - /// <devdoc> - /// Sees if the name given matches the name filter we have. - /// </devdoc> - /// <internalonly/> - private bool MatchPattern (string relativePath) - { - string name = System.IO.Path.GetFileName (relativePath); - return name != null ? - PatternMatcher.StrictMatchPattern (_filter, name) : - false; - } - - /// <devdoc> - /// Raises the event to each handler in the list. - /// </devdoc> - /// <internalonly/> - private void NotifyInternalBufferOverflowEvent () - { - _onErrorHandler?.Invoke (this, new ErrorEventArgs ( - new InternalBufferOverflowException (SR.Format (SR.FSW_BufferOverflow, _directory)))); - } - - /// <devdoc> - /// Raises the event to each handler in the list. - /// </devdoc> - /// <internalonly/> - private void NotifyRenameEventArgs (WatcherChangeTypes action, string name, string oldName) - { - // filter if there's no handler or neither new name or old name match a specified pattern - RenamedEventHandler handler = _onRenamedHandler; - if (handler != null && - (MatchPattern (name) || MatchPattern (oldName))) { - handler (this, new RenamedEventArgs (action, _directory, name, oldName)); - } - } - - /// <devdoc> - /// Raises the event to each handler in the list. - /// </devdoc> - /// <internalonly/> - private void NotifyFileSystemEventArgs (WatcherChangeTypes changeType, string name) - { - FileSystemEventHandler handler = null; - switch (changeType) { - case WatcherChangeTypes.Created: - handler = _onCreatedHandler; - break; - case WatcherChangeTypes.Deleted: - handler = _onDeletedHandler; - break; - case WatcherChangeTypes.Changed: - handler = _onChangedHandler; - break; - default: - Debug.Fail ("Unknown FileSystemEvent change type! Value: " + changeType); - break; - } - - if (handler != null && MatchPattern (string.IsNullOrEmpty (name) ? _directory : name)) { - handler (this, new FileSystemEventArgs (changeType, _directory, name)); - } - } - - /// <devdoc> - /// Raises the <see cref='System.IO.FileSystemWatcher.Changed'/> event. - /// </devdoc> - [SuppressMessage ("Microsoft.Security", "CA2109:ReviewVisibleEventHandlers", MessageId = "0#", Justification = "Changing from protected to private would be a breaking change")] - protected internal void OnChanged (FileSystemEventArgs e) - { - InvokeOn (e, _onChangedHandler); - } - - /// <devdoc> - /// Raises the <see cref='System.IO.FileSystemWatcher.Created'/> event. - /// </devdoc> - [SuppressMessage ("Microsoft.Security", "CA2109:ReviewVisibleEventHandlers", MessageId = "0#", Justification = "Changing from protected to private would be a breaking change")] - protected internal void OnCreated (FileSystemEventArgs e) - { - InvokeOn (e, _onCreatedHandler); - } - - /// <devdoc> - /// Raises the <see cref='System.IO.FileSystemWatcher.Deleted'/> event. - /// </devdoc> - [SuppressMessage ("Microsoft.Security", "CA2109:ReviewVisibleEventHandlers", MessageId = "0#", Justification = "Changing from protected to private would be a breaking change")] - protected internal void OnDeleted (FileSystemEventArgs e) - { - InvokeOn (e, _onDeletedHandler); - } - - private void InvokeOn (FileSystemEventArgs e, FileSystemEventHandler handler) - { - if (handler != null) { - ISynchronizeInvoke syncObj = SynchronizingObject; - if (syncObj != null && syncObj.InvokeRequired) - syncObj.BeginInvoke (handler, new object [] { this, e }); - else - handler (this, e); - } - } - - /// <devdoc> - /// Raises the <see cref='System.IO.FileSystemWatcher.Error'/> event. - /// </devdoc> - [SuppressMessage ("Microsoft.Security", "CA2109:ReviewVisibleEventHandlers", MessageId = "0#", Justification = "Changing from protected to private would be a breaking change")] - protected internal void OnError (ErrorEventArgs e) - { - ErrorEventHandler handler = _onErrorHandler; - if (handler != null) { - ISynchronizeInvoke syncObj = SynchronizingObject; - if (syncObj != null && syncObj.InvokeRequired) - syncObj.BeginInvoke (handler, new object [] { this, e }); - else - handler (this, e); - } - } - - /// <devdoc> - /// Raises the <see cref='System.IO.FileSystemWatcher.Renamed'/> event. - /// </devdoc> - [SuppressMessage ("Microsoft.Security", "CA2109:ReviewVisibleEventHandlers", MessageId = "0#", Justification = "Changing from protected to private would be a breaking change")] - protected internal void OnRenamed (RenamedEventArgs e) - { - RenamedEventHandler handler = _onRenamedHandler; - if (handler != null) { - ISynchronizeInvoke syncObj = SynchronizingObject; - if (syncObj != null && syncObj.InvokeRequired) - syncObj.BeginInvoke (handler, new object [] { this, e }); - else - handler (this, e); - } - } - - public WaitForChangedResult WaitForChanged (WatcherChangeTypes changeType) => - WaitForChanged (changeType, Timeout.Infinite); - - public WaitForChangedResult WaitForChanged (WatcherChangeTypes changeType, int timeout) - { - // The full framework implementation doesn't do any argument validation, so - // none is done here, either. - - var tcs = new TaskCompletionSource<WaitForChangedResult> (); - FileSystemEventHandler fseh = null; - RenamedEventHandler reh = null; - - // Register the event handlers based on what events are desired. The full framework - // doesn't register for the Error event, so this doesn't either. - if ((changeType & (WatcherChangeTypes.Created | WatcherChangeTypes.Deleted | WatcherChangeTypes.Changed)) != 0) { - fseh = (s, e) => { - if ((e.ChangeType & changeType) != 0) { - tcs.TrySetResult (new WaitForChangedResult { ChangeType = e.ChangeType, Name = e.Name, OldName = null, TimedOut = false }); - } - }; - if ((changeType & WatcherChangeTypes.Created) != 0) Created += fseh; - if ((changeType & WatcherChangeTypes.Deleted) != 0) Deleted += fseh; - if ((changeType & WatcherChangeTypes.Changed) != 0) Changed += fseh; - } - if ((changeType & WatcherChangeTypes.Renamed) != 0) { - reh = (s, e) => { - if ((e.ChangeType & changeType) != 0) { - tcs.TrySetResult (new WaitForChangedResult { ChangeType = e.ChangeType, Name = e.Name, OldName = e.OldName, TimedOut = false }); - } - }; - Renamed += reh; - } - try { - // Enable the FSW if it wasn't already. - bool wasEnabled = EnableRaisingEvents; - if (!wasEnabled) { - EnableRaisingEvents = true; - } - - // Block until an appropriate event arrives or until we timeout. - Debug.Assert (EnableRaisingEvents, "Expected EnableRaisingEvents to be true"); - tcs.Task.Wait (timeout); - - // Reset the enabled state to what it was. - EnableRaisingEvents = wasEnabled; - } finally { - // Unregister the event handlers. - if (reh != null) { - Renamed -= reh; - } - if (fseh != null) { - if ((changeType & WatcherChangeTypes.Changed) != 0) Changed -= fseh; - if ((changeType & WatcherChangeTypes.Deleted) != 0) Deleted -= fseh; - if ((changeType & WatcherChangeTypes.Created) != 0) Created -= fseh; - } - } - - // Return the results. - return tcs.Task.Status == TaskStatus.RanToCompletion ? - tcs.Task.Result : - new WaitForChangedResult { ChangeType = 0, Name = null, OldName = null, TimedOut = true }; - } - - /// <devdoc> - /// Stops and starts this object. - /// </devdoc> - /// <internalonly/> - private void Restart () - { - if ((!IsSuspended ()) && _enabled) { - StopRaisingEvents (); - StartRaisingEventsIfNotDisposed (); - } - } - - private void StartRaisingEventsIfNotDisposed () - { - //Cannot allocate the directoryHandle and the readBuffer if the object has been disposed; finalization has been suppressed. - if (_disposed) - throw new ObjectDisposedException (GetType ().Name); - StartRaisingEvents (); - } - - public override ISite Site { - get { - return base.Site; - } - set { - base.Site = value; - - // set EnableRaisingEvents to true at design time so the user - // doesn't have to manually. - if (Site != null && Site.DesignMode) - EnableRaisingEvents = true; - } - } - - public ISynchronizeInvoke SynchronizingObject { - get { - return _synchronizingObject; - } - - set { - _synchronizingObject = value; - } - } - - public void BeginInit () - { - bool oldEnabled = _enabled; - StopRaisingEvents (); - _enabled = oldEnabled; - _initializing = true; - } - - public void EndInit () - { - _initializing = false; - // Start listening to events if _enabled was set to true at some point. - if (_directory.Length != 0 && _enabled) - StartRaisingEvents (); - } - - private bool IsSuspended () - { - return _initializing || DesignMode; - } - } -} diff --git a/main/src/core/MonoDevelop.Core/MonoDevelop.FSW/OSX/Interop.CoreFoundation.cs b/main/src/core/MonoDevelop.Core/MonoDevelop.FSW/OSX/Interop.CoreFoundation.cs deleted file mode 100644 index 292a69ba91..0000000000 --- a/main/src/core/MonoDevelop.Core/MonoDevelop.FSW/OSX/Interop.CoreFoundation.cs +++ /dev/null @@ -1,110 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System; -using System.Runtime.InteropServices; - -using Microsoft.Win32.SafeHandles; - -using CFStringRef = System.IntPtr; -using CFArrayRef = System.IntPtr; - -namespace MonoDevelop.FSW.OSX -{ - internal static partial class Interop - { - internal static partial class CoreFoundation - { - /// <summary> - /// Tells the OS what encoding the passed in String is in. These come from the CFString.h header file in the CoreFoundation framework. - /// </summary> - private enum CFStringBuiltInEncodings : uint - { - kCFStringEncodingMacRoman = 0, - kCFStringEncodingWindowsLatin1 = 0x0500, - kCFStringEncodingISOLatin1 = 0x0201, - kCFStringEncodingNextStepLatin = 0x0B01, - kCFStringEncodingASCII = 0x0600, - kCFStringEncodingUnicode = 0x0100, - kCFStringEncodingUTF8 = 0x08000100, - kCFStringEncodingNonLossyASCII = 0x0BFF, - - kCFStringEncodingUTF16 = 0x0100, - kCFStringEncodingUTF16BE = 0x10000100, - kCFStringEncodingUTF16LE = 0x14000100, - kCFStringEncodingUTF32 = 0x0c000100, - kCFStringEncodingUTF32BE = 0x18000100, - kCFStringEncodingUTF32LE = 0x1c000100 - } - - /// <summary> - /// Creates a CFStringRef from a 8-bit String object. Follows the "Create Rule" where if you create it, you delete it. - /// </summary> - /// <param name="allocator">Should be IntPtr.Zero</param> - /// <param name="str">The string to get a CFStringRef for</param> - /// <param name="encoding">The encoding of the str variable. This should be UTF 8 for OS X</param> - /// <returns>Returns a pointer to a CFString on success; otherwise, returns IntPtr.Zero</returns> - /// <remarks>For *nix systems, the CLR maps ANSI to UTF-8, so be explicit about that</remarks> - [DllImport (Interop.Libraries.CoreFoundationLibrary, CharSet = CharSet.Ansi)] - private static extern SafeCreateHandle CFStringCreateWithCString ( - IntPtr allocator, - string str, - CFStringBuiltInEncodings encoding); - - /// <summary> - /// Creates a CFStringRef from a 8-bit String object. Follows the "Create Rule" where if you create it, you delete it. - /// </summary> - /// <param name="str">The string to get a CFStringRef for</param> - /// <returns>Returns a valid SafeCreateHandle to a CFString on success; otherwise, returns an invalid SafeCreateHandle</returns> - internal static SafeCreateHandle CFStringCreateWithCString (string str) - { - return CFStringCreateWithCString (IntPtr.Zero, str, CFStringBuiltInEncodings.kCFStringEncodingUTF8); - } - - /// <summary> - /// Creates a pointer to an unmanaged CFArray containing the input values. Follows the "Create Rule" where if you create it, you delete it. - /// </summary> - /// <param name="allocator">Should be IntPtr.Zero</param> - /// <param name="values">The values to put in the array</param> - /// <param name="numValues">The number of values in the array</param> - /// <param name="callbacks">Should be IntPtr.Zero</param> - /// <returns>Returns a pointer to a CFArray on success; otherwise, returns IntPtr.Zero</returns> - [DllImport (Interop.Libraries.CoreFoundationLibrary)] - private static extern SafeCreateHandle CFArrayCreate ( - IntPtr allocator, - [MarshalAs(UnmanagedType.LPArray)] - IntPtr[] values, - UIntPtr numValues, - IntPtr callbacks); - - /// <summary> - /// Creates a pointer to an unmanaged CFArray containing the input values. Follows the "Create Rule" where if you create it, you delete it. - /// </summary> - /// <param name="values">The values to put in the array</param> - /// <param name="numValues">The number of values in the array</param> - /// <returns>Returns a valid SafeCreateHandle to a CFArray on success; otherwise, returns an invalid SafeCreateHandle</returns> - internal static SafeCreateHandle CFArrayCreate (IntPtr [] values, UIntPtr numValues) - { - return CFArrayCreate (IntPtr.Zero, values, numValues, IntPtr.Zero); - } - - /// <summary> - /// You should retain a Core Foundation object when you receive it from elsewhere - /// (that is, you did not create or copy it) and you want it to persist. If you - /// retain a Core Foundation object you are responsible for releasing it - /// </summary> - /// <param name="ptr">The CFType object to retain. This value must not be NULL</param> - /// <returns>The input value</returns> - [DllImport (Interop.Libraries.CoreFoundationLibrary)] - internal extern static IntPtr CFRetain (IntPtr ptr); - - /// <summary> - /// Decrements the reference count on the specified object and, if the ref count hits 0, cleans up the object. - /// </summary> - /// <param name="ptr">The pointer on which to decrement the reference count.</param> - [DllImport (Interop.Libraries.CoreFoundationLibrary)] - internal extern static void CFRelease (IntPtr ptr); - } - } -} diff --git a/main/src/core/MonoDevelop.Core/MonoDevelop.FSW/OSX/Interop.Error.cs b/main/src/core/MonoDevelop.Core/MonoDevelop.FSW/OSX/Interop.Error.cs deleted file mode 100644 index 7356bf5ace..0000000000 --- a/main/src/core/MonoDevelop.Core/MonoDevelop.FSW/OSX/Interop.Error.cs +++ /dev/null @@ -1,209 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System; -using System.Runtime.InteropServices; - -namespace MonoDevelop.FSW.OSX -{ - internal static partial class Interop - { - /// <summary>Common Unix errno error codes.</summary> - internal enum Error - { - // These values were defined in src/Native/System.Native/fxerrno.h - // - // They compare against values obtained via Interop.Sys.GetLastError() not Marshal.GetLastWin32Error() - // which obtains the raw errno that varies between unixes. The strong typing as an enum is meant to - // prevent confusing the two. Casting to or from int is suspect. Use GetLastErrorInfo() if you need to - // correlate these to the underlying platform values or obtain the corresponding error message. - // - - SUCCESS = 0, - - E2BIG = 0x10001, // Argument list too long. - EACCES = 0x10002, // Permission denied. - EADDRINUSE = 0x10003, // Address in use. - EADDRNOTAVAIL = 0x10004, // Address not available. - EAFNOSUPPORT = 0x10005, // Address family not supported. - EAGAIN = 0x10006, // Resource unavailable, try again (same value as EWOULDBLOCK), - EALREADY = 0x10007, // Connection already in progress. - EBADF = 0x10008, // Bad file descriptor. - EBADMSG = 0x10009, // Bad message. - EBUSY = 0x1000A, // Device or resource busy. - ECANCELED = 0x1000B, // Operation canceled. - ECHILD = 0x1000C, // No child processes. - ECONNABORTED = 0x1000D, // Connection aborted. - ECONNREFUSED = 0x1000E, // Connection refused. - ECONNRESET = 0x1000F, // Connection reset. - EDEADLK = 0x10010, // Resource deadlock would occur. - EDESTADDRREQ = 0x10011, // Destination address required. - EDOM = 0x10012, // Mathematics argument out of domain of function. - EDQUOT = 0x10013, // Reserved. - EEXIST = 0x10014, // File exists. - EFAULT = 0x10015, // Bad address. - EFBIG = 0x10016, // File too large. - EHOSTUNREACH = 0x10017, // Host is unreachable. - EIDRM = 0x10018, // Identifier removed. - EILSEQ = 0x10019, // Illegal byte sequence. - EINPROGRESS = 0x1001A, // Operation in progress. - EINTR = 0x1001B, // Interrupted function. - EINVAL = 0x1001C, // Invalid argument. - EIO = 0x1001D, // I/O error. - EISCONN = 0x1001E, // Socket is connected. - EISDIR = 0x1001F, // Is a directory. - ELOOP = 0x10020, // Too many levels of symbolic links. - EMFILE = 0x10021, // File descriptor value too large. - EMLINK = 0x10022, // Too many links. - EMSGSIZE = 0x10023, // Message too large. - EMULTIHOP = 0x10024, // Reserved. - ENAMETOOLONG = 0x10025, // Filename too long. - ENETDOWN = 0x10026, // Network is down. - ENETRESET = 0x10027, // Connection aborted by network. - ENETUNREACH = 0x10028, // Network unreachable. - ENFILE = 0x10029, // Too many files open in system. - ENOBUFS = 0x1002A, // No buffer space available. - ENODEV = 0x1002C, // No such device. - ENOENT = 0x1002D, // No such file or directory. - ENOEXEC = 0x1002E, // Executable file format error. - ENOLCK = 0x1002F, // No locks available. - ENOLINK = 0x10030, // Reserved. - ENOMEM = 0x10031, // Not enough space. - ENOMSG = 0x10032, // No message of the desired type. - ENOPROTOOPT = 0x10033, // Protocol not available. - ENOSPC = 0x10034, // No space left on device. - ENOSYS = 0x10037, // Function not supported. - ENOTCONN = 0x10038, // The socket is not connected. - ENOTDIR = 0x10039, // Not a directory or a symbolic link to a directory. - ENOTEMPTY = 0x1003A, // Directory not empty. - ENOTRECOVERABLE = 0x1003B, // State not recoverable. - ENOTSOCK = 0x1003C, // Not a socket. - ENOTSUP = 0x1003D, // Not supported (same value as EOPNOTSUP). - ENOTTY = 0x1003E, // Inappropriate I/O control operation. - ENXIO = 0x1003F, // No such device or address. - EOVERFLOW = 0x10040, // Value too large to be stored in data type. - EOWNERDEAD = 0x10041, // Previous owner died. - EPERM = 0x10042, // Operation not permitted. - EPIPE = 0x10043, // Broken pipe. - EPROTO = 0x10044, // Protocol error. - EPROTONOSUPPORT = 0x10045, // Protocol not supported. - EPROTOTYPE = 0x10046, // Protocol wrong type for socket. - ERANGE = 0x10047, // Result too large. - EROFS = 0x10048, // Read-only file system. - ESPIPE = 0x10049, // Invalid seek. - ESRCH = 0x1004A, // No such process. - ESTALE = 0x1004B, // Reserved. - ETIMEDOUT = 0x1004D, // Connection timed out. - ETXTBSY = 0x1004E, // Text file busy. - EXDEV = 0x1004F, // Cross-device link. - ESOCKTNOSUPPORT = 0x1005E, // Socket type not supported. - EPFNOSUPPORT = 0x10060, // Protocol family not supported. - ESHUTDOWN = 0x1006C, // Socket shutdown. - EHOSTDOWN = 0x10070, // Host is down. - ENODATA = 0x10071, // No data available. - - // POSIX permits these to have the same value and we make them always equal so - // that CoreFX cannot introduce a dependency on distinguishing between them that - // would not work on all platforms. - EOPNOTSUPP = ENOTSUP, // Operation not supported on socket. - EWOULDBLOCK = EAGAIN, // Operation would block. - } - - - // Represents a platform-agnostic Error and underlying platform-specific errno - internal struct ErrorInfo - { - private Error _error; - private int _rawErrno; - - internal ErrorInfo (int errno) - { - _error = Interop.Sys.ConvertErrorPlatformToPal (errno); - _rawErrno = errno; - } - - internal ErrorInfo (Error error) - { - _error = error; - _rawErrno = -1; - } - - internal Error Error { - get { return _error; } - } - - internal int RawErrno { - get { return _rawErrno == -1 ? (_rawErrno = Interop.Sys.ConvertErrorPalToPlatform (_error)) : _rawErrno; } - } - - internal string GetErrorMessage () - { - return Interop.Sys.StrError (RawErrno); - } - - public override string ToString () - { - return string.Format ( - "RawErrno: {0} Error: {1} GetErrorMessage: {2}", // No localization required; text is member names used for debugging purposes - RawErrno, Error, GetErrorMessage ()); - } - } - - internal partial class Sys - { - internal static Error GetLastError () - { - return ConvertErrorPlatformToPal (Marshal.GetLastWin32Error ()); - } - - internal static ErrorInfo GetLastErrorInfo () - { - return new ErrorInfo (Marshal.GetLastWin32Error ()); - } - - internal static unsafe string StrError (int platformErrno) - { - int maxBufferLength = 1024; // should be long enough for most any UNIX error - byte* buffer = stackalloc byte [maxBufferLength]; - byte* message = StrErrorR (platformErrno, buffer, maxBufferLength); - - if (message == null) { - // This means the buffer was not large enough, but still contains - // as much of the error message as possible and is guaranteed to - // be null-terminated. We're not currently resizing/retrying because - // maxBufferLength is large enough in practice, but we could do - // so here in the future if necessary. - message = buffer; - } - - return Marshal.PtrToStringAnsi ((IntPtr)message); - } - - [DllImport (Libraries.SystemNative, EntryPoint = "SystemNative_ConvertErrorPlatformToPal")] - internal static extern Error ConvertErrorPlatformToPal (int platformErrno); - - [DllImport (Libraries.SystemNative, EntryPoint = "SystemNative_ConvertErrorPalToPlatform")] - internal static extern int ConvertErrorPalToPlatform (Error error); - - [DllImport (Libraries.SystemNative, EntryPoint = "SystemNative_StrErrorR")] - private static extern unsafe byte* StrErrorR (int platformErrno, byte* buffer, int bufferSize); - } - } - - // NOTE: extension method can't be nested inside Interop class. - internal static class InteropErrorExtensions - { - // Intended usage is e.g. Interop.Error.EFAIL.Info() for brevity - // vs. new Interop.ErrorInfo(Interop.Error.EFAIL) for synthesizing - // errors. Errors originated from the system should be obtained - // via GetLastErrorInfo(), not GetLastError().Info() as that will - // convert twice, which is not only inefficient but also lossy if - // we ever encounter a raw errno that no equivalent in the Error - // enum. - public static Interop.ErrorInfo Info (this Interop.Error error) - { - return new Interop.ErrorInfo (error); - } - } -} diff --git a/main/src/core/MonoDevelop.Core/MonoDevelop.FSW/OSX/Interop.EventStream.cs b/main/src/core/MonoDevelop.Core/MonoDevelop.FSW/OSX/Interop.EventStream.cs deleted file mode 100644 index 6adf8c19a5..0000000000 --- a/main/src/core/MonoDevelop.Core/MonoDevelop.FSW/OSX/Interop.EventStream.cs +++ /dev/null @@ -1,209 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System; -using System.Runtime.InteropServices; - -using Microsoft.Win32.SafeHandles; - -using CFStringRef = System.IntPtr; -using CFArrayRef = System.IntPtr; -using FSEventStreamRef = System.IntPtr; -using size_t = System.IntPtr; -using FSEventStreamEventId = System.UInt64; -using CFTimeInterval = System.Double; -using CFRunLoopRef = System.IntPtr; - -namespace MonoDevelop.FSW.OSX -{ - internal static partial class Interop - { - internal static partial class EventStream - { - /// <summary> - /// This constant specifies that we don't want historical file system events, only new ones - /// </summary> - internal const ulong kFSEventStreamEventIdSinceNow = 0xFFFFFFFFFFFFFFFF; - - /// <summary> - /// Flags that describe what happened in the event that was received. These come from the FSEvents.h header file in the CoreServices framework. - /// </summary> - [Flags] - internal enum FSEventStreamEventFlags : uint - { - /* flags when creating the stream. */ - kFSEventStreamEventFlagNone = 0x00000000, - kFSEventStreamEventFlagMustScanSubDirs = 0x00000001, - kFSEventStreamEventFlagUserDropped = 0x00000002, - kFSEventStreamEventFlagKernelDropped = 0x00000004, - kFSEventStreamEventFlagEventIdsWrapped = 0x00000008, - kFSEventStreamEventFlagHistoryDone = 0x00000010, - kFSEventStreamEventFlagRootChanged = 0x00000020, - kFSEventStreamEventFlagMount = 0x00000040, - kFSEventStreamEventFlagUnmount = 0x00000080, - /* These flags are only set if you specified the FileEvents */ - kFSEventStreamEventFlagItemCreated = 0x00000100, - kFSEventStreamEventFlagItemRemoved = 0x00000200, - kFSEventStreamEventFlagItemInodeMetaMod = 0x00000400, - kFSEventStreamEventFlagItemRenamed = 0x00000800, - kFSEventStreamEventFlagItemModified = 0x00001000, - kFSEventStreamEventFlagItemFinderInfoMod = 0x00002000, - kFSEventStreamEventFlagItemChangeOwner = 0x00004000, - kFSEventStreamEventFlagItemXattrMod = 0x00008000, - kFSEventStreamEventFlagItemIsFile = 0x00010000, - kFSEventStreamEventFlagItemIsDir = 0x00020000, - kFSEventStreamEventFlagItemIsSymlink = 0x00040000, - kFSEventStreamEventFlagOwnEvent = 0x00080000, - kFSEventStreamEventFlagItemIsHardlink = 0x00100000, - kFSEventStreamEventFlagItemIsLastHardlink = 0x00200000, - } - - /// <summary> - /// Flags that describe what kind of event stream should be created (and therefore what events should be - /// piped into this stream). These come from the FSEvents.h header file in the CoreServices framework. - /// </summary> - [Flags] - internal enum FSEventStreamCreateFlags : uint - { - kFSEventStreamCreateFlagNone = 0x00000000, - kFSEventStreamCreateFlagUseCFTypes = 0x00000001, - kFSEventStreamCreateFlagNoDefer = 0x00000002, - kFSEventStreamCreateFlagWatchRoot = 0x00000004, - kFSEventStreamCreateFlagIgnoreSelf = 0x00000008, - kFSEventStreamCreateFlagFileEvents = 0x00000010 - } - - /// <summary> - /// The EventStream callback that will be called for every event batch. - /// </summary> - /// <param name="streamReference">The stream that was created for this callback.</param> - /// <param name="clientCallBackInfo">A pointer to optional context info; otherwise, IntPtr.Zero.</param> - /// <param name="numEvents">The number of paths, events, and IDs. Path[2] corresponds to Event[2] and ID[2], etc.</param> - /// <param name="eventPaths">The paths that have changed somehow, according to their corresponding event.</param> - /// <param name="eventFlags">The events for the corresponding path.</param> - /// <param name="eventIds">The machine-and-disk-drive-unique Event ID for the specific event.</param> - [UnmanagedFunctionPointer (CallingConvention.Cdecl)] - internal delegate void FSEventStreamCallback ( - FSEventStreamRef streamReference, - IntPtr clientCallBackInfo, - size_t numEvents, - [MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 2)] - String[] eventPaths, - [MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 2)] - FSEventStreamEventFlags[] eventFlags, - [MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 2)] - FSEventStreamEventId[] eventIds); - - /// <summary> - /// Internal wrapper to create a new EventStream to listen to events from the core OS (such as File System events). - /// </summary> - /// <param name="allocator">Should be IntPtr.Zero</param> - /// <param name="cb">A callback instance that will be called for every event batch.</param> - /// <param name="context">Should be IntPtr.Zero</param> - /// <param name="pathsToWatch">A CFArray of the path(s) to watch for events.</param> - /// <param name="sinceWhen"> - /// The start point to receive events from. This can be to retrieve historical events or only new events. - /// To get historical events, pass in the corresponding ID of the event you want to start from. - /// To get only new events, pass in kFSEventStreamEventIdSinceNow. - /// </param> - /// <param name="latency">Coalescing period to wait before sending events.</param> - /// <param name="flags">Flags to say what kind of events should be sent through this stream.</param> - /// <returns>On success, returns a pointer to an FSEventStream object; otherwise, returns IntPtr.Zero</returns> - /// <remarks>For *nix systems, the CLR maps ANSI to UTF-8, so be explicit about that</remarks> - [DllImport (Interop.Libraries.CoreServicesLibrary, CharSet = CharSet.Ansi)] - private static extern SafeEventStreamHandle FSEventStreamCreate ( - IntPtr allocator, - FSEventStreamCallback cb, - IntPtr context, - SafeCreateHandle pathsToWatch, - FSEventStreamEventId sinceWhen, - CFTimeInterval latency, - FSEventStreamCreateFlags flags); - - /// <summary> - /// Creates a new EventStream to listen to events from the core OS (such as File System events). - /// </summary> - /// <param name="cb">A callback instance that will be called for every event batch.</param> - /// <param name="pathsToWatch">A CFArray of the path(s) to watch for events.</param> - /// <param name="sinceWhen"> - /// The start point to receive events from. This can be to retrieve historical events or only new events. - /// To get historical events, pass in the corresponding ID of the event you want to start from. - /// To get only new events, pass in kFSEventStreamEventIdSinceNow. - /// </param> - /// <param name="latency">Coalescing period to wait before sending events.</param> - /// <param name="flags">Flags to say what kind of events should be sent through this stream.</param> - /// <returns>On success, returns a valid SafeCreateHandle to an FSEventStream object; otherwise, returns an invalid SafeCreateHandle</returns> - internal static SafeEventStreamHandle FSEventStreamCreate ( - FSEventStreamCallback cb, - SafeCreateHandle pathsToWatch, - FSEventStreamEventId sinceWhen, - CFTimeInterval latency, - FSEventStreamCreateFlags flags) - { - return FSEventStreamCreate (IntPtr.Zero, cb, IntPtr.Zero, pathsToWatch, sinceWhen, latency, flags); - } - - /// <summary> - /// Attaches an EventStream to a RunLoop so events can be received. This should usually be the current thread's RunLoop. - /// </summary> - /// <param name="streamRef">The stream to attach to the RunLoop</param> - /// <param name="runLoop">The RunLoop to attach the stream to</param> - /// <param name="runLoopMode">The mode of the RunLoop; this should usually be kCFRunLoopDefaultMode. See the documentation for RunLoops for more info.</param> - [DllImport (Interop.Libraries.CoreServicesLibrary)] - internal static extern void FSEventStreamScheduleWithRunLoop ( - SafeEventStreamHandle streamRef, - CFRunLoopRef runLoop, - SafeCreateHandle runLoopMode); - - /// <summary> - /// Starts receiving events on the specified stream. - /// </summary> - /// <param name="streamRef">The stream to receive events on.</param> - /// <returns>Returns true if the stream was started; otherwise, returns false and no events will be received.</returns> - [DllImport (Interop.Libraries.CoreServicesLibrary)] - internal static extern bool FSEventStreamStart (SafeEventStreamHandle streamRef); - - /// <summary> - /// Stops receiving events on the specified stream. The stream can be restarted and not miss any events. - /// </summary> - /// <param name="streamRef">The stream to stop receiving events on.</param> - [DllImport (Interop.Libraries.CoreServicesLibrary)] - internal static extern void FSEventStreamStop (SafeEventStreamHandle streamRef); - - /// <summary> - /// Stops receiving events on the specified stream. The stream can be restarted and not miss any events. - /// </summary> - /// <param name="streamRef">The stream to stop receiving events on.</param> - [DllImport (Interop.Libraries.CoreServicesLibrary)] - internal static extern void FSEventStreamStop (IntPtr streamRef); - - /// <summary> - /// Invalidates an EventStream and removes it from any RunLoops. - /// </summary> - /// <param name="streamRef">The FSEventStream to invalidate</param> - /// <remarks>This can only be called after FSEventStreamScheduleWithRunLoop has be called</remarks> - [DllImport (Interop.Libraries.CoreServicesLibrary)] - internal static extern void FSEventStreamInvalidate (IntPtr streamRef); - - /// <summary> - /// Removes the event stream from the RunLoop. - /// </summary> - /// <param name="streamRef">The stream to remove from the RunLoop</param> - /// <param name="runLoop">The RunLoop to remove the stream from.</param> - /// <param name="runLoopMode">The mode of the RunLoop; this should usually be kCFRunLoopDefaultMode. See the documentation for RunLoops for more info.</param> - [DllImport (Interop.Libraries.CoreServicesLibrary)] - internal static extern void FSEventStreamUnscheduleFromRunLoop ( - SafeEventStreamHandle streamRef, - CFRunLoopRef runLoop, - SafeCreateHandle runLoopMode); - - /// <summary> - /// Releases a reference count on the specified EventStream and, if necessary, cleans the stream up. - /// </summary> - /// <param name="streamRef">The stream on which to decrement the reference count.</param> - [DllImport (Interop.Libraries.CoreServicesLibrary)] - internal static extern void FSEventStreamRelease (IntPtr streamRef); - } - } -} diff --git a/main/src/core/MonoDevelop.Core/MonoDevelop.FSW/OSX/Interop.IOError.cs b/main/src/core/MonoDevelop.Core/MonoDevelop.FSW/OSX/Interop.IOError.cs deleted file mode 100644 index 2ea4514e6c..0000000000 --- a/main/src/core/MonoDevelop.Core/MonoDevelop.FSW/OSX/Interop.IOError.cs +++ /dev/null @@ -1,164 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System; -using System.Diagnostics; -using System.IO; -using System.Runtime.InteropServices; -using Microsoft.Win32.SafeHandles; - -namespace MonoDevelop.FSW.OSX -{ - internal static partial class Interop - { - private static void ThrowExceptionForIoErrno (ErrorInfo errorInfo, string path, bool isDirectory, Func<ErrorInfo, ErrorInfo> errorRewriter) - { - Debug.Assert (errorInfo.Error != Error.SUCCESS); - Debug.Assert (errorInfo.Error != Error.EINTR, "EINTR errors should be handled by the native shim and never bubble up to managed code"); - - if (errorRewriter != null) { - errorInfo = errorRewriter (errorInfo); - } - - throw Interop.GetExceptionForIoErrno (errorInfo, path, isDirectory); - } - - internal static void CheckIo (Error error, string path = null, bool isDirectory = false, Func<ErrorInfo, ErrorInfo> errorRewriter = null) - { - if (error != Interop.Error.SUCCESS) { - ThrowExceptionForIoErrno (error.Info (), path, isDirectory, errorRewriter); - } - } - - /// <summary> - /// Validates the result of system call that returns greater than or equal to 0 on success - /// and less than 0 on failure, with errno set to the error code. - /// If the system call failed for any reason, an exception is thrown. Otherwise, the system call succeeded. - /// </summary> - /// <param name="result">The result of the system call.</param> - /// <param name="path">The path with which this error is associated. This may be null.</param> - /// <param name="isDirectory">true if the <paramref name="path"/> is known to be a directory; otherwise, false.</param> - /// <param name="errorRewriter">Optional function to change an error code prior to processing it.</param> - /// <returns> - /// On success, returns the non-negative result long that was validated. - /// </returns> - internal static long CheckIo (long result, string path = null, bool isDirectory = false, Func<ErrorInfo, ErrorInfo> errorRewriter = null) - { - if (result < 0) { - ThrowExceptionForIoErrno (Sys.GetLastErrorInfo (), path, isDirectory, errorRewriter); - } - - return result; - } - - /// <summary> - /// Validates the result of system call that returns greater than or equal to 0 on success - /// and less than 0 on failure, with errno set to the error code. - /// If the system call failed for any reason, an exception is thrown. Otherwise, the system call succeeded. - /// </summary> - /// <returns> - /// On success, returns the non-negative result int that was validated. - /// </returns> - internal static int CheckIo (int result, string path = null, bool isDirectory = false, Func<ErrorInfo, ErrorInfo> errorRewriter = null) - { - CheckIo ((long)result, path, isDirectory, errorRewriter); - - return result; - } - - /// <summary> - /// Validates the result of system call that returns greater than or equal to 0 on success - /// and less than 0 on failure, with errno set to the error code. - /// If the system call failed for any reason, an exception is thrown. Otherwise, the system call succeeded. - /// </summary> - /// <returns> - /// On success, returns the non-negative result IntPtr that was validated. - /// </returns> - internal static IntPtr CheckIo (IntPtr result, string path = null, bool isDirectory = false, Func<ErrorInfo, ErrorInfo> errorRewriter = null) - { - CheckIo ((long)result, path, isDirectory, errorRewriter); - - return result; - } - - /// <summary> - /// Validates the result of system call that returns greater than or equal to 0 on success - /// and less than 0 on failure, with errno set to the error code. - /// If the system call failed for any reason, an exception is thrown. Otherwise, the system call succeeded. - /// </summary> - /// <returns> - /// On success, returns the valid SafeFileHandle that was validated. - /// </returns> - internal static TSafeHandle CheckIo<TSafeHandle> (TSafeHandle handle, string path = null, bool isDirectory = false, Func<ErrorInfo, ErrorInfo> errorRewriter = null) - where TSafeHandle : SafeHandle - { - if (handle.IsInvalid) { - ThrowExceptionForIoErrno (Sys.GetLastErrorInfo (), path, isDirectory, errorRewriter); - } - - return handle; - } - - /// <summary> - /// Gets an Exception to represent the supplied error info. - /// </summary> - /// <param name="errorInfo">The error info</param> - /// <param name="path">The path with which this error is associated. This may be null.</param> - /// <param name="isDirectory">true if the <paramref name="path"/> is known to be a directory; otherwise, false.</param> - /// <returns></returns> - internal static Exception GetExceptionForIoErrno (ErrorInfo errorInfo, string path = null, bool isDirectory = false) - { - // Translate the errno into a known set of exception types. For cases where multiple errnos map - // to the same exception type, include an inner exception with the details. - switch (errorInfo.Error) { - case Error.ENOENT: - if (isDirectory) { - return !string.IsNullOrEmpty (path) ? - new DirectoryNotFoundException (SR.Format (SR.IO_PathNotFound_Path, path)) : - new DirectoryNotFoundException (SR.IO_PathNotFound_NoPathName); - } else { - return !string.IsNullOrEmpty (path) ? - new FileNotFoundException (SR.Format (SR.IO_FileNotFound_FileName, path), path) : - new FileNotFoundException (SR.IO_FileNotFound); - } - - case Error.EACCES: - case Error.EBADF: - case Error.EPERM: - Exception inner = GetIOException (errorInfo); - return !string.IsNullOrEmpty (path) ? - new UnauthorizedAccessException (SR.Format (SR.UnauthorizedAccess_IODenied_Path, path), inner) : - new UnauthorizedAccessException (SR.UnauthorizedAccess_IODenied_NoPathName, inner); - - case Error.ENAMETOOLONG: - return new PathTooLongException (SR.IO_PathTooLong); - - case Error.EWOULDBLOCK: - return !string.IsNullOrEmpty (path) ? - new IOException (SR.Format (SR.IO_SharingViolation_File, path), errorInfo.RawErrno) : - new IOException (SR.IO_SharingViolation_NoFileName, errorInfo.RawErrno); - - case Error.ECANCELED: - return new OperationCanceledException (); - - case Error.EFBIG: - return new ArgumentOutOfRangeException ("value", SR.ArgumentOutOfRange_FileLengthTooBig); - - case Error.EEXIST: - if (!string.IsNullOrEmpty (path)) { - return new IOException (SR.Format (SR.IO_FileExists_Name, path), errorInfo.RawErrno); - } - goto default; - - default: - return GetIOException (errorInfo); - } - } - - internal static Exception GetIOException (Interop.ErrorInfo errorInfo) - { - return new IOException (errorInfo.GetErrorMessage (), errorInfo.RawErrno); - } - } -} diff --git a/main/src/core/MonoDevelop.Core/MonoDevelop.FSW/OSX/Interop.Libraries.cs b/main/src/core/MonoDevelop.Core/MonoDevelop.FSW/OSX/Interop.Libraries.cs deleted file mode 100644 index 68d88a1739..0000000000 --- a/main/src/core/MonoDevelop.Core/MonoDevelop.FSW/OSX/Interop.Libraries.cs +++ /dev/null @@ -1,41 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -namespace MonoDevelop.FSW.OSX -{ - // OSX - internal static partial class Interop - { - internal static partial class Libraries - { - internal const string CoreFoundationLibrary = "/System/Library/Frameworks/CoreFoundation.framework/CoreFoundation"; - internal const string CoreServicesLibrary = "/System/Library/Frameworks/CoreServices.framework/CoreServices"; -#if false - internal const string libproc = "libproc"; - internal const string LibSystemCommonCrypto = "/usr/lib/system/libcommonCrypto"; - internal const string LibSystemKernel = "/usr/lib/system/libsystem_kernel"; - internal const string SystemConfigurationLibrary = "/System/Library/Frameworks/SystemConfiguration.framework/SystemConfiguration"; - internal const string AppleCryptoNative = "System.Security.Cryptography.Native.Apple"; -#endif - } - } - - // Unix - internal static partial class Interop - { - internal static partial class Libraries - { - // Shims - internal const string SystemNative = "libsystemnative.dylib"; -#if false - internal const string HttpNative = "System.Net.Http.Native"; - internal const string NetSecurityNative = "System.Net.Security.Native"; - internal const string CryptoNative = "System.Security.Cryptography.Native.OpenSsl"; - internal const string GlobalizationNative = "System.Globalization.Native"; - internal const string CompressionNative = "System.IO.Compression.Native"; -#endif - } - } - -} diff --git a/main/src/core/MonoDevelop.Core/MonoDevelop.FSW/OSX/Interop.PathConf.cs b/main/src/core/MonoDevelop.Core/MonoDevelop.FSW/OSX/Interop.PathConf.cs deleted file mode 100644 index 994859f832..0000000000 --- a/main/src/core/MonoDevelop.Core/MonoDevelop.FSW/OSX/Interop.PathConf.cs +++ /dev/null @@ -1,70 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System; -using System.Runtime.InteropServices; - -namespace MonoDevelop.FSW.OSX -{ - internal static partial class Interop - { - internal static partial class Sys - { - internal static int DEFAULT_PC_NAME_MAX = 255; - - internal enum PathConfName : int - { - PC_LINK_MAX = 1, - PC_MAX_CANON = 2, - PC_MAX_INPUT = 3, - PC_NAME_MAX = 4, - PC_PATH_MAX = 5, - PC_PIPE_BUF = 6, - PC_CHOWN_RESTRICTED = 7, - PC_NO_TRUNC = 8, - PC_VDISABLE = 9, - } - - /// <summary>The maximum path length for the system. -1 if it hasn't yet been initialized.</summary> - private static int s_maxPath = -1; - - /// <summary>The maximum name length for the system. -1 if it hasn't yet been initialized.</summary> - private static int s_maxName = -1; - - internal static int MaxPath { - get { - // Benign race condition on cached value - if (s_maxPath < 0) { - // GetMaximumPath returns a long from PathConf - // but our callers expect an int so we need to convert. - long temp = GetMaximumPath (); - if (temp > int.MaxValue) - s_maxPath = int.MaxValue; - else - s_maxPath = Convert.ToInt32 (temp); - } - return s_maxPath; - } - } - - internal static int MaxName { - get { - // Benign race condition on cached value - if (s_maxName < 0) { - int result = PathConf ("/", PathConfName.PC_NAME_MAX); - s_maxName = result >= 0 ? result : DEFAULT_PC_NAME_MAX; - } - - return s_maxName; - } - } - - [DllImport (Libraries.SystemNative, EntryPoint = "SystemNative_PathConf", SetLastError = true)] - private static extern int PathConf (string path, PathConfName name); - - [DllImport (Libraries.SystemNative, EntryPoint = "SystemNative_GetMaximumPath")] - private static extern long GetMaximumPath (); - } - } -} diff --git a/main/src/core/MonoDevelop.Core/MonoDevelop.FSW/OSX/Interop.RealPath.cs b/main/src/core/MonoDevelop.Core/MonoDevelop.FSW/OSX/Interop.RealPath.cs deleted file mode 100644 index 6f1b0bfdde..0000000000 --- a/main/src/core/MonoDevelop.Core/MonoDevelop.FSW/OSX/Interop.RealPath.cs +++ /dev/null @@ -1,23 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System.Runtime.InteropServices; - -namespace MonoDevelop.FSW.OSX -{ - internal static partial class Interop - { - internal static partial class Sys - { - /// <summary> - /// Takes a path containing relative subpaths or links and returns the absolute path. - /// This function works on both files and folders and returns a null-terminated string. - /// </summary> - /// <param name="path">The path to the file system object</param> - /// <returns>Returns the result string on success and null on failure</returns> - [DllImport (Libraries.SystemNative, EntryPoint = "SystemNative_RealPath", SetLastError = true)] - internal static extern string RealPath (string path); - } - } -} diff --git a/main/src/core/MonoDevelop.Core/MonoDevelop.FSW/OSX/Interop.RunLoop.cs b/main/src/core/MonoDevelop.Core/MonoDevelop.FSW/OSX/Interop.RunLoop.cs deleted file mode 100644 index e13f12ddfb..0000000000 --- a/main/src/core/MonoDevelop.Core/MonoDevelop.FSW/OSX/Interop.RunLoop.cs +++ /dev/null @@ -1,80 +0,0 @@ - -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System; -using System.Runtime.InteropServices; - -using Microsoft.Win32.SafeHandles; - -using CFRunLoopRef = System.IntPtr; -using CFRunLoopSourceRef = System.IntPtr; -using CFStringRef = System.IntPtr; - -namespace MonoDevelop.FSW.OSX -{ - internal static partial class Interop - { - internal static partial class RunLoop - { - /// <summary> - /// This constant specifies that we want to use the default Run mode for the thread's Run loop. - /// </summary> - /// <remarks> - /// For more information, see the Apple documentation: https://developer.apple.com/library/mac/documentation/CoreFoundation/Reference/CFRunLoopRef/index.html - /// </remarks> - internal static SafeCreateHandle kCFRunLoopDefaultMode = Interop.CoreFoundation.CFStringCreateWithCString ("kCFRunLoopDefaultMode"); - - /// <summary> - /// Starts the current thread's RunLoop. If the RunLoop is already running, creates a new, nested, RunLoop in the same stack. - /// </summary> - [DllImport (Interop.Libraries.CoreFoundationLibrary)] - internal extern static void CFRunLoopRun (); - - /// <summary> - /// Notifies a RunLoop to stop and return control to the execution context that called CFRunLoopRun - /// </summary> - /// <param name="rl">The RunLoop to notify to stop</param> - [DllImport (Interop.Libraries.CoreFoundationLibrary)] - internal extern static void CFRunLoopStop (CFRunLoopRef rl); - - /// <summary> - /// Retrieves the RunLoop associated with the current thread; all threads automatically have a RunLoop. - /// Follows the "Get Rule" where you do not own the object unless you CFRetain it; in which case, you must also CFRelease it as well. - /// </summary> - /// <returns>Returns a pointer to a CFRunLoop on success; otherwise, returns IntPtr.Zero</returns> - [DllImport (Interop.Libraries.CoreFoundationLibrary)] - internal static extern CFRunLoopRef CFRunLoopGetCurrent (); - - /// <summary> - /// Adds a CFRunLoopSource object to a run loop mode. - /// </summary> - /// <param name="rl">The run loop to modify.</param> - /// <param name="source">The run loop source to add. The source is retained by the run loop.</param> - /// <param name="mode">The run loop mode to which to add source.</param> - [DllImport (Interop.Libraries.CoreFoundationLibrary)] - internal static extern void CFRunLoopAddSource (CFRunLoopRef rl, CFRunLoopSourceRef source, CFStringRef mode); - - /// <summary> - /// Removes a CFRunLoopSource object from a run loop mode. - /// </summary> - /// <param name="rl">The run loop to modify.</param> - /// <param name="source">The run loop source to remove.</param> - /// <param name="mode">The run loop mode of rl from which to remove source.</param> - [DllImport (Interop.Libraries.CoreFoundationLibrary)] - internal static extern void CFRunLoopRemoveSource (CFRunLoopRef rl, CFRunLoopSourceRef source, CFStringRef mode); - - /// <summary> - /// Returns a bool that indicates whether the run loop is waiting for an event. - /// </summary> - /// <param name="rl">The run loop to examine.</param> - /// <returns>true if rl has no events to process and is blocking, - /// waiting for a source or timer to become ready to fire; - /// false if rl either is not running or is currently processing - /// a source, timer, or observer.</returns> - [DllImport (Interop.Libraries.CoreFoundationLibrary)] - internal static extern bool CFRunLoopIsWaiting (CFRunLoopRef rl); - } - } -} diff --git a/main/src/core/MonoDevelop.Core/MonoDevelop.FSW/OSX/Interop.Sync.cs b/main/src/core/MonoDevelop.Core/MonoDevelop.FSW/OSX/Interop.Sync.cs deleted file mode 100644 index dd8bfb35f9..0000000000 --- a/main/src/core/MonoDevelop.Core/MonoDevelop.FSW/OSX/Interop.Sync.cs +++ /dev/null @@ -1,21 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System; -using System.Runtime.InteropServices; - -namespace MonoDevelop.FSW.OSX -{ - internal static partial class Interop - { - internal static partial class Sys - { - /// <summary> - /// Forces a write of all modified I/O buffers to their storage mediums. - /// </summary> - [DllImport (Libraries.SystemNative, EntryPoint = "SystemNative_Sync")] - internal static extern void Sync (); - } - } -} diff --git a/main/src/core/MonoDevelop.Core/MonoDevelop.FSW/OSX/PathInternal.CaseSensitivity.cs b/main/src/core/MonoDevelop.Core/MonoDevelop.FSW/OSX/PathInternal.CaseSensitivity.cs deleted file mode 100644 index 631050677f..0000000000 --- a/main/src/core/MonoDevelop.Core/MonoDevelop.FSW/OSX/PathInternal.CaseSensitivity.cs +++ /dev/null @@ -1,53 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System; -using System.IO; -using System.Diagnostics; - -namespace MonoDevelop.FSW.OSX -{ - /// <summary>Contains internal path helpers that are shared between many projects.</summary> - internal static partial class PathInternal - { - private static readonly bool s_isCaseSensitive = GetIsCaseSensitive (); - - /// <summary>Returns a comparison that can be used to compare file and directory names for equality.</summary> - internal static StringComparison StringComparison { - get { - return s_isCaseSensitive ? - StringComparison.Ordinal : - StringComparison.OrdinalIgnoreCase; - } - } - - /// <summary>Gets whether the system is case-sensitive.</summary> - internal static bool IsCaseSensitive { get { return s_isCaseSensitive; } } - - /// <summary> - /// Determines whether the file system is case sensitive. - /// </summary> - /// <remarks> - /// Ideally we'd use something like pathconf with _PC_CASE_SENSITIVE, but that is non-portable, - /// not supported on Windows or Linux, etc. For now, this function creates a tmp file with capital letters - /// and then tests for its existence with lower-case letters. This could return invalid results in corner - /// cases where, for example, different file systems are mounted with differing sensitivities. - /// </remarks> - private static bool GetIsCaseSensitive () - { - try { - string pathWithUpperCase = Path.Combine (Path.GetTempPath (), "CASESENSITIVETEST" + Guid.NewGuid ().ToString ("N")); - using (new FileStream (pathWithUpperCase, FileMode.CreateNew, FileAccess.ReadWrite, FileShare.None, 0x1000, FileOptions.DeleteOnClose)) { - string lowerCased = pathWithUpperCase.ToLowerInvariant (); - return !File.Exists (lowerCased); - } - } catch (Exception exc) { - // In case something goes terribly wrong, we don't want to fail just because - // of a casing test, so we assume case-insensitive-but-preserving. - Debug.Fail ("Casing test failed: " + exc); - return false; - } - } - } -} diff --git a/main/src/core/MonoDevelop.Core/MonoDevelop.FSW/OSX/PathInternal.Unix.cs b/main/src/core/MonoDevelop.Core/MonoDevelop.FSW/OSX/PathInternal.Unix.cs deleted file mode 100644 index 229cf1c3b8..0000000000 --- a/main/src/core/MonoDevelop.Core/MonoDevelop.FSW/OSX/PathInternal.Unix.cs +++ /dev/null @@ -1,107 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System.Diagnostics; -using System.IO; -using System.Text; - -namespace MonoDevelop.FSW.OSX -{ - /// <summary>Contains internal path helpers that are shared between many projects.</summary> - internal static partial class PathInternal - { - // There is only one invalid path character in Unix - private const char InvalidPathChar = '\0'; - internal static char [] GetInvalidPathChars () => new char [] { InvalidPathChar }; - - internal static readonly int MaxComponentLength = Interop.Sys.MaxName; - - internal const string ParentDirectoryPrefix = @"../"; - - /// <summary>Returns a value indicating if the given path contains invalid characters.</summary> - internal static bool HasIllegalCharacters (string path) - { - Debug.Assert (path != null); - return path.IndexOf (InvalidPathChar) >= 0; - } - - internal static int GetRootLength (string path) - { - return path.Length > 0 && IsDirectorySeparator (path [0]) ? 1 : 0; - } - - internal static bool IsDirectorySeparator (char c) - { - // The alternate directory separator char is the same as the directory separator, - // so we only need to check one. - Debug.Assert (Path.DirectorySeparatorChar == Path.AltDirectorySeparatorChar); - return c == Path.DirectorySeparatorChar; - } - - /// <summary> - /// Returns true if the path is too long - /// </summary> - internal static bool IsPathTooLong (string fullPath) - { - return fullPath.Length >= Interop.Sys.MaxPath; - } - - /// <summary> - /// Returns true if the directory is too long - /// </summary> - internal static bool IsDirectoryTooLong (string fullPath) - { - return fullPath.Length >= Interop.Sys.MaxPath; - } - - /// <summary> - /// Normalize separators in the given path. Compresses forward slash runs. - /// </summary> - internal static string NormalizeDirectorySeparators (string path) - { - if (string.IsNullOrEmpty (path)) return path; - - // Make a pass to see if we need to normalize so we can potentially skip allocating - bool normalized = true; - - for (int i = 0; i < path.Length; i++) { - if (IsDirectorySeparator (path [i]) - && (i + 1 < path.Length && IsDirectorySeparator (path [i + 1]))) { - normalized = false; - break; - } - } - - if (normalized) return path; - - StringBuilder builder = new StringBuilder (path.Length); - - for (int i = 0; i < path.Length; i++) { - char current = path [i]; - - // Skip if we have another separator following - if (IsDirectorySeparator (current) - && (i + 1 < path.Length && IsDirectorySeparator (path [i + 1]))) - continue; - - builder.Append (current); - } - - return builder.ToString (); - } - - /// <summary> - /// Returns true if the character is a directory or volume separator. - /// </summary> - /// <param name="ch">The character to test.</param> - internal static bool IsDirectoryOrVolumeSeparator (char ch) - { - // The directory separator, volume separator, and the alternate directory - // separator should be the same on Unix, so we only need to check one. - Debug.Assert (Path.DirectorySeparatorChar == Path.AltDirectorySeparatorChar); - Debug.Assert (Path.DirectorySeparatorChar == Path.VolumeSeparatorChar); - return ch == Path.DirectorySeparatorChar; - } - } -} diff --git a/main/src/core/MonoDevelop.Core/MonoDevelop.FSW/OSX/PathInternal.cs b/main/src/core/MonoDevelop.Core/MonoDevelop.FSW/OSX/PathInternal.cs deleted file mode 100644 index 60a71e057a..0000000000 --- a/main/src/core/MonoDevelop.Core/MonoDevelop.FSW/OSX/PathInternal.cs +++ /dev/null @@ -1,187 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System; -using System.IO; -using System.Diagnostics; -using System.Text; - -namespace MonoDevelop.FSW.OSX -{ - /// <summary>Contains internal path helpers that are shared between many projects.</summary> - internal static partial class PathInternal - { - /// <summary> - /// Checks for invalid path characters in the given path. - /// </summary> - /// <exception cref="System.ArgumentNullException">Thrown if the path is null.</exception> - /// <exception cref="System.ArgumentException">Thrown if the path has invalid characters.</exception> - /// <param name="path">The path to check for invalid characters.</param> - internal static void CheckInvalidPathChars (string path) - { - if (path == null) - throw new ArgumentNullException (nameof (path)); - - if (HasIllegalCharacters (path)) - throw new ArgumentException (SR.Argument_InvalidPathChars, nameof (path)); - } - - - /// <summary> - /// Returns true if the given StringBuilder starts with the given value. - /// </summary> - /// <param name="value">The string to compare against the start of the StringBuilder.</param> - internal static bool StartsWithOrdinal (this StringBuilder builder, string value) - { - if (value == null || builder.Length < value.Length) - return false; - - for (int i = 0; i < value.Length; i++) { - if (builder [i] != value [i]) return false; - } - return true; - } - - /// <summary> - /// Returns true if the given string starts with the given value. - /// </summary> - /// <param name="value">The string to compare against the start of the source string.</param> - internal static bool StartsWithOrdinal (this string source, string value) - { - if (value == null || source.Length < value.Length) - return false; - - return source.StartsWith (value, StringComparison.Ordinal); - } - - /// <summary> - /// Trims the specified characters from the end of the StringBuilder. - /// </summary> - internal static StringBuilder TrimEnd (this StringBuilder builder, params char [] trimChars) - { - if (trimChars == null || trimChars.Length == 0) - return builder; - - int end = builder.Length - 1; - - for (; end >= 0; end--) { - int i = 0; - char ch = builder [end]; - for (; i < trimChars.Length; i++) { - if (trimChars [i] == ch) break; - } - if (i == trimChars.Length) { - // Not a trim char - break; - } - } - - builder.Length = end + 1; - return builder; - } - - /// <summary> - /// Returns the start index of the filename - /// in the given path, or 0 if no directory - /// or volume separator is found. - /// </summary> - /// <param name="path">The path in which to find the index of the filename.</param> - /// <remarks> - /// This method returns path.Length for - /// inputs like "/usr/foo/" on Unix. As such, - /// it is not safe for being used to index - /// the string without additional verification. - /// </remarks> - internal static int FindFileNameIndex (string path) - { - Debug.Assert (path != null); - CheckInvalidPathChars (path); - - for (int i = path.Length - 1; i >= 0; i--) { - char ch = path [i]; - if (IsDirectoryOrVolumeSeparator (ch)) - return i + 1; - } - - return 0; // the whole path is the filename - } - - /// <summary> - /// Returns true if the path ends in a directory separator. - /// </summary> - internal static bool EndsInDirectorySeparator (string path) => - !string.IsNullOrEmpty (path) && IsDirectorySeparator (path [path.Length - 1]); - - /// <summary> - /// Get the common path length from the start of the string. - /// </summary> - internal static int GetCommonPathLength (string first, string second, bool ignoreCase) - { - int commonChars = EqualStartingCharacterCount (first, second, ignoreCase: ignoreCase); - - // If nothing matches - if (commonChars == 0) - return commonChars; - - // Or we're a full string and equal length or match to a separator - if (commonChars == first.Length - && (commonChars == second.Length || IsDirectorySeparator (second [commonChars]))) - return commonChars; - - if (commonChars == second.Length && IsDirectorySeparator (first [commonChars])) - return commonChars; - - // It's possible we matched somewhere in the middle of a segment e.g. C:\Foodie and C:\Foobar. - while (commonChars > 0 && !IsDirectorySeparator (first [commonChars - 1])) - commonChars--; - - return commonChars; - } - - /// <summary> - /// Gets the count of common characters from the left optionally ignoring case - /// </summary> - internal static unsafe int EqualStartingCharacterCount (string first, string second, bool ignoreCase) - { - if (string.IsNullOrEmpty (first) || string.IsNullOrEmpty (second)) return 0; - - int commonChars = 0; - - fixed (char* f = first) - fixed (char* s = second) { - char* l = f; - char* r = s; - char* leftEnd = l + first.Length; - char* rightEnd = r + second.Length; - - while (l != leftEnd && r != rightEnd - && (*l == *r || (ignoreCase && char.ToUpperInvariant ((*l)) == char.ToUpperInvariant ((*r))))) { - commonChars++; - l++; - r++; - } - } - - return commonChars; - } - - /// <summary> - /// Returns true if the two paths have the same root - /// </summary> - internal static bool AreRootsEqual (string first, string second, StringComparison comparisonType) - { - int firstRootLength = GetRootLength (first); - int secondRootLength = GetRootLength (second); - - return firstRootLength == secondRootLength - && string.Compare ( - strA: first, - indexA: 0, - strB: second, - indexB: 0, - length: firstRootLength, - comparisonType: comparisonType) == 0; - } - } -} diff --git a/main/src/core/MonoDevelop.Core/MonoDevelop.FSW/OSX/PatternMatcher.cs b/main/src/core/MonoDevelop.Core/MonoDevelop.FSW/OSX/PatternMatcher.cs deleted file mode 100644 index 32ce8e3024..0000000000 --- a/main/src/core/MonoDevelop.Core/MonoDevelop.FSW/OSX/PatternMatcher.cs +++ /dev/null @@ -1,473 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System; - -namespace MonoDevelop.FSW.OSX -{ - internal static class PatternMatcher - { - /// <devdoc> - /// Private constants (directly from C header files) - /// </devdoc> - private const int MATCHES_ARRAY_SIZE = 16; - private const char ANSI_DOS_STAR = '>'; - private const char ANSI_DOS_QM = '<'; - private const char DOS_DOT = '"'; - - /// <devdoc> - /// Tells whether a given name matches the expression given with a strict (i.e. UNIX like) - /// semantics. This code is a port of unmanaged code. Original code comment follows: - /// - /// Routine Description: - /// - /// This routine compares a Dbcs name and an expression and tells the caller - /// if the name is in the language defined by the expression. The input name - /// cannot contain wildcards, while the expression may contain wildcards. - /// - /// Expression wild cards are evaluated as shown in the nondeterministic - /// finite automatons below. Note that ~* and ~? are DOS_STAR and DOS_QM. - /// - /// - /// ~* is DOS_STAR, ~? is DOS_QM, and ~. is DOS_DOT - /// - /// - /// S - /// <-----< - /// X | | e Y - /// X * Y == (0)----->-(1)->-----(2)-----(3) - /// - /// - /// S-. - /// <-----< - /// X | | e Y - /// X ~* Y == (0)----->-(1)->-----(2)-----(3) - /// - /// - /// - /// X S S Y - /// X ?? Y == (0)---(1)---(2)---(3)---(4) - /// - /// - /// - /// X . . Y - /// X ~.~. Y == (0)---(1)----(2)------(3)---(4) - /// | |________| - /// | ^ | - /// |_______________| - /// ^EOF or .^ - /// - /// - /// X S-. S-. Y - /// X ~?~? Y == (0)---(1)-----(2)-----(3)---(4) - /// | |________| - /// | ^ | - /// |_______________| - /// ^EOF or .^ - /// - /// - /// - /// where S is any single character - /// - /// S-. is any single character except the final . - /// - /// e is a null character transition - /// - /// EOF is the end of the name string - /// - /// In words: - /// - /// * matches 0 or more characters. - /// - /// ? matches exactly 1 character. - /// - /// DOS_STAR matches 0 or more characters until encountering and matching - /// the final . in the name. - /// - /// DOS_QM matches any single character, or upon encountering a period or - /// end of name string, advances the expression to the end of the - /// set of contiguous DOS_QMs. - /// - /// DOS_DOT matches either a . or zero characters beyond name string. - /// - /// Arguments: - /// - /// Expression - Supplies the input expression to check against - /// - /// Name - Supplies the input name to check for. - /// - /// Return Value: - /// - /// BOOLEAN - TRUE if Name is an element in the set of strings denoted - /// by the input Expression and FALSE otherwise. - /// - /// </devdoc> - public static bool StrictMatchPattern (string expression, string name) - { - // - // The idea behind the algorithm is pretty simple. We keep track of - // all possible locations in the regular expression that are matching - // the name. If when the name has been exhausted one of the locations - // in the expression is also just exhausted, the name is in the language - // defined by the regular expression. - // - - if (name == null || name.Length == 0 || expression == null || expression.Length == 0) { - return false; - } - - // - // Special case by far the most common wild card search of * or *.* - // - - if (expression.Equals ("*") || expression.Equals ("*.*")) { - return true; - } - - // If this class is ever exposed for generic use, - // we need to make sure that name doesn't contain wildcards. Currently - // the only component that calls this method is FileSystemWatcher and - // it will never pass a name that contains a wildcard. - - - // - // Also special case expressions of the form *X. With this and the prior - // case we have covered virtually all normal queries. - // - if (expression [0] == '*' && expression.IndexOf ('*', 1) == -1) { - int rightLength = expression.Length - 1; - // if name is shorter that the stuff to the right of * in expression, we don't - // need to do the string compare, otherwise we compare rightlength characters - // and the end of both strings. - if (name.Length >= rightLength && - string.Compare (expression, 1, name, name.Length - rightLength, rightLength, PathInternal.StringComparison) == 0) { - return true; - } - } - - // - // Walk through the name string, picking off characters. We go one - // character beyond the end because some wild cards are able to match - // zero characters beyond the end of the string. - // - // With each new name character we determine a new set of states that - // match the name so far. We use two arrays that we swap back and forth - // for this purpose. One array lists the possible expression states for - // all name characters up to but not including the current one, and other - // array is used to build up the list of states considering the current - // name character as well. The arrays are then switched and the process - // repeated. - // - // There is not a one-to-one correspondence between state number and - // offset into the expression. This is evident from the NFAs in the - // initial comment to this function. State numbering is not continuous. - // This allows a simple conversion between state number and expression - // offset. Each character in the expression can represent one or two - // states. * and DOS_STAR generate two states: ExprOffset*2 and - // ExprOffset*2 + 1. All other expression characters can produce only - // a single state. Thus ExprOffset = State/2. - // - // - // Here is a short description of the variables involved: - // - // NameOffset - The offset of the current name char being processed. - // - // ExprOffset - The offset of the current expression char being processed. - // - // SrcCount - Prior match being investigated with current name char - // - // DestCount - Next location to put a matching assuming current name char - // - // NameFinished - Allows one more iteration through the Matches array - // after the name is exhausted (to come *s for example) - // - // PreviousDestCount - This is used to prevent entry duplication, see comment - // - // PreviousMatches - Holds the previous set of matches (the Src array) - // - // CurrentMatches - Holds the current set of matches (the Dest array) - // - // AuxBuffer, LocalBuffer - the storage for the Matches arrays - // - - // - // Set up the initial variables - // - int nameOffset; - int exprOffset; - int length; - - int srcCount; - int destCount; - int previousDestCount; - int matchesCount; - - char nameChar = '\0'; - char exprChar = '\0'; - - int [] previousMatches = new int [MATCHES_ARRAY_SIZE]; - int [] currentMatches = new int [MATCHES_ARRAY_SIZE]; - - int maxState; - int currentState; - - bool nameFinished = false; - - previousMatches [0] = 0; - matchesCount = 1; - - nameOffset = 0; - maxState = expression.Length * 2; - - while (!nameFinished) { - if (nameOffset < name.Length) { - nameChar = name [nameOffset]; - nameOffset++; - } else { - nameFinished = true; - - // - // if we have already exhausted the expression, C#. Don't - // continue. - // - if (previousMatches [matchesCount - 1] == maxState) { - break; - } - } - - // - // Now, for each of the previous stored expression matches, see what - // we can do with this name character. - // - srcCount = 0; - destCount = 0; - previousDestCount = 0; - - while (srcCount < matchesCount) { - // - // We have to carry on our expression analysis as far as possible - // for each character of name, so we loop here until the - // expression stops matching. A clue here is that expression - // cases that can match zero or more characters end with a - // continue, while those that can accept only a single character - // end with a break. - // - exprOffset = ((previousMatches [srcCount++] + 1) / 2); - length = 0; - - while (true) { - if (exprOffset == expression.Length) { - break; - } - - // - // The first time through the loop we don't want - // to increment ExprOffset. - // - - exprOffset += length; - - currentState = exprOffset * 2; - - if (exprOffset == expression.Length) { - currentMatches [destCount++] = maxState; - break; - } - - exprChar = expression [exprOffset]; - length = 1; - - // - // We may be about to exhaust the local - // space for ExpressionMatches[][], so we have to allocate - // some pool if this is the case. - // - - if (destCount >= MATCHES_ARRAY_SIZE - 2) { - int newSize = currentMatches.Length * 2; - int [] tmp = new int [newSize]; - Array.Copy (currentMatches, 0, tmp, 0, currentMatches.Length); - currentMatches = tmp; - - tmp = new int [newSize]; - Array.Copy (previousMatches, 0, tmp, 0, previousMatches.Length); - previousMatches = tmp; - } - - // - // * matches any character zero or more times. - // - - if (exprChar == '*') { - currentMatches [destCount++] = currentState; - currentMatches [destCount++] = (currentState + 1); - continue; - } - - // - // DOS_STAR matches any character except . zero or more times. - // - - if (exprChar == ANSI_DOS_STAR) { - bool iCanEatADot = false; - - // - // If we are at a period, determine if we are allowed to - // consume it, i.e. make sure it is not the last one. - // - if (!nameFinished && (nameChar == '.')) { - char tmpChar; - int offset; - - int nameLength = name.Length; - for (offset = nameOffset; offset < nameLength; offset++) { - tmpChar = name [offset]; - length = 1; - - if (tmpChar == '.') { - iCanEatADot = true; - break; - } - } - } - - if (nameFinished || (nameChar != '.') || iCanEatADot) { - currentMatches [destCount++] = currentState; - currentMatches [destCount++] = (currentState + 1); - continue; - } else { - // - // We are at a period. We can only match zero - // characters (i.e. the epsilon transition). - // - currentMatches [destCount++] = (currentState + 1); - continue; - } - } - - // - // The following expression characters all match by consuming - // a character, thus force the expression, and thus state - // forward. - // - currentState += length * 2; - - // - // DOS_QM is the most complicated. If the name is finished, - // we can match zero characters. If this name is a '.', we - // don't match, but look at the next expression. Otherwise - // we match a single character. - // - if (exprChar == ANSI_DOS_QM) { - if (nameFinished || (nameChar == '.')) { - continue; - } - - currentMatches [destCount++] = currentState; - break; - } - - // - // A DOS_DOT can match either a period, or zero characters - // beyond the end of name. - // - if (exprChar == DOS_DOT) { - if (nameFinished) { - continue; - } - - if (nameChar == '.') { - currentMatches [destCount++] = currentState; - break; - } - } - - // - // From this point on a name character is required to even - // continue, let alone make a match. - // - if (nameFinished) { - break; - } - - // - // If this expression was a '?' we can match it once. - // - if (exprChar == '?') { - currentMatches [destCount++] = currentState; - break; - } - - // - // Finally, check if the expression char matches the name char - // - - if (PathInternal.IsCaseSensitive ? - (exprChar == nameChar) : - (char.ToUpperInvariant (exprChar) == char.ToUpperInvariant (nameChar))) { - currentMatches [destCount++] = currentState; - break; - } - - // - // The expression didn't match so go look at the next - // previous match. - // - - break; - } - - - // - // Prevent duplication in the destination array. - // - // Each of the arrays is monotonically increasing and non- - // duplicating, thus we skip over any source element in the src - // array if we just added the same element to the destination - // array. This guarantees non-duplication in the dest. array. - // - - if ((srcCount < matchesCount) && (previousDestCount < destCount)) { - while (previousDestCount < destCount) { - int previousLength = previousMatches.Length; - while ((srcCount < previousLength) && (previousMatches [srcCount] < currentMatches [previousDestCount])) { - srcCount += 1; - } - previousDestCount += 1; - } - } - } - - // - // If we found no matches in the just finished iteration, it's time - // to bail. - // - - if (destCount == 0) { - return false; - } - - // - // Swap the meaning the two arrays - // - - { - int [] tmp; - - tmp = previousMatches; - - previousMatches = currentMatches; - - currentMatches = tmp; - } - - matchesCount = destCount; - } - - currentState = previousMatches [matchesCount - 1]; - - return currentState == maxState; - } - } -} diff --git a/main/src/core/MonoDevelop.Core/MonoDevelop.FSW/OSX/SR.cs b/main/src/core/MonoDevelop.Core/MonoDevelop.FSW/OSX/SR.cs deleted file mode 100644 index 2f8b13d9eb..0000000000 --- a/main/src/core/MonoDevelop.Core/MonoDevelop.FSW/OSX/SR.cs +++ /dev/null @@ -1,100 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System; -using System.Resources; -using System.Runtime.CompilerServices; - -namespace MonoDevelop.FSW.OSX -{ - internal partial class SR - { -#if false - private static ResourceManager s_resourceManager; - - private static ResourceManager ResourceManager - => s_resourceManager ?? (s_resourceManager = new ResourceManager (ResourceType)); -#endif - - // This method is used to decide if we need to append the exception message parameters to the message when calling SR.Format. - // by default it returns false. - [MethodImpl (MethodImplOptions.NoInlining)] - private static bool UsingResourceKeys () - { - return false; - } - -#if false - internal static string GetResourceString (string resourceKey, string defaultString) - { - string resourceString = null; - try { resourceString = ResourceManager.GetString (resourceKey); } catch (MissingManifestResourceException) { } - - if (defaultString != null && resourceKey.Equals (resourceString, StringComparison.Ordinal)) { - return defaultString; - } - - return resourceString; - } -#endif - - internal static string Format (string resourceFormat, params object [] args) - { - if (args != null) { - if (UsingResourceKeys ()) { - return resourceFormat + string.Join (", ", args); - } - - return string.Format (resourceFormat, args); - } - - return resourceFormat; - } - - internal static string Format (string resourceFormat, object p1) - { - if (UsingResourceKeys ()) { - return string.Join (", ", resourceFormat, p1); - } - - return string.Format (resourceFormat, p1); - } - - internal static string Format (string resourceFormat, object p1, object p2) - { - if (UsingResourceKeys ()) { - return string.Join (", ", resourceFormat, p1, p2); - } - - return string.Format (resourceFormat, p1, p2); - } - - internal static string Format (string resourceFormat, object p1, object p2, object p3) - { - if (UsingResourceKeys ()) { - return string.Join (", ", resourceFormat, p1, p2, p3); - } - - return string.Format (resourceFormat, p1, p2, p3); - } - - internal static readonly string Argument_InvalidPathChars = "Illegal characters in path."; - internal static readonly string ArgumentOutOfRange_FileLengthTooBig = "Specified file length was too large for the file system."; - internal static readonly string BufferSizeTooLarge = "The specified buffer size is too large. FileSystemWatcher cannot allocate {0} bytes for the internal buffer."; - internal static readonly string EventStream_FailedToStart = "Failed to start the EventStream"; - internal static readonly string FSW_BufferOverflow = "Too many changes at once in directory:{0}."; - internal static readonly string InvalidDirName = "The directory name {0} is invalid."; - internal static readonly string InvalidEnumArgument = "The value of argument '{0}' ({1}) is invalid for Enum type '{2}'."; - internal static readonly string IO_FileExists_Name = "The file '{0}' already exists."; - internal static readonly string IO_FileNotFound = "Unable to find the specified file."; - internal static readonly string IO_FileNotFound_FileName = "Could not find file '{0}'."; - internal static readonly string IO_PathNotFound_NoPathName = "Could not find a part of the path."; - internal static readonly string IO_PathNotFound_Path = "Could not find a part of the path '{0}'."; - internal static readonly string IO_PathTooLong = "The specified file name or path is too long, or a component of the specified path is too long."; - internal static readonly string IO_SharingViolation_File = "The process cannot access the file '{0}' because it is being used by another process."; - internal static readonly string IO_SharingViolation_NoFileName = "The process cannot access the file because it is being used by another process."; - internal static readonly string UnauthorizedAccess_IODenied_NoPathName = "Access to the path is denied."; - internal static readonly string UnauthorizedAccess_IODenied_Path = "Access to the path '{0}' is denied."; - } -} diff --git a/main/src/core/MonoDevelop.Core/MonoDevelop.FSW/OSX/SafeCreateHandle.OSX.cs b/main/src/core/MonoDevelop.Core/MonoDevelop.FSW/OSX/SafeCreateHandle.OSX.cs deleted file mode 100644 index fc6c4fa90c..0000000000 --- a/main/src/core/MonoDevelop.Core/MonoDevelop.FSW/OSX/SafeCreateHandle.OSX.cs +++ /dev/null @@ -1,41 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System; -using System.Diagnostics; -using System.Runtime.InteropServices; - -namespace MonoDevelop.FSW.OSX -{ - /// <summary> - /// This class is a wrapper around the Create pattern in OS X where - /// if a Create* function is called, the caller must also CFRelease - /// on the same pointer in order to correctly free the memory. - /// </summary> - [System.Security.SecurityCritical] - internal sealed partial class SafeCreateHandle : SafeHandle - { - internal SafeCreateHandle () : base (IntPtr.Zero, true) { } - - internal SafeCreateHandle (IntPtr ptr) : base (IntPtr.Zero, true) - { - this.SetHandle (ptr); - } - - [System.Security.SecurityCritical] - protected override bool ReleaseHandle () - { - Interop.CoreFoundation.CFRelease (handle); - - return true; - } - - public override bool IsInvalid { - [System.Security.SecurityCritical] - get { - return handle == IntPtr.Zero; - } - } - } -} diff --git a/main/src/core/MonoDevelop.Core/MonoDevelop.FSW/OSX/SafeEventStreamHandle.OSX.cs b/main/src/core/MonoDevelop.Core/MonoDevelop.FSW/OSX/SafeEventStreamHandle.OSX.cs deleted file mode 100644 index f8e13691cd..0000000000 --- a/main/src/core/MonoDevelop.Core/MonoDevelop.FSW/OSX/SafeEventStreamHandle.OSX.cs +++ /dev/null @@ -1,44 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System; -using System.Diagnostics; -using System.Runtime.InteropServices; - -namespace MonoDevelop.FSW.OSX -{ - /// <summary> - /// This class is a wrapper around the EventStream and Create pattern. - /// Usually, the Create pattern has the caller call Create* to allocate - /// and CFRelease to free; however, FSEventStream has it's own release - /// function, so we need to extend the pattern to account for that. - /// </summary> - [System.Security.SecurityCritical] - internal sealed partial class SafeEventStreamHandle : SafeHandle - { - internal SafeEventStreamHandle () : base (IntPtr.Zero, true) { } - - internal SafeEventStreamHandle (IntPtr ptr) : base (IntPtr.Zero, true) - { - this.SetHandle (ptr); - } - - [System.Security.SecurityCritical] - protected override bool ReleaseHandle () - { - Interop.EventStream.FSEventStreamStop (handle); - Interop.EventStream.FSEventStreamInvalidate (handle); - Interop.EventStream.FSEventStreamRelease (handle); - - return true; - } - - public override bool IsInvalid { - [System.Security.SecurityCritical] - get { - return handle == IntPtr.Zero; - } - } - } -} diff --git a/main/src/core/MonoDevelop.Core/MonoDevelop.Projects.MSBuild/MSBuildFileFormat.cs b/main/src/core/MonoDevelop.Core/MonoDevelop.Projects.MSBuild/MSBuildFileFormat.cs index e35886fdfe..57f4a1f42b 100644 --- a/main/src/core/MonoDevelop.Core/MonoDevelop.Projects.MSBuild/MSBuildFileFormat.cs +++ b/main/src/core/MonoDevelop.Core/MonoDevelop.Projects.MSBuild/MSBuildFileFormat.cs @@ -37,7 +37,7 @@ using System.Linq; namespace MonoDevelop.Projects.MSBuild { - public abstract class MSBuildFileFormat + public abstract class MSBuildFileFormat : IComparable<MSBuildFileFormat>, IEquatable<MSBuildFileFormat> { readonly SlnFileFormat slnFileFormat; @@ -51,9 +51,6 @@ namespace MonoDevelop.Projects.MSBuild public static readonly MSBuildFileFormat VS2010 = new MSBuildFileFormatVS10 (); public static readonly MSBuildFileFormat VS2012 = new MSBuildFileFormatVS12 (); - [Obsolete("This is the same as VS2012")] - public static readonly MSBuildFileFormat VS2017 = VS2012; - public static IEnumerable<MSBuildFileFormat> GetSupportedFormats () { yield return VS2012; @@ -68,12 +65,6 @@ namespace MonoDevelop.Projects.MSBuild } public static MSBuildFileFormat DefaultFormat => VS2012; - - [Obsolete ("Use ProductDescription or ID")] - public string Name => "MSBuild"; - - [Obsolete] - public abstract Version Version { get; } internal SlnFileFormat SlnFileFormat { get { return slnFileFormat; } @@ -229,17 +220,41 @@ namespace MonoDevelop.Projects.MSBuild } return string.Empty; } - + public abstract string Id { get; } + + #region IComparable<MSBuildFileFormat> implementation and overloads + + public override bool Equals (object obj) => obj is MSBuildFileFormat other && Equals (other); + public bool Equals (MSBuildFileFormat other) => other != null && Id == other.Id; + public override int GetHashCode () => Id.GetHashCode (); + + public int CompareTo (MSBuildFileFormat other) => Version.Parse (SlnVersion).CompareTo (Version.Parse (other.SlnVersion)); + + public static bool operator == (MSBuildFileFormat a, MSBuildFileFormat b) + { + if (ReferenceEquals (a, b)) + return true; + + if (a is null) + return b is null; + + return a.Equals (b); + } + + public static bool operator != (MSBuildFileFormat a, MSBuildFileFormat b) => !(a == b); + public static bool operator < (MSBuildFileFormat a, MSBuildFileFormat b) => a.CompareTo (b) < 0; + public static bool operator > (MSBuildFileFormat a, MSBuildFileFormat b) => a.CompareTo (b) > 0; + public static bool operator <= (MSBuildFileFormat a, MSBuildFileFormat b) => a.CompareTo (b) <= 0; + public static bool operator >= (MSBuildFileFormat a, MSBuildFileFormat b) => a.CompareTo (b) >= 0; + + #endregion } class MSBuildFileFormatVS05 : MSBuildFileFormat { public override string Id => "MSBuild05"; - [Obsolete("Unused")] - public override Version Version => new Version ("2005"); - public override string DefaultProductVersion => "8.0.50727"; public override string DefaultToolsVersion => "2.0"; public override string DefaultSchemaVersion => "2.0"; @@ -255,9 +270,6 @@ namespace MonoDevelop.Projects.MSBuild { public override string Id => "MSBuild08"; - [Obsolete ("Unused")] - public override Version Version => new Version ("2008"); - public override string DefaultProductVersion => "9.0.21022"; public override string DefaultToolsVersion => "3.5"; public override string DefaultSchemaVersion => "2.0"; @@ -279,9 +291,6 @@ namespace MonoDevelop.Projects.MSBuild { public override string Id => "MSBuild10"; - [Obsolete ("Unused")] - public override Version Version => new Version ("2010"); - public override string DefaultProductVersion => "8.0.30703"; public override string DefaultSchemaVersion => "2.0"; public override string DefaultToolsVersion => "4.0"; @@ -294,9 +303,6 @@ namespace MonoDevelop.Projects.MSBuild { public override string Id => "MSBuild12"; - [Obsolete ("Unused")] - public override Version Version => new Version ("2012"); - // This is mostly irrelevant, the builder always uses the latest // tools version. It's only used for new projects created with // the old project template engine. diff --git a/main/src/core/MonoDevelop.Core/MonoDevelop.Projects.MSBuild/MSBuildProcessService.cs b/main/src/core/MonoDevelop.Core/MonoDevelop.Projects.MSBuild/MSBuildProcessService.cs index 2062fae1a8..40cf37edf2 100644 --- a/main/src/core/MonoDevelop.Core/MonoDevelop.Projects.MSBuild/MSBuildProcessService.cs +++ b/main/src/core/MonoDevelop.Core/MonoDevelop.Projects.MSBuild/MSBuildProcessService.cs @@ -63,9 +63,19 @@ namespace MonoDevelop.Projects.MSBuild return MSBuildProjectService.GetMSBuildBinPath (Runtime.SystemAssemblyService.CurrentRuntime); } + static FilePath GetMSBuildBinDirectory (TargetRuntime runtime) + { + return MSBuildProjectService.GetMSBuildBinPath (runtime); + } + static FilePath GetMSBuildBinPath () { - FilePath binDirectory = GetMSBuildBinDirectory (); + return GetMSBuildBinPath (Runtime.SystemAssemblyService.CurrentRuntime); + } + + internal static FilePath GetMSBuildBinPath (TargetRuntime runtime) + { + FilePath binDirectory = GetMSBuildBinDirectory (runtime); FilePath binPath = binDirectory.Combine ("MSBuild.dll"); if (File.Exists (binPath)) { return binPath; diff --git a/main/src/core/MonoDevelop.Core/MonoDevelop.Projects.MSBuild/RemoteBuildEngineManager.cs b/main/src/core/MonoDevelop.Core/MonoDevelop.Projects.MSBuild/RemoteBuildEngineManager.cs index ec0a17aa31..d9d2e29b79 100644 --- a/main/src/core/MonoDevelop.Core/MonoDevelop.Projects.MSBuild/RemoteBuildEngineManager.cs +++ b/main/src/core/MonoDevelop.Core/MonoDevelop.Projects.MSBuild/RemoteBuildEngineManager.cs @@ -266,10 +266,8 @@ namespace MonoDevelop.Projects.MSBuild builders.Add (builderKey, builder); builder.ReferenceCount = 0; builder.BuildSessionId = buildSessionId; - builder.Disconnected += async delegate { - using (await buildersLock.EnterAsync ().ConfigureAwait (false)) - builders.Remove (builder); - }; + + builder.Disconnected += OnBuilderDisconnected; if (setBusy) builder.SetBusy (); if (buildSessionId != null) { @@ -280,6 +278,13 @@ namespace MonoDevelop.Projects.MSBuild }); } + // PERF: Avoid making this an instance method, as it will cause delegates to be retained. + static async Task OnBuilderDisconnected (object sender, EventArgs args) + { + var disconnectedBuilder = (RemoteBuildEngine)sender; + using (await buildersLock.EnterAsync ().ConfigureAwait (false)) + builders.Remove (disconnectedBuilder); + } /// <summary> /// Unloads a project from all engines diff --git a/main/src/core/MonoDevelop.Core/MonoDevelop.Projects.MSBuild/SdkResolution.cs b/main/src/core/MonoDevelop.Core/MonoDevelop.Projects.MSBuild/SdkResolution.cs index 90092361ef..e4808bd77c 100644 --- a/main/src/core/MonoDevelop.Core/MonoDevelop.Projects.MSBuild/SdkResolution.cs +++ b/main/src/core/MonoDevelop.Core/MonoDevelop.Projects.MSBuild/SdkResolution.cs @@ -3,13 +3,13 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.IO; using System.Linq; using System.Reflection; using System.Runtime.Serialization; using Microsoft.Build.Framework; using MonoDevelop.Core.Assemblies; -using MonoDevelop.Core; namespace MonoDevelop.Projects.MSBuild { @@ -24,6 +24,7 @@ namespace MonoDevelop.Projects.MSBuild readonly object _lockObject = new object (); IList<SdkResolver> _resolvers; TargetRuntime runtime; + Version msbuildVersion; internal SdkResolution (TargetRuntime runtime) { @@ -58,7 +59,7 @@ namespace MonoDevelop.Projects.MSBuild try { var buildEngineLogger = new SdkLoggerImpl (logger, buildEventContext); foreach (var sdkResolver in _resolvers) { - var context = new SdkResolverContextImpl (buildEngineLogger, projectFile, solutionPath); + var context = new SdkResolverContextImpl (buildEngineLogger, projectFile, solutionPath, msbuildVersion); var resultFactory = new SdkResultFactoryImpl (sdk); try { var result = (SdkResultImpl)sdkResolver.Resolve (sdk, context, resultFactory); @@ -96,10 +97,21 @@ namespace MonoDevelop.Projects.MSBuild { lock (_lockObject) { if (_resolvers != null) return; + msbuildVersion = GetMSBuildVersion (); _resolvers = LoadResolvers (logger); } } + Version GetMSBuildVersion () + { + var msbuildFileName = MSBuildProcessService.GetMSBuildBinPath (runtime); + if (!File.Exists (msbuildFileName)) + return null; + + var versionInfo = FileVersionInfo.GetVersionInfo (msbuildFileName); + return new Version (versionInfo.FileMajorPart, versionInfo.FileMinorPart, versionInfo.FileBuildPart, versionInfo.FilePrivatePart); + } + IList<SdkResolver> LoadResolvers (ILoggingService logger) { // Add the MonoDevelop resolver, which resolves SDKs registered by add-ins. @@ -282,16 +294,12 @@ namespace MonoDevelop.Projects.MSBuild public SdkReference Sdk { get; } - public string Path { get; } - - public string Version { get; } - public IEnumerable<string> Errors { get; } public IEnumerable<string> Warnings { get; } } - class SdkResultFactoryImpl : SdkResultFactory + internal class SdkResultFactoryImpl : SdkResultFactory { readonly SdkReference _sdkReference; @@ -313,11 +321,12 @@ namespace MonoDevelop.Projects.MSBuild sealed class SdkResolverContextImpl : SdkResolverContext { - public SdkResolverContextImpl (SdkLogger logger, string projectFilePath, string solutionPath) + public SdkResolverContextImpl (SdkLogger logger, string projectFilePath, string solutionPath, Version msbuildVersion) { Logger = logger; ProjectFilePath = projectFilePath; SolutionFilePath = solutionPath; + MSBuildVersion = msbuildVersion; } } } diff --git a/main/src/core/MonoDevelop.Core/MonoDevelop.Projects/DotNetProject.cs b/main/src/core/MonoDevelop.Core/MonoDevelop.Projects/DotNetProject.cs index 7631c0cb30..aeae42b1d2 100644 --- a/main/src/core/MonoDevelop.Core/MonoDevelop.Projects/DotNetProject.cs +++ b/main/src/core/MonoDevelop.Core/MonoDevelop.Projects/DotNetProject.cs @@ -1915,8 +1915,14 @@ namespace MonoDevelop.Projects { bool externalConsole = false, pauseConsole = false; - var dotNetExecutionCommand = executionCommand as DotNetExecutionCommand; - if (dotNetExecutionCommand != null) {
+ if (executionCommand is ProcessExecutionCommand processExecutionCommand) { + if (!string.IsNullOrEmpty (processExecutionCommand.WorkingDirectory) && !Directory.Exists (processExecutionCommand.WorkingDirectory)) { + monitor.ReportError (GettextCatalog.GetString ("Can not execute. The run configuration working directory doesn't exist at {0}", processExecutionCommand.WorkingDirectory), null); + return; + } + } + + if (executionCommand is DotNetExecutionCommand dotNetExecutionCommand) {
dotNetExecutionCommand.UserAssemblyPaths = GetUserAssemblyPaths (configuration); externalConsole = dotNetExecutionCommand.ExternalConsole; pauseConsole = dotNetExecutionCommand.PauseConsoleOutput; diff --git a/main/src/core/MonoDevelop.Core/MonoDevelop.Projects/FileWatcherService.cs b/main/src/core/MonoDevelop.Core/MonoDevelop.Projects/FileWatcherService.cs index 6d3bc6eae2..d61fc51705 100644 --- a/main/src/core/MonoDevelop.Core/MonoDevelop.Projects/FileWatcherService.cs +++ b/main/src/core/MonoDevelop.Core/MonoDevelop.Projects/FileWatcherService.cs @@ -1,322 +1,319 @@ -// -// FileWatcherService.cs -// -// Author: -// Matt Ward <matt.ward@microsoft.com> -// -// Copyright (c) 2018 Microsoft -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. - -using System; -using System.Collections.Generic; -using System.Diagnostics; +//
+// FileWatcherService.cs
+//
+// Author:
+// Matt Ward <matt.ward@microsoft.com>
+//
+// Copyright (c) 2018 Microsoft
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
using System.IO;
-using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using MonoDevelop.Core; -using MonoDevelop.FSW; - -namespace MonoDevelop.Projects -{ - public static class FileWatcherService - { - // We don't want more than 8 threads for FileSystemWatchers. - const int maxWatchers = 8; - - static readonly PathTree tree = new PathTree (); - static readonly Dictionary<FilePath, FileWatcherWrapper> watchers = new Dictionary<FilePath, FileWatcherWrapper> (); - static readonly Dictionary<object, HashSet<FilePath>> monitoredDirectories = new Dictionary<object, HashSet<FilePath>> (); - static CancellationTokenSource cancellationTokenSource = new CancellationTokenSource (); - - public static Task Add (WorkspaceItem item) - { - lock (watchers) { - item.RootDirectoriesChanged += OnRootDirectoriesChanged; - return WatchDirectories (item, item.GetRootDirectories ()); - } - } - - public static Task Remove (WorkspaceItem item) - { - lock (watchers) { - item.RootDirectoriesChanged -= OnRootDirectoriesChanged; - return WatchDirectories (item, null); - } - } - - static void OnRootDirectoriesChanged (object sender, EventArgs args) - { - lock (watchers) { - var item = (WorkspaceItem)sender; - WatchDirectories (item, item.GetRootDirectories ()).Ignore (); - } - } - - static Task UpdateWatchersAsync () - { - cancellationTokenSource.Cancel (); - cancellationTokenSource = new CancellationTokenSource (); - CancellationToken token = cancellationTokenSource.Token; - - return Task.Run (() => UpdateWatchers (token)); - } - - static void UpdateWatchers (CancellationToken token) - { - if (token.IsCancellationRequested) - return; - - lock (watchers) { - if (token.IsCancellationRequested) - return; - - var newPathsToWatch = tree.Normalize (maxWatchers).Select (node => (FilePath)node.GetPath ().ToString ()); - var newWatchers = new HashSet<FilePath> (newPathsToWatch.Where (dir => Directory.Exists (dir))); - if (newWatchers.Count == 0 && watchers.Count == 0) { - // Unchanged. - return; - } - - List<FilePath> toRemove; - if (newWatchers.Count == 0) - toRemove = watchers.Keys.ToList (); - else { - toRemove = new List<FilePath> (); - foreach (var kvp in watchers) { - var directory = kvp.Key; - if (!newWatchers.Contains (directory)) - toRemove.Add (directory); - } - } - - // After this point, the watcher update is real and a destructive operation, so do not use the token. - if (token.IsCancellationRequested) - return; - - // First remove the watchers, so we don't spin too many threads. - foreach (var directory in toRemove) { - RemoveWatcher_NoLock (directory); - } - - // Add the new ones. - if (newWatchers.Count == 0) - return; - - foreach (var path in newWatchers) { - // Don't modify a watcher that already exists. - if (watchers.ContainsKey (path)) { - continue; - } - var watcher = new FileWatcherWrapper (path); - watchers.Add (path, watcher); - try { - watcher.EnableRaisingEvents = true; - } catch (UnauthorizedAccessException e) { - LoggingService.LogWarning ("Access to " + path + " denied. Stopping file watcher.", e); - watcher.Dispose (); - watchers.Remove (path); - } - } - - } - } - - static void RemoveWatcher_NoLock (FilePath directory) - { - Debug.Assert (Monitor.IsEntered (watchers)); - - if (watchers.TryGetValue (directory, out FileWatcherWrapper watcher)) { - watcher.EnableRaisingEvents = false; - watcher.Dispose (); - watchers.Remove (directory); - } - } - - public static Task WatchDirectories (object id, IEnumerable<FilePath> directories) - { - lock (watchers) { - HashSet<FilePath> set = null; - if (directories != null) - set = new HashSet<FilePath> (directories.Where (x => !x.IsNullOrEmpty)); - - if (RegisterDirectoriesInTree_NoLock (id, set)) - return UpdateWatchersAsync (); - return Task.CompletedTask; - } - } - - static bool RegisterDirectoriesInTree_NoLock (object id, HashSet<FilePath> set) - { - Debug.Assert (Monitor.IsEntered (watchers)); - - // Remove paths subscribed for this id. - - bool modified = false; - - if (monitoredDirectories.TryGetValue (id, out var oldDirectories)) { - HashSet<FilePath> toRemove = null; - if (set != null) { - toRemove = new HashSet<FilePath> (oldDirectories); - // Remove the old ones which are not in the new set. - toRemove.ExceptWith (set); - } else - toRemove = oldDirectories; - - foreach (var dir in toRemove) { - var node = tree.RemoveNode (dir, id); - - bool wasRemoved = node != null && !node.IsLive; - modified |= wasRemoved; - } - } - - // Remove the current registered directories - monitoredDirectories.Remove (id); - if (set == null) - return modified; - - HashSet<FilePath> toAdd = null; - if (oldDirectories != null) { - toAdd = new HashSet<FilePath> (set); - toAdd.ExceptWith (oldDirectories); - } else - toAdd = set; - - // Apply new ones if we have any - if (set.Count > 0) { - monitoredDirectories [id] = set; - foreach (var path in toAdd) { - tree.AddNode (path, id, out bool isNew); - - // We have only modified the tree if there is any new pathtree node item added - modified |= isNew; - } - } - return modified; - } - - /// <summary> - /// Used by unit tests to ensure the file watcher is up to date. - /// </summary> - internal static Task Update () - { - lock (watchers) { - return UpdateWatchersAsync (); - } - } - } - - class FileWatcherWrapper : IDisposable - { - FSW.FileSystemWatcher watcher; - - public FileWatcherWrapper (FilePath path) - { - Path = path; - watcher = new FSW.FileSystemWatcher (path) {
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+using MonoDevelop.Core;
+using MonoDevelop.FSW;
+
+namespace MonoDevelop.Projects
+{
+ public static class FileWatcherService
+ {
+ // We don't want more than 8 threads for FileSystemWatchers.
+ const int maxWatchers = 8;
+
+ static readonly PathTree tree = new PathTree ();
+ static readonly Dictionary<FilePath, FileWatcherWrapper> watchers = new Dictionary<FilePath, FileWatcherWrapper> ();
+ static readonly Dictionary<object, HashSet<FilePath>> monitoredDirectories = new Dictionary<object, HashSet<FilePath>> ();
+ static CancellationTokenSource cancellationTokenSource = new CancellationTokenSource ();
+
+ public static Task Add (WorkspaceItem item)
+ {
+ lock (watchers) {
+ item.RootDirectoriesChanged += OnRootDirectoriesChanged;
+ return WatchDirectories (item, item.GetRootDirectories ());
+ }
+ }
+
+ public static Task Remove (WorkspaceItem item)
+ {
+ lock (watchers) {
+ item.RootDirectoriesChanged -= OnRootDirectoriesChanged;
+ return WatchDirectories (item, null);
+ }
+ }
+
+ static void OnRootDirectoriesChanged (object sender, EventArgs args)
+ {
+ lock (watchers) {
+ var item = (WorkspaceItem)sender;
+ WatchDirectories (item, item.GetRootDirectories ()).Ignore ();
+ }
+ }
+
+ static Task UpdateWatchersAsync ()
+ {
+ cancellationTokenSource.Cancel ();
+ cancellationTokenSource = new CancellationTokenSource ();
+ CancellationToken token = cancellationTokenSource.Token;
+
+ return Task.Run (() => UpdateWatchers (token));
+ }
+ static HashSet<FilePath> newWatchers = new HashSet<FilePath>(); + static List<FilePath> toRemove = new List<FilePath> (); + + static void UpdateWatchers (CancellationToken token)
+ {
+ if (token.IsCancellationRequested)
+ return;
+ lock (watchers) {
+ if (token.IsCancellationRequested)
+ return;
+ newWatchers.Clear ();
+ foreach (var node in tree.Normalize (maxWatchers)) {
+ if (token.IsCancellationRequested)
+ return;
+ var dir = node.GetPath ().ToString ();
+ if (Directory.Exists (dir))
+ newWatchers.Add (dir);
+ }
+ if (newWatchers.Count == 0 && watchers.Count == 0) {
+ // Unchanged.
+ return;
+ }
+ toRemove.Clear ();
+ foreach (var kvp in watchers) {
+ var directory = kvp.Key;
+ if (!newWatchers.Contains (directory))
+ toRemove.Add (directory);
+ }
+
+ // After this point, the watcher update is real and a destructive operation, so do not use the token.
+ if (token.IsCancellationRequested)
+ return;
+
+ // First remove the watchers, so we don't spin too many threads.
+ foreach (var directory in toRemove) {
+ RemoveWatcher_NoLock (directory);
+ }
+
+ // Add the new ones.
+ foreach (var path in newWatchers) {
+ // Don't modify a watcher that already exists.
+ if (watchers.ContainsKey (path)) {
+ continue;
+ }
+ var watcher = new FileWatcherWrapper (path);
+ watchers.Add (path, watcher);
+ try {
+ watcher.EnableRaisingEvents = true;
+ } catch (UnauthorizedAccessException e) {
+ LoggingService.LogWarning ("Access to " + path + " denied. Stopping file watcher.", e);
+ watcher.Dispose ();
+ watchers.Remove (path);
+ }
+ }
+
+ }
+ }
+
+ static void RemoveWatcher_NoLock (FilePath directory)
+ {
+ Debug.Assert (Monitor.IsEntered (watchers));
+
+ if (watchers.TryGetValue (directory, out FileWatcherWrapper watcher)) {
+ watcher.EnableRaisingEvents = false;
+ watcher.Dispose ();
+ watchers.Remove (directory);
+ }
+ }
+
+ public static Task WatchDirectories (object id, IEnumerable<FilePath> directories)
+ {
+ lock (watchers) {
+ HashSet<FilePath> set = null;
+ if (directories != null)
+ set = new HashSet<FilePath> (directories.Where (x => !x.IsNullOrEmpty));
+
+ if (RegisterDirectoriesInTree_NoLock (id, set))
+ return UpdateWatchersAsync ();
+ return Task.CompletedTask;
+ }
+ }
+
+ static bool RegisterDirectoriesInTree_NoLock (object id, HashSet<FilePath> set)
+ {
+ Debug.Assert (Monitor.IsEntered (watchers));
+
+ // Remove paths subscribed for this id.
+
+ bool modified = false;
+
+ if (monitoredDirectories.TryGetValue (id, out var oldDirectories)) {
+ HashSet<FilePath> toRemove = null;
+ if (set != null) {
+ toRemove = new HashSet<FilePath> (oldDirectories);
+ // Remove the old ones which are not in the new set.
+ toRemove.ExceptWith (set);
+ } else
+ toRemove = oldDirectories;
+
+ foreach (var dir in toRemove) {
+ var node = tree.RemoveNode (dir, id);
+
+ bool wasRemoved = node != null && !node.IsLive;
+ modified |= wasRemoved;
+ }
+ }
+
+ // Remove the current registered directories
+ monitoredDirectories.Remove (id);
+ if (set == null)
+ return modified;
+
+ HashSet<FilePath> toAdd = null;
+ if (oldDirectories != null) {
+ toAdd = new HashSet<FilePath> (set);
+ toAdd.ExceptWith (oldDirectories);
+ } else
+ toAdd = set;
+
+ // Apply new ones if we have any
+ if (set.Count > 0) {
+ monitoredDirectories [id] = set;
+ foreach (var path in toAdd) {
+ tree.AddNode (path, id, out bool isNew);
+
+ // We have only modified the tree if there is any new pathtree node item added
+ modified |= isNew;
+ }
+ }
+ return modified;
+ }
+
+ /// <summary>
+ /// Used by unit tests to ensure the file watcher is up to date.
+ /// </summary>
+ internal static Task Update ()
+ {
+ lock (watchers) {
+ return UpdateWatchersAsync ();
+ }
+ }
+ }
+
+ class FileWatcherWrapper : IDisposable
+ {
+ FileSystemWatcher watcher;
+
+ public FileWatcherWrapper (FilePath path)
+ {
+ Path = path;
+ watcher = new FileSystemWatcher (path) {
// Need LastWrite otherwise no file change events are generated by the native file watcher.
NotifyFilter = NotifyFilters.LastWrite | NotifyFilters.FileName | NotifyFilters.DirectoryName,
IncludeSubdirectories = true,
InternalBufferSize = 32768
- }; - - watcher.Changed += OnFileChanged; - watcher.Created += OnFileCreated; - watcher.Deleted += OnFileDeleted; - watcher.Renamed += OnFileRenamed; - watcher.Error += OnFileWatcherError; - } - - public FilePath Path { get; } - - public bool EnableRaisingEvents { - get { return watcher.EnableRaisingEvents; } - set { watcher.EnableRaisingEvents = value; } - } - - public void Dispose () - { - watcher.Changed -= OnFileChanged; - watcher.Created -= OnFileCreated; - watcher.Deleted -= OnFileDeleted; - watcher.Renamed -= OnFileRenamed; - watcher.Error -= OnFileWatcherError; - watcher.Dispose (); - } - - void OnFileChanged (object sender, FileSystemEventArgs e) - { - FileService.NotifyFileChanged (e.FullPath); - } - - void OnFileCreated (object sender, FileSystemEventArgs e) - { - FileService.NotifyFileCreated (e.FullPath); - - // The native file watcher sometimes generates a single Created event for a file when it is renamed - // from a non-monitored directory to a monitored directory. So this is turned into a Changed - // event so the file will be reloaded. - FileService.NotifyFileChanged (e.FullPath); - } - - void OnFileDeleted (object sender, FileSystemEventArgs e) - { - // The native file watcher sometimes generates a Changed, Created and Deleted event in - // that order from a single native file event. So check the file has been deleted before raising - // a FileRemoved event. - if (!File.Exists (e.FullPath) && !Directory.Exists (e.FullPath)) - FileService.NotifyFileRemoved (e.FullPath); - } - - /// <summary> - /// File rename events have various problems. - /// 1. They are sometimes raised out of order. - /// 2. Sometimes the rename information is incorrect with the wrong file names being used. - /// 3. Some applications use a rename to update the original file so these are turned into - /// a change event and a remove event. - /// </summary> - void OnFileRenamed (object sender, RenamedEventArgs e) - { - FileService.NotifyFileRenamedExternally (e.OldFullPath, e.FullPath); - // Some applications, such as TextEdit.app, will create a backup file - // and then rename that to the original file. This results in no file - // change event being generated by the file watcher. To handle this - // a rename is treated as a file change for the destination file. - FileService.NotifyFileChanged (e.FullPath); - - // Deleting a file with Finder will move the file to the ~/.Trashes - // folder. To handle this a remove event is fired for the source - // file being renamed. Also handle file events being received out of - // order on saving a file in TextEdit.app - with a rename event of - // the original file to the temp file being the last event even though - // the original file still exists. - if (File.Exists (e.OldFullPath)) - FileService.NotifyFileChanged (e.OldFullPath); - else - FileService.NotifyFileRemoved (e.OldFullPath); - } - - void OnFileWatcherError (object sender, ErrorEventArgs e) - { - LoggingService.LogError ("FileService.FileWatcher error", e.GetException ()); - } - } -} + };
+
+ watcher.Changed += OnFileChanged;
+ watcher.Created += OnFileCreated;
+ watcher.Deleted += OnFileDeleted;
+ watcher.Renamed += OnFileRenamed;
+ watcher.Error += OnFileWatcherError;
+ }
+
+ public FilePath Path { get; }
+
+ public bool EnableRaisingEvents {
+ get { return watcher.EnableRaisingEvents; }
+ set { watcher.EnableRaisingEvents = value; }
+ }
+
+ public void Dispose ()
+ {
+ watcher.Changed -= OnFileChanged;
+ watcher.Created -= OnFileCreated;
+ watcher.Deleted -= OnFileDeleted;
+ watcher.Renamed -= OnFileRenamed;
+ watcher.Error -= OnFileWatcherError;
+ watcher.Dispose ();
+ }
+
+ void OnFileChanged (object sender, FileSystemEventArgs e)
+ {
+ FileService.NotifyFileChanged (e.FullPath);
+ }
+
+ void OnFileCreated (object sender, FileSystemEventArgs e)
+ {
+ FileService.NotifyFileCreated (e.FullPath);
+
+ // The native file watcher sometimes generates a single Created event for a file when it is renamed
+ // from a non-monitored directory to a monitored directory. So this is turned into a Changed
+ // event so the file will be reloaded.
+ FileService.NotifyFileChanged (e.FullPath);
+ }
+
+ void OnFileDeleted (object sender, FileSystemEventArgs e)
+ {
+ // The native file watcher sometimes generates a Changed, Created and Deleted event in
+ // that order from a single native file event. So check the file has been deleted before raising
+ // a FileRemoved event.
+ if (!File.Exists (e.FullPath) && !Directory.Exists (e.FullPath))
+ FileService.NotifyFileRemoved (e.FullPath);
+ }
+
+ /// <summary>
+ /// File rename events have various problems.
+ /// 1. They are sometimes raised out of order.
+ /// 2. Sometimes the rename information is incorrect with the wrong file names being used.
+ /// 3. Some applications use a rename to update the original file so these are turned into
+ /// a change event and a remove event.
+ /// </summary>
+ void OnFileRenamed (object sender, RenamedEventArgs e)
+ {
+ FileService.NotifyFileRenamedExternally (e.OldFullPath, e.FullPath);
+ // Some applications, such as TextEdit.app, will create a backup file
+ // and then rename that to the original file. This results in no file
+ // change event being generated by the file watcher. To handle this
+ // a rename is treated as a file change for the destination file.
+ FileService.NotifyFileChanged (e.FullPath);
+
+ // Deleting a file with Finder will move the file to the ~/.Trashes
+ // folder. To handle this a remove event is fired for the source
+ // file being renamed. Also handle file events being received out of
+ // order on saving a file in TextEdit.app - with a rename event of
+ // the original file to the temp file being the last event even though
+ // the original file still exists.
+ if (File.Exists (e.OldFullPath))
+ FileService.NotifyFileChanged (e.OldFullPath);
+ else
+ FileService.NotifyFileRemoved (e.OldFullPath);
+ }
+
+ void OnFileWatcherError (object sender, ErrorEventArgs e)
+ {
+ LoggingService.LogError ("FileService.FileWatcher error", e.GetException ());
+ }
+ }
+}
diff --git a/main/src/core/MonoDevelop.Core/MonoDevelop.Projects/Project.cs b/main/src/core/MonoDevelop.Core/MonoDevelop.Projects/Project.cs index c08e57f1de..22afe401ea 100644 --- a/main/src/core/MonoDevelop.Core/MonoDevelop.Projects/Project.cs +++ b/main/src/core/MonoDevelop.Core/MonoDevelop.Projects/Project.cs @@ -4216,6 +4216,11 @@ namespace MonoDevelop.Projects try { IsReevaluating = true; + // Re-evaluating may change MSBuild items and cause the custom tool generator to run. If a + // custom MSBuild target is run it may run before the project builder is refreshed so the + // target may not available. To avoid this shutdown the project builder before re-evaluating. + ShutdownProjectBuilder (); + // Reevaluate the msbuild project monitorItemsModifiedDuringReevaluation = true; await sourceProject.EvaluateAsync (); @@ -4528,6 +4533,13 @@ namespace MonoDevelop.Projects void OnFileCreatedExternally (FilePath fileName) { + if (sourceProject == null) { + // sometimes this method is called after disposing this class. + // (i.e. when quitting MD or creating a new project.) + LoggingService.LogWarning ("File created externally not processed. {0}", fileName); + return; + } + // Check file is inside the project directory. The file globs would exclude the file anyway // if the relative path starts with "..\" but checking here avoids checking the file globs. if (!fileName.IsChildPathOf (BaseDirectory)) diff --git a/main/src/core/MonoDevelop.Core/MonoDevelop.Projects/ProjectItem.cs b/main/src/core/MonoDevelop.Core/MonoDevelop.Projects/ProjectItem.cs index c37c7ed678..62fcb98017 100644 --- a/main/src/core/MonoDevelop.Core/MonoDevelop.Projects/ProjectItem.cs +++ b/main/src/core/MonoDevelop.Core/MonoDevelop.Projects/ProjectItem.cs @@ -49,7 +49,8 @@ namespace MonoDevelop.Projects get { return project; } - internal set { + // HACK: internal + set { project = value; OnProjectSet (); } diff --git a/main/src/core/MonoDevelop.Core/packages.config b/main/src/core/MonoDevelop.Core/packages.config index 22e96f2c44..7c1fa9d24f 100644 --- a/main/src/core/MonoDevelop.Core/packages.config +++ b/main/src/core/MonoDevelop.Core/packages.config @@ -35,8 +35,8 @@ <package id="Microsoft.VisualStudio.Text.Logic" version="15.8.519" targetFramework="net45" />
<package id="Microsoft.VisualStudio.Text.UI" version="15.8.519" targetFramework="net45" />
<package id="Microsoft.VisualStudio.Text.UI.Wpf" version="15.8.519" targetFramework="net45" />
- <package id="Microsoft.VisualStudio.Threading" version="15.6.46" targetFramework="net461" />
- <package id="Microsoft.VisualStudio.Threading.Analyzers" version="15.6.46" targetFramework="net461" />
+ <package id="Microsoft.VisualStudio.Threading" version="15.8.209" targetFramework="net461" />
+ <package id="Microsoft.VisualStudio.Threading.Analyzers" version="15.8.209" targetFramework="net461" />
<package id="Microsoft.VisualStudio.Validation" version="15.3.32" targetFramework="net461" />
<package id="Microsoft.Win32.Primitives" version="4.0.1" targetFramework="net461" />
<package id="Mono.Cecil" version="0.10.0" targetFramework="net471" />
diff --git a/main/src/core/MonoDevelop.Ide/ExtensionModel/Commands.addin.xml b/main/src/core/MonoDevelop.Ide/ExtensionModel/Commands.addin.xml index 9bb9cb676f..333f953015 100644 --- a/main/src/core/MonoDevelop.Ide/ExtensionModel/Commands.addin.xml +++ b/main/src/core/MonoDevelop.Ide/ExtensionModel/Commands.addin.xml @@ -221,6 +221,10 @@ defaultHandler = "MonoDevelop.Ide.Commands.SolutionItemOptionsHandler" _label = "_Options" _description = "Show options window" /> + <Command id = "MonoDevelop.Ide.Commands.ProjectCommands.SetStartupProjects" + defaultHandler = "MonoDevelop.Ide.Commands.SetStartupProjectsHandler" + _label = "Se_t Startup Projects..." + _description = "Set multiple startup projects" /> <Command id = "MonoDevelop.Ide.Commands.ProjectCommands.SolutionOptions" defaultHandler = "MonoDevelop.Ide.Commands.SolutionOptionsHandler" icon = "gtk-preferences" @@ -252,7 +256,7 @@ _description = "Adds and existing folder and its contents" _label = "_Add Existing Folder..." /> <Command id = "MonoDevelop.Ide.Commands.ProjectCommands.NewFolder" - _label = "New _Folder" + _label = "New _Folder…" _description = "Create a new folder" icon = "md-new-folder" /> <Command id = "MonoDevelop.Ide.Commands.ProjectCommands.IncludeToProject" diff --git a/main/src/core/MonoDevelop.Ide/ExtensionModel/MainMenu.addin.xml b/main/src/core/MonoDevelop.Ide/ExtensionModel/MainMenu.addin.xml index 007c4a671a..aeec88b1a6 100644 --- a/main/src/core/MonoDevelop.Ide/ExtensionModel/MainMenu.addin.xml +++ b/main/src/core/MonoDevelop.Ide/ExtensionModel/MainMenu.addin.xml @@ -205,6 +205,7 @@ <CommandItem id = "MonoDevelop.Ide.Commands.ProjectCommands.ExportPolicy" /> <SeparatorItem id = "PolicyToolsEnd" /> <SeparatorItem id = "OptionsSeparator" /> + <CommandItem id = "MonoDevelop.Ide.Commands.ProjectCommands.SetStartupProjects" /> <CommandItem id = "MonoDevelop.Ide.Commands.ProjectCommands.SolutionOptions" /> <CommandItem id = "MonoDevelop.Ide.Commands.ProjectCommands.ProjectOptions" /> <SeparatorItem id = "ProjectToolsSeparator" /> diff --git a/main/src/core/MonoDevelop.Ide/Gui/MonoDevelop.Ide.Gui.OptionPanels.LoadSavePanelWidget.cs b/main/src/core/MonoDevelop.Ide/Gui/MonoDevelop.Ide.Gui.OptionPanels.LoadSavePanelWidget.cs index 4a8327ae06..6622f7e893 100644 --- a/main/src/core/MonoDevelop.Ide/Gui/MonoDevelop.Ide.Gui.OptionPanels.LoadSavePanelWidget.cs +++ b/main/src/core/MonoDevelop.Ide/Gui/MonoDevelop.Ide.Gui.OptionPanels.LoadSavePanelWidget.cs @@ -10,15 +10,15 @@ namespace MonoDevelop.Ide.Gui.OptionPanels private global::Gtk.VBox vbox18;
private global::Gtk.Label loadLabel;
private global::Gtk.HBox hbox14;
- private global::Gtk.Label label25;
private global::Gtk.HBox hbox10;
private global::Gtk.VBox vbox65;
private global::Gtk.CheckButton loadUserDataCheckButton;
- private global::Gtk.CheckButton loadPrevProjectCheckButton;
+ private global::Gtk.RadioButton openStartWindowRadioButton;
+ private global::Gtk.RadioButton loadPrevProjectRadioButton;
+ private global::Gtk.RadioButton emptyEnvironmentRadioButton;
private global::Gtk.VBox vbox19;
private global::Gtk.Label saveLabel;
private global::Gtk.HBox hbox11;
- private global::Gtk.Label label21;
private global::Gtk.VBox vbox20;
private global::Gtk.CheckButton createBackupCopyCheckButton;
@@ -43,24 +43,14 @@ namespace MonoDevelop.Ide.Gui.OptionPanels this.locationLabel.Yalign = 0F;
this.locationLabel.LabelProp = global::Mono.Unix.Catalog.GetString ("Default _Solution location");
this.locationLabel.UseUnderline = true;
- this.vbox26.Add (this.locationLabel);
- global::Gtk.Box.BoxChild w1 = ((global::Gtk.Box.BoxChild)(this.vbox26 [this.locationLabel]));
- w1.Position = 0;
- w1.Expand = false;
- w1.Fill = false;
+ this.vbox26.PackStart (this.locationLabel, false, false, 0);
+
// Container child vbox26.Gtk.Box+BoxChild
this.folderEntry = new global::MonoDevelop.Components.FolderEntry ();
this.folderEntry.Name = "folderEntry";
this.folderEntry.DisplayAsRelativePath = false;
- this.vbox26.Add (this.folderEntry);
- global::Gtk.Box.BoxChild w2 = ((global::Gtk.Box.BoxChild)(this.vbox26 [this.folderEntry]));
- w2.Position = 1;
- w2.Expand = false;
- w2.Fill = false;
- this.vbox17.Add (this.vbox26);
- global::Gtk.Box.BoxChild w3 = ((global::Gtk.Box.BoxChild)(this.vbox17 [this.vbox26]));
- w3.Position = 0;
- w3.Expand = false;
+ this.vbox26.PackStart (this.folderEntry, false, false, 0);
+ this.vbox17.PackStart (this.vbox26, false, false, 0);
// Container child vbox17.Gtk.Box+BoxChild
this.vbox18 = new global::Gtk.VBox ();
this.vbox18.Name = "vbox18";
@@ -72,27 +62,12 @@ namespace MonoDevelop.Ide.Gui.OptionPanels this.loadLabel.Yalign = 0F;
this.loadLabel.LabelProp = global::Mono.Unix.Catalog.GetString ("<b>Load</b>");
this.loadLabel.UseMarkup = true;
- this.vbox18.Add (this.loadLabel);
- global::Gtk.Box.BoxChild w4 = ((global::Gtk.Box.BoxChild)(this.vbox18 [this.loadLabel]));
- w4.Position = 0;
- w4.Expand = false;
- w4.Fill = false;
+ this.vbox18.PackStart (this.loadLabel, false, false, 0);
// Container child vbox18.Gtk.Box+BoxChild
this.hbox14 = new global::Gtk.HBox ();
this.hbox14.Name = "hbox14";
this.hbox14.Spacing = 6;
// Container child hbox14.Gtk.Box+BoxChild
- this.label25 = new global::Gtk.Label ();
- this.label25.Name = "label25";
- this.label25.Xalign = 0F;
- this.label25.Yalign = 0F;
- this.label25.LabelProp = " ";
- this.hbox14.Add (this.label25);
- global::Gtk.Box.BoxChild w5 = ((global::Gtk.Box.BoxChild)(this.hbox14 [this.label25]));
- w5.Position = 0;
- w5.Expand = false;
- w5.Fill = false;
- // Container child hbox14.Gtk.Box+BoxChild
this.hbox10 = new global::Gtk.HBox ();
this.hbox10.Name = "hbox10";
this.hbox10.Spacing = 6;
@@ -106,38 +81,60 @@ namespace MonoDevelop.Ide.Gui.OptionPanels this.loadUserDataCheckButton.Label = global::Mono.Unix.Catalog.GetString ("Load user-specific settings with the document");
this.loadUserDataCheckButton.DrawIndicator = true;
this.loadUserDataCheckButton.UseUnderline = true;
- this.vbox65.Add (this.loadUserDataCheckButton);
- global::Gtk.Box.BoxChild w6 = ((global::Gtk.Box.BoxChild)(this.vbox65 [this.loadUserDataCheckButton]));
- w6.Position = 0;
- w6.Expand = false;
- w6.Fill = false;
- // Container child vbox65.Gtk.Box+BoxChild
- this.loadPrevProjectCheckButton = new global::Gtk.CheckButton ();
- this.loadPrevProjectCheckButton.Name = "loadPrevProjectCheckButton";
- this.loadPrevProjectCheckButton.Label = global::Mono.Unix.Catalog.GetString ("_Load previous solution on startup");
- this.loadPrevProjectCheckButton.DrawIndicator = true;
- this.loadPrevProjectCheckButton.UseUnderline = true;
- this.vbox65.Add (this.loadPrevProjectCheckButton);
- global::Gtk.Box.BoxChild w7 = ((global::Gtk.Box.BoxChild)(this.vbox65 [this.loadPrevProjectCheckButton]));
- w7.Position = 1;
- w7.Expand = false;
- w7.Fill = false;
- this.hbox10.Add (this.vbox65);
- global::Gtk.Box.BoxChild w8 = ((global::Gtk.Box.BoxChild)(this.hbox10 [this.vbox65]));
- w8.Position = 0;
- w8.Expand = false;
- w8.Fill = false;
- this.hbox14.Add (this.hbox10);
- global::Gtk.Box.BoxChild w9 = ((global::Gtk.Box.BoxChild)(this.hbox14 [this.hbox10]));
- w9.Position = 1;
- this.vbox18.Add (this.hbox14);
- global::Gtk.Box.BoxChild w10 = ((global::Gtk.Box.BoxChild)(this.vbox18 [this.hbox14]));
- w10.Position = 1;
- this.vbox17.Add (this.vbox18);
- global::Gtk.Box.BoxChild w11 = ((global::Gtk.Box.BoxChild)(this.vbox17 [this.vbox18]));
- w11.Position = 1;
- w11.Expand = false;
- // Container child vbox17.Gtk.Box+BoxChild
+ this.vbox65.PackStart (this.loadUserDataCheckButton, false, false, 0); + + // Startup options + var startSectionVbox = new global::Gtk.VBox ();
+ startSectionVbox.Name = "startVbox";
+ startSectionVbox.Spacing = 6;
+
+ var startSectionLabel = new global::Gtk.Label ();
+ startSectionLabel.Name = "startSectionLabel";
+ startSectionLabel.Xalign = 0F;
+ startSectionLabel.Yalign = 0F;
+ startSectionLabel.LabelProp = global::Mono.Unix.Catalog.GetString ("<b>Start</b>");
+ startSectionLabel.UseMarkup = true;
+ startSectionVbox.PackStart (startSectionLabel, false, false, 0);
+
+ var startContentHbox = new global::Gtk.HBox ();
+ startSectionVbox.PackStart (startContentHbox, false, false, 0);
+
+ var startContentVbox = new global::Gtk.VBox ();
+ startContentVbox.Name = "startContentVbox";
+ startContentVbox.Spacing = 6;
+ startContentHbox.PackStart (startContentVbox, false, false, 24);
+ + this.openStartWindowRadioButton = new global::Gtk.RadioButton ((global::Gtk.RadioButton) null) { + Name = "openStartWindowCheckButton",
+ Label = global::Mono.Unix.Catalog.GetString ("_Always show me the Start Window"),
+ DrawIndicator = true,
+ UseUnderline = true + };
+ startContentVbox.PackStart (this.openStartWindowRadioButton, false, false, 0);
+
+ // Container child vbox65.Gtk.Box+BoxChild + this.loadPrevProjectRadioButton = new global::Gtk.RadioButton (this.openStartWindowRadioButton);
+ this.loadPrevProjectRadioButton.Name = "loadPrevProjectCheckButton";
+ this.loadPrevProjectRadioButton.Label = global::Mono.Unix.Catalog.GetString ("_Load previous solution on startup");
+ this.loadPrevProjectRadioButton.DrawIndicator = true;
+ this.loadPrevProjectRadioButton.UseUnderline = true;
+ startContentVbox.PackStart (this.loadPrevProjectRadioButton, false, false, 0);
+
+ this.emptyEnvironmentRadioButton = new global::Gtk.RadioButton (this.openStartWindowRadioButton) { + Name = "emptyEnvironmentCheckButton",
+ Label = global::Mono.Unix.Catalog.GetString ("_Show empty environment"),
+ DrawIndicator = true,
+ UseUnderline = true + };
+ startContentVbox.PackStart (this.emptyEnvironmentRadioButton, false, false, 0);
+
+ this.hbox10.PackStart (this.vbox65, false, false, 0);
+ this.hbox14.PackStart (this.hbox10, false, false, 24);
+ this.vbox18.PackStart (this.hbox14, false, false, 0);
+ this.vbox17.PackStart (startSectionVbox, false, false, 0);
+ this.vbox17.PackStart (this.vbox18, false, false, 0);
+ + // Container child vbox17.Gtk.Box+BoxChild this.vbox19 = new global::Gtk.VBox ();
this.vbox19.Name = "vbox19";
this.vbox19.Spacing = 6;
@@ -148,27 +145,12 @@ namespace MonoDevelop.Ide.Gui.OptionPanels this.saveLabel.Yalign = 0F;
this.saveLabel.LabelProp = global::Mono.Unix.Catalog.GetString ("<b>Save</b>");
this.saveLabel.UseMarkup = true;
- this.vbox19.Add (this.saveLabel);
- global::Gtk.Box.BoxChild w12 = ((global::Gtk.Box.BoxChild)(this.vbox19 [this.saveLabel]));
- w12.Position = 0;
- w12.Expand = false;
- w12.Fill = false;
+ this.vbox19.PackStart (this.saveLabel, false, false, 0);
// Container child vbox19.Gtk.Box+BoxChild
this.hbox11 = new global::Gtk.HBox ();
this.hbox11.Name = "hbox11";
this.hbox11.Spacing = 6;
// Container child hbox11.Gtk.Box+BoxChild
- this.label21 = new global::Gtk.Label ();
- this.label21.Name = "label21";
- this.label21.Xalign = 0F;
- this.label21.Yalign = 0F;
- this.label21.LabelProp = " ";
- this.hbox11.Add (this.label21);
- global::Gtk.Box.BoxChild w13 = ((global::Gtk.Box.BoxChild)(this.hbox11 [this.label21]));
- w13.Position = 0;
- w13.Expand = false;
- w13.Fill = false;
- // Container child hbox11.Gtk.Box+BoxChild
this.vbox20 = new global::Gtk.VBox ();
this.vbox20.Name = "vbox20";
this.vbox20.Spacing = 6;
@@ -178,20 +160,12 @@ namespace MonoDevelop.Ide.Gui.OptionPanels this.createBackupCopyCheckButton.Label = global::Mono.Unix.Catalog.GetString ("Always create backup copy");
this.createBackupCopyCheckButton.DrawIndicator = true;
this.createBackupCopyCheckButton.UseUnderline = true;
- this.vbox20.Add (this.createBackupCopyCheckButton);
- global::Gtk.Box.BoxChild w14 = ((global::Gtk.Box.BoxChild)(this.vbox20 [this.createBackupCopyCheckButton]));
- w14.Position = 0;
- w14.Expand = false;
- w14.Fill = false;
- this.hbox11.Add (this.vbox20);
- global::Gtk.Box.BoxChild w15 = ((global::Gtk.Box.BoxChild)(this.hbox11 [this.vbox20]));
- w15.Position = 1;
- this.vbox19.Add (this.hbox11);
- global::Gtk.Box.BoxChild w16 = ((global::Gtk.Box.BoxChild)(this.vbox19 [this.hbox11]));
- w16.Position = 1;
- this.vbox17.Add (this.vbox19);
+ this.vbox20.PackStart (this.createBackupCopyCheckButton, false, false, 0);
+ this.hbox11.PackStart (this.vbox20, false, false, 24);
+ this.vbox19.PackStart (this.hbox11, false, false, 0);
+ this.vbox17.PackStart (this.vbox19, false, false, 0);
global::Gtk.Box.BoxChild w17 = ((global::Gtk.Box.BoxChild)(this.vbox17 [this.vbox19]));
- w17.Position = 2;
+ w17.Position = 3;
this.Add (this.vbox17);
if ((this.Child != null)) {
this.Child.ShowAll ();
diff --git a/main/src/core/MonoDevelop.Ide/MonoDevelop.Components.AtkCocoaHelper/AtkCocoaHelperMac.cs b/main/src/core/MonoDevelop.Ide/MonoDevelop.Components.AtkCocoaHelper/AtkCocoaHelperMac.cs index 5650cb953e..60314a14b0 100644 --- a/main/src/core/MonoDevelop.Ide/MonoDevelop.Components.AtkCocoaHelper/AtkCocoaHelperMac.cs +++ b/main/src/core/MonoDevelop.Ide/MonoDevelop.Components.AtkCocoaHelper/AtkCocoaHelperMac.cs @@ -37,6 +37,7 @@ using Foundation; using Gdk; using ObjCRuntime; using MetalPerformanceShaders; +using MonoDevelop.Components.Mac; namespace MonoDevelop.Components.AtkCocoaHelper { @@ -291,29 +292,38 @@ namespace MonoDevelop.Components.AtkCocoaHelper nsa.AccessibilityOrientation = orientation == Gtk.Orientation.Vertical ? NSAccessibilityOrientation.Vertical : NSAccessibilityOrientation.Horizontal; } - public static void SetTitleFor (this Atk.Object o, params Atk.Object [] objects) + static NSArray ToAccessibilityArray (this Atk.Object[] objects) { - var nsa = GetNSAccessibilityElement (o); - if (nsa == null) { - return; - } + if (objects == null) + return null; - NSObject [] titleElements = new NSObject [objects.Length]; - int idx = 0; + var array = new NSMutableArray ((nuint)objects.Length); foreach (var obj in objects) { var nsao = GetNSAccessibilityElement (obj); if (nsao == null) { - return; + continue; } - titleElements [idx] = (NSObject)nsao; - idx++; + array.Add ((NSObject)nsao); } + return array; + } - nsa.AccessibilityServesAsTitleForUIElements = titleElements; + static readonly IntPtr selSetAccessibilityServesAsTitleForUIElements_Handle = Selector.GetHandle ("setAccessibilityServesAsTitleForUIElements:"); + public static void SetTitleFor (this Atk.Object o, params Atk.Object [] objects) + { + var nsa = GetNSAccessibilityElement (o); + if (nsa == null) { + return; + } + + using (var titleElements = objects.ToAccessibilityArray ()) { + nsa.SendSelector (selSetAccessibilityServesAsTitleForUIElements_Handle, titleElements); + } } + static readonly IntPtr selSetAccessibilityTabs_Handle = Selector.GetHandle ("setAccessibilityTabs:"); public static void SetTabs (this Atk.Object o, params AccessibilityElementProxy [] tabs) { var nsa = GetNSAccessibilityElement (o); @@ -321,7 +331,9 @@ namespace MonoDevelop.Components.AtkCocoaHelper return; } - nsa.AccessibilityTabs = ConvertToRealProxyArray (tabs); + using (var realTabs = tabs.ConvertToRealProxyArray ()) { + nsa.SendSelector (selSetAccessibilityTabs_Handle, realTabs); + } } public static void SetTabs (this Atk.Object o, params Atk.Object [] tabs) @@ -331,16 +343,12 @@ namespace MonoDevelop.Components.AtkCocoaHelper return; } - NSObject [] realTabs = new NSObject [tabs.Length]; - int i = 0; - foreach (var tab in tabs) { - realTabs [i] = (NSObject)GetNSAccessibilityElement (tab); - i++; + using (var realTabs = tabs.ToAccessibilityArray ()) { + nsa.SendSelector (selSetAccessibilityTabs_Handle, realTabs); } - - nsa.AccessibilityTabs = realTabs; } + static readonly IntPtr selAccessibilityServesAsTitleForUIElements_Handle = Selector.GetHandle ("setAccessibilityServesAsTitleForUIElements:"); public static void AddElementToTitle (this Atk.Object title, Atk.Object o) { var titleNsa = GetNSAccessibilityElement (title); @@ -350,20 +358,12 @@ namespace MonoDevelop.Components.AtkCocoaHelper return; } - NSObject [] oldElements = titleNsa.AccessibilityServesAsTitleForUIElements; - int length = oldElements != null ? oldElements.Length : 0; - - if (oldElements != null && oldElements.IndexOf ((NSObject)nsa) != -1) { - return; + IntPtr ptr = Messaging.IntPtr_objc_msgSend (titleNsa.Handle, selAccessibilityServesAsTitleForUIElements_Handle); + using (var array = Runtime.GetNSObject<NSArray> (ptr)) + using (var copy = array != null ? (NSMutableArray)array.MutableCopy () : new NSMutableArray (1)) { + copy.Add ((NSObject)nsa); + nsa.SendSelector (selSetAccessibilityServesAsTitleForUIElements_Handle, copy); } - - NSObject [] titleElements = new NSObject [length + 1]; - if (oldElements != null) { - oldElements.CopyTo (titleElements, 0); - } - titleElements [length] = (NSObject)nsa; - - titleNsa.AccessibilityServesAsTitleForUIElements = titleElements; } public static void RemoveElementFromTitle (this Atk.Object title, Atk.Object o) @@ -375,48 +375,56 @@ namespace MonoDevelop.Components.AtkCocoaHelper return; } - if (titleNsa.AccessibilityServesAsTitleForUIElements == null) { - return; - } + IntPtr ptr = Messaging.IntPtr_objc_msgSend (titleNsa.Handle, selAccessibilityServesAsTitleForUIElements_Handle); + using (var array = Runtime.GetNSObject<NSArray> (ptr)) { + if (array == null) + return; + + var nso = (NSObject)nsa; + var index = (nint)array.IndexOf (nso); + if (index == NSRange.NotFound) + return; - List<NSObject> oldElements = new List<NSObject> (titleNsa.AccessibilityServesAsTitleForUIElements); - oldElements.Remove ((NSObject)nsa); + using (var copy = (NSMutableArray)array.MutableCopy ()) + using (var set = NSIndexSet.FromIndex (index)) { + copy.RemoveObjectsAtIndexes (set); - titleNsa.AccessibilityServesAsTitleForUIElements = oldElements.ToArray (); + nsa.SendSelector (selSetAccessibilityServesAsTitleForUIElements_Handle, copy); + } + } } - static RealAccessibilityElementProxy [] ConvertToRealProxyArray (AccessibilityElementProxy [] proxies) + static NSArray ConvertToRealProxyArray (this AccessibilityElementProxy [] proxies) { if (proxies == null) { return null; } - var realProxies = new RealAccessibilityElementProxy [proxies.Length]; - int idx = 0; + var array = new NSMutableArray ((nuint)proxies.Length); foreach (var p in proxies) { - var rp = p.Proxy as RealAccessibilityElementProxy; - if (rp == null) { + if (!(p.Proxy is RealAccessibilityElementProxy rp)) { throw new Exception ($"Invalid type {p.GetType ()} in accessibleChildren"); } - realProxies [idx] = rp; - idx++; + array.Add (rp); } - return realProxies; + return array; } - public static void ReplaceAccessibilityElements (this Atk.Object parent, AccessibilityElementProxy [] children) + static void SendSelector (this INSAccessibility nsa, IntPtr selectorHandle, NSArray array) { - var nsa = GetNSAccessibilityElement (parent); - - if (nsa == null) { - return; - } + Messaging.void_objc_msgSend_IntPtr (nsa.Handle, selectorHandle, array != null ? array.Handle : IntPtr.Zero); + } - nsa.AccessibilityChildren = ConvertToRealProxyArray (children); + static readonly IntPtr selAccessibilityChildren_Handle = Selector.GetHandle ("accessibilityChildren"); + static readonly IntPtr selSetAccessibilityChildren_Handle = Selector.GetHandle ("setAccessibilityChildren:"); + public static void ReplaceAccessibilityElements (this Atk.Object parent, AccessibilityElementProxy [] children) + { + parent.SetAccessibleChildren (children); } + static readonly IntPtr selSetAccessibilityColumns_Handle = Selector.GetHandle ("setAccessibilityColumns:"); public static void SetColumns (this Atk.Object parent, AccessibilityElementProxy [] columns) { var nsa = GetNSAccessibilityElement (parent); @@ -425,9 +433,12 @@ namespace MonoDevelop.Components.AtkCocoaHelper return; } - nsa.AccessibilityColumns = ConvertToRealProxyArray (columns); + using (var array = columns.ConvertToRealProxyArray ()) { + nsa.SendSelector (selSetAccessibilityColumns_Handle, array); + } } + static readonly IntPtr selSetAccessibilityRows_Handle = Selector.GetHandle ("setAccessibilityRows:"); public static void SetRows (this Atk.Object parent, AccessibilityElementProxy [] rows) { var nsa = GetNSAccessibilityElement (parent); @@ -436,7 +447,9 @@ namespace MonoDevelop.Components.AtkCocoaHelper return; } - nsa.AccessibilityRows = ConvertToRealProxyArray (rows); + using (var array = rows.ConvertToRealProxyArray ()) { + nsa.SendSelector (selSetAccessibilityRows_Handle, array); + } } public static void AddAccessibleElement (this Atk.Object o, AccessibilityElementProxy child) @@ -460,34 +473,27 @@ namespace MonoDevelop.Components.AtkCocoaHelper return; } - var children = nsa.AccessibilityChildren; - - if (children == null || children.Length == 0) { - return; - } - var p = child.Proxy as RealAccessibilityElementProxy; if (p == null) { throw new Exception ($"Invalid proxy child type {p.GetType ()}"); } - var idx = children.IndexOf (p); - if (idx == -1) { - return; - } + var childrenHandle = Messaging.IntPtr_objc_msgSend (nsa.Handle, selAccessibilityChildren_Handle); + using (var array = Runtime.GetNSObject<NSArray> (childrenHandle)) { + if (array == null) + return; - var newChildren = new NSObject [children.Length - 1]; + var nso = (NSObject)nsa; + var index = (nint)array.IndexOf (nso); + if (index == NSRange.NotFound) + return; - for (int i = 0, j = 0; i < children.Length; i++) { - if (i == idx) { - continue; + using (var copy = (NSMutableArray)array.MutableCopy ()) + using (var set = NSIndexSet.FromIndex (index)) { + copy.RemoveObjectsAtIndexes (set); + nsa.SendSelector (selSetAccessibilityChildren_Handle, array); } - - newChildren [j] = children [i]; - j++; } - - nsa.AccessibilityChildren = newChildren; } public static void TransferAccessibleChild (this Atk.Object from, Atk.Object to, Atk.Object child) @@ -500,21 +506,28 @@ namespace MonoDevelop.Components.AtkCocoaHelper return; } - var fromChildren = fromNsa.AccessibilityChildren; + var fromChildren = Messaging.IntPtr_objc_msgSend (fromNsa.Handle, selAccessibilityChildren_Handle); + using (var fromArray = Runtime.GetNSObject<NSArray> (fromChildren)) { + if (fromArray == null) + return; - if (fromChildren == null || fromChildren.Length == 0) { - return; + var nso = (NSObject)childNsa; + var index = (nint)fromArray.IndexOf (nso); + if (index != NSRange.NotFound) { + using (var copy = (NSMutableArray)fromArray.MutableCopy ()) + using (var set = NSIndexSet.FromIndex (index)) { + copy.RemoveObjectsAtIndexes (set); + fromNsa.SendSelector (selSetAccessibilityChildren_Handle, copy); + } + } } - var fromList = fromChildren.ToList (); - fromList.Remove ((NSObject) childNsa); - fromNsa.AccessibilityChildren = fromList.ToArray (); - - var toChildren = toNsa.AccessibilityChildren; - List<NSObject> toList = toChildren == null ? new List<NSObject> () : toChildren.ToList (); - - toList.Add ((NSObject)childNsa); - toNsa.AccessibilityChildren = toList.ToArray (); + var toChildren = Messaging.IntPtr_objc_msgSend (fromNsa.Handle, selAccessibilityChildren_Handle); + using (var toArray = Runtime.GetNSObject<NSArray> (toChildren)) + using (var copy = toArray != null ? (NSMutableArray)toArray.MutableCopy () : new NSMutableArray (1)) { + copy.Add ((NSObject)childNsa); + toNsa.SendSelector (selSetAccessibilityChildren_Handle, copy); + } } public static void SetAccessibleChildren (this Atk.Object o, AccessibilityElementProxy [] children) @@ -524,9 +537,13 @@ namespace MonoDevelop.Components.AtkCocoaHelper return; } - nsa.AccessibilityChildren = ConvertToRealProxyArray (children); + using (var array = children.ConvertToRealProxyArray ()) { + nsa.SendSelector (selSetAccessibilityChildren_Handle, array); + } } + static readonly IntPtr selAccessibilityLinkedUIElements_Handle = Selector.GetHandle ("accessibilityLinkedUIElements"); + static readonly IntPtr selSetAccessibilityLinkedUIElements_Handle = Selector.GetHandle ("setAccessibilityLinkedUIElements:"); public static void AddLinkedUIElement (this Atk.Object o, Atk.Object linked) { var nsa = GetNSAccessibilityElement (o); @@ -535,18 +552,12 @@ namespace MonoDevelop.Components.AtkCocoaHelper return; } - var current = nsa.AccessibilityLinkedUIElements; - NSObject [] newLinkedElements; - if (current != null) { - int length = nsa.AccessibilityLinkedUIElements.Length; - newLinkedElements = new NSObject [length + 1]; - Array.Copy (nsa.AccessibilityLinkedUIElements, newLinkedElements, length); - newLinkedElements [length] = (NSObject)linkedNSA; - } else { - newLinkedElements = new NSObject [] { (NSObject)linkedNSA }; + var current = Messaging.IntPtr_objc_msgSend (nsa.Handle, selAccessibilityLinkedUIElements_Handle); + using (var array = Runtime.GetNSObject<NSArray> (current)) + using (var copy = array != null ? (NSMutableArray)array.MutableCopy () : new NSMutableArray (1)) { + copy.Add ((NSObject)linkedNSA); + nsa.SendSelector (selSetAccessibilityLinkedUIElements_Handle, copy); } - - nsa.AccessibilityLinkedUIElements = newLinkedElements; } public static void AddLinkedUIElement (this Atk.Object o, params Atk.Object [] linked) @@ -556,23 +567,15 @@ namespace MonoDevelop.Components.AtkCocoaHelper return; } - var current = nsa.AccessibilityLinkedUIElements; - - int length = current != null ? current.Length : 0; - var newLinkedElements = new NSObject [length + linked.Length]; - - if (current != null) { - Array.Copy (current, newLinkedElements, length); - } - - int idx = length; - foreach (var e in linked) { - var nsaLinked = GetNSAccessibilityElement (e); - newLinkedElements [idx] = (NSObject)nsaLinked; - idx++; + var current = Messaging.IntPtr_objc_msgSend (nsa.Handle, selAccessibilityLinkedUIElements_Handle); + using (var array = Runtime.GetNSObject<NSArray> (current)) + using (var copy = array != null ? (NSMutableArray)array.MutableCopy () : new NSMutableArray ((nuint)linked.Length)) { + foreach (var e in linked) { + var nsaLinked = GetNSAccessibilityElement (e); + copy.Add ((NSObject)nsaLinked); + } + nsa.SendSelector (selSetAccessibilityLinkedUIElements_Handle, copy); } - - nsa.AccessibilityLinkedUIElements = newLinkedElements; } public static void MakeAccessibilityAnnouncement (this Atk.Object o, string message) diff --git a/main/src/core/MonoDevelop.Ide/MonoDevelop.Components.AutoTest.Results/GtkNotebookResult.cs b/main/src/core/MonoDevelop.Ide/MonoDevelop.Components.AutoTest.Results/GtkNotebookResult.cs index c3d799acd3..89f59380bd 100644 --- a/main/src/core/MonoDevelop.Ide/MonoDevelop.Components.AutoTest.Results/GtkNotebookResult.cs +++ b/main/src/core/MonoDevelop.Ide/MonoDevelop.Components.AutoTest.Results/GtkNotebookResult.cs @@ -73,6 +73,12 @@ namespace MonoDevelop.Components.AutoTest.Results } return false; } + + protected override void Dispose (bool disposing) + { + noteBook = null; + base.Dispose (disposing); + } } } diff --git a/main/src/core/MonoDevelop.Ide/MonoDevelop.Components.AutoTest.Results/GtkTreeModelResult.cs b/main/src/core/MonoDevelop.Ide/MonoDevelop.Components.AutoTest.Results/GtkTreeModelResult.cs index 7e3dbbccbd..6aaee7e68c 100644 --- a/main/src/core/MonoDevelop.Ide/MonoDevelop.Components.AutoTest.Results/GtkTreeModelResult.cs +++ b/main/src/core/MonoDevelop.Ide/MonoDevelop.Components.AutoTest.Results/GtkTreeModelResult.cs @@ -282,6 +282,13 @@ namespace MonoDevelop.Components.AutoTest.Results base.SetProperty (modelValue, propertyName, value); } } + + protected override void Dispose (bool disposing) + { + ParentWidget = null; + TModel = null; + base.Dispose (disposing); + } } } diff --git a/main/src/core/MonoDevelop.Ide/MonoDevelop.Components.AutoTest.Results/GtkWidgetResult.cs b/main/src/core/MonoDevelop.Ide/MonoDevelop.Components.AutoTest.Results/GtkWidgetResult.cs index 6beaa805eb..b530748d1d 100644 --- a/main/src/core/MonoDevelop.Ide/MonoDevelop.Components.AutoTest.Results/GtkWidgetResult.cs +++ b/main/src/core/MonoDevelop.Ide/MonoDevelop.Components.AutoTest.Results/GtkWidgetResult.cs @@ -530,6 +530,12 @@ namespace MonoDevelop.Components.AutoTest.Results { base.SetProperty (resultWidget, propertyName, value); } + + protected override void Dispose (bool disposing) + { + resultWidget = null; + base.Dispose (disposing); + } } } diff --git a/main/src/core/MonoDevelop.Ide/MonoDevelop.Components.AutoTest.Results/NSObjectResult.cs b/main/src/core/MonoDevelop.Ide/MonoDevelop.Components.AutoTest.Results/NSObjectResult.cs index c597a1d5cf..5fbc66abf0 100644 --- a/main/src/core/MonoDevelop.Ide/MonoDevelop.Components.AutoTest.Results/NSObjectResult.cs +++ b/main/src/core/MonoDevelop.Ide/MonoDevelop.Components.AutoTest.Results/NSObjectResult.cs @@ -366,7 +366,13 @@ namespace MonoDevelop.Components.AutoTest.Results pinfo.SetValue (ResultObject, runtime); return true; } -#endregion + #endregion + + protected override void Dispose (bool disposing) + { + ResultObject = null; + base.Dispose (disposing); + } } } diff --git a/main/src/core/MonoDevelop.Ide/MonoDevelop.Components.AutoTest.Results/ObjectResult.cs b/main/src/core/MonoDevelop.Ide/MonoDevelop.Components.AutoTest.Results/ObjectResult.cs index b96c6978e0..bdb2f5a912 100644 --- a/main/src/core/MonoDevelop.Ide/MonoDevelop.Components.AutoTest.Results/ObjectResult.cs +++ b/main/src/core/MonoDevelop.Ide/MonoDevelop.Components.AutoTest.Results/ObjectResult.cs @@ -132,5 +132,11 @@ namespace MonoDevelop.Components.AutoTest.Results { SetProperty (value, propertyName, newValue); } + + protected override void Dispose (bool disposing) + { + value = null; + base.Dispose (disposing); + } } } diff --git a/main/src/core/MonoDevelop.Ide/MonoDevelop.Components.AutoTest/AppQuery.cs b/main/src/core/MonoDevelop.Ide/MonoDevelop.Components.AutoTest/AppQuery.cs index 3eeb5623a7..629e369713 100644 --- a/main/src/core/MonoDevelop.Ide/MonoDevelop.Components.AutoTest/AppQuery.cs +++ b/main/src/core/MonoDevelop.Ide/MonoDevelop.Components.AutoTest/AppQuery.cs @@ -39,7 +39,7 @@ using AppKit; namespace MonoDevelop.Components.AutoTest { - public class AppQuery : MarshalByRefObject + public class AppQuery : MarshalByRefObject, IDisposable { AppResult rootNode; List<Operation> operations = new List<Operation> (); @@ -368,7 +368,13 @@ namespace MonoDevelop.Components.AutoTest { var operationChain = string.Join (".", operations.Select (x => x.ToString ())); return string.Format ("c => c.{0};", operationChain); - } + } + + public void Dispose () + { + rootNode?.Dispose (); + rootNode = null; + } } } diff --git a/main/src/core/MonoDevelop.Ide/MonoDevelop.Components.AutoTest/AppResult.cs b/main/src/core/MonoDevelop.Ide/MonoDevelop.Components.AutoTest/AppResult.cs index 4bce0b11d5..a1c7373613 100644 --- a/main/src/core/MonoDevelop.Ide/MonoDevelop.Components.AutoTest/AppResult.cs +++ b/main/src/core/MonoDevelop.Ide/MonoDevelop.Components.AutoTest/AppResult.cs @@ -35,7 +35,7 @@ using MonoDevelop.Core; namespace MonoDevelop.Components.AutoTest { - public abstract class AppResult : MarshalByRefObject + public abstract class AppResult : MarshalByRefObject, IDisposable { //public Gtk.Widget ResultWidget { get; private set; } @@ -229,5 +229,18 @@ namespace MonoDevelop.Components.AutoTest return haystack != null && (haystack.IndexOf (needle, StringComparison.Ordinal) > -1); } } + + public void Dispose () + { + Dispose (true); + } + + protected virtual void Dispose (bool disposing) + { + FirstChild?.Dispose (); + NextSibling?.Dispose (); + + FirstChild = NextSibling = ParentNode = PreviousSibling = null; + } } } diff --git a/main/src/core/MonoDevelop.Ide/MonoDevelop.Components.AutoTest/AutoTestClientSession.cs b/main/src/core/MonoDevelop.Ide/MonoDevelop.Components.AutoTest/AutoTestClientSession.cs index f699cc3b44..01d3baa316 100644 --- a/main/src/core/MonoDevelop.Ide/MonoDevelop.Components.AutoTest/AutoTestClientSession.cs +++ b/main/src/core/MonoDevelop.Ide/MonoDevelop.Components.AutoTest/AutoTestClientSession.cs @@ -122,6 +122,8 @@ namespace MonoDevelop.Components.AutoTest return null; } + public void DisconnectQueries () => session.DisconnectQueries (); + public void Stop () { if (service != null) diff --git a/main/src/core/MonoDevelop.Ide/MonoDevelop.Components.AutoTest/AutoTestService.cs b/main/src/core/MonoDevelop.Ide/MonoDevelop.Components.AutoTest/AutoTestService.cs index 0ecdb78e83..8041c26115 100644 --- a/main/src/core/MonoDevelop.Ide/MonoDevelop.Components.AutoTest/AutoTestService.cs +++ b/main/src/core/MonoDevelop.Ide/MonoDevelop.Components.AutoTest/AutoTestService.cs @@ -163,9 +163,11 @@ namespace MonoDevelop.Components.AutoTest public void DetachClient (IAutoTestClient client) { - if (client == this.client) + if (client == this.client) { this.client = null; - else + currentSession?.Dispose (); + currentSession = null; + } else throw new InvalidOperationException ("Not connected"); } } diff --git a/main/src/core/MonoDevelop.Ide/MonoDevelop.Components.AutoTest/AutoTestSession.cs b/main/src/core/MonoDevelop.Ide/MonoDevelop.Components.AutoTest/AutoTestSession.cs index b7fad4b533..16d385b73b 100644 --- a/main/src/core/MonoDevelop.Ide/MonoDevelop.Components.AutoTest/AutoTestSession.cs +++ b/main/src/core/MonoDevelop.Ide/MonoDevelop.Components.AutoTest/AutoTestSession.cs @@ -40,6 +40,7 @@ using MonoDevelop.Components.Commands; using MonoDevelop.Core; using System.Xml; +using System.Runtime.Remoting; namespace MonoDevelop.Components.AutoTest { @@ -58,6 +59,8 @@ namespace MonoDevelop.Components.AutoTest set { SessionDebug.DebugObject = value; } } + readonly List<AppQuery> queries = new List<AppQuery> (); + public AutoTestSession () { } @@ -67,6 +70,40 @@ namespace MonoDevelop.Components.AutoTest return null; } + ~AutoTestSession() + { + Dispose (false); + } + + public void Dispose() + { + GC.SuppressFinalize (this); + Dispose (true); + } + + public void DisconnectQueries() + { + lock (queries) { + foreach (var query in queries) { + RemotingServices.Disconnect (query); + query.Dispose (); + } + queries.Clear (); + } + } + + bool disposed; + protected virtual void Dispose(bool disposing) + { + if (disposed) + return; + + disposed = true; + RemotingServices.Disconnect (this); + + DisconnectQueries (); + } + [Serializable] public struct MemoryStats { public long PrivateMemory; @@ -334,6 +371,8 @@ namespace MonoDevelop.Components.AutoTest AppQuery query = new AppQuery (); query.SessionDebug = SessionDebug; + lock (queries) + queries.Add (query); return query; } diff --git a/main/src/core/MonoDevelop.Ide/MonoDevelop.Components.Commands/CommandManager.cs b/main/src/core/MonoDevelop.Ide/MonoDevelop.Components.Commands/CommandManager.cs index 2c079b852a..bb0c33fefc 100644 --- a/main/src/core/MonoDevelop.Ide/MonoDevelop.Components.Commands/CommandManager.cs +++ b/main/src/core/MonoDevelop.Ide/MonoDevelop.Components.Commands/CommandManager.cs @@ -2405,7 +2405,7 @@ namespace MonoDevelop.Components.Commands // It then queries widgets, which resurrects widget wrappers, which breaks on managed widgets if (this.disposed) return; - + var activeWidget = GetActiveWidget (rootWidget); foreach (ICommandBar toolbar in toolbars) { toolbar.Update (activeWidget); diff --git a/main/src/core/MonoDevelop.Ide/MonoDevelop.Components.Docking/DockContainer.cs b/main/src/core/MonoDevelop.Ide/MonoDevelop.Components.Docking/DockContainer.cs index 16b90c9854..9b63190523 100644 --- a/main/src/core/MonoDevelop.Ide/MonoDevelop.Components.Docking/DockContainer.cs +++ b/main/src/core/MonoDevelop.Ide/MonoDevelop.Components.Docking/DockContainer.cs @@ -1,5 +1,4 @@ // -// DockContainer.cs // // Author: // Lluis Sanchez Gual @@ -36,6 +35,7 @@ using Gtk; using Gdk; using System.Linq; using MonoDevelop.Components.AtkCocoaHelper; +using MonoDevelop.Core; using MonoDevelop.Ide.Gui; namespace MonoDevelop.Components.Docking @@ -52,8 +52,8 @@ namespace MonoDevelop.Components.Docking bool needsRelayout = true; - PlaceholderWindow placeholderWindow; - PadTitleWindow padTitleWindow; + volatile PlaceholderWindow placeholderWindow; + volatile PadTitleWindow padTitleWindow; public DockContainer (DockFrame frame) { @@ -403,7 +403,15 @@ namespace MonoDevelop.Components.Docking internal bool UpdatePlaceholder (DockItem item, Gdk.Size size, bool allowDocking) { - if (placeholderWindow == null) + if (!Runtime.IsMainThread) { + var msg = "UpdatePlaceholder called from background thread."; + LoggingService.LogInternalError ($"{msg}\n{Environment.StackTrace}", new InvalidOperationException (msg)); + } + + var placeholderWindow = this.placeholderWindow; + var padTitleWindow = this.padTitleWindow; + + if (placeholderWindow == null || padTitleWindow == null) return false; int px, py; diff --git a/main/src/core/MonoDevelop.Ide/MonoDevelop.Components.PropertyGrid.Editors/EventEditor.cs b/main/src/core/MonoDevelop.Ide/MonoDevelop.Components.PropertyGrid.Editors/EventEditor.cs index a07c47b6ca..3cbdd0fdd4 100644 --- a/main/src/core/MonoDevelop.Ide/MonoDevelop.Components.PropertyGrid.Editors/EventEditor.cs +++ b/main/src/core/MonoDevelop.Ide/MonoDevelop.Components.PropertyGrid.Editors/EventEditor.cs @@ -45,14 +45,20 @@ namespace MonoDevelop.Components.PropertyGrid.PropertyEditors IEventBindingService evtBind;
protected override void Initialize () - { - IComponent comp = Instance as IComponent; - evtBind = (IEventBindingService) comp.Site.GetService (typeof (IEventBindingService)); - base.Initialize ();
+ {
+ IComponent comp = Instance as IComponent;
+ if (comp != null) { + evtBind = (IEventBindingService)comp.Site.GetService (typeof (IEventBindingService));
+ base.Initialize (); + } } protected override IPropertyEditor CreateEditor (Gdk.Rectangle cell_area, Gtk.StateType state) - { + {
+ if (evtBind == null) { + return null; + } + //get existing method names ICollection IColl = evtBind.GetCompatibleMethods (evtBind.GetEvent (Property)) ; string[] methods = new string [IColl.Count + 1]; diff --git a/main/src/core/MonoDevelop.Ide/MonoDevelop.Components/BaseFileEntry.cs b/main/src/core/MonoDevelop.Ide/MonoDevelop.Components/BaseFileEntry.cs index 89b90e9b4d..afe01c968e 100644 --- a/main/src/core/MonoDevelop.Ide/MonoDevelop.Components/BaseFileEntry.cs +++ b/main/src/core/MonoDevelop.Ide/MonoDevelop.Components/BaseFileEntry.cs @@ -154,6 +154,12 @@ namespace MonoDevelop.Components pathEntry.SetCommonAccessibilityAttributes (name, label, help); } + + public void SetEntryAccessibleTitleUIElement (Atk.Object accessible) + { + pathEntry.Accessible.SetTitleUIElement (accessible); + } + public Atk.Object EntryAccessible { get { return pathEntry.Accessible; diff --git a/main/src/core/MonoDevelop.Ide/MonoDevelop.Components/GtkWorkarounds.cs b/main/src/core/MonoDevelop.Ide/MonoDevelop.Components/GtkWorkarounds.cs index 98cd671d2c..a6c2af0429 100644 --- a/main/src/core/MonoDevelop.Ide/MonoDevelop.Components/GtkWorkarounds.cs +++ b/main/src/core/MonoDevelop.Ide/MonoDevelop.Components/GtkWorkarounds.cs @@ -1538,7 +1538,11 @@ namespace MonoDevelop.Components [DllImport ("libgdk-win32-2.0-0.dll", CallingConvention = CallingConvention.Cdecl)] static extern void gdk_event_free (IntPtr raw); - public static void FreeEvent (IntPtr raw) => gdk_event_free (raw); + public static void FreeEvent (IntPtr raw) + { + if (raw != IntPtr.Zero) + gdk_event_free (raw); + } } public readonly struct KeyboardShortcut : IEquatable<KeyboardShortcut> diff --git a/main/src/core/MonoDevelop.Ide/MonoDevelop.Components/ImageLoader.cs b/main/src/core/MonoDevelop.Ide/MonoDevelop.Components/ImageLoader.cs index dccc878c76..8c26c12d1e 100644 --- a/main/src/core/MonoDevelop.Ide/MonoDevelop.Components/ImageLoader.cs +++ b/main/src/core/MonoDevelop.Ide/MonoDevelop.Components/ImageLoader.cs @@ -28,7 +28,9 @@ using System; using System.IO; using MonoDevelop.Core; +using MonoDevelop.Core.Web; using System.Net; +using System.Net.Http; namespace MonoDevelop.Components { @@ -62,43 +64,30 @@ namespace MonoDevelop.Components var finfo = new FileInfo (cachePath); - WebRequestHelper.GetResponseAsync ( - () => (HttpWebRequest)WebRequest.Create (url), - r => { - if (finfo.Exists) - r.IfModifiedSince = finfo.LastWriteTime; - } - ).ContinueWith (t => { + var client = HttpClientProvider.CreateHttpClient (url); + if (finfo.Exists) + client.DefaultRequestHeaders.IfModifiedSince = finfo.LastWriteTime; + client.GetAsync (url, HttpCompletionOption.ResponseHeadersRead).ContinueWith (async t => { try { - if (t.IsFaulted) { - var wex = t.Exception.Flatten ().InnerException as WebException; - if (wex != null) { - var resp = wex.Response as HttpWebResponse; - if (resp != null) { - // If the errorcode is NotModified the file we cached on disk is still the latest one. - if (resp.StatusCode == HttpStatusCode.NotModified) { - Cleanup (); - return; - } - //if 404, there is no gravatar for the user - if (resp.StatusCode == HttpStatusCode.NotFound) { - image = null; - Cleanup (); - return; - } - } + using (var response = t.Result) { + // If the errorcode is NotModified the file we cached on disk is still the latest one. + if (t.Result.StatusCode == HttpStatusCode.NotModified) { + Cleanup (); + return; + } + //if 404, there is no gravatar for the user + if (t.Result.StatusCode == HttpStatusCode.NotFound) { + image = null; + Cleanup (); + return; } - } - using (var response = t.Result) { finfo.Directory.Create (); // Copy out the new file and reload it if (response.StatusCode == HttpStatusCode.OK) { using (var tempFile = File.Create (tempPath)) { - using (var stream = response.GetResponseStream ()) { - stream.CopyTo (tempFile); - } + await response.Content.CopyToAsync (tempFile); } FileService.SystemRename (tempPath, cachePath); } @@ -108,7 +97,7 @@ namespace MonoDevelop.Components var aex = ex as AggregateException; if (aex != null) ex = aex.Flatten ().InnerException; - var wex = ex as WebException; + var wex = ex?.InnerException as WebException; if (wex != null && wex.Status.IsCannotReachInternetError ()) LoggingService.LogWarning ("Gravatar service could not be reached."); else @@ -116,6 +105,7 @@ namespace MonoDevelop.Components Cleanup (); } finally { try { + client.Dispose (); if (File.Exists (tempPath)) File.Delete (tempPath); } catch (Exception ex) { diff --git a/main/src/core/MonoDevelop.Ide/MonoDevelop.Components/InformationPopoverWidget.cs b/main/src/core/MonoDevelop.Ide/MonoDevelop.Components/InformationPopoverWidget.cs index 707aba213a..446faa8a23 100644 --- a/main/src/core/MonoDevelop.Ide/MonoDevelop.Components/InformationPopoverWidget.cs +++ b/main/src/core/MonoDevelop.Ide/MonoDevelop.Components/InformationPopoverWidget.cs @@ -108,11 +108,24 @@ namespace MonoDevelop.Components ShowPopover (); } + bool WorkaroundNestedDialogFlickering () + { + // There seems to be a problem with Gdk.Window focus events when the parent + // window is transient for another modal window (i.e. dialogs on top of the Ide preferences window). + // A native tooltip seems to confuse Gdk in this case and it rapidly fires LeaveNotify/EnterNotify + // events leading to fast flickering of the tooltip. + if (ParentWindow != null && Surface.ToolkitEngine.GetNativeWindow (ParentWindow) is Gtk.Window gtkWindow) { + if (gtkWindow.TransientFor?.TransientFor != null) + return true; + } + return false; + } + void ShowPopover () { if (popover != null) popover.Destroy (); - popover = TooltipPopoverWindow.Create (); + popover = TooltipPopoverWindow.Create (!WorkaroundNestedDialogFlickering ()); popover.ShowArrow = true; popover.Text = message; popover.Severity = severity; @@ -137,6 +150,13 @@ namespace MonoDevelop.Components DestroyPopover (); } + protected override void OnPreferredSizeChanged () + { + base.OnPreferredSizeChanged (); + if (!Visible) + DestroyPopover (); + } + void DestroyPopover () { if (popover != null) { diff --git a/main/src/core/MonoDevelop.Ide/MonoDevelop.Components/Mac/Messaging.cs b/main/src/core/MonoDevelop.Ide/MonoDevelop.Components/Mac/Messaging.cs index e50cb83ce3..fecd358c0f 100644 --- a/main/src/core/MonoDevelop.Ide/MonoDevelop.Components/Mac/Messaging.cs +++ b/main/src/core/MonoDevelop.Ide/MonoDevelop.Components/Mac/Messaging.cs @@ -42,6 +42,9 @@ namespace MonoDevelop.Components.Mac public static extern void void_objc_msgSend (IntPtr handle, IntPtr sel); [DllImport (LIBOBJC_DYLIB, EntryPoint="objc_msgSend")] + public static extern void void_objc_msgSend_IntPtr (IntPtr receiver, IntPtr selector, IntPtr arg1); + + [DllImport (LIBOBJC_DYLIB, EntryPoint="objc_msgSend")] public static extern bool bool_objc_msgSend_IntPtr_IntPtr (IntPtr handle, IntPtr sel, IntPtr a1, IntPtr a2); [DllImport (LIBOBJC_DYLIB, EntryPoint="objc_msgSendSuper")] diff --git a/main/src/core/MonoDevelop.Ide/MonoDevelop.Components/SearchEntry.cs b/main/src/core/MonoDevelop.Ide/MonoDevelop.Components/SearchEntry.cs index 25d1017485..ecbc055321 100644 --- a/main/src/core/MonoDevelop.Ide/MonoDevelop.Components/SearchEntry.cs +++ b/main/src/core/MonoDevelop.Ide/MonoDevelop.Components/SearchEntry.cs @@ -684,6 +684,13 @@ namespace MonoDevelop.Components } + public void SetEntryAccessibilityAttributes (string name, string label, string help) + { + entry.SetCommonAccessibilityAttributes (name, label, help); + } + + public Atk.Object EntryAccessible => entry.Accessible; + private class FramelessEntry : Entry { private SearchEntry parent; diff --git a/main/src/core/MonoDevelop.Ide/MonoDevelop.Components/Tabstrip.cs b/main/src/core/MonoDevelop.Ide/MonoDevelop.Components/Tabstrip.cs index 335cc4bbeb..84bcfd207e 100644 --- a/main/src/core/MonoDevelop.Ide/MonoDevelop.Components/Tabstrip.cs +++ b/main/src/core/MonoDevelop.Ide/MonoDevelop.Components/Tabstrip.cs @@ -32,6 +32,7 @@ using Cairo; using Gtk; using System.Linq; using MonoDevelop.Components.AtkCocoaHelper; +using MonoDevelop.Core; using MonoDevelop.Ide.Gui; using MonoDevelop.Ide.Fonts; using MonoDevelop.Ide; @@ -258,7 +259,11 @@ namespace MonoDevelop.Components protected override bool OnButtonPressEvent (Gdk.EventButton evnt) { if (hoverTab != null) { - ActiveTab = tabs.IndexOf (hoverTab); + try { + ActiveTab = tabs.IndexOf (hoverTab); + } catch (Exception ex) { + LoggingService.LogInternalError (ex); + } } return base.OnButtonPressEvent (evnt); } diff --git a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.CodeCompletion/CompletionDataList.cs b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.CodeCompletion/CompletionDataList.cs index 9cde49a350..a1d8ba495d 100644 --- a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.CodeCompletion/CompletionDataList.cs +++ b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.CodeCompletion/CompletionDataList.cs @@ -192,7 +192,7 @@ namespace MonoDevelop.Ide.CodeCompletion { // default - word with highest match rating in the list. int idx = -1; - if (DefaultCompletionString != null && DefaultCompletionString.StartsWith (partialWord, StringComparison.OrdinalIgnoreCase)) { + if (DefaultCompletionString != null && string.IsNullOrEmpty(partialWord)) { partialWord = DefaultCompletionString; } CompletionDataMatcher matcher = null; diff --git a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.CodeCompletion/CompletionListWindow.cs b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.CodeCompletion/CompletionListWindow.cs index 9e7fef13ea..a954a6d4b2 100644 --- a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.CodeCompletion/CompletionListWindow.cs +++ b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.CodeCompletion/CompletionListWindow.cs @@ -263,12 +263,6 @@ namespace MonoDevelop.Ide.CodeCompletion controller.HideWindow (); } - [Obsolete("Use CompletionWindowManager.ToggleCategoryMode")] - public void ToggleCategoryMode () - { - controller.ToggleCategoryMode (); - } - /// <summary> /// Gets or sets a value indicating that shift was pressed during enter. /// </summary> diff --git a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.CodeCompletion/RoslynCompletionData.cs b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.CodeCompletion/RoslynCompletionData.cs index 5565aca800..310e017785 100644 --- a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.CodeCompletion/RoslynCompletionData.cs +++ b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.CodeCompletion/RoslynCompletionData.cs @@ -245,6 +245,8 @@ namespace MonoDevelop.Ide.CodeCompletion } } + public static bool RequestInsertText { get; internal set; } + public override void InsertCompletionText (CompletionListWindow window, ref KeyActions ka, KeyDescriptor descriptor) { var document = IdeApp.Workbench.ActiveDocument; @@ -258,7 +260,13 @@ namespace MonoDevelop.Ide.CodeCompletion internal void InsertCompletionText (TextEditor editor, DocumentContext context, ref KeyActions ka, KeyDescriptor descriptor) { - var completionChange = Provider.GetChangeAsync (doc, CompletionItem, null, default (CancellationToken)).WaitAndGetResult (default (CancellationToken)); + CompletionChange completionChange; + try { + RequestInsertText = true; + completionChange = Provider.GetChangeAsync (doc, CompletionItem, null, default (CancellationToken)).WaitAndGetResult (default (CancellationToken)); + } finally { + RequestInsertText = false; + } var currentBuffer = editor.GetPlatformTextBuffer (); var textChange = completionChange.TextChange; diff --git a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.CodeTemplates/CodeTemplate.cs b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.CodeTemplates/CodeTemplate.cs index b2e4f6e945..6e579b74d2 100644 --- a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.CodeTemplates/CodeTemplate.cs +++ b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.CodeTemplates/CodeTemplate.cs @@ -320,11 +320,15 @@ namespace MonoDevelop.Ide.CodeTemplates s = variableDecarations [name].Default; } if (s != null) { - link.AddLink (new TextSegment (sb.Length, s.Length)); - if (isNew) { - link.GetStringFunc = delegate (Func<string, string> callback) { - return expansion.RunFunction (context, callback, variableDecarations [name].Function); - }; + if (!link.IsEditable) { + result.TextLinks.Remove (link); + } else { + link.AddLink (new TextSegment (sb.Length, s.Length)); + if (isNew) { + link.GetStringFunc = delegate (Func<string, string> callback) { + return expansion.RunFunction (context, callback, variableDecarations [name].Function); + }; + } } sb.Append (s); } diff --git a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.CodeTemplates/CodeTemplatePanel.cs b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.CodeTemplates/CodeTemplatePanel.cs index 191409be2b..034be04164 100644 --- a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.CodeTemplates/CodeTemplatePanel.cs +++ b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.CodeTemplates/CodeTemplatePanel.cs @@ -32,6 +32,7 @@ using MonoDevelop.Core; using MonoDevelop.Ide.Gui.Dialogs; using MonoDevelop.Components; using MonoDevelop.Ide.Editor; +using MonoDevelop.Ide.Gui; namespace MonoDevelop.Ide.CodeTemplates { @@ -177,7 +178,7 @@ namespace MonoDevelop.Ide.CodeTemplates GLib.Markup.EscapeText (GettextCatalog.GetString (template.Description)) + ")"; } else { crt.Markup = GLib.Markup.EscapeText (template.Shortcut) + " <span foreground=\"" + - GetColorString (Style.Text (StateType.Insensitive)) + "\">(" + Styles.SecondaryTextColorHexString + "\">(" + GLib.Markup.EscapeText (GettextCatalog.GetString (template.Description)) + ")</span>"; } } @@ -210,11 +211,6 @@ namespace MonoDevelop.Ide.CodeTemplates return templateStore.AppendValues (null, groupName, "<b>" + groupName + "</b>"); } - internal static string GetColorString (Gdk.Color color) - { - return string.Format ("#{0:X02}{1:X02}{2:X02}", color.Red / 256, color.Green / 256, color.Blue / 256); - } - TreeIter InsertTemplate (CodeTemplate template) { TreeIter iter = GetGroup (template.Group); diff --git a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Codons/ItemTemplateExtensionNode.cs b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Codons/ItemTemplateExtensionNode.cs index 5da1f66ff8..7c27b795bd 100644 --- a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Codons/ItemTemplateExtensionNode.cs +++ b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Codons/ItemTemplateExtensionNode.cs @@ -37,6 +37,13 @@ namespace MonoDevelop.Ide.Codons public string ScanPath { get { + // If the path starts with '${' then the path contains a placeholder + // that the StringParserService will replace. The path is returned + // without calling Addin.GetFilePath to prevent the addin directory + // being prefixed to the path. + if (path != null && path.StartsWith ("${", StringComparison.Ordinal)) { + return path; + } return Addin.GetFilePath (path); } } diff --git a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Commands/FileCommands.cs b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Commands/FileCommands.cs index 414a354ff8..48d067d554 100644 --- a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Commands/FileCommands.cs +++ b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Commands/FileCommands.cs @@ -82,7 +82,7 @@ namespace MonoDevelop.Ide.Commands protected override void Run () { var dlg = new OpenFileDialog (GettextCatalog.GetString ("File to Open"), MonoDevelop.Components.FileChooserAction.Open) { - TransientFor = IdeApp.Workbench.RootWindow, + TransientFor = IdeServices.DesktopService.GetFocusedTopLevelWindow (), ShowEncodingSelector = true, ShowViewerSelector = true, }; diff --git a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Commands/ProjectCommands.cs b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Commands/ProjectCommands.cs index 7d9a07dc49..f8464dec79 100644 --- a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Commands/ProjectCommands.cs +++ b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Commands/ProjectCommands.cs @@ -87,7 +87,8 @@ namespace MonoDevelop.Ide.Commands SelectActiveConfiguration, SelectActiveRuntime, EditSolutionItem, - Unload + Unload, + SetStartupProjects } internal class SolutionOptionsHandler : CommandHandler @@ -131,6 +132,40 @@ namespace MonoDevelop.Ide.Commands } } + internal class SetStartupProjectsHandler : CommandHandler + { + protected override void Update (CommandInfo info) + { + info.Enabled = IdeApp.ProjectOperations.CurrentSelectedSolution?.GetAllProjects ()?.Skip (1)?.Any () ?? false; + } + + protected override void Run () + { + var sol = IdeApp.ProjectOperations.CurrentSelectedSolution; + if (sol != null) { + MultiItemSolutionRunConfiguration config = null; + if (!sol.MultiStartupRunConfigurations.Any ()) { + Xwt.Toolkit.NativeEngine.Invoke (() => { + using (var dlg = new NewSolutionRunConfigurationDialog ()) { + if (dlg.Run ().Id == "create") { + config = new MultiItemSolutionRunConfiguration (dlg.RunConfigurationName, dlg.RunConfigurationName); + sol.MultiStartupRunConfigurations.Add (config); + sol.StartupConfiguration = config; + } + } + }); + } else { + config = sol.MultiStartupRunConfigurations.FirstOrDefault (); + } + + // Show run configurations dialog + if (config != null) { + IdeApp.ProjectOperations.ShowRunConfiguration (sol, config); + } + } + } + } + internal class EditReferencesHandler : CommandHandler { protected override void Update (CommandInfo info) diff --git a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Commands/ViewCommands.cs b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Commands/ViewCommands.cs index 9735c4473b..06976176a6 100644 --- a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Commands/ViewCommands.cs +++ b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Commands/ViewCommands.cs @@ -77,6 +77,8 @@ namespace MonoDevelop.Ide.Commands ci.Icon = pad.Icon; ci.UseMarkup = true; ci.Description = string.Format (descFormat, pad.Title); + // We only want these commands enabled if the main window is visible + ci.Enabled = IdeApp.Workbench.RootWindow.Visible; ActionCommand cmd = IdeApp.CommandService.GetActionCommand ("Pad|" + pad.Id); if (cmd != null) ci.AccelKey = cmd.AccelKey; @@ -130,7 +132,7 @@ namespace MonoDevelop.Ide.Commands pad.Visible = true; pad.BringToFront (true); - Counters.PadShown.Inc (new Dictionary<string,string> {{ "Pad", pad.Id }}); + Counters.PadShown.Inc (1, null, new Dictionary<string,object> {{ "Pad", pad.Id }}); } } diff --git a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Composition/CompositionManager.Caching.cs b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Composition/CompositionManager.Caching.cs index 3b02af4b92..9e71fd04a2 100644 --- a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Composition/CompositionManager.Caching.cs +++ b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Composition/CompositionManager.Caching.cs @@ -27,7 +27,9 @@ using System; using System.Collections.Generic; using System.IO; using System.Linq; -using System.Reflection; +using System.Reflection;
+using System.Reflection.Metadata; +using System.Reflection.PortableExecutable; using System.Threading.Tasks; using Microsoft.VisualStudio.Composition; using Mono.Addins; @@ -52,18 +54,20 @@ namespace MonoDevelop.Ide.Composition /// </summary> internal class Caching { - internal static bool writeCache; - readonly ICachingFaultInjector cachingFaultInjector; + readonly ICachingFaultInjector cachingFaultInjector;
Task saveTask; - public HashSet<Assembly> Assemblies { get; } + readonly HashSet<Assembly> loadedAssemblies;
+ public HashSet<Assembly> MefAssemblies { get; }
internal string MefCacheFile { get; } internal string MefCacheControlFile { get; } - public Caching (HashSet<Assembly> assemblies, Func<string, string> getCacheFilePath = null, ICachingFaultInjector cachingFaultInjector = null) + public Caching (HashSet<Assembly> mefAssemblies, Func<string, string> getCacheFilePath = null, ICachingFaultInjector cachingFaultInjector = null) { getCacheFilePath = getCacheFilePath ?? (file => Path.Combine (AddinManager.CurrentAddin.PrivateDataPath, file)); - Assemblies = assemblies; + loadedAssemblies = new HashSet<Assembly> (AppDomain.CurrentDomain.GetAssemblies ());
+
+ MefAssemblies = mefAssemblies; MefCacheFile = getCacheFilePath ("mef-cache"); MefCacheControlFile = getCacheFilePath ("mef-cache-control"); this.cachingFaultInjector = cachingFaultInjector; @@ -99,59 +103,78 @@ namespace MonoDevelop.Ide.Composition // If we don't have a control file, bail early if (!File.Exists (MefCacheControlFile) || !File.Exists (MefCacheFile)) return false; +
+ // Read the cache from disk + var serializer = new JsonSerializer (); + MefControlCache controlCache; - using (var timer = Counters.CompositionCacheControl.BeginTiming ()) { - // Read the cache from disk - var serializer = new JsonSerializer (); - MefControlCache controlCache; - - try { - using (var sr = File.OpenText (MefCacheControlFile)) { - controlCache = (MefControlCache)serializer.Deserialize (sr, typeof(MefControlCache)); - } - } catch (Exception ex) { - LoggingService.LogError ("Could not deserialize MEF cache control", ex); - DeleteFiles (); - return false; - } - - //this can return null (if the cache format changed?). clean up and start over. - if (controlCache == null) {
- LoggingService.LogError ("MEF cache control deserialized as null"); - DeleteFiles (); - return false;
+ try { + using (var sr = File.OpenText (MefCacheControlFile)) { + controlCache = (MefControlCache)serializer.Deserialize (sr, typeof (MefControlCache)); } + } catch (Exception ex) { + LoggingService.LogError ("Could not deserialize MEF cache control", ex); + DeleteFiles (); + return false; + } - var currentAssemblies = new HashSet<string> (Assemblies.Select (asm => asm.Location)); + //this can return null (if the cache format changed?). clean up and start over. + if (controlCache == null) {
+ LoggingService.LogError ("MEF cache control deserialized as null"); + DeleteFiles (); + return false;
+ }
- // Short-circuit on number of assemblies change - if (controlCache.AssemblyInfos.Length != currentAssemblies.Count) + try { + // Short-circuit on number of assemblies change
+ if (controlCache.MefAssemblyInfos.Count != MefAssemblies.Count) + return false; +
+ if (!ValidateAssemblyCacheListIntegrity (MefAssemblies, controlCache.MefAssemblyInfos, cachingFaultInjector))
+ return false; +
+ if (!ValidateAssemblyCacheListIntegrity (loadedAssemblies, controlCache.AdditionalInputAssemblyInfos, cachingFaultInjector))
return false; + } catch (Exception e) {
+ LoggingService.LogError ("MEF cache validation failed", e); + return false; + }
- // Validate that the assemblies match and we have the same time stamps on them. - foreach (var assemblyInfo in controlCache.AssemblyInfos) { - cachingFaultInjector?.FaultAssemblyInfo (assemblyInfo); - if (!currentAssemblies.Contains (assemblyInfo.Location)) - return false; + return true; + }
+
+ static bool ValidateAssemblyCacheListIntegrity (HashSet<Assembly> assemblies, List<MefControlCacheAssemblyInfo> cachedAssemblyInfos, ICachingFaultInjector cachingFaultInjector) + {
+ var currentAssemblies = new Dictionary<string, Guid> (assemblies.Count);
+ foreach (var asm in assemblies) {
+ if (asm.IsDynamic)
+ continue;
- if (File.GetLastWriteTimeUtc (assemblyInfo.Location) != assemblyInfo.LastWriteTimeUtc) - return false; - } - } + currentAssemblies.Add (asm.Location, asm.ManifestModule.ModuleVersionId); + }
+
+ foreach (var assemblyInfo in cachedAssemblyInfos) { + cachingFaultInjector?.FaultAssemblyInfo (assemblyInfo); + if (!currentAssemblies.TryGetValue (assemblyInfo.Location, out var mvid)) + return false; + + if (mvid != assemblyInfo.ModuleVersionId) + return false; + }
return true; } - internal Task Write (RuntimeComposition runtimeComposition, CachedComposition cacheManager) + internal Task Write (RuntimeComposition runtimeComposition, ComposableCatalog catalog, CachedComposition cacheManager) { return Runtime.RunInMainThread (async () => { IdeApp.Exiting += IdeApp_Exiting; saveTask = Task.Run (async () => { try { - await WriteMefCache (runtimeComposition, cacheManager); + await WriteMefCache (runtimeComposition, catalog, cacheManager); } catch (Exception ex) { - LoggingService.LogError ("Failed to write MEF cache", ex); + LoggingService.LogInternalError ("Failed to write MEF cache", ex); } }); await saveTask; @@ -161,10 +184,10 @@ namespace MonoDevelop.Ide.Composition }); } - async Task WriteMefCache (RuntimeComposition runtimeComposition, CachedComposition cacheManager) + async Task WriteMefCache (RuntimeComposition runtimeComposition, ComposableCatalog catalog, CachedComposition cacheManager) { using (var timer = Counters.CompositionSave.BeginTiming ()) { - WriteMefCacheControl (timer); + WriteMefCacheControl (catalog, timer); // Serialize the MEF cache. using (var stream = File.Open (MefCacheFile, FileMode.Create)) { @@ -173,14 +196,41 @@ namespace MonoDevelop.Ide.Composition } } - void WriteMefCacheControl (ITimeTracker timer) - { + void WriteMefCacheControl (ComposableCatalog catalog, ITimeTracker timer) + {
+ var mefAssemblyNames = new HashSet<string> ();
+ var mefAssemblyInfos = new List<MefControlCacheAssemblyInfo> ();
+
+ foreach (var assembly in MefAssemblies) { + mefAssemblyNames.Add (assembly.GetName ().ToString ());
+
+ mefAssemblyInfos.Add (new MefControlCacheAssemblyInfo { + Location = assembly.Location, + ModuleVersionId = assembly.ManifestModule.ModuleVersionId, + }); + }
+
+ var additionalInputAssemblies = new List<MefControlCacheAssemblyInfo> ();
+ var loadedMap = loadedAssemblies.ToDictionary (x => x.GetName ().ToString (), x => x);
+
+ foreach (var asm in catalog.GetInputAssemblies ()) {
+ var assemblyName = asm.ToString (); + if (mefAssemblyNames.Contains (assemblyName))
+ continue;
+
+ bool found = loadedMap.TryGetValue (assemblyName, out var assembly);
+ System.Diagnostics.Debug.Assert (found);
+
+ additionalInputAssemblies.Add (new MefControlCacheAssemblyInfo { + Location = assembly.Location,
+ ModuleVersionId = assembly.ManifestModule.ModuleVersionId, + }); + } + // Create cache control data. var controlCache = new MefControlCache { - AssemblyInfos = Assemblies.Select (asm => new MefControlCacheAssemblyInfo { - Location = asm.Location, - LastWriteTimeUtc = File.GetLastWriteTimeUtc (asm.Location), - }).ToArray (), + MefAssemblyInfos = mefAssemblyInfos,
+ AdditionalInputAssemblyInfos = additionalInputAssemblies, }; var serializer = new JsonSerializer (); @@ -197,7 +247,10 @@ namespace MonoDevelop.Ide.Composition class MefControlCache { [JsonRequired] - public MefControlCacheAssemblyInfo [] AssemblyInfos; + public List<MefControlCacheAssemblyInfo> MefAssemblyInfos; + + [JsonRequired]
+ public List<MefControlCacheAssemblyInfo> AdditionalInputAssemblyInfos; } [Serializable] @@ -207,7 +260,7 @@ namespace MonoDevelop.Ide.Composition public string Location; [JsonRequired] - public DateTime LastWriteTimeUtc; + public Guid ModuleVersionId; } } } diff --git a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Composition/CompositionManager.cs b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Composition/CompositionManager.cs index 39e3eaaf25..299fbac057 100644 --- a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Composition/CompositionManager.cs +++ b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Composition/CompositionManager.cs @@ -107,34 +107,56 @@ namespace MonoDevelop.Ide.Composition async Task InitializeInstanceAsync () {
- var assemblies = ReadAssembliesFromAddins ();
- var caching = new Caching (assemblies);
+ var timings = new Dictionary<string, long> ();
+ var metadata = new CompositionLoadMetadata (timings);
- // Try to use cached MEF data
- if (caching.CanUse ()) {
- RuntimeComposition = await TryCreateRuntimeCompositionFromCache (caching);
- } + using (var timer = Counters.CompositionLoad.BeginTiming (metadata)) { + var fullTimer = System.Diagnostics.Stopwatch.StartNew ();
+ var stepTimer = System.Diagnostics.Stopwatch.StartNew ();
+
+ var mefAssemblies = ReadAssembliesFromAddins (timer);
+ timings ["ReadFromAddins"] = stepTimer.ElapsedMilliseconds;
+ stepTimer.Restart ();
+ + var caching = new Caching (mefAssemblies); + + // Try to use cached MEF data + + var canUse = metadata.ValidCache = caching.CanUse (); + if (canUse) {
+ LoggingService.LogInfo ("Creating MEF composition from cache"); + RuntimeComposition = await TryCreateRuntimeCompositionFromCache (caching); + }
+ timings ["LoadFromCache"] = stepTimer.ElapsedMilliseconds;
+ stepTimer.Restart (); + + // Otherwise fallback to runtime discovery. + if (RuntimeComposition == null) {
+ LoggingService.LogInfo ("Creating MEF composition from runtime"); + var (runtimeComposition, catalog) = await CreateRuntimeCompositionFromDiscovery (caching, timer);
+ RuntimeComposition = runtimeComposition; - // Otherwise fallback to runtime discovery. - if (RuntimeComposition == null) {
- RuntimeComposition = await CreateRuntimeCompositionFromDiscovery (caching);
+ CachedComposition cacheManager = new CachedComposition (); + caching.Write (RuntimeComposition, catalog, cacheManager).Ignore (); + }
+ timings ["LoadRuntimeComposition"] = stepTimer.ElapsedMilliseconds;
+ stepTimer.Restart ();
+ + ExportProviderFactory = RuntimeComposition.CreateExportProviderFactory (); + ExportProvider = ExportProviderFactory.CreateExportProvider (); + HostServices = MefV1HostServices.Create (ExportProvider.AsExportProvider ()); + ExportProviderV1 = NetFxAdapters.AsExportProvider (ExportProvider);
- CachedComposition cacheManager = new CachedComposition (); - caching.Write (RuntimeComposition, cacheManager).Ignore ();
+ timings ["CreateServices"] = stepTimer.ElapsedMilliseconds;
+ metadata.Duration = fullTimer.ElapsedMilliseconds;
}
- - ExportProviderFactory = RuntimeComposition.CreateExportProviderFactory (); - ExportProvider = ExportProviderFactory.CreateExportProvider (); - HostServices = MefV1HostServices.Create (ExportProvider.AsExportProvider ()); - ExportProviderV1 = NetFxAdapters.AsExportProvider (ExportProvider);
}
internal static async Task<RuntimeComposition> TryCreateRuntimeCompositionFromCache (Caching caching)
{
- CachedComposition cacheManager = new CachedComposition ();
+ var cacheManager = new CachedComposition ();
try { - using (Counters.CompositionCache.BeginTiming ()) using (var cacheStream = caching.OpenCacheStream ()) { return await cacheManager.LoadRuntimeCompositionAsync (cacheStream, StandardResolver); }
@@ -145,73 +167,72 @@ namespace MonoDevelop.Ide.Composition return null;
}
- internal static async Task<RuntimeComposition> CreateRuntimeCompositionFromDiscovery (Caching caching)
+ internal static async Task<(RuntimeComposition, ComposableCatalog)> CreateRuntimeCompositionFromDiscovery (Caching caching, ITimeTracker timer = null)
{
- using (var timer = Counters.CompositionDiscovery.BeginTiming ()) {
- var parts = await Discovery.CreatePartsAsync (caching.Assemblies);
- timer.Trace ("Composition parts discovered");
-
- ComposableCatalog catalog = ComposableCatalog.Create (StandardResolver)
- .WithCompositionService ()
- .AddParts (parts);
-
- var discoveryErrors = catalog.DiscoveredParts.DiscoveryErrors;
- if (!discoveryErrors.IsEmpty) {
- foreach (var error in discoveryErrors) {
- LoggingService.LogInfo ("MEF discovery error", error);
- }
+ var parts = await Discovery.CreatePartsAsync (caching.MefAssemblies);
+ timer?.Trace ("Composition parts discovered");
+
+ ComposableCatalog catalog = ComposableCatalog.Create (StandardResolver)
+ .WithCompositionService ()
+ .AddParts (parts);
- // throw new ApplicationException ("MEF discovery errors");
+ var discoveryErrors = catalog.DiscoveredParts.DiscoveryErrors;
+ if (!discoveryErrors.IsEmpty) {
+ foreach (var error in discoveryErrors) {
+ LoggingService.LogInfo ("MEF discovery error", error);
}
- CompositionConfiguration configuration = CompositionConfiguration.Create (catalog);
+ // throw new ApplicationException ("MEF discovery errors");
+ }
- if (!configuration.CompositionErrors.IsEmpty) {
- // capture the errors in an array for easier debugging
- var errors = configuration.CompositionErrors.SelectMany (e => e).ToArray ();
- foreach (var error in errors) {
- LoggingService.LogInfo ("MEF composition error: " + error.Message);
- }
+ CompositionConfiguration configuration = CompositionConfiguration.Create (catalog);
- // For now while we're still transitioning to VSMEF it's useful to work
- // even if the composition has some errors. TODO: re-enable this.
- //configuration.ThrowOnErrors ();
+ if (!configuration.CompositionErrors.IsEmpty) {
+ // capture the errors in an array for easier debugging
+ var errors = configuration.CompositionErrors.SelectMany (e => e).ToArray ();
+ foreach (var error in errors) {
+ LoggingService.LogInfo ("MEF composition error: " + error.Message);
}
- timer.Trace ("Composition configured");
-
- var runtimeComposition = RuntimeComposition.CreateRuntimeComposition (configuration);
- return runtimeComposition;
+ // For now while we're still transitioning to VSMEF it's useful to work
+ // even if the composition has some errors. TODO: re-enable this.
+ //configuration.ThrowOnErrors ();
}
+ timer?.Trace ("Composition configured");
+
+ var runtimeComposition = RuntimeComposition.CreateRuntimeComposition (configuration);
+ timer?.Trace ("Composition created");
+
+ return (runtimeComposition, catalog);
}
- internal static HashSet<Assembly> ReadAssembliesFromAddins ()
+ internal static HashSet<Assembly> ReadAssembliesFromAddins (ITimeTracker<CompositionLoadMetadata> timer = null)
{
- using (var timer = Counters.CompositionAddinLoad.BeginTiming ()) {
- var assemblies = new HashSet<Assembly> ();
- ReadAssemblies (assemblies, "/MonoDevelop/Ide/TypeService/PlatformMefHostServices", timer);
- ReadAssemblies (assemblies, "/MonoDevelop/Ide/TypeService/MefHostServices", timer);
- ReadAssemblies (assemblies, "/MonoDevelop/Ide/Composition", timer);
- return assemblies;
- }
+ var readAssemblies = new HashSet<Assembly> ();
+
+ timer?.Trace ("Start: reading assemblies");
+ ReadAssemblies (readAssemblies, "/MonoDevelop/Ide/TypeService/PlatformMefHostServices");
+ ReadAssemblies (readAssemblies, "/MonoDevelop/Ide/TypeService/MefHostServices");
+ ReadAssemblies (readAssemblies, "/MonoDevelop/Ide/Composition");
+ timer?.Trace ("Start: end reading assemblies");
+
+ return readAssemblies;
- void ReadAssemblies (HashSet<Assembly> assemblies, string extensionPath, ITimeTracker timer)
+ void ReadAssemblies (HashSet<Assembly> assemblies, string extensionPath)
{
foreach (var node in AddinManager.GetExtensionNodes (extensionPath)) {
if (node is AssemblyExtensionNode assemblyNode) {
try {
string id = assemblyNode.Addin.Id;
string assemblyName = assemblyNode.FileName;
- timer.Trace ("Start: " + assemblyName);
// Make sure the add-in that registered the assembly is loaded, since it can bring other
// other assemblies required to load this one
+
AddinManager.LoadAddin (null, id);
var assemblyFilePath = assemblyNode.Addin.GetFilePath (assemblyNode.FileName);
var assembly = Runtime.LoadAssemblyFrom (assemblyFilePath);
assemblies.Add (assembly);
-
- timer.Trace ("Loaded: " + assemblyName);
} catch (Exception e) {
LoggingService.LogError ("Composition can't load assembly: " + assemblyNode.FileName, e);
}
diff --git a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Desktop/PlatformService.cs b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Desktop/PlatformService.cs index 5f84a7b3b8..f337143d22 100644 --- a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Desktop/PlatformService.cs +++ b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Desktop/PlatformService.cs @@ -59,13 +59,6 @@ namespace MonoDevelop.Ide.Desktop public abstract string Name { get; } - [Obsolete] - public virtual string DefaultControlLeftRightBehavior { - get { - return "MonoDevelop"; - } - } - public virtual void Initialize () { } @@ -505,6 +498,25 @@ namespace MonoDevelop.Ide.Desktop { } + public virtual Window GetParentForModalWindow () + { + foreach (var w in Gtk.Window.ListToplevels ()) + if (w.Visible && w.HasToplevelFocus && w.Modal) + return w; + + return GetFocusedTopLevelWindow (); + } + + public virtual Window GetFocusedTopLevelWindow () + { + // use the first "normal" toplevel window (skipping docks, popups, etc.) or the main IDE window + Window gtkToplevel = Gtk.Window.ListToplevels ().FirstOrDefault (w => w.Visible && w.HasToplevelFocus && + (w.TypeHint == Gdk.WindowTypeHint.Dialog || + w.TypeHint == Gdk.WindowTypeHint.Normal || + w.TypeHint == Gdk.WindowTypeHint.Utility)); + return gtkToplevel ?? IdeApp.Workbench.RootWindow; + } + public virtual void FocusWindow (Window window) { window.GrabFocus (); diff --git a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Editor.Extension/EditorFormattingServiceTextEditorExtension.cs b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Editor.Extension/EditorFormattingServiceTextEditorExtension.cs index a0f99acc8c..becedf94c4 100644 --- a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Editor.Extension/EditorFormattingServiceTextEditorExtension.cs +++ b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Editor.Extension/EditorFormattingServiceTextEditorExtension.cs @@ -32,6 +32,7 @@ using Microsoft.CodeAnalysis.Shared.Extensions; using System.Threading.Tasks; using System.Collections.Generic; using Microsoft.CodeAnalysis.Text; +using System.Linq; namespace MonoDevelop.Ide.Editor.Extension { @@ -66,7 +67,7 @@ namespace MonoDevelop.Ide.Editor.Extension bool TryFormat (IEditorFormattingService formattingService, char typedChar, int position, bool formatOnReturn, CancellationToken cancellationToken) { var document = DocumentContext.AnalysisDocument; - IList<TextChange> changes; + IEnumerable<TextChange> changes; if (formatOnReturn) { if (!formattingService.SupportsFormatOnReturn) return false; @@ -75,12 +76,15 @@ namespace MonoDevelop.Ide.Editor.Extension if (!formattingService.SupportsFormattingOnTypedCharacter (document, typedChar)) return false; changes = formattingService.GetFormattingChangesAsync (document, typedChar, position, cancellationToken).WaitAndGetResult (cancellationToken); + var line = Editor.GetLineByOffset (position); + if (typedChar == '#') { + changes = changes.Where (c => c.Span.Start >= line.Offset); + } } - if (changes == null || changes.Count == 0) { + if (changes == null) { return false; } - Editor.ApplyTextChanges (changes); return true; } diff --git a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Editor.Extension/KeyDescriptor.cs b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Editor.Extension/KeyDescriptor.cs index e3c42db2d7..d11ed56097 100644 --- a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Editor.Extension/KeyDescriptor.cs +++ b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Editor.Extension/KeyDescriptor.cs @@ -59,6 +59,74 @@ namespace MonoDevelop.Ide.Editor.Extension return string.Format ("[KeyDescriptor: SpecialKey={0}, KeyChar={1}, ModifierKeys={2}]", SpecialKey, KeyChar, ModifierKeys); } + #region XWT + public static KeyDescriptor FromXwt (Xwt.Key key, char c, Xwt.ModifierKeys modifiers) + { + return new KeyDescriptor (ConvertKey (key), c, ConvertModifiers (modifiers), Tuple.Create (key, modifiers)); + } + + static SpecialKey ConvertKey (Xwt.Key key) + { + switch (key) { + case Xwt.Key.BackSpace: + return SpecialKey.BackSpace; + case Xwt.Key.Tab: + case Xwt.Key.NumPadTab: + return SpecialKey.Tab; + case Xwt.Key.Return: + case Xwt.Key.NumPadEnter: + return SpecialKey.Return; + case Xwt.Key.Escape: + return SpecialKey.Escape; + case Xwt.Key.Space: + case Xwt.Key.NumPadSpace: + return SpecialKey.Space; + case Xwt.Key.PageUp: + return SpecialKey.PageUp; + case Xwt.Key.PageDown: + return SpecialKey.PageDown; + case Xwt.Key.End: + case Xwt.Key.NumPadEnd: + return SpecialKey.End; + case Xwt.Key.Home: + case Xwt.Key.NumPadHome: + return SpecialKey.Home; + case Xwt.Key.Left: + case Xwt.Key.NumPadLeft: + return SpecialKey.Left; + case Xwt.Key.Up: + case Xwt.Key.NumPadUp: + return SpecialKey.Up; + case Xwt.Key.Right: + case Xwt.Key.NumPadRight: + return SpecialKey.Right; + case Xwt.Key.Down: + case Xwt.Key.NumPadDown: + return SpecialKey.Down; + case Xwt.Key.Delete: + case Xwt.Key.NumPadDelete: + return SpecialKey.Delete; + } + return SpecialKey.None; + } + + static ModifierKeys ConvertModifiers (Xwt.ModifierKeys s) + { + ModifierKeys m = ModifierKeys.None; + if ((s & Xwt.ModifierKeys.Shift) != 0) + m |= ModifierKeys.Shift; + if ((s & Xwt.ModifierKeys.Control) != 0) + m |= ModifierKeys.Control; + if ((s & Xwt.ModifierKeys.Alt) != 0) + m |= ModifierKeys.Alt; + if ((s & Xwt.ModifierKeys.Command) != 0) + m |= ModifierKeys.Command; + if ((s & Xwt.ModifierKeys.Command) != 0) + m |= ModifierKeys.Command; + return m; + } + #endregion + #region GTK public static KeyDescriptor FromGtk (Gdk.Key key, char ch, Gdk.ModifierType state) { diff --git a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Editor.Highlighting/EditorTheme.cs b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Editor.Highlighting/EditorTheme.cs index d35c26d936..8b06eff4a5 100644 --- a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Editor.Highlighting/EditorTheme.cs +++ b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Editor.Highlighting/EditorTheme.cs @@ -332,5 +332,10 @@ namespace MonoDevelop.Ide.Editor.Highlighting } EditorTheme IEditorThemeProvider.GetEditorTheme () => this; + + public override string ToString () + { + return string.Format ("[EditorTheme: Name={0}]", Name); + } } }
\ No newline at end of file diff --git a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Editor.Highlighting/ISyntaxHighlighting.cs b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Editor.Highlighting/ISyntaxHighlighting.cs index 6ff8b67139..74b3652f72 100644 --- a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Editor.Highlighting/ISyntaxHighlighting.cs +++ b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Editor.Highlighting/ISyntaxHighlighting.cs @@ -61,6 +61,11 @@ namespace MonoDevelop.Ide.Editor.Highlighting TextSegment = textSegment; Segments = segments; } + + public override string ToString () + { + return string.Format ("[HighlightedLine: TextSegment={0}, #Segments={1}, IsContinuedBeyondLineEnd={2}]", TextSegment, Segments?.Count, IsContinuedBeyondLineEnd); + } } /// <summary> diff --git a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Editor.Highlighting/SyntaxHighlightingService.cs b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Editor.Highlighting/SyntaxHighlightingService.cs index 63f28b5746..d4c5ab9f4b 100644 --- a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Editor.Highlighting/SyntaxHighlightingService.cs +++ b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Editor.Highlighting/SyntaxHighlightingService.cs @@ -711,7 +711,10 @@ namespace MonoDevelop.Ide.Editor.Highlighting { HslColor result; if (!style.TryGetColor (key, out result)) { - DefaultColorStyle.TryGetColor (key, out result); + if (!DefaultColorStyle.TryGetColor (key, out result)) { + LoggingService.LogError ("SyntaxHighlightingService.GetColor color " + key + " not found in theme " + style); + return default; + } } return result; } diff --git a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Editor/Caret.cs b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Editor/Caret.cs index db7e5009c5..acd90387d3 100644 --- a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Editor/Caret.cs +++ b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Editor/Caret.cs @@ -73,10 +73,12 @@ namespace MonoDevelop.Ide.Editor public class CaretLocationEventArgs : DocumentLocationEventArgs { + public Microsoft.VisualStudio.Text.ITextSnapshot Snapshot { get; } public CaretChangeReason CaretChangeReason { get; } - public CaretLocationEventArgs (DocumentLocation location, CaretChangeReason reason) : base (location) + public CaretLocationEventArgs (DocumentLocation location, Microsoft.VisualStudio.Text.ITextSnapshot textSnapshot, CaretChangeReason reason) : base (location) { + Snapshot = textSnapshot; CaretChangeReason = reason; } } diff --git a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Editor/InternalExtensionAPI/ITextEditorImpl.cs b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Editor/InternalExtensionAPI/ITextEditorImpl.cs index 0d963f174e..c80ef8eb79 100644 --- a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Editor/InternalExtensionAPI/ITextEditorImpl.cs +++ b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Editor/InternalExtensionAPI/ITextEditorImpl.cs @@ -199,7 +199,9 @@ namespace MonoDevelop.Ide.Editor string GetMarkup (int offset, int length, MarkupOptions options); - IndentationTracker IndentationTracker { get; set; } + Task<string> GetMarkupAsync (int offset, int length, MarkupOptions options, CancellationToken cancellationToken); + + IndentationTracker IndentationTracker { get; set; } void SetSelectionSurroundingProvider (SelectionSurroundingProvider surroundingProvider); void SetTextPasteHandler (TextPasteHandler textPasteHandler); diff --git a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Editor/OSXEditor.cs b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Editor/OSXEditor.cs index 5e97624536..fc35dba10c 100644 --- a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Editor/OSXEditor.cs +++ b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Editor/OSXEditor.cs @@ -66,9 +66,12 @@ namespace MonoDevelop.Ide.Editor { var editorFont = Xwt.Drawing.Font.FromName(fontName); - using (var nsFont = NSFont.FromFontName(editorFont.Family, (nfloat)editorFont.Size)) - using (var lm = new NSLayoutManager()) - return lm.DefaultLineHeightForFont(nsFont); + using (var nsFont = NSFont.FromFontName (editorFont.Family, (nfloat)editorFont.Size)) { + if (nsFont == null) + return -1; + using (var lm = new NSLayoutManager ()) + return lm.DefaultLineHeightForFont (nsFont); + } } } } diff --git a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Editor/TextEditor.cs b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Editor/TextEditor.cs index 868e8c603b..900ef6baf4 100644 --- a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Editor/TextEditor.cs +++ b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Editor/TextEditor.cs @@ -1226,10 +1226,13 @@ namespace MonoDevelop.Ide.Editor return GetContents<T> ().FirstOrDefault (); } - public IEnumerable<T> GetContents<T>() where T : class + public IEnumerable<T> GetContents<T> () where T : class { - T result = textEditorImpl as T; - if (result != null) + if (isDisposed) { + LoggingService.LogError ($"Error retrieving TextEditor.GetContents<{typeof(T)}>\n {Environment.StackTrace}"); + yield break; + } + if (textEditorImpl is T result) yield return result; var ext = textEditorImpl.EditorExtension; while (ext != null) { @@ -1243,6 +1246,10 @@ namespace MonoDevelop.Ide.Editor public IEnumerable<object> GetContents (Type type) { var res = Enumerable.Empty<object> (); + if (isDisposed) { + LoggingService.LogError ($"Error retrieving TextEditor.GetContents({type})\n {Environment.StackTrace}"); + return res; + } if (type.IsInstanceOfType (textEditorImpl)) res = res.Concat (textEditorImpl); @@ -1262,7 +1269,7 @@ namespace MonoDevelop.Ide.Editor return GetMarkup (offset, length, new MarkupOptions (MarkupFormat.Pango, fitIdeStyle)); } - [Obsolete ("Use GetMarkup")] + [Obsolete ("Use GetMarkupAsync")] public string GetPangoMarkup (ISegment segment, bool fitIdeStyle = false) { if (segment == null) @@ -1286,6 +1293,22 @@ namespace MonoDevelop.Ide.Editor return textEditorImpl.GetMarkup (segment.Offset, segment.Length, options); } + public Task<string> GetMarkupAsync (int offset, int length, MarkupOptions options, CancellationToken cancellationToken = default) + { + if (options == null) + throw new ArgumentNullException (nameof (options)); + return textEditorImpl.GetMarkupAsync (offset, length, options, cancellationToken); + } + + public Task<string> GetMarkupAsync (ISegment segment, MarkupOptions options, CancellationToken cancellationToken = default) + { + if (options == null) + throw new ArgumentNullException (nameof (options)); + if (segment == null) + throw new ArgumentNullException (nameof (segment)); + return textEditorImpl.GetMarkupAsync (segment.Offset, segment.Length, options, cancellationToken); + } + public static implicit operator Microsoft.CodeAnalysis.Text.SourceText (TextEditor editor) { return new MonoDevelopSourceText (editor); diff --git a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Editor/TextEditorViewContent.cs b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Editor/TextEditorViewContent.cs index b47ec23c96..9df4b77a04 100644 --- a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Editor/TextEditorViewContent.cs +++ b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Editor/TextEditorViewContent.cs @@ -1,4 +1,4 @@ -// +// // TextEditorViewContent.cs // // Author: @@ -115,7 +115,7 @@ namespace MonoDevelop.Ide.Editor } } // Calling base class after initializing the editor extension chain - // so that it picks additional content from the extensionasdfas dfasdf asdf asdf asdf asdf sadfsaf + // so that it picks additional content from the extension base.OnContentChanged (); } @@ -125,7 +125,7 @@ namespace MonoDevelop.Ide.Editor if (textEditor != null) { if (FilePath != textEditorImpl.ContentName && !string.IsNullOrEmpty (textEditorImpl.ContentName)) AutoSave.RemoveAutoSaveFile (textEditorImpl.ContentName); - textEditor.FileName = FilePath; + textEditor.FileName = FilePath; // TOTEST: VSTS #770920 if (documentContext != null) textEditor.InitializeExtensionChain (documentContext); UpdateTextEditorOptions (null, null); @@ -372,8 +372,10 @@ namespace MonoDevelop.Ide.Editor } #endregion - - + protected override void OnGrabFocus () + { + textEditor.GrabFocus (); + } } }
\ No newline at end of file diff --git a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.ExternalTools/ExternalToolPanel.cs b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.ExternalTools/ExternalToolPanel.cs index 7e984d5b06..803b6f3985 100644 --- a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.ExternalTools/ExternalToolPanel.cs +++ b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.ExternalTools/ExternalToolPanel.cs @@ -153,7 +153,7 @@ namespace MonoDevelop.Ide.ExternalTools GettextCatalog.GetString ("Enter the title for this command")); browseButton.Accessible.SetCommonAttributes ("ExternalTools.Command", null, GettextCatalog.GetString ("Enter or select the path for the external command")); - browseButton.Accessible.SetTitleUIElement (commandLabel.Accessible); + browseButton.SetEntryAccessibleTitleUIElement (commandLabel.Accessible); argumentTextBox.SetCommonAccessibilityAttributes ("ExternalTools.Arguments", "", GettextCatalog.GetString ("Enter the arguments for the external command")); argumentTextBox.SetAccessibilityLabelRelationship (argumentLabel); diff --git a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.FindInFiles/FindInFilesDialog.cs b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.FindInFiles/FindInFilesDialog.cs index 58f710ad14..b33d81cc41 100644 --- a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.FindInFiles/FindInFilesDialog.cs +++ b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.FindInFiles/FindInFilesDialog.cs @@ -242,33 +242,37 @@ namespace MonoDevelop.Ide.FindInFiles void SetupAccessibility () { comboboxentryFind.SetCommonAccessibilityAttributes ("FindInFilesDialog.comboboxentryFind", - "Find", + labelFind, GettextCatalog.GetString ("Enter string to find")); - comboboxentryFind.SetAccessibilityLabelRelationship (labelFind); + + comboboxScope.SetCommonAccessibilityAttributes ("FindInFilesDialog.comboboxScope", + labelScope, + GettextCatalog.GetString ("Select where to search")); } void SetupAccessibilityForReplace () { comboboxentryReplace.SetCommonAccessibilityAttributes ("FindInFilesDialog.comboboxentryReplace", - "Replace", + labelReplace, GettextCatalog.GetString ("Enter string to replace")); - comboboxentryReplace.SetAccessibilityLabelRelationship (labelReplace); } void SetupAccessibilityForPath () { comboboxentryPath.SetCommonAccessibilityAttributes ("FindInFilesDialog.comboboxentryPath", - "Path", + labelPath, GettextCatalog.GetString ("Enter the Path")); - comboboxentryPath.SetAccessibilityLabelRelationship (labelPath); + + buttonBrowsePaths.SetCommonAccessibilityAttributes ("FindInFilesDialog.buttonBrowsePaths", + GettextCatalog.GetString ("Browse Path"), + GettextCatalog.GetString ("Select a folder")); } void SetupAccessibilityForSearch () { - searchentryFileMask.SetCommonAccessibilityAttributes ("FindInFilesDialog.searchentryFileMask", - "File Mask", + searchentryFileMask.SetEntryAccessibilityAttributes ("FindInFilesDialog.searchentryFileMask", + labelFileMask.Text, GettextCatalog.GetString ("Enter the file mask")); - searchentryFileMask.SetAccessibilityLabelRelationship (labelFileMask); } static void TableAddRow (Table table, uint row, Widget column1, Widget column2) @@ -432,9 +436,7 @@ namespace MonoDevelop.Ide.FindInFiles labelPath.MnemonicWidget = comboboxentryPath; - SetupAccessibilityForPath (); - - buttonBrowsePaths = new Button { Label = "..." }; + buttonBrowsePaths = new Button { Label = "…" }; buttonBrowsePaths.Clicked += ButtonBrowsePathsClicked; buttonBrowsePaths.Show (); hboxPath.PackStart (buttonBrowsePaths, false, false, 0); @@ -454,6 +456,8 @@ namespace MonoDevelop.Ide.FindInFiles checkbuttonRecursively.Show (); TableAddRow (tableFindAndReplace, row, null, checkbuttonRecursively); + + SetupAccessibilityForPath (); } void HideDirectoryPathUI () diff --git a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.FindInFiles/SearchResult.cs b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.FindInFiles/SearchResult.cs index 6e7a803edd..965089b4e2 100644 --- a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.FindInFiles/SearchResult.cs +++ b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.FindInFiles/SearchResult.cs @@ -121,7 +121,7 @@ namespace MonoDevelop.Ide.FindInFiles const int maximumMarkupLength = 78; - void CreateMarkup (SearchResultWidget widget, TextEditor doc, Editor.IDocumentLine line) + async void CreateMarkup (SearchResultWidget widget, TextEditor doc, Editor.IDocumentLine line) { int startIndex = 0, endIndex = 0; @@ -175,35 +175,31 @@ namespace MonoDevelop.Ide.FindInFiles markup = FormatMarkup (PangoHelper.ColorMarkupBackground (selectedMarkup, (int)startIndex, (int)endIndex, searchColor), trimStart, trimEnd, tabSize); selectedMarkup = FormatMarkup (PangoHelper.ColorMarkupBackground (selectedMarkup, (int)startIndex, (int)endIndex, selectedSearchColor), trimStart, trimEnd, tabSize); - Task.Run (delegate { - var newMarkup = doc.GetMarkup (line.Offset + markupStartOffset + indent, length, new MarkupOptions (MarkupFormat.Pango)); - Runtime.RunInMainThread (delegate { - newMarkup = widget.AdjustColors (newMarkup); + var newMarkup = await doc.GetMarkupAsync (line.Offset + markupStartOffset + indent, length, new MarkupOptions (MarkupFormat.Pango)); + newMarkup = widget.AdjustColors (newMarkup); - try { - double delta = Math.Abs (b1 - b2); - if (delta < 0.1) { - var color1 = SyntaxHighlightingService.GetColor (widget.HighlightStyle, EditorThemeColors.FindHighlight); - if (color1.L + 0.5 > 1.0) { - color1.L -= 0.5; - } else { - color1.L += 0.5; - } - searchColor = color1; - } - if (startIndex != endIndex) { - newMarkup = PangoHelper.ColorMarkupBackground (newMarkup, (int)startIndex, (int)endIndex, searchColor); - } - } catch (Exception e) { - LoggingService.LogError ("Error while setting the text renderer markup to: " + newMarkup, e); + try { + double delta = Math.Abs (b1 - b2); + if (delta < 0.1) { + var color1 = SyntaxHighlightingService.GetColor (widget.HighlightStyle, EditorThemeColors.FindHighlight); + if (color1.L + 0.5 > 1.0) { + color1.L -= 0.5; + } else { + color1.L += 0.5; } + searchColor = color1; + } + if (startIndex != endIndex) { + newMarkup = PangoHelper.ColorMarkupBackground (newMarkup, (int)startIndex, (int)endIndex, searchColor); + } + } catch (Exception e) { + LoggingService.LogError ("Error while setting the text renderer markup to: " + newMarkup, e); + } - newMarkup = FormatMarkup (newMarkup, trimStart, trimEnd, tabSize); + newMarkup = FormatMarkup (newMarkup, trimStart, trimEnd, tabSize); - this.markup = newMarkup; - widget.QueueDraw (); - }); - }); + this.markup = newMarkup; + widget.QueueDraw (); } static string FormatMarkup (string str, bool trimeStart, bool trimEnd, int tabSize) diff --git a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Gui.Dialogs/CommonAboutDialog.cs b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Gui.Dialogs/CommonAboutDialog.cs index 33b444c068..754b69e3f6 100644 --- a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Gui.Dialogs/CommonAboutDialog.cs +++ b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Gui.Dialogs/CommonAboutDialog.cs @@ -59,7 +59,6 @@ namespace MonoDevelop.Ide.Gui.Dialogs { Name = "wizard_dialog"; Title = string.Format (GettextCatalog.GetString ("About {0}"), BrandingService.ApplicationLongName); - TransientFor = IdeApp.Workbench.RootWindow; AllowGrow = false; HasSeparator = false; BorderWidth = 0; @@ -118,21 +117,22 @@ namespace MonoDevelop.Ide.Gui.Dialogs ChangeColor (cw); } } - + static CommonAboutDialog instance; - + public static void ShowAboutDialog () { if (Platform.IsMac) { if (instance == null) { instance = new CommonAboutDialog (); - MessageService.PlaceDialog (instance, IdeApp.Workbench.RootWindow); + MessageService.PlaceDialog (instance, WelcomePage.WelcomePageService.WelcomeWindow ?? IdeApp.Workbench.RootWindow); instance.Response += delegate { instance.Destroy (); instance.Dispose (); instance = null; }; } + instance.Present (); return; } diff --git a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Gui.Dialogs/NewFolderDialog.cs b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Gui.Dialogs/NewFolderDialog.cs new file mode 100644 index 0000000000..03785e23a7 --- /dev/null +++ b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Gui.Dialogs/NewFolderDialog.cs @@ -0,0 +1,221 @@ +// +// NewFolderDialog.cs +// +// Author: +// Matt Ward <matt.ward@microsoft.com> +// +// Copyright (c) 2019 Microsoft +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +using System; +using System.IO; +using System.Threading.Tasks; +using MonoDevelop.Components; +using MonoDevelop.Components.AtkCocoaHelper; +using MonoDevelop.Core; +using MonoDevelop.Ide.Gui.Pads.ProjectPad; +using MonoDevelop.Ide.Tasks; +using Xwt; + +namespace MonoDevelop.Ide.Gui.Dialogs +{ + class NewFolderDialog : Xwt.Dialog + { + readonly FilePath parentFolder; + TextEntry folderNameTextEntry; + DialogButton addButton; + InformationPopoverWidget warningPopover; + + public NewFolderDialog (FilePath parentFolder) + { + this.parentFolder = parentFolder; + + Build (); + + folderNameTextEntry.Text = GetDefaultFolderName (); + folderNameTextEntry.SetFocus (); + folderNameTextEntry.Changed += FolderNameTextEntryChanged; + addButton.Clicked += AddButtonClicked; + } + + public FilePath NewFolderCreated { get; private set; } + + void Build () + { + Padding = 0; + Resizable = false; + Width = 320; + Title = GettextCatalog.GetString ("New Folder"); + + var mainVBox = new VBox (); + + var folderNameHBox = new HBox (); + folderNameHBox.Margin = 20; + var folderNameLabel = new Label (); + folderNameLabel.Text = GettextCatalog.GetString ("Folder Name:"); + folderNameHBox.PackStart (folderNameLabel); + + folderNameTextEntry = new TextEntry (); + folderNameHBox.PackStart (folderNameTextEntry, true, true); + folderNameTextEntry.SetCommonAccessibilityAttributes ( + "NewFolderDialog.FolderNameTextEntry", + folderNameLabel.Text, + GettextCatalog.GetString ("Enter the name for the new folder")); + + warningPopover = new InformationPopoverWidget (); + warningPopover.Visible = false; + folderNameHBox.PackStart (warningPopover); + + mainVBox.PackStart (folderNameHBox); + + var cancelButton = new DialogButton (Command.Cancel); + Buttons.Add (cancelButton); + + addButton = new DialogButton (Command.Add); + Buttons.Add (addButton); + + DefaultCommand = addButton.Command; + + Content = mainVBox; + } + + protected override void Dispose (bool disposing) + { + base.Dispose (disposing); + if (disposing) { + folderNameTextEntry.Changed -= FolderNameTextEntryChanged; + addButton.Clicked -= AddButtonClicked; + } + } + + internal static Task<FilePath> Open (FilePath parentFolder) + { + var result = new TaskCompletionSource<FilePath> (); + Toolkit.NativeEngine.Invoke (delegate { + using (var dialog = new NewFolderDialog (parentFolder)) { + dialog.Run (MessageDialog.RootWindow); + result.SetResult (dialog.NewFolderCreated); + } + }); + return result.Task; + } + + string GetDefaultFolderName () + { + string childFolderName = GettextCatalog.GetString ("New Folder"); + string directoryName = Path.Combine (parentFolder, childFolderName); + int index = -1; + + if (Directory.Exists (directoryName)) { + while (Directory.Exists (directoryName + (++index + 1))) { + } + } + + if (index >= 0) { + return childFolderName += index + 1; + } + return childFolderName; + } + + void FolderNameTextEntryChanged (object sender, EventArgs e) + { + FilePath directoryPath = GetNewFolderPath (); + + if (folderNameTextEntry.Text.Length == 0) { + addButton.Sensitive = false; + HidePopoverMessage (); + } else if (!IsValidFolderName (directoryPath, folderNameTextEntry.Text)) { + ShowWarning (GettextCatalog.GetString ("The name you have chosen contains illegal characters. Please choose a different name.")); + addButton.Sensitive = false; + } else if (Directory.Exists (directoryPath)) { + ShowWarning (GettextCatalog.GetString ("Folder name is already in use. Please choose a different name.")); + addButton.Sensitive = false; + } else { + addButton.Sensitive = true; + HidePopoverMessage (); + } + } + + void AddButtonClicked (object sender, EventArgs e) + { + try { + bool canClose = AddNewFolder (); + if (canClose) { + Close (); + } + } catch (Exception ex) { + LoggingService.LogError ("Could not create folder", ex); + ShowError (GettextCatalog.GetString ("An error occurred creating the folder. {0}", ex.Message)); + } + } + + FilePath GetNewFolderPath () + { + return parentFolder.Combine (folderNameTextEntry.Text); + } + + bool AddNewFolder () + { + FilePath directoryPath = GetNewFolderPath (); + + Directory.CreateDirectory (directoryPath); + NewFolderCreated = directoryPath; + + return true; + } + + bool IsValidFolderName (FilePath folderPath, string folderName) + { + return FileService.IsValidPath (folderPath) && + !ProjectFolderCommandHandler.ContainsDirectorySeparator (folderName); + } + + protected override void OnCommandActivated (Command cmd) + { + if (cmd == Command.Add) { + // Prevent dialog closing after Add button is activated since an alert message dialog may have been shown. + return; + } + base.OnCommandActivated (cmd); + } + + void ShowWarning (string message) + { + ShowPopoverMessage (message, TaskSeverity.Warning); + } + + void ShowError (string message) + { + ShowPopoverMessage (message, TaskSeverity.Error); + } + + void ShowPopoverMessage (string message, TaskSeverity severity) + { + warningPopover.Message = message; + warningPopover.Severity = severity; + warningPopover.Show (); + } + + void HidePopoverMessage () + { + warningPopover.Hide (); + } + } +} diff --git a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Gui.Dialogs/PolicyOptionsPanel.cs b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Gui.Dialogs/PolicyOptionsPanel.cs index e7e33028f1..f394643d00 100644 --- a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Gui.Dialogs/PolicyOptionsPanel.cs +++ b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Gui.Dialogs/PolicyOptionsPanel.cs @@ -122,8 +122,9 @@ namespace MonoDevelop.Ide.Gui.Dialogs } policyCombo.Changed += HandlePolicyComboChanged; - - return vbox; + + var widget = new PolicyOptionsWidgetContainer (vbox); + return widget; } void LoadPolicy (T policy) @@ -340,5 +341,21 @@ namespace MonoDevelop.Ide.Gui.Dialogs loading = false; } } + + /// <summary> + /// Container for the VBox used to display the options panel information. + /// This is needed since using just a VBox causes Voice Over on the Mac to + /// read out the UI widgets multiple times with using the arrow keys. + /// </summary> + class PolicyOptionsWidgetContainer : Bin + { + public PolicyOptionsWidgetContainer (VBox child) + { + BinContainer.Attach (this); + + Add (child); + ShowAll (); + } + } } } diff --git a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Gui.OptionPanels/LoadSavePanel.cs b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Gui.OptionPanels/LoadSavePanel.cs index ce367f5dd7..5412251028 100644 --- a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Gui.OptionPanels/LoadSavePanel.cs +++ b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Gui.OptionPanels/LoadSavePanel.cs @@ -81,7 +81,9 @@ namespace MonoDevelop.Ide.Gui.OptionPanels loadUserDataCheckButton.Active = IdeApp.Preferences.LoadDocumentUserProperties; createBackupCopyCheckButton.Active = IdeApp.Preferences.CreateFileBackupCopies; - loadPrevProjectCheckButton.Active = IdeApp.Preferences.LoadPrevSolutionOnStartup.Value; + openStartWindowRadioButton.Active = IdeApp.Preferences.StartupBehaviour.Value == OnStartupBehaviour.ShowStartWindow; + loadPrevProjectRadioButton.Active = IdeApp.Preferences.StartupBehaviour.Value == OnStartupBehaviour.LoadPreviousSolution; + emptyEnvironmentRadioButton.Active = IdeApp.Preferences.StartupBehaviour.Value == OnStartupBehaviour.EmptyEnvironment; SetupAccessibility (); } @@ -95,8 +97,12 @@ namespace MonoDevelop.Ide.Gui.OptionPanels loadUserDataCheckButton.SetCommonAccessibilityAttributes ("LoadSavePanel.loadUserData", "", GettextCatalog.GetString ("Check to load the user specific settings with the solution")); - loadPrevProjectCheckButton.SetCommonAccessibilityAttributes ("LoadSavePanel.loadPrevious", "", + openStartWindowRadioButton.SetCommonAccessibilityAttributes ("LoadSavePanel.openStartWindow", "", + GettextCatalog.GetString ("Check to load the Start Window when starting the application")); + loadPrevProjectRadioButton.SetCommonAccessibilityAttributes ("LoadSavePanel.loadPrevious", "", GettextCatalog.GetString ("Check to load the previous solution when starting the application")); + emptyEnvironmentRadioButton.SetCommonAccessibilityAttributes ("LoadSavePanel.emptyEnvironment", "", + GettextCatalog.GetString ("Check to load an empty environment when starting the application")); createBackupCopyCheckButton.SetCommonAccessibilityAttributes ("LoadSavePanel.createBackup", "", GettextCatalog.GetString ("Check to always create a backup copy")); } @@ -116,7 +122,14 @@ namespace MonoDevelop.Ide.Gui.OptionPanels public void Store () { - IdeApp.Preferences.LoadPrevSolutionOnStartup.Value = loadPrevProjectCheckButton.Active; + if (openStartWindowRadioButton.Active) { + IdeApp.Preferences.StartupBehaviour.Value = OnStartupBehaviour.ShowStartWindow; + } else if (loadPrevProjectRadioButton.Active) { + IdeApp.Preferences.StartupBehaviour.Value = OnStartupBehaviour.LoadPreviousSolution; + } else if (emptyEnvironmentRadioButton.Active) { + IdeApp.Preferences.StartupBehaviour.Value = OnStartupBehaviour.EmptyEnvironment; + } + IdeApp.Preferences.LoadDocumentUserProperties.Value = loadUserDataCheckButton.Active; IdeApp.Preferences.CreateFileBackupCopies.Value = createBackupCopyCheckButton.Active; IdeApp.Preferences.ProjectsDefaultPath.Value = folderEntry.Path; diff --git a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Gui.Pads.ProjectPad/FolderNodeBuilder.cs b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Gui.Pads.ProjectPad/FolderNodeBuilder.cs index b81a7f6ebe..dcf9e34ad0 100644 --- a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Gui.Pads.ProjectPad/FolderNodeBuilder.cs +++ b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Gui.Pads.ProjectPad/FolderNodeBuilder.cs @@ -438,7 +438,6 @@ namespace MonoDevelop.Ide.Gui.Pads.ProjectPad void OnFileInserted (ITreeNavigator nav) { nav.Selected = true; - Tree.StartLabelEdit (); } ///<summary>Imports files and folders from a target folder into the current folder</summary> @@ -549,27 +548,20 @@ namespace MonoDevelop.Ide.Gui.Pads.ProjectPad // project node is collapsed and Refresh was used the project node would not expand and the new folder // node would not be selected. CurrentNode.Expanded = true; - Project project = CurrentNode.GetParentDataItem (typeof(Project), true) as Project; - + + var project = CurrentNode.GetParentDataItem (typeof (Project), true) as Project; string baseFolderPath = GetFolderPath (CurrentNode.DataItem); - string directoryName = Path.Combine (baseFolderPath, GettextCatalog.GetString("New Folder")); - int index = -1; - if (Directory.Exists(directoryName)) { - while (Directory.Exists(directoryName + (++index + 1))) ; - } - - if (index >= 0) { - directoryName += index + 1; - } - - Directory.CreateDirectory (directoryName); - - ProjectFile newFolder = new ProjectFile (directoryName); + FilePath folder = await NewFolderDialog.Open (baseFolderPath); + + if (folder.IsNull) + return; + + var newFolder = new ProjectFile (folder); newFolder.Subtype = Subtype.Directory; project.Files.Add (newFolder); - Tree.AddNodeInsertCallback (new ProjectFolder (directoryName, project), new TreeNodeCallback (OnFileInserted)); + Tree.AddNodeInsertCallback (new ProjectFolder (folder, project), new TreeNodeCallback (OnFileInserted)); await IdeApp.ProjectOperations.SaveAsync (project); } diff --git a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Gui.Pads.ProjectPad/ProjectNodeBuilder.cs b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Gui.Pads.ProjectPad/ProjectNodeBuilder.cs index 7aae5b5548..d313c4f2e1 100644 --- a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Gui.Pads.ProjectPad/ProjectNodeBuilder.cs +++ b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Gui.Pads.ProjectPad/ProjectNodeBuilder.cs @@ -451,7 +451,7 @@ namespace MonoDevelop.Ide.Gui.Pads.ProjectPad project.ParentSolution.StartupItem = project; await project.ParentSolution.SaveUserProperties (); } - + public override void DeleteItem () { Project prj = CurrentNode.DataItem as Project; diff --git a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Gui.Pads.ProjectPad/ProjectPadContextMenu.addin.xml b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Gui.Pads.ProjectPad/ProjectPadContextMenu.addin.xml index 864d1822a8..c711f6e07d 100644 --- a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Gui.Pads.ProjectPad/ProjectPadContextMenu.addin.xml +++ b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Gui.Pads.ProjectPad/ProjectPadContextMenu.addin.xml @@ -52,6 +52,9 @@ <Condition id="ItemType" value="Project"> <CommandItem id = "MonoDevelop.Ide.Commands.ProjectCommands.SetAsStartupProject" /> </Condition> + <Condition id="ItemType" value="Solution"> + <CommandItem id="MonoDevelop.Ide.Commands.ProjectCommands.SetStartupProjects" /> + </Condition> <SeparatorItem id = "RunSectionEnd" /> <!-- Publish section --> @@ -151,7 +154,7 @@ </Condition> <Condition id="ItemType" value="IBuildTarget"> <CommandItem id = "MonoDevelop.Ide.Commands.ProjectCommands.Options" /> - </Condition> + </Condition> <Condition id="ItemType" value="ProjectItem"> <CommandItem id = "MonoDevelop.Ide.Commands.FileCommands.ShowProperties" /> </Condition> diff --git a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Gui.Shell/DefaultWorkbench.cs b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Gui.Shell/DefaultWorkbench.cs index 2410849231..6e2a93e553 100644 --- a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Gui.Shell/DefaultWorkbench.cs +++ b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Gui.Shell/DefaultWorkbench.cs @@ -1,4 +1,4 @@ -// DefaultWorkbench.cs +// DefaultWorkbench.cs // // Author: // Mike Krüger @@ -80,6 +80,7 @@ namespace MonoDevelop.Ide.Gui.Shell IWorkbenchWindow lastActive; bool closeAll; + bool? fullScreenState = null; Rectangle normalBounds = new Rectangle(0, 0, MinimumWidth, MinimumHeight); @@ -148,13 +149,20 @@ namespace MonoDevelop.Ide.Gui.Shell public DockFrame DockFrame { get { return dock; } } - + public bool FullScreen { get { return IdeServices.DesktopService.GetIsFullscreen (this); } set { - IdeServices.DesktopService.SetIsFullscreen (this, value); + // If this window is not visible, don't set full screen mode + // until it is, as that would conflict with other windows we + // might be opening before (Start Window, for instance) + if (Visible) { + IdeServices.DesktopService.SetIsFullscreen (this, value); + } else { + fullScreenState = value; + } } } @@ -623,6 +631,16 @@ namespace MonoDevelop.Ide.Gui.Shell } } + protected override void OnShown () + { + base.OnShown (); + if (fullScreenState != null && fullScreenState != IdeServices.DesktopService.GetIsFullscreen (this)) { + IdeServices.DesktopService.SetIsFullscreen (this, (bool)fullScreenState); + fullScreenState = null; + } + } + + bool closing; void OnClosing (object o, Gtk.DeleteEventArgs e) { // don't allow Gtk to close the workspace, in case Close() leaves the synchronization context diff --git a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Gui/Styles.cs b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Gui/Styles.cs index c566cf06e8..bb3392ae93 100644 --- a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Gui/Styles.cs +++ b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Gui/Styles.cs @@ -473,7 +473,7 @@ namespace MonoDevelop.Ide.Gui PrimaryBackgroundColor = BaseBackgroundColor; SecondaryBackgroundDarkerColor = Color.FromName ("#e7eaee"); SecondaryBackgroundLighterColor = Color.FromName ("#f9f9fb"); - SecondaryTextColorHexString = "#888888"; + SecondaryTextColorHexString = "#767676"; SecondaryTextColor = Color.FromName (SecondaryTextColorHexString); SecondarySelectionTextColor = Color.FromName ("#ffffff"); PadBackground = Color.FromName ("#fafafa"); @@ -597,7 +597,7 @@ namespace MonoDevelop.Ide.Gui PrimaryBackgroundColor = BaseBackgroundColor; SecondaryBackgroundDarkerColor = Color.FromName ("#484848"); SecondaryBackgroundLighterColor = SeparatorColor; - SecondaryTextColorHexString = "#777777"; + SecondaryTextColorHexString = "#ababab"; SecondaryTextColor = Color.FromName (SecondaryTextColorHexString); SecondarySelectionTextColor = Color.FromName ("#ffffff"); PadBackground = Color.FromName ("#525252"); diff --git a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Gui/Workbench.cs b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Gui/Workbench.cs index 21c09974fe..fc40d29999 100644 --- a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Gui/Workbench.cs +++ b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Gui/Workbench.cs @@ -1,4 +1,4 @@ -// +// // Workbench.cs // // Author: diff --git a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.ProgressMonitoring/MultiTaskDialogProgressMonitor.cs b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.ProgressMonitoring/MultiTaskDialogProgressMonitor.cs index 742d104691..a76e6a3594 100644 --- a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.ProgressMonitoring/MultiTaskDialogProgressMonitor.cs +++ b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.ProgressMonitoring/MultiTaskDialogProgressMonitor.cs @@ -68,7 +68,7 @@ namespace MonoDevelop.Ide.ProgressMonitoring public MultiTaskDialogProgressMonitor (bool showProgress, bool allowCancel, bool showDetails, IDictionary<string, string> taskLabelAliases): base (Runtime.MainSynchronizationContext) { if (showProgress) { - var parent = MessageService.GetDefaultModalParent (); + var parent = IdeServices.DesktopService.GetParentForModalWindow (); dialog = new MultiTaskProgressDialog (allowCancel, showDetails, taskLabelAliases) { DestroyWithParent = true, Modal = true, diff --git a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Projects.OptionPanels/RunConfigurationsList.cs b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Projects.OptionPanels/RunConfigurationsList.cs index 5fb0b15aff..320f9ed81e 100644 --- a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Projects.OptionPanels/RunConfigurationsList.cs +++ b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Projects.OptionPanels/RunConfigurationsList.cs @@ -38,17 +38,19 @@ namespace MonoDevelop.Ide.Projects.OptionPanels Xwt.ListStore listStore; Xwt.DataField<RunConfiguration> configCol = new Xwt.DataField<RunConfiguration> (); Xwt.DataField<string> configNameCol = new Xwt.DataField<string> (); + Xwt.DataField<string> accessibleCol = new Xwt.DataField<string> (); Xwt.DataField<Xwt.Drawing.Image> configIconCol = new Xwt.DataField<Xwt.Drawing.Image> (); public RunConfigurationsList () { - listStore = new Xwt.ListStore (configCol, configNameCol, configIconCol); + listStore = new Xwt.ListStore (configCol, configNameCol, configIconCol, accessibleCol); list = new Xwt.ListView (listStore); list.HeadersVisible = false; var imgCell = new ImageCellView { ImageField = configIconCol }; var textCell = new TextCellView { MarkupField = configNameCol }; list.Columns.Add (GettextCatalog.GetString ("Name"), imgCell, textCell); + textCell.AccessibleFields.Label = accessibleCol; Content = list; @@ -64,7 +66,7 @@ namespace MonoDevelop.Ide.Projects.OptionPanels var r = listStore.AddRow (); var txt = "<b>" + c.Name + "</b>\n" + c.Summary; var icon = !string.IsNullOrEmpty (c.IconId) ? ImageService.GetIcon (c.IconId) : ImageService.GetIcon ("md-prefs-play", Gtk.IconSize.Dnd); - listStore.SetValues (r, configCol, c, configNameCol, txt, configIconCol, icon); + listStore.SetValues (r, configCol, c, configNameCol, txt, configIconCol, icon, accessibleCol, c.Name + " " + c.Summary); } if (currentRow != -1) { if (currentRow < listStore.RowCount) diff --git a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Projects.OptionPanels/SolutionRunConfigurationsPanel.cs b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Projects.OptionPanels/SolutionRunConfigurationsPanel.cs index 1076e98024..0fa378a615 100644 --- a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Projects.OptionPanels/SolutionRunConfigurationsPanel.cs +++ b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Projects.OptionPanels/SolutionRunConfigurationsPanel.cs @@ -53,11 +53,11 @@ namespace MonoDevelop.Ide.Projects.OptionPanels Solution = (Solution)dataObject; - foreach (var rc in Solution.MultiStartupRunConfigurations) - configs.Add (new SolutionRunConfigInfo { ProjectConfig = rc, EditedConfig = new MultiItemSolutionRunConfiguration (rc) }); - - foreach (var c in configs) + foreach (var rc in Solution.MultiStartupRunConfigurations) { + var c = new SolutionRunConfigInfo { ProjectConfig = rc, EditedConfig = new MultiItemSolutionRunConfiguration (rc) }; + configs.Add (c); AddPanel (c); + } ParentDialog.ExpandChildren (this); } @@ -120,7 +120,7 @@ namespace MonoDevelop.Ide.Projects.OptionPanels internal void ShowConfiguration (MultiItemSolutionRunConfiguration editedConfig) { - var rc = configs.First (ci => ci.EditedConfig == editedConfig); + var rc = configs.First (ci => ci.EditedConfig.Name == editedConfig.Name); var section = sections [rc]; ParentDialog.ShowPage (section); } diff --git a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Projects/ApplyPolicyDialog.cs b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Projects/ApplyPolicyDialog.cs index c301f2cbbf..5e6d401e43 100644 --- a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Projects/ApplyPolicyDialog.cs +++ b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Projects/ApplyPolicyDialog.cs @@ -31,6 +31,7 @@ using MonoDevelop.Core; using System.Collections.Generic; using System.Text; using MonoDevelop.Components; +using MonoDevelop.Components.AtkCocoaHelper; namespace MonoDevelop.Ide.Projects { @@ -59,6 +60,22 @@ namespace MonoDevelop.Ide.Projects combPolicies.Active = 0; OnRadioCustomToggled (null, null); UpdateContentLabels (); + + combPolicies.Accessible.Name = "ApplyPolicyDialog.PolicyCombo"; + combPolicies.SetAccessibilityLabelRelationship (label2); + CombPolicies_Changed (null, null); + combPolicies.Changed += CombPolicies_Changed; + } + + protected override void OnDestroyed () + { + combPolicies.Changed -= CombPolicies_Changed; + base.OnDestroyed (); + } + + void CombPolicies_Changed (object sender, EventArgs e) + { + combPolicies.Accessible.Description = GettextCatalog.GetString ("Select policy, current: {0}", combPolicies.ActiveText); } protected void OnRadioCustomToggled (object sender, System.EventArgs e) @@ -189,7 +206,7 @@ namespace MonoDevelop.Ide.Projects public PoliciesListSummaryTree () : base (new Gtk.ListStore (typeof (string))) { - CanFocus = false; + CanFocus = true; HeadersVisible = false; store = (Gtk.ListStore) Model; this.AppendColumn ("", new Gtk.CellRendererText (), "text", 0); @@ -204,6 +221,8 @@ namespace MonoDevelop.Ide.Projects var win = evnt.Window; win.Clear (); if (string.IsNullOrEmpty (message)) { + if (ShowEmptyItem) + return base.OnExposeEvent (evnt); return true; } @@ -233,7 +252,9 @@ namespace MonoDevelop.Ide.Projects } } } - + + bool ShowEmptyItem { get; set; } + public void SetPolicies (PolicyContainer pset) { if (pset == null) { @@ -279,6 +300,13 @@ namespace MonoDevelop.Ide.Projects } StringBuilderCache.Free (sb); HasPolicies = sorted.Count > 0; + if (!HasPolicies) { + store.AppendValues (GettextCatalog.GetString ("No policies")); + ShowEmptyItem = true; + } + if (store.GetIterFirst (out var iter)) { + Selection.SelectIter (iter); + } } } -}
\ No newline at end of file +} diff --git a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Projects/ExportProjectPolicyDialog.cs b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Projects/ExportProjectPolicyDialog.cs index b10e87b681..a8574cac8c 100644 --- a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Projects/ExportProjectPolicyDialog.cs +++ b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Projects/ExportProjectPolicyDialog.cs @@ -64,7 +64,6 @@ namespace MonoDevelop.Ide.Projects tree.SetPolicies (policyProvider.Policies); if (!tree.HasPolicies) { - tree.Message = GettextCatalog.GetString ("No policies"); buttonOk.Sensitive = false; } diff --git a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Projects/ExportSolutionDialog.cs b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Projects/ExportSolutionDialog.cs index 1da1730a71..91a85b8c31 100644 --- a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Projects/ExportSolutionDialog.cs +++ b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Projects/ExportSolutionDialog.cs @@ -1,4 +1,4 @@ -// ExportProjectDialog.cs +// ExportProjectDialog.cs // // Author: // Lluis Sanchez Gual <lluis@novell.com> @@ -43,7 +43,7 @@ namespace MonoDevelop.Ide.Projects { this.Build(); - labelNewFormat.Text = selectedFormat.ProductDescription; + labelNewFormat.Text = selectedFormat?.ProductDescription ?? item.FileFormat.ProductDescription; formats = MSBuildFileFormat.GetSupportedFormats (item).ToArray (); foreach (var format in formats) { diff --git a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Projects/GtkNewProjectDialogBackend.UI.cs b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Projects/GtkNewProjectDialogBackend.UI.cs index 6760e5b3dd..82622c4289 100644 --- a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Projects/GtkNewProjectDialogBackend.UI.cs +++ b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Projects/GtkNewProjectDialogBackend.UI.cs @@ -106,12 +106,6 @@ namespace MonoDevelop.Ide.Projects Name = "wizard_dialog";
Title = GettextCatalog.GetString ("New Project");
- if (IdeApp.Workbench.RootWindow.Visible) { - WindowPosition = WindowPosition.CenterOnParent; - TransientFor = IdeApp.Workbench.RootWindow; - } else
- // FIXME: align on a native toplevel if available - WindowPosition = WindowPosition.Center;
projectConfigurationWidget = new GtkProjectConfigurationWidget ();
projectConfigurationWidget.Name = "projectConfigurationWidget";
diff --git a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Projects/GtkNewProjectDialogBackend.cs b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Projects/GtkNewProjectDialogBackend.cs index cd8f235b4f..2750141b56 100644 --- a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Projects/GtkNewProjectDialogBackend.cs +++ b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Projects/GtkNewProjectDialogBackend.cs @@ -44,7 +44,7 @@ namespace MonoDevelop.Ide.Projects partial class GtkNewProjectDialogBackend : INewProjectDialogBackend
{
INewProjectDialogController controller;
- Menu popupMenu;
+ Xwt.Menu popupMenu;
bool isLastPressedKeySpace;
public GtkNewProjectDialogBackend ()
@@ -98,7 +98,7 @@ namespace MonoDevelop.Ide.Projects public void ShowDialog ()
{
- MessageService.ShowCustomDialog (this);
+ MessageService.ShowCustomDialog (this, IdeServices.DesktopService.GetFocusedTopLevelWindow ());
}
public void CloseDialog ()
@@ -150,23 +150,24 @@ namespace MonoDevelop.Ide.Projects void HandlePopup (SolutionTemplate template, uint eventTime)
{
- if (popupMenu == null) {
- popupMenu = new Menu (); - popupMenu.AttachToWidget (this, null); - } - ClearPopupMenuItems (); - AddLanguageMenuItems (popupMenu, template); - popupMenu.ModifyBg (StateType.Normal, Styles.NewProjectDialog.TemplateLanguageButtonBackground.ToGdkColor ()); - popupMenu.ShowAll (); - - MenuPositionFunc posFunc = (Menu m, out int x, out int y, out bool pushIn) => { - Gdk.Rectangle rect = languageCellRenderer.GetLanguageRect (); - Gdk.Rectangle screenRect = GtkUtil.ToScreenCoordinates (templatesTreeView, templatesTreeView.GdkWindow, rect); - x = screenRect.X; - y = screenRect.Bottom; - pushIn = false; - };
- popupMenu.Popup (null, null, posFunc, 0, eventTime);
+ var engine = Platform.IsMac ? Xwt.Toolkit.NativeEngine : Xwt.Toolkit.CurrentEngine;
+ var xwtParent = Xwt.Toolkit.CurrentEngine.WrapWidget (templatesTreeView);
+ engine.Invoke (() => {
+ if (popupMenu == null) {
+ popupMenu = new Xwt.Menu (); + }
+ ClearPopupMenuItems ();
+ AddLanguageMenuItems (popupMenu, template); + Gdk.Rectangle rect = languageCellRenderer.GetLanguageRect ();
+
+ try {
+ popupMenu.Popup (xwtParent, rect.X, rect.Bottom);
+ } catch {
+ // popup at mouse position if the toolkit is not supported
+ popupMenu.Popup ();
+ }
+
+ });
}
[GLib.ConnectBefore]
@@ -202,10 +203,8 @@ namespace MonoDevelop.Ide.Projects }
void ClearPopupMenuItems ()
- {
- foreach (Widget widget in popupMenu.Children) {
- widget.Destroy ();
- }
+ { + popupMenu.Items.Clear ();
}
void PerformShowMenu (object sender, EventArgs args)
@@ -218,17 +217,18 @@ namespace MonoDevelop.Ide.Projects HandlePopup (template, Gdk.EventHelper.GetTime (null));
}
- void AddLanguageMenuItems (Menu menu, SolutionTemplate template)
+ void AddLanguageMenuItems (Xwt.Menu menu, SolutionTemplate template)
{
foreach (string language in template.AvailableLanguages.OrderBy (item => item)) {
- var menuItem = new MenuItem (language);
- menuItem.Activated += (o, e) => {
+ var menuItem = new Xwt.MenuItem (language);
+ menuItem.Accessible.Label = LanguageCellRenderer.GetAccessibleLanguageName (language);
+ menuItem.Clicked += (o, e) => {
languageCellRenderer.SelectedLanguage = language;
controller.SelectedLanguage = language;
templatesTreeView.QueueDraw ();
ShowSelectedTemplate ();
};
- menu.Append (menuItem);
+ menu.Items.Add (menuItem);
}
}
@@ -283,7 +283,7 @@ namespace MonoDevelop.Ide.Projects protected override void OnDestroyed ()
{
if (popupMenu != null) {
- popupMenu.Destroy ();
+ popupMenu.Dispose ();
popupMenu = null;
}
diff --git a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Projects/LanguageCellRenderer.cs b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Projects/LanguageCellRenderer.cs index 2f5b402682..4a9b957455 100644 --- a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Projects/LanguageCellRenderer.cs +++ b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Projects/LanguageCellRenderer.cs @@ -53,9 +53,19 @@ namespace MonoDevelop.Ide.Projects } set { selectedLanguage = value; - Text = value; + Text = GetAccessibleLanguageName (value); } } + + internal static string GetAccessibleLanguageName (string language) + { + switch (language) { + case "C#": return "C Sharp"; + case "F#": return "F Sharp"; + default: return language; + } + } + public bool RenderRecentTemplate { get; set; } int textWidth = 0; diff --git a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Projects/NewSolutionRunConfigurationDialog.cs b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Projects/NewSolutionRunConfigurationDialog.cs new file mode 100644 index 0000000000..e6a0986279 --- /dev/null +++ b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Projects/NewSolutionRunConfigurationDialog.cs @@ -0,0 +1,98 @@ +// +// NewSolutionRunConfigurationDialog.cs +// +// Author: +// Rodrigo Moya <rodrigo.moya@xamarin.com> +// +// Copyright (c) 2019 +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +using System; +using Xwt; +using MonoDevelop.Core; + +namespace MonoDevelop.Ide.Projects +{ + public class NewSolutionRunConfigurationDialog : Dialog + { + readonly TextEntry runConfigNameEntry; + readonly DialogButton createButton; + + public NewSolutionRunConfigurationDialog () + { + Title = GettextCatalog.GetString ("Create Solution Run Configuration"); + Resizable = false; + + var box = new VBox (); + Content = box; + + var label = new Label (GettextCatalog.GetString ("Solution run configurations let you run multiple projects at once. Please provide a name to\nbe shown in the toolbar for this Solution run configuration.")) { + CanGetFocus = false, + Wrap = WrapMode.Word + }; + box.PackStart (label, expand: true, marginBottom: 12); + + var hbox = new HBox (); + hbox.PackStart (new Label (GettextCatalog.GetString ("Run configuration name:")) { + CanGetFocus = false + }); + + runConfigNameEntry = new TextEntry { + Text = GettextCatalog.GetString ("Multiple Projects") + }; + runConfigNameEntry.Changed += OnConfigNameChanged; + hbox.PackStart (runConfigNameEntry, true, true); + + box.PackStart (hbox, true, true); + + createButton = new DialogButton (new Command ("create", GettextCatalog.GetString ("Create Run Configuration"))); + Buttons.Add (Command.Cancel); + Buttons.Add (createButton); + DefaultCommand = createButton.Command; + } + + public string RunConfigurationName => runConfigNameEntry.Text; + + void OnConfigNameChanged (object sender, EventArgs e) + { + createButton.Sensitive = !string.IsNullOrEmpty (runConfigNameEntry.Text); + } + + protected override void OnCommandActivated (Command cmd) + { + if (cmd.Id == createButton.Command.Id) { + Respond (cmd); + return; + } + base.OnCommandActivated (cmd); + } + + protected override void OnShown () + { + base.OnShown (); + runConfigNameEntry.SetFocus (); + } + + protected override void Dispose (bool disposing) + { + runConfigNameEntry.Changed -= OnConfigNameChanged; + base.Dispose (disposing); + } + } +} diff --git a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.RoslynServices/MonoDevelopProjectCacheHostServiceFactory.cs b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.RoslynServices/MonoDevelopProjectCacheHostServiceFactory.cs index 47a1092c47..8b838368d7 100644 --- a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.RoslynServices/MonoDevelopProjectCacheHostServiceFactory.cs +++ b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.RoslynServices/MonoDevelopProjectCacheHostServiceFactory.cs @@ -26,6 +26,7 @@ // Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System;
+using System.Collections.Generic; using System.Composition;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Host;
@@ -44,23 +45,22 @@ namespace MonoDevelop.Ide.RoslynServices public IWorkspaceService CreateService (HostWorkspaceServices workspaceServices) { // we support active document tracking only for visual studio workspace host. - if (workspaceServices.Workspace is MonoDevelopWorkspace) { + if (workspaceServices.Workspace is MonoDevelopWorkspace monoDevelopWorkspace) { // We will finish setting this up in VisualStudioWorkspaceImpl.DeferredInitializationState - var projectCacheService = new ProjectCacheService (workspaceServices.Workspace, ImplicitCacheTimeoutInMS);
+ var projectCacheService = new MonoDevelopProjectCacheService (monoDevelopWorkspace, ImplicitCacheTimeoutInMS);
var documentTrackingService = workspaceServices.GetService<IDocumentTrackingService> (); // Subscribe to events so that we can cache items from the active document's project var manager = new ActiveProjectCacheManager (documentTrackingService, projectCacheService); + projectCacheService.Manager = manager; // Subscribe to requests to clear the cache - var workspaceCacheService = workspaceServices.GetService<IWorkspaceCacheService> ();
- if (workspaceCacheService != null) {
- workspaceCacheService.CacheFlushRequested += (s, e) => manager.Clear ();
- } + var workspaceCacheService = workspaceServices.GetService<IWorkspaceCacheService> (); + projectCacheService.SubscribeToFlushRequested (workspaceCacheService);
// Also clear the cache when the solution is cleared or removed. - workspaceServices.Workspace.WorkspaceChanged += (s, e) => {
- if (e.Kind == WorkspaceChangeKind.SolutionCleared || e.Kind == WorkspaceChangeKind.SolutionRemoved) {
+ monoDevelopWorkspace.WorkspaceChanged += (s, e) => {
+ if (e.Kind == WorkspaceChangeKind.SolutionCleared || e.Kind == WorkspaceChangeKind.SolutionRemoved) { manager.Clear ();
}
};
@@ -71,7 +71,45 @@ namespace MonoDevelop.Ide.RoslynServices return new ProjectCacheService (workspaceServices.Workspace); } - class ActiveProjectCacheManager + class MonoDevelopProjectCacheService : ProjectCacheService, IDisposable + { + internal ActiveProjectCacheManager Manager { get; set; } + IWorkspaceCacheService cacheService; + + public MonoDevelopProjectCacheService (Workspace workspace) : base (workspace) + { + } + + public MonoDevelopProjectCacheService (Workspace workspace, int implicitCacheTimeout) : base (workspace, implicitCacheTimeout) + { + } + + public void SubscribeToFlushRequested (IWorkspaceCacheService cacheService) + { + this.cacheService = cacheService; + cacheService.CacheFlushRequested += OnCacheFlushRequested; + } + + void OnCacheFlushRequested (object sender, EventArgs args) + { + Manager.Clear (); + } + + public void Dispose() + { + if (cacheService != null) { + cacheService.CacheFlushRequested -= OnCacheFlushRequested; + cacheService = null; + } + + Manager?.Dispose (); + Manager = null; + } + } + + //public void GetManager + + class ActiveProjectCacheManager : IDisposable { readonly IDocumentTrackingService _documentTrackingService; readonly ProjectCacheService _projectCacheService;
@@ -91,6 +129,13 @@ namespace MonoDevelop.Ide.RoslynServices } } + public void Dispose () + { + if (_documentTrackingService != null) { + _documentTrackingService.ActiveDocumentChanged -= UpdateCache; + } + } + void UpdateCache (object sender, DocumentId activeDocument) { lock (lockObject) { diff --git a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Templates/TemplateWizard.cs b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Templates/TemplateWizard.cs index 08ff2a23ab..811dc651ec 100644 --- a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Templates/TemplateWizard.cs +++ b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Templates/TemplateWizard.cs @@ -28,6 +28,8 @@ using System; using System.Collections.Generic; using System.Linq; +using MonoDevelop.Core; +using MonoDevelop.Core.StringParsing; using MonoDevelop.Ide.Projects; using MonoDevelop.Projects; @@ -109,6 +111,34 @@ namespace MonoDevelop.Ide.Templates public virtual void ItemsCreated (IEnumerable<IWorkspaceFileObject> items) { + if (!(items.FirstOrDefault () is Solution solution)) + return; + + CreateMultiProjectStartUp (solution); + } + + /// <summary> + /// Adds MultiStartupConfiguration when there are + /// more than one project and one of them is a Backend project + /// </summary> + /// <param name="solution">Solution.</param> + void CreateMultiProjectStartUp (Solution solution) + { + if (Parameters.GetBoolValue ("CreateBackEndProject") != true || Parameters.GetBoolValue ("IncludeBackEndProject") != true) + return; + + var config = new MultiItemSolutionRunConfiguration ("multiprojId", GettextCatalog.GetString ("Multiple Projects")); + foreach (var proj in solution.GetAllProjects ()) { + if (!proj.SupportsExecute ()) + continue; + var startupItem = new StartupItem (proj, null); + config.Items.Add (startupItem); + } + + solution.MultiStartupRunConfigurations.Add (config); + solution.StartupConfiguration = config; + + solution.SaveAsync (new ProgressMonitor ()).Ignore (); } public virtual IEnumerable<ProjectConfigurationControl> GetFinalPageControls () diff --git a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.TypeSystem/FoldingRegion.cs b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.TypeSystem/FoldingRegion.cs index 91f5b99b93..9619d25455 100644 --- a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.TypeSystem/FoldingRegion.cs +++ b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.TypeSystem/FoldingRegion.cs @@ -71,6 +71,11 @@ namespace MonoDevelop.Ide.TypeSystem public FoldingRegion (DocumentRegion region, FoldType type) : this (null, region, type) { } + + public override string ToString () + { + return string.Format ("[FoldingRegion: Name={0}, IsFoldedByDefault={1}, Region={2}, Type={3}]", Name, IsFoldedByDefault, Region, Type); + } } public enum FoldType diff --git a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.TypeSystem/HackyWorkspaceFilesCache.cs b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.TypeSystem/HackyWorkspaceFilesCache.cs new file mode 100644 index 0000000000..5660f5e906 --- /dev/null +++ b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.TypeSystem/HackyWorkspaceFilesCache.cs @@ -0,0 +1,237 @@ +// +// HackyWorkspaceFilesCache.cs +// +// Author: +// Marius Ungureanu <maungu@microsoft.com> +// +// Copyright (c) 2019 Microsoft Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.IO; +using System.Linq; +using MonoDevelop.Core; +using MonoDevelop.Core.FeatureConfiguration; +using MonoDevelop.Projects; +using Newtonsoft.Json; + +namespace MonoDevelop.Ide.TypeSystem +{ + class HackyWorkspaceFilesCache + { + const int format = 1; + readonly bool enabled = FeatureSwitchService.IsFeatureEnabled ("HackyCache").GetValueOrDefault (); + + readonly FilePath cacheDir; + + Dictionary<FilePath, ProjectCache> cachedItems = new Dictionary<FilePath, ProjectCache> (); + + public HackyWorkspaceFilesCache (Solution solution) + { + if (!IdeApp.IsInitialized || !enabled || solution == null) + return; + + LoggingService.LogDebug ("HackyWorkspaceCache enabled"); + cacheDir = solution.GetPreferencesDirectory ().Combine ("hacky-project-cache"); + Directory.CreateDirectory (cacheDir); + + LoadCache (solution); + } + + void LoadCache (Solution sol) + { + var solConfig = sol.GetConfiguration (IdeApp.Workspace.ActiveConfiguration); + + var serializer = new JsonSerializer (); + foreach (var project in sol.GetAllProjects ()) { + var config = solConfig.GetMappedConfiguration (project); + + var projectFilePath = project.FileName; + var cacheFilePath = GetProjectCacheFile (project, config); + + try { + if (!File.Exists (cacheFilePath)) + continue; + + using (var sr = File.OpenText (cacheFilePath)) { + var value = (ProjectCache)serializer.Deserialize (sr, typeof (ProjectCache)); + + if (format != value.Format || value.TimeStamp != File.GetLastWriteTimeUtc (projectFilePath)) + continue; + + cachedItems [projectFilePath] = value; + + } + } catch (Exception ex) { + LoggingService.LogError ("Could not deserialize project cache", ex); + TryDeleteFile (cacheFilePath); + continue; + } + } + } + + string GetProjectCacheFile (Project proj, string configuration) + { + return cacheDir.Combine (proj.FileName.FileNameWithoutExtension + "-" + configuration + ".json"); + } + + static void TryDeleteFile (string file) + { + try { + File.Delete (file); + } catch (Exception ex) { + LoggingService.LogError ("Could not delete cache file", ex); + } + } + + public void Update (ProjectConfiguration projConfig, Project proj, MonoDevelopWorkspace.ProjectDataMap projectMap, + ImmutableArray<ProjectFile> files, + ImmutableArray<FilePath> analyzers, + ImmutableArray<MonoDevelopMetadataReference> metadataReferences, + ImmutableArray<Microsoft.CodeAnalysis.ProjectReference> projectReferences) + { + if (!enabled) + return; + + var paths = new string [files.Length]; + var actions = new string [files.Length]; + + for (int i = 0; i < files.Length; ++i) { + paths [i] = files [i].FilePath; + actions [i] = files [i].BuildAction; + } + + var projectRefs = new ReferenceItem [projectReferences.Length]; + for (int i = 0; i < projectReferences.Length; ++i) { + var pr = projectReferences [i]; + var mdProject = projectMap.GetMonoProject (pr.ProjectId); + projectRefs [i] = new ReferenceItem { + FilePath = mdProject.FileName, + Aliases = pr.Aliases.ToArray (), + }; + } + + var item = new ProjectCache { + Format = format, + Analyzers = analyzers.Select(x => (string)x).ToArray (), + Files = paths, + BuildActions = actions, + TimeStamp = File.GetLastWriteTimeUtc (proj.FileName), + MetadataReferences = metadataReferences.Select(x => { + var ri = new ReferenceItem { + FilePath = x.FilePath, + Aliases = x.Properties.Aliases.ToArray (), + }; + return ri; + }).ToArray (), + ProjectReferences = projectRefs, + }; + + var cacheFile = GetProjectCacheFile (proj, projConfig.Id); + + var serializer = new JsonSerializer (); + using (var fs = File.Open (cacheFile, FileMode.Create)) + using (var sw = new StreamWriter (fs)) { + serializer.Serialize (sw, item); + } + } + + + public bool TryGetCachedItems (Project p, MonoDevelopMetadataReferenceManager provider, MonoDevelopWorkspace.ProjectDataMap projectMap, + out ImmutableArray<ProjectFile> files, + out ImmutableArray<FilePath> analyzers, + out ImmutableArray<MonoDevelopMetadataReference> metadataReferences, + out ImmutableArray<Microsoft.CodeAnalysis.ProjectReference> projectReferences) + { + files = ImmutableArray<ProjectFile>.Empty; + analyzers = ImmutableArray<FilePath>.Empty; + metadataReferences = ImmutableArray<MonoDevelopMetadataReference>.Empty; + projectReferences = ImmutableArray<Microsoft.CodeAnalysis.ProjectReference>.Empty; + + if (!cachedItems.TryGetValue (p.FileName, out var cachedData)) { + return false; + } + + cachedItems.Remove (p.FileName); + + if (cachedData.TimeStamp != File.GetLastWriteTimeUtc (p.FileName)) + return false; + + var filesBuilder = ImmutableArray.CreateBuilder<ProjectFile> (cachedData.Files.Length); + for (int i = 0; i < cachedData.Files.Length; ++i) { + filesBuilder.Add(new ProjectFile (cachedData.Files [i], cachedData.BuildActions [i]) { + Project = p, + }); + } + + files = filesBuilder.MoveToImmutable (); + + var analyzersBuilder = ImmutableArray.CreateBuilder<FilePath> (cachedData.Analyzers.Length); + foreach (var analyzer in cachedData.Analyzers) + analyzersBuilder.Add (analyzer); + analyzers = analyzersBuilder.MoveToImmutable (); + + var mrBuilder = ImmutableArray.CreateBuilder<MonoDevelopMetadataReference> (cachedData.MetadataReferences.Length); + foreach (var item in cachedData.MetadataReferences) { + var aliases = item.Aliases != null ? item.Aliases.ToImmutableArray () : default; + var reference = provider.GetOrCreateMetadataReference (item.FilePath, new Microsoft.CodeAnalysis.MetadataReferenceProperties(aliases: aliases)); + mrBuilder.Add (reference); + } + metadataReferences = mrBuilder.MoveToImmutable (); + + var sol = p.ParentSolution; + var solConfig = sol.GetConfiguration (IdeApp.Workspace.ActiveConfiguration); + var allProjects = sol.GetAllProjects ().ToDictionary (x => x.FileName, x => x); + + var prBuilder = ImmutableArray.CreateBuilder<Microsoft.CodeAnalysis.ProjectReference> (cachedData.ProjectReferences.Length); + foreach (var item in cachedData.ProjectReferences) { + if (!allProjects.TryGetValue (item.FilePath, out var mdProject)) + return false; + + var aliases = item.Aliases != null ? item.Aliases.ToImmutableArray () : default; + var pr = new Microsoft.CodeAnalysis.ProjectReference (projectMap.GetOrCreateId (mdProject, null), aliases.ToImmutableArray ()); + prBuilder.Add (pr); + } + projectReferences = prBuilder.MoveToImmutable (); + return true; + } + + [Serializable] + class ProjectCache + { + public int Format; + public DateTime TimeStamp; + + public ReferenceItem [] ProjectReferences; + public ReferenceItem[] MetadataReferences; + public string [] Files; + public string [] BuildActions; + public string [] Analyzers; + } + + [Serializable] + internal class ReferenceItem + { + public string FilePath; + public string [] Aliases = Array.Empty<string> (); + } + } +} diff --git a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.TypeSystem/MonoDevelopPersistentStorageLocationService.cs b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.TypeSystem/MonoDevelopPersistentStorageLocationService.cs index f5cc57498c..3bf82a7740 100644 --- a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.TypeSystem/MonoDevelopPersistentStorageLocationService.cs +++ b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.TypeSystem/MonoDevelopPersistentStorageLocationService.cs @@ -40,36 +40,129 @@ using Microsoft.CodeAnalysis.SQLite; using Microsoft.CodeAnalysis.Storage; using Microsoft.CodeAnalysis.SolutionSize; using MonoDevelop.Core; +using System.Diagnostics.Contracts; +using System.Diagnostics; namespace MonoDevelop.Ide.TypeSystem { [ExportWorkspaceService (typeof (IPersistentStorageLocationService), ServiceLayer.Host), Shared] class MonoDevelopPersistentStorageLocationService : IPersistentStorageLocationService { + private readonly object _gate = new object (); + private WorkspaceId primaryWorkspace = WorkspaceId.Empty; + private SolutionId _currentSolutionId = null; + private string _currentWorkingFolderPath = null; + + public event EventHandler<PersistentStorageLocationChangingEventArgs> StorageLocationChanging; + + [ImportingConstructor] + [Obsolete (MefConstruction.ImportingConstructorMessage, error: true)] + public MonoDevelopPersistentStorageLocationService () + { + } + + public IDisposable RegisterPrimaryWorkspace (WorkspaceId id) + { + if (primaryWorkspace.Equals (WorkspaceId.Empty)) { + primaryWorkspace = id; + return new WorkspaceRegistration (this); + } + return null; + } + + class WorkspaceRegistration : IDisposable + { + readonly MonoDevelopPersistentStorageLocationService service; + bool disposed; + + public WorkspaceRegistration (MonoDevelopPersistentStorageLocationService service) => this.service = service; + + public void Dispose () + { + if (!disposed) { + service.DisconnectCurrentStorage (); + disposed = true; + } + } + } + public bool IsSupported (Workspace workspace) => workspace is MonoDevelopWorkspace; - // PERF: cache for the solution location. This is needed due to roslyn querying GetStorageLocation a lot of times. - internal ConditionalWeakTable<SolutionId, string> storageMap = new ConditionalWeakTable<SolutionId, string> (); + public string TryGetStorageLocation (SolutionId solutionId) + { + lock (_gate) { + if (solutionId == _currentSolutionId) { + return _currentWorkingFolderPath; + } + } - public event EventHandler<PersistentStorageLocationChangingEventArgs> StorageLocationChanging; + return null; + } - internal void NotifyStorageLocationChanging (SolutionId sol, string path) + internal void SetupSolution (MonoDevelopWorkspace visualStudioWorkspace) { - lock (storageMap) { - if (storageMap.TryGetValue (sol, out string cached) && path == cached) + lock (_gate) { + // Don't trigger events for workspaces other than those we want to inspect. + if (!primaryWorkspace.Equals (visualStudioWorkspace.Id)) + return; + + if (visualStudioWorkspace.CurrentSolution.Id == _currentSolutionId && _currentWorkingFolderPath != null) { return; + } - StorageLocationChanging?.Invoke (this, new PersistentStorageLocationChangingEventArgs (sol, path, true)); - storageMap.Remove (sol); - storageMap.Add (sol, path); + var solution = visualStudioWorkspace.MonoDevelopSolution; + solution.Modified += OnSolutionModified; + if (string.IsNullOrWhiteSpace (solution.BaseDirectory)) + return; + + var workingFolderPath = solution.GetPreferencesDirectory (); + + try { + if (!string.IsNullOrWhiteSpace (workingFolderPath)) { + OnWorkingFolderChanging_NoLock ( + new PersistentStorageLocationChangingEventArgs ( + visualStudioWorkspace.CurrentSolution.Id, + workingFolderPath, + mustUseNewStorageLocationImmediately: false)); + } + } catch { + // don't crash just because solution having problem getting working folder information + } } } - public string TryGetStorageLocation (SolutionId solutionId) + async void OnSolutionModified (object sender, MonoDevelop.Projects.WorkspaceItemEventArgs args) { - lock (storageMap) { - storageMap.TryGetValue (solutionId, out var path); - return path; + var sol = (MonoDevelop.Projects.Solution)args.Item; + var workspace = await IdeServices.TypeSystemService.GetWorkspaceAsync (sol, CancellationToken.None); + if (workspace.Id.Equals (primaryWorkspace)) { + DisconnectCurrentStorage (); + } + } + + private void OnWorkingFolderChanging_NoLock (PersistentStorageLocationChangingEventArgs eventArgs) + { + StorageLocationChanging?.Invoke (this, eventArgs); + + _currentSolutionId = eventArgs.SolutionId; + _currentWorkingFolderPath = eventArgs.NewStorageLocation; + } + + void DisconnectCurrentStorage () + { + lock (_gate) { + var workspace = IdeServices.TypeSystemService.GetWorkspace (primaryWorkspace); + var solution = workspace.MonoDevelopSolution; + if (solution != null) + solution.Modified -= OnSolutionModified; + + // We want to make sure everybody synchronously detaches + OnWorkingFolderChanging_NoLock ( + new PersistentStorageLocationChangingEventArgs ( + _currentSolutionId, + newStorageLocation: null, + mustUseNewStorageLocationImmediately: true)); + primaryWorkspace = WorkspaceId.Empty; } } } diff --git a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.TypeSystem/MonoDevelopWorkspace.ProjectData.cs b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.TypeSystem/MonoDevelopWorkspace.ProjectData.cs index ff49e10dd0..3d62435d25 100644 --- a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.TypeSystem/MonoDevelopWorkspace.ProjectData.cs +++ b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.TypeSystem/MonoDevelopWorkspace.ProjectData.cs @@ -48,9 +48,10 @@ namespace MonoDevelop.Ide.TypeSystem DocumentData = new DocumentMap (projectId); this.metadataReferences = new List<MonoDevelopMetadataReference> (metadataReferences.Length); - System.Diagnostics.Debug.Assert (Monitor.IsEntered (ws.updatingProjectDataLock)); - foreach (var metadataReference in metadataReferences) { - AddMetadataReference_NoLock (metadataReference, ws); + lock (this.metadataReferences) { + foreach (var metadataReference in metadataReferences) { + AddMetadataReference_NoLock (metadataReference, ws); + } } } @@ -61,7 +62,7 @@ namespace MonoDevelop.Ide.TypeSystem if (!workspaceRef.TryGetTarget (out var workspace)) return; - lock (workspace.updatingProjectDataLock) { + lock (metadataReferences) { if (!RemoveMetadataReference_NoLock (reference, workspace)) return; workspace.OnMetadataReferenceRemoved (projectId, args.OldSnapshot); @@ -73,7 +74,7 @@ namespace MonoDevelop.Ide.TypeSystem void AddMetadataReference_NoLock (MonoDevelopMetadataReference metadataReference, MonoDevelopWorkspace ws) { - System.Diagnostics.Debug.Assert (Monitor.IsEntered (ws.updatingProjectDataLock)); + System.Diagnostics.Debug.Assert (Monitor.IsEntered (metadataReferences)); metadataReferences.Add (metadataReference); metadataReference.SnapshotUpdated += OnMetadataReferenceUpdated; @@ -81,7 +82,7 @@ namespace MonoDevelop.Ide.TypeSystem bool RemoveMetadataReference_NoLock (MonoDevelopMetadataReference metadataReference, MonoDevelopWorkspace ws) { - System.Diagnostics.Debug.Assert (Monitor.IsEntered (ws.updatingProjectDataLock)); + System.Diagnostics.Debug.Assert (Monitor.IsEntered (metadataReferences)); metadataReference.SnapshotUpdated -= OnMetadataReferenceUpdated; return metadataReferences.Remove (metadataReference); @@ -92,9 +93,10 @@ namespace MonoDevelop.Ide.TypeSystem if (!workspaceRef.TryGetTarget (out var ws)) return; - System.Diagnostics.Debug.Assert (Monitor.IsEntered (ws.updatingProjectDataLock)); - foreach (var reference in metadataReferences) - reference.SnapshotUpdated -= OnMetadataReferenceUpdated; + lock (metadataReferences) { + foreach (var reference in metadataReferences) + reference.SnapshotUpdated -= OnMetadataReferenceUpdated; + } } } } diff --git a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.TypeSystem/MonoDevelopWorkspace.ProjectDataMap.cs b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.TypeSystem/MonoDevelopWorkspace.ProjectDataMap.cs index 3cdf74b32b..2d28bfd99e 100644 --- a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.TypeSystem/MonoDevelopWorkspace.ProjectDataMap.cs +++ b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.TypeSystem/MonoDevelopWorkspace.ProjectDataMap.cs @@ -26,6 +26,7 @@ using System; using System.Collections.Generic; using System.Collections.Immutable; +using System.Linq; using Microsoft.CodeAnalysis; namespace MonoDevelop.Ide.TypeSystem @@ -39,8 +40,8 @@ namespace MonoDevelop.Ide.TypeSystem ImmutableDictionary<ProjectId, MonoDevelop.Projects.Project> projectIdToMdProjectMap = ImmutableDictionary<ProjectId, MonoDevelop.Projects.Project>.Empty; readonly Dictionary<MonoDevelop.Projects.Project, ProjectId> projectIdMap = new Dictionary<MonoDevelop.Projects.Project, ProjectId> (); - // FIXME: Make this private - internal readonly Dictionary<ProjectId, ProjectData> projectDataMap = new Dictionary<ProjectId, ProjectData> (); + readonly Dictionary<ProjectId, ProjectData> projectDataMap = new Dictionary<ProjectId, ProjectData> (); + readonly object updatingProjectDataLock = new object (); public ProjectDataMap (MonoDevelopWorkspace workspace) { @@ -84,17 +85,36 @@ namespace MonoDevelop.Ide.TypeSystem } } - internal void RemoveProject (MonoDevelop.Projects.Project project, ProjectId id) + internal void RemoveProject (MonoDevelop.Projects.Project project) { + ProjectId projectId; + lock (gate) { - if (projectIdMap.TryGetValue (project, out ProjectId val)) + if (projectIdMap.TryGetValue (project, out projectId)) { projectIdMap.Remove (project); - projectIdToMdProjectMap = projectIdToMdProjectMap.Remove (val); + projectIdToMdProjectMap = projectIdToMdProjectMap.Remove (projectId); + } + } + + if (projectId != null) { + RemoveData (projectId); } + } + + internal MonoDevelop.Projects.Project RemoveProject (ProjectId id) + { + MonoDevelop.Projects.Project actualProject; - lock (Workspace.updatingProjectDataLock) { - projectDataMap.Remove (id); + lock (gate) { + if (projectIdToMdProjectMap.TryGetValue (id, out actualProject)) { + projectIdMap.Remove (actualProject); + projectIdToMdProjectMap = projectIdToMdProjectMap.Remove (id); + } } + + RemoveData (id); + + return actualProject; } internal MonoDevelop.Projects.Project GetMonoProject (ProjectId projectId) @@ -106,14 +126,14 @@ namespace MonoDevelop.Ide.TypeSystem internal bool Contains (ProjectId projectId) { - lock (Workspace.updatingProjectDataLock) { + lock (updatingProjectDataLock) { return projectDataMap.ContainsKey (projectId); } } internal ProjectData GetData (ProjectId id) { - lock (Workspace.updatingProjectDataLock) { + lock (updatingProjectDataLock) { projectDataMap.TryGetValue (id, out ProjectData result); return result; } @@ -121,7 +141,7 @@ namespace MonoDevelop.Ide.TypeSystem internal ProjectData RemoveData (ProjectId id) { - lock (Workspace.updatingProjectDataLock) { + lock (updatingProjectDataLock) { if (projectDataMap.TryGetValue (id, out ProjectData result)) { projectDataMap.Remove (id); result.Disconnect (); @@ -132,12 +152,18 @@ namespace MonoDevelop.Ide.TypeSystem internal ProjectData CreateData (ProjectId id, ImmutableArray<MonoDevelopMetadataReference> metadataReferences) { - lock (Workspace.updatingProjectDataLock) { + lock (updatingProjectDataLock) { var result = new ProjectData (id, metadataReferences, Workspace); projectDataMap [id] = result; return result; } } + internal ProjectId[] GetProjectIds () + { + lock (updatingProjectDataLock) { + return projectDataMap.Keys.ToArray (); + } + } } } }
\ No newline at end of file diff --git a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.TypeSystem/MonoDevelopWorkspace.ProjectSystemHandler.cs b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.TypeSystem/MonoDevelopWorkspace.ProjectSystemHandler.cs index b059f08a0e..1b6f1cf608 100644 --- a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.TypeSystem/MonoDevelopWorkspace.ProjectSystemHandler.cs +++ b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.TypeSystem/MonoDevelopWorkspace.ProjectSystemHandler.cs @@ -1,4 +1,4 @@ -// +// // MonoDevelopWorkspace.ProjectSystemHandler.cs // // Author: @@ -36,18 +36,22 @@ using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Host; using MonoDevelop.Core; using MonoDevelop.Ide.Editor.Projection; +using MonoDevelop.Projects; namespace MonoDevelop.Ide.TypeSystem { public partial class MonoDevelopWorkspace { - internal class ProjectSystemHandler + internal class ProjectSystemHandler : IDisposable { readonly MonoDevelopWorkspace workspace; readonly ProjectDataMap projectMap; readonly ProjectionData projections; readonly Lazy<MetadataReferenceHandler> metadataHandler; readonly Lazy<HostDiagnosticUpdateSource> hostDiagnosticUpdateSource; + readonly HackyWorkspaceFilesCache hackyCache; + readonly List<MonoDevelopAnalyzer> analyzersToDispose = new List<MonoDevelopAnalyzer> (); + IDisposable persistentStorageLocationServiceRegistration; bool added; readonly object addLock = new object (); @@ -59,9 +63,14 @@ namespace MonoDevelop.Ide.TypeSystem this.workspace = workspace; this.projectMap = projectMap; this.projections = projections; + this.hackyCache = new HackyWorkspaceFilesCache (workspace.MonoDevelopSolution); metadataHandler = new Lazy<MetadataReferenceHandler> (() => new MetadataReferenceHandler (workspace.MetadataReferenceManager, projectMap)); hostDiagnosticUpdateSource = new Lazy<HostDiagnosticUpdateSource> (() => new HostDiagnosticUpdateSource (workspace, workspace.compositionManager.GetExportedValue<IDiagnosticUpdateSourceRegistrationService> ())); + + var persistentStorageLocationService = (MonoDevelopPersistentStorageLocationService)workspace.Services.GetService<IPersistentStorageLocationService> (); + if (workspace.MonoDevelopSolution != null) + persistentStorageLocationServiceRegistration = persistentStorageLocationService.RegisterPrimaryWorkspace (workspace.Id); } #region Solution mapping @@ -80,26 +89,6 @@ namespace MonoDevelop.Ide.TypeSystem return result; } } - - static async void OnSolutionModified (object sender, MonoDevelop.Projects.WorkspaceItemEventArgs args) - { - var sol = (MonoDevelop.Projects.Solution)args.Item; - var workspace = await IdeApp.TypeSystemService.GetWorkspaceAsync (sol, CancellationToken.None); - var solId = workspace.ProjectHandler.GetSolutionId (sol); - if (solId == null) - return; - - NotifySolutionModified (sol, solId, workspace); - } - - static void NotifySolutionModified (MonoDevelop.Projects.Solution sol, SolutionId solId, MonoDevelopWorkspace workspace) - { - if (string.IsNullOrWhiteSpace (sol.BaseDirectory)) - return; - - var locService = (MonoDevelopPersistentStorageLocationService)workspace.Services.GetService<IPersistentStorageLocationService> (); - locService.NotifyStorageLocationChanging (solId, sol.GetPreferencesDirectory ()); - } #endregion class SolutionData @@ -117,51 +106,66 @@ namespace MonoDevelop.Ide.TypeSystem if (fileName.IsNullOrEmpty) fileName = new FilePath (p.Name + ".dll"); - var (references, projectReferences) = await metadataHandler.Value.CreateReferences (p, token); - if (token.IsCancellationRequested) - return null; + if (!hackyCache.TryGetCachedItems (p, workspace.MetadataReferenceManager, projectMap, out var sourceFiles, out var analyzerFiles, out var references, out var projectReferences)) { + (references, projectReferences) = await metadataHandler.Value.CreateReferences (p, token).ConfigureAwait (false); + if (token.IsCancellationRequested) + return null; - var sourceFiles = await p.GetSourceFilesAsync (config?.Selector).ConfigureAwait (false); - if (token.IsCancellationRequested) - return null; + sourceFiles = await p.GetSourceFilesAsync (config?.Selector).ConfigureAwait (false); + if (token.IsCancellationRequested) + return null; + + analyzerFiles = await p.GetAnalyzerFilesAsync (config?.Selector).ConfigureAwait (false); + + if (config != null) + hackyCache.Update (config, p, projectMap, sourceFiles, analyzerFiles, references, projectReferences); + } - var analyzerFiles = await p.GetAnalyzerFilesAsync (config?.Selector).ConfigureAwait (false); if (token.IsCancellationRequested) return null; var loader = workspace.Services.GetService<IAnalyzerService> ().GetLoader (); - lock (workspace.updatingProjectDataLock) { + ProjectData projectData, oldProjectData; + List<DocumentInfo> mainDocuments, additionalDocuments; + try { + await workspace.LoadLock.WaitAsync ().ConfigureAwait (false); //when reloading e.g. after a save, preserve document IDs - var oldProjectData = projectMap.RemoveData (projectId); - var projectData = projectMap.CreateData (projectId, references); + oldProjectData = projectMap.RemoveData (projectId); + projectData = projectMap.CreateData (projectId, references); - var documents = CreateDocuments (projectData, p, token, sourceFiles, oldProjectData); + var documents = await CreateDocuments (projectData, p, token, sourceFiles, oldProjectData).ConfigureAwait (false); if (documents == null) return null; - // TODO: Pass in the WorkspaceMetadataFileReferenceResolver - var info = ProjectInfo.Create ( - projectId, - VersionStamp.Create (), - p.Name, - fileName.FileNameWithoutExtension, - (p as MonoDevelop.Projects.DotNetProject)?.RoslynLanguageName ?? LanguageNames.CSharp, - p.FileName, - fileName, - cp?.CreateCompilationOptions (), - cp?.CreateParseOptions (config), - documents.Item1, - projectReferences, - references.Select (x => x.CurrentSnapshot), - analyzerReferences: analyzerFiles.SelectAsArray (x => { - var analyzer = new MonoDevelopAnalyzer (x, hostDiagnosticUpdateSource.Value, projectId, workspace, loader, LanguageNames.CSharp); - return analyzer.GetReference (); - }), - additionalDocuments: documents.Item2 - ); - return info; + mainDocuments = documents.Item1; + additionalDocuments = documents.Item2; + } finally { + workspace.LoadLock.Release (); } + + // TODO: Pass in the WorkspaceMetadataFileReferenceResolver + var info = ProjectInfo.Create ( + projectId, + VersionStamp.Create (), + p.Name, + fileName.FileNameWithoutExtension, + (p as MonoDevelop.Projects.DotNetProject)?.RoslynLanguageName ?? LanguageNames.CSharp, + p.FileName, + fileName, + cp?.CreateCompilationOptions (), + cp?.CreateParseOptions (config), + mainDocuments, + projectReferences, + references.Select (x => x.CurrentSnapshot), + analyzerReferences: analyzerFiles.SelectAsArray (x => { + var analyzer = new MonoDevelopAnalyzer (x, hostDiagnosticUpdateSource.Value, projectId, workspace, loader, LanguageNames.CSharp); + analyzersToDispose.Add (analyzer); + return analyzer.GetReference (); + }), + additionalDocuments: additionalDocuments + ); + return info; } async Task<ConcurrentBag<ProjectInfo>> CreateProjectInfos (IEnumerable<MonoDevelop.Projects.Project> mdProjects, CancellationToken token) @@ -268,9 +272,9 @@ namespace MonoDevelop.Ide.TypeSystem lock (addLock) { if (!added) { added = true; - solution.Modified += OnSolutionModified; - NotifySolutionModified (solution, solutionId, workspace); workspace.OnSolutionAdded (solutionInfo); + var service = (MonoDevelopPersistentStorageLocationService)workspace.Services.GetService<IPersistentStorageLocationService> (); + service.SetupSolution (workspace); lock (workspace.generatedFiles) { foreach (var generatedFile in workspace.generatedFiles) { if (!workspace.IsDocumentOpen (generatedFile.Key.Id)) @@ -298,7 +302,7 @@ namespace MonoDevelop.Ide.TypeSystem return node.Parser.CanGenerateAnalysisDocument (mimeType, f.BuildAction, p.SupportedLanguages); } - Tuple<List<DocumentInfo>, List<DocumentInfo>> CreateDocuments (ProjectData projectData, MonoDevelop.Projects.Project p, CancellationToken token, ImmutableArray<MonoDevelop.Projects.ProjectFile> sourceFiles, ProjectData oldProjectData) + async Task<Tuple<List<DocumentInfo>, List<DocumentInfo>>> CreateDocuments (ProjectData projectData, MonoDevelop.Projects.Project p, CancellationToken token, ImmutableArray<MonoDevelop.Projects.ProjectFile> sourceFiles, ProjectData oldProjectData) { var documents = new List<DocumentInfo> (); // We don' add additionalDocuments anymore because they were causing slowdown of compilation generation @@ -319,7 +323,7 @@ namespace MonoDevelop.Ide.TypeSystem continue; documents.Add (CreateDocumentInfo (solutionData, p.Name, projectData, f)); } else { - foreach (var projectedDocument in GenerateProjections (f, projectData.DocumentData, p, oldProjectData, null)) { + foreach (var projectedDocument in await GenerateProjections (f, projectData.DocumentData, p, token, oldProjectData, null)) { var projectedId = projectData.DocumentData.GetOrCreate (projectedDocument.FilePath, oldProjectData?.DocumentData); if (!duplicates.Add (projectedId)) continue; @@ -337,41 +341,44 @@ namespace MonoDevelop.Ide.TypeSystem return Tuple.Create (documents, additionalDocuments); } - IEnumerable<DocumentInfo> GenerateProjections (MonoDevelop.Projects.ProjectFile f, DocumentMap documentMap, MonoDevelop.Projects.Project p, ProjectData oldProjectData, HashSet<DocumentId> duplicates) + async Task<List<DocumentInfo>> GenerateProjections (MonoDevelop.Projects.ProjectFile f, DocumentMap documentMap, MonoDevelop.Projects.Project p, CancellationToken token, ProjectData oldProjectData, HashSet<DocumentId> duplicates) { var mimeType = IdeServices.DesktopService.GetMimeTypeForUri (f.FilePath); var node = IdeApp.TypeSystemService.GetTypeSystemParserNode (mimeType, f.BuildAction); if (node == null || !node.Parser.CanGenerateProjection (mimeType, f.BuildAction, p.SupportedLanguages)) - yield break; + return new List<DocumentInfo> (); + var options = new ParseOptions { FileName = f.FilePath, Project = p, Content = TextFileProvider.Instance.GetReadOnlyTextEditorData (f.FilePath), }; - var generatedProjections = node.Parser.GenerateProjections (options); + var generatedProjections = await node.Parser.GenerateProjections (options, token); var list = new List<Projection> (); var entry = new ProjectionEntry { File = f, Projections = list, }; - foreach (var projection in generatedProjections.Result) { + var result = new List<DocumentInfo> (generatedProjections.Count); + foreach (var projection in generatedProjections) { list.Add (projection); if (duplicates != null && !duplicates.Add (documentMap.GetOrCreate (projection.Document.FileName, oldProjectData?.DocumentData))) continue; var plainName = projection.Document.FileName.FileName; var folders = GetFolders (p.Name, f); - yield return DocumentInfo.Create ( + result.Add(DocumentInfo.Create ( documentMap.GetOrCreate (projection.Document.FileName, oldProjectData?.DocumentData), plainName, folders, SourceCodeKind.Regular, TextLoader.From (TextAndVersion.Create (new MonoDevelopSourceText (projection.Document), VersionStamp.Create (), projection.Document.FileName)), projection.Document.FileName, - false + false) ); } projections.AddProjectionEntry (entry); + return result; } static DocumentInfo CreateDocumentInfo (SolutionData data, string projectName, ProjectData id, MonoDevelop.Projects.ProjectFile f) @@ -396,6 +403,17 @@ namespace MonoDevelop.Ide.TypeSystem { return new [] { projectName }.Concat (f.ProjectVirtualPath.ParentDirectory.ToString ().Split (Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar)); } + + public void Dispose () + { + persistentStorageLocationServiceRegistration?.Dispose (); + + solutionIdMap.Clear (); + + foreach (var analyzer in analyzersToDispose) + analyzer.Dispose (); + analyzersToDispose.Clear (); + } } } } diff --git a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.TypeSystem/MonoDevelopWorkspace.cs b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.TypeSystem/MonoDevelopWorkspace.cs index 14f66e6d0d..c460b5d491 100644 --- a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.TypeSystem/MonoDevelopWorkspace.cs +++ b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.TypeSystem/MonoDevelopWorkspace.cs @@ -1,4 +1,4 @@ -// +// // MonoDevelopWorkspace.cs // // Author: @@ -74,8 +74,9 @@ namespace MonoDevelop.Ide.TypeSystem internal readonly WorkspaceId Id;
CancellationTokenSource src = new CancellationTokenSource ();
- bool disposed;
- readonly object updatingProjectDataLock = new object ();
+ bool disposed; + + internal readonly SemaphoreSlim LoadLock = new SemaphoreSlim (1, 1);
Lazy<MonoDevelopMetadataReferenceManager> manager;
Lazy<MetadataReferenceHandler> metadataHandler;
ProjectionData Projections { get; }
@@ -83,7 +84,7 @@ namespace MonoDevelop.Ide.TypeSystem ProjectDataMap ProjectMap { get; }
ProjectSystemHandler ProjectHandler { get; }
- public MonoDevelop.Projects.Solution MonoDevelopSolution { get; }
+ public MonoDevelop.Projects.Solution MonoDevelopSolution { get; private set; }
internal MonoDevelopMetadataReferenceManager MetadataReferenceManager => manager.Value;
@@ -260,14 +261,43 @@ namespace MonoDevelop.Ide.TypeSystem protected internal override bool PartialSemanticsEnabled => backgroundCompiler != null;
+ // This is called by OnSolutionRemoved and on Dispose.
+ protected override void ClearSolutionData ()
+ {
+ if (MonoDevelopSolution != null) {
+ foreach (var prj in MonoDevelopSolution.GetAllProjects ()) {
+ ProjectMap.RemoveProject (prj);
+ UnloadMonoProject (prj);
+ }
+ }
+
+ base.ClearSolutionData ();
+ }
+
+ // This is called by OnProjectRemoved.
+ protected override void ClearProjectData (ProjectId projectId)
+ {
+ var actualProject = ProjectMap.RemoveProject (projectId);
+ UnloadMonoProject (actualProject);
+
+ base.ClearProjectData (projectId);
+ }
+
protected override void Dispose (bool finalize)
{
- base.Dispose (finalize);
if (disposed)
return;
- disposed = true;
+ disposed = true; + + var cacheService = Services.GetService<IWorkspaceCacheService> ();
+ if (cacheService != null)
+ cacheService.CacheFlushRequested -= OnCacheFlushRequested;
+
+ var cacheHostService = Services.GetService<IProjectCacheHostService> () as IDisposable;
+ cacheHostService?.Dispose (); + ProjectHandler.Dispose ();
MetadataReferenceManager.ClearCache ();
TypeSystemService.EnableSourceAnalysis.Changed -= OnEnableSourceAnalysisChanged;
@@ -275,14 +305,8 @@ namespace MonoDevelop.Ide.TypeSystem desktopService.MemoryMonitor.StatusChanged -= OnMemoryStatusChanged;
CancelLoad ();
- - if (workspace != null)
+ if (workspace != null) {
workspace.ActiveConfigurationChanged -= HandleActiveConfigurationChanged;
-
- if (MonoDevelopSolution != null) {
- foreach (var prj in MonoDevelopSolution.GetAllProjects ()) {
- UnloadMonoProject (prj);
- }
}
var solutionCrawler = Services.GetService<ISolutionCrawlerRegistrationService> ();
@@ -290,8 +314,13 @@ namespace MonoDevelop.Ide.TypeSystem if (backgroundCompiler != null) {
backgroundCompiler.Dispose ();
- backgroundCompiler = null; // PartialSemanticsEnabled will now return false
+ backgroundCompiler = null; // PartialSemanticsEnabled will now return false }
+
+ base.Dispose (finalize);
+
+ // Do this at the end so solution removal from base disposal is done properly.
+ MonoDevelopSolution = null;
}
internal void InformDocumentTextChange (DocumentId id, SourceText text)
@@ -705,9 +734,9 @@ namespace MonoDevelop.Ide.TypeSystem var projections = await node.Parser.GenerateProjections (options);
UpdateProjectionEntry (file, projections);
var projectId = GetProjectId (project);
- var projectdata = ProjectMap.GetData (projectId);
- foreach (var projected in projections) {
- OnDocumentTextChanged (projectdata.DocumentData.Get (projected.Document.FileName), new MonoDevelopSourceText (projected.Document), PreservationMode.PreserveValue);
+ var projectdata = ProjectMap.GetData (projectId); + foreach (var projected in projections) { + OnDocumentTextChanged (projectdata.DocumentData.Get (projected.Document.FileName), new MonoDevelopSourceText (projected.Document), PreservationMode.PreserveValue); }
}
}
@@ -1070,54 +1099,49 @@ namespace MonoDevelop.Ide.TypeSystem return project.GetAdditionalDocument (documentId);
}
- internal Task UpdateFileContent (string fileName, string text)
+ internal async Task UpdateFileContent (string fileName, string text)
{
SourceText newText = SourceText.From (text);
var tasks = new List<Task> ();
- lock (updatingProjectDataLock) {
- foreach (var kv in ProjectMap.projectDataMap) {
- var projectId = kv.Key;
- var docId = this.GetDocumentId (projectId, fileName);
- if (docId != null) {
- try {
- if (this.GetDocument (docId) != null) {
- base.OnDocumentTextChanged (docId, newText, PreservationMode.PreserveIdentity);
- } else if (this.GetAdditionalDocument (docId) != null) {
- base.OnAdditionalDocumentTextChanged (docId, newText, PreservationMode.PreserveIdentity);
- }
- } catch (Exception e) {
- LoggingService.LogWarning ("Roslyn error on text change", e);
- }
- }
- var monoProject = GetMonoProject (projectId);
- if (monoProject != null) {
- var pf = monoProject.GetProjectFile (fileName);
- if (pf != null) {
- var mimeType = desktopService.GetMimeTypeForUri (fileName);
- if (typeSystemService.CanParseProjections (monoProject, mimeType, fileName)) {
- var parseOptions = new ParseOptions { Project = monoProject, FileName = fileName, Content = new StringTextSource (text), BuildAction = pf.BuildAction };
- var task = typeSystemService.ParseProjection (parseOptions, mimeType);
- tasks.Add (task);
- }
- }
- }
- }
+ try { + await LoadLock.WaitAsync ();
+ foreach (var projectId in ProjectMap.GetProjectIds ()) { + var docId = this.GetDocumentId (projectId, fileName); + if (docId != null) { + try { + if (this.GetDocument (docId) != null) { + base.OnDocumentTextChanged (docId, newText, PreservationMode.PreserveIdentity); + } else if (this.GetAdditionalDocument (docId) != null) { + base.OnAdditionalDocumentTextChanged (docId, newText, PreservationMode.PreserveIdentity); + } + } catch (Exception e) { + LoggingService.LogWarning ("Roslyn error on text change", e); + } + } + var monoProject = GetMonoProject (projectId); + if (monoProject != null) { + var pf = monoProject.GetProjectFile (fileName); + if (pf != null) { + var mimeType = desktopService.GetMimeTypeForUri (fileName); + if (typeSystemService.CanParseProjections (monoProject, mimeType, fileName)) { + var parseOptions = new ParseOptions { Project = monoProject, FileName = fileName, Content = new StringTextSource (text), BuildAction = pf.BuildAction }; + var task = typeSystemService.ParseProjection (parseOptions, mimeType); + tasks.Add (task); + } + } + } + } + } finally { + LoadLock.Release (); }
- return Task.WhenAll (tasks);
+ await Task.WhenAll (tasks);
}
internal void RemoveProject (MonoDevelop.Projects.Project project)
{
var id = GetProjectId (project);
if (id != null) {
- foreach (var docId in GetOpenDocumentIds (id).ToList ()) {
- ClearOpenDocument (docId);
- }
-
- ProjectMap.RemoveProject (project, id);
- UnloadMonoProject (project);
-
OnProjectRemoved (id);
}
}
diff --git a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.TypeSystem/TypeSystemParserNode.cs b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.TypeSystem/TypeSystemParserNode.cs index fbd88c1e4c..3124ad9a0d 100644 --- a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.TypeSystem/TypeSystemParserNode.cs +++ b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.TypeSystem/TypeSystemParserNode.cs @@ -84,16 +84,5 @@ namespace MonoDevelop.Ide.TypeSystem } return false; } - - [Obsolete ("Use p.IsCompileable")] - public static bool IsCompileableFile (ProjectFile file, out Microsoft.CodeAnalysis.SourceCodeKind sck) - => IsCompileableFile (null, file, out sck); - - [Obsolete ("Use p.IsCompileable")] - public static bool IsCompileableFile (MonoDevelop.Projects.Project p, ProjectFile file, out Microsoft.CodeAnalysis.SourceCodeKind sck) - { - sck = file.SourceCodeKind; - return p.IsCompileable (file.FilePath); - } } } diff --git a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.TypeSystem/TypeSystemService.cs b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.TypeSystem/TypeSystemService.cs index f3e04140e8..cf888d20a3 100644 --- a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.TypeSystem/TypeSystemService.cs +++ b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.TypeSystem/TypeSystemService.cs @@ -285,11 +285,16 @@ namespace MonoDevelop.Ide.TypeSystem var projectFile = options.Project.GetProjectFile (options.FileName); if (projectFile != null) { ws.UpdateProjectionEntry (projectFile, result.Projections); - foreach (var projection in result.Projections) { - var docId = ws.GetDocumentId (projectId, projection.Document.FileName); - if (docId != null) { - ws.InformDocumentTextChange (docId, new MonoDevelopSourceText (projection.Document)); + await ws.LoadLock.WaitAsync (); + try { + foreach (var projection in result.Projections) { + var docId = ws.GetDocumentId (projectId, projection.Document.FileName); + if (docId != null) { + ws.InformDocumentTextChange (docId, new MonoDevelopSourceText (projection.Document)); + } } + } finally { + ws.LoadLock.Release (); } } } diff --git a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.WelcomePage/IWelcomeWindowProvider.cs b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.WelcomePage/IWelcomeWindowProvider.cs index f63f42dcc0..ffe880beb9 100644 --- a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.WelcomePage/IWelcomeWindowProvider.cs +++ b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.WelcomePage/IWelcomeWindowProvider.cs @@ -32,12 +32,20 @@ namespace MonoDevelop.Ide.WelcomePage { public class WelcomeWindowShowOptions { - public WelcomeWindowShowOptions (bool closeSolution) + public WelcomeWindowShowOptions (bool closeSolution, bool userInitated = false) { CloseSolution = closeSolution; + UserInitiated = userInitated; } public bool CloseSolution { get; set; } + + /// <summary> + /// Gets or sets a value indicating whether the user initiated the + /// operation (i.e. Window->Start Window menu) + /// </summary> + /// <value><c>true</c> if user initiated; otherwise, <c>false</c>.</value> + public bool UserInitiated { get; set; } } [TypeExtensionPoint] diff --git a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.WelcomePage/WelcomePageCommands.cs b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.WelcomePage/WelcomePageCommands.cs index 5d63d80e13..8ba73b2f08 100644 --- a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.WelcomePage/WelcomePageCommands.cs +++ b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.WelcomePage/WelcomePageCommands.cs @@ -29,9 +29,6 @@ using MonoDevelop.Core; using MonoDevelop.Components.Commands; -using MonoDevelop.Ide.Gui; -using MonoDevelop.Ide; -using System.Linq; namespace MonoDevelop.Ide.WelcomePage { @@ -39,7 +36,7 @@ namespace MonoDevelop.Ide.WelcomePage { public static void Show () { - WelcomePageService.ShowWelcomePageOrWindow (options: new WelcomeWindowShowOptions (false)); + WelcomePageService.ShowWelcomePageOrWindow (options: new WelcomeWindowShowOptions (false, true)); } protected override void Run() diff --git a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.csproj b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.csproj index 779b9d6a47..069ee054b0 100644 --- a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.csproj +++ b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.csproj @@ -46,8 +46,8 @@ <Reference Include="Microsoft.VisualStudio.Composition, Version=15.6.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"> <HintPath>..\..\..\packages\Microsoft.VisualStudio.Composition.15.6.36\lib\net45\Microsoft.VisualStudio.Composition.dll</HintPath> </Reference> - <Reference Include="Microsoft.VisualStudio.Threading, Version=15.6.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"> - <HintPath>..\..\..\packages\Microsoft.VisualStudio.Threading.15.6.46\lib\net45\Microsoft.VisualStudio.Threading.dll</HintPath> + <Reference Include="Microsoft.VisualStudio.Threading, Version=15.8.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"> + <HintPath>..\..\..\packages\Microsoft.VisualStudio.Threading.15.8.209\lib\net45\Microsoft.VisualStudio.Threading.dll</HintPath> </Reference> <Reference Include="Microsoft.VisualStudio.Validation, Version=15.3.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"> <HintPath>..\..\..\packages\Microsoft.VisualStudio.Validation.15.3.32\lib\net45\Microsoft.VisualStudio.Validation.dll</HintPath> @@ -135,6 +135,7 @@ <Reference Include="YamlDotNet"> <HintPath>..\..\..\packages\YamlDotNet.Signed.5.2.1\lib\net45\YamlDotNet.dll</HintPath> </Reference> + <Reference Include="System.Net.Http" /> </ItemGroup> <ItemGroup> <ProjectReference Include="..\MonoDevelop.Core\MonoDevelop.Core.csproj"> @@ -4276,6 +4277,9 @@ <Compile Include="MonoDevelop.Ide.Extensions\StartupInfo.cs" /> <Compile Include="MonoDevelop.Ide\IdeServices.cs" /> <Compile Include="MonoDevelop.Ide.Tasks\TaskSeverity.cs" /> + <Compile Include="MonoDevelop.Ide.Projects\NewSolutionRunConfigurationDialog.cs" /> + <Compile Include="MonoDevelop.Ide.TypeSystem\HackyWorkspaceFilesCache.cs" /> + <Compile Include="MonoDevelop.Ide.Gui.Dialogs\NewFolderDialog.cs" /> </ItemGroup> <ItemGroup> <Data Include="options\DefaultEditingLayout.xml" /> diff --git a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide/DesktopService.cs b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide/DesktopService.cs index 27fb9ebda6..1bae6697f8 100644 --- a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide/DesktopService.cs +++ b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide/DesktopService.cs @@ -1,4 +1,4 @@ -// +// // IdeApp.DesktopService.cs // // Author: @@ -144,13 +144,6 @@ namespace MonoDevelop.Ide get { return PlatformService.Name; } } - [Obsolete] - public string DefaultControlLeftRightBehavior { - get { - return PlatformService.DefaultControlLeftRightBehavior; - } - } - public void ShowUrl (string url) { PlatformService.ShowUrl (url); @@ -364,6 +357,16 @@ namespace MonoDevelop.Ide PlatformService.GrabDesktopFocus (window); } + public Window GetParentForModalWindow () + { + return PlatformService.GetParentForModalWindow (); + } + + public Window GetFocusedTopLevelWindow () + { + return PlatformService.GetFocusedTopLevelWindow (); + } + public void FocusWindow (Window window) { if (window != null) diff --git a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide/IdePreferences.cs b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide/IdePreferences.cs index 4513095e1a..d2990eed1b 100644 --- a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide/IdePreferences.cs +++ b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide/IdePreferences.cs @@ -65,7 +65,14 @@ namespace MonoDevelop.Ide Compact, VeryCompact } - + + public enum OnStartupBehaviour + { + ShowStartWindow, + LoadPreviousSolution, + EmptyEnvironment + } + public class IdePreferences { internal IdePreferences () @@ -95,7 +102,7 @@ namespace MonoDevelop.Ide public readonly ConfigurationProperty<string> UserInterfaceLanguage = Runtime.Preferences.UserInterfaceLanguage; public readonly ConfigurationProperty<string> UserInterfaceThemeName = ConfigurationProperty.Create ("MonoDevelop.Ide.UserInterfaceTheme", Platform.IsLinux ? "" : "Light"); public readonly ConfigurationProperty<WorkbenchCompactness> WorkbenchCompactness = ConfigurationProperty.Create ("MonoDevelop.Ide.WorkbenchCompactness", MonoDevelop.Ide.WorkbenchCompactness.Normal); - public readonly ConfigurationProperty<bool> LoadPrevSolutionOnStartup = ConfigurationProperty.Create ("SharpDevelop.LoadPrevProjectOnStartup", false); + public readonly ConfigurationProperty<OnStartupBehaviour> StartupBehaviour = ConfigurationProperty.Create ("MonoDevelop.Ide.StartupBehaviour", OnStartupBehaviour.ShowStartWindow); public readonly ConfigurationProperty<bool> CreateFileBackupCopies = ConfigurationProperty.Create ("SharpDevelop.CreateBackupCopy", false); public ConfigurationProperty<bool> LoadDocumentUserProperties => IdeApp.Workbench.DocumentManager.Preferences.LoadDocumentUserProperties; public readonly ConfigurationProperty<bool> EnableDocumentSwitchDialog = ConfigurationProperty.Create ("MonoDevelop.Core.Gui.EnableDocumentSwitchDialog", true); diff --git a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide/IdeStartup.cs b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide/IdeStartup.cs index f02398a7d7..33d1dc875d 100644 --- a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide/IdeStartup.cs +++ b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide/IdeStartup.cs @@ -1,4 +1,4 @@ -// +// // IdeStartup.cs // // Author: @@ -67,6 +67,7 @@ namespace MonoDevelop.Ide static Stopwatch timeToCodeTimer = new Stopwatch (); static Dictionary<string, long> sectionTimings = new Dictionary<string, long> (); static bool hideWelcomePage; + static StartupInfo startupInfo; static TimeToCodeMetadata ttcMetadata; @@ -121,7 +122,7 @@ namespace MonoDevelop.Ide IdeTheme.InitializeGtk (BrandingService.ApplicationName, ref args); - var startupInfo = new StartupInfo (options, args); + startupInfo = new StartupInfo (options, args); IdeApp.Customizer = options.IdeCustomizer ?? new IdeCustomizer (); try { @@ -282,8 +283,9 @@ namespace MonoDevelop.Ide // XBC #33699 Counters.Initialization.Trace ("Initializing IdeApp"); - hideWelcomePage = startupInfo.HasFiles; + hideWelcomePage = startupInfo.HasFiles || IdeApp.Preferences.StartupBehaviour.Value != OnStartupBehaviour.ShowStartWindow; await IdeApp.Initialize (monitor); + sectionTimings ["AppInitialization"] = startupSectionTimer.ElapsedMilliseconds; startupSectionTimer.Restart (); @@ -303,7 +305,7 @@ namespace MonoDevelop.Ide // load previous combine RecentFile openedProject = null; - if (IdeApp.Preferences.LoadPrevSolutionOnStartup && !startupInfo.HasSolutionFile && !IdeApp.Workspace.WorkspaceItemIsOpening && !IdeApp.Workspace.IsOpen) { + if (IdeApp.Preferences.StartupBehaviour.Value == OnStartupBehaviour.LoadPreviousSolution && !startupInfo.HasSolutionFile && !IdeApp.Workspace.WorkspaceItemIsOpening && !IdeApp.Workspace.IsOpen) { openedProject = IdeServices.DesktopService.RecentFiles.MostRecentlyUsedProject; if (openedProject != null) { var metadata = GetOpenWorkspaceOnStartupMetadata (); @@ -463,19 +465,21 @@ namespace MonoDevelop.Ide WelcomePage.WelcomePageService.ShowWelcomePage (); Counters.Initialization.Trace ("Showed welcome page"); IdeApp.Workbench.Show (); + } else if (hideWelcomePage && !startupInfo.OpenedFiles) { + IdeApp.Workbench.Show (); } return false; } - void CreateStartupMetadata (StartupInfo startupInfo, Dictionary<string, long> timings) + void CreateStartupMetadata (StartupInfo si, Dictionary<string, long> timings) { var result = IdeServices.DesktopService.PlatformTelemetry; if (result == null) { return; } - var startupMetadata = GetStartupMetadata (startupInfo, result, timings); + var startupMetadata = GetStartupMetadata (si, result, timings); Counters.Startup.Inc (startupMetadata); if (ttcMetadata != null) { @@ -880,7 +884,8 @@ namespace MonoDevelop.Ide IsInitialRunAfterUpgrade = IdeApp.IsInitialRunAfterUpgrade, TimeSinceMachineStart = (long)platformDetails.TimeSinceMachineStart.TotalMilliseconds, TimeSinceLogin = (long)platformDetails.TimeSinceLogin.TotalMilliseconds, - Timings = timings + Timings = timings, + StartupBehaviour = IdeApp.Preferences.StartupBehaviour.Value }; } diff --git a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide/MessageService.cs b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide/MessageService.cs index 5a41930ccf..79175fd06b 100644 --- a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide/MessageService.cs +++ b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide/MessageService.cs @@ -134,7 +134,7 @@ namespace MonoDevelop.Ide static Window defaultRootWindow; public static Window RootWindow { get { - if (WelcomePageService.WelcomePageVisible) + if (WelcomePageService.WelcomeWindowVisible) return WelcomePageService.WelcomeWindow; return defaultRootWindow; } @@ -322,7 +322,7 @@ namespace MonoDevelop.Ide // if dialog is modal, make sure it's parented on any existing modal dialog Gtk.Dialog dialog = dlg; if (dialog.Modal) { - parent = GetDefaultModalParent (); + parent = IdeServices.DesktopService.GetParentForModalWindow (); } //ensure the dialog has a parent @@ -355,10 +355,13 @@ namespace MonoDevelop.Ide }).Wait (); #endif + var initialRootWindow = Xwt.MessageDialog.RootWindow; try { + Xwt.MessageDialog.RootWindow = Xwt.Toolkit.CurrentEngine.WrapWindow (dialog); IdeApp.DisableIdleActions (); return GtkWorkarounds.RunDialogWithNotification (dialog); } finally { + Xwt.MessageDialog.RootWindow = initialRootWindow; IdeApp.EnableIdleActions (); } } @@ -381,28 +384,6 @@ namespace MonoDevelop.Ide dialog.Shown -= HandleShown; } #endif - - /// <summary> - /// Gets a default parent for modal dialogs. - /// </summary> - public static Window GetDefaultModalParent () - { - foreach (Gtk.Window w in Gtk.Window.ListToplevels ()) - if (w.Visible && w.HasToplevelFocus && w.Modal) - return w; - return GetFocusedToplevel (); - } - - static Window GetFocusedToplevel () - { - // TODO: support native toplevels - // use the first "normal" toplevel window (skipping docks, popups, etc.) or the main IDE window - Window gtkToplevel = Gtk.Window.ListToplevels ().FirstOrDefault (w => w.HasToplevelFocus && - (w.TypeHint == Gdk.WindowTypeHint.Dialog || - w.TypeHint == Gdk.WindowTypeHint.Normal || - w.TypeHint == Gdk.WindowTypeHint.Utility)); - return gtkToplevel ?? RootWindow; - } /// <summary> /// Positions a dialog relative to its parent on platforms where default placement is known to be poor. @@ -413,39 +394,57 @@ namespace MonoDevelop.Ide if (!Platform.IsMac) return; - Gtk.Window child = childControl; - //modal windows should always be placed o top of existing modal windows - if (child.Modal) - parent = GetDefaultModalParent (); - - //else center on the focused toplevel - if (parent == null) - parent = GetFocusedToplevel (); + if (parent == null) { + if (childControl.nativeWidget is Gtk.Window gtkChild) { + if (gtkChild.Modal) + parent = IdeServices.DesktopService.GetParentForModalWindow (); + } + } - if (parent != null) - CenterWindow (child, parent); + CenterWindow (childControl, parent); } - + /// <summary>Centers a window relative to its parent.</summary> static void CenterWindow (Window childControl, Window parentControl) { - // TODO: support cross-toolkit centering - if (!(parentControl.nativeWidget is Gtk.Window)) { - // FIXME: center on screen if no Gtk parent given for a Gtk dialog - if (childControl.nativeWidget is Gtk.Window gtkChild) - gtkChild.WindowPosition = Gtk.WindowPosition.Center; + var gtkChild = childControl?.nativeWidget as Gtk.Window; + var gtkParent = parentControl?.nativeWidget as Gtk.Window; +#if MAC + var nsChild = childControl?.nativeWidget as NSWindow; + var nsParent = parentControl?.nativeWidget as NSWindow ?? parentControl; + + if (nsChild != null) { + if (nsParent == null || !nsParent.IsVisible) { + nsChild.Center (); + } else { + int x = (int)(Math.Max (0, nsParent.Frame.Left + (nsParent.Frame.Width - nsChild.Frame.Width) / 2)); + int y = (int)(Math.Max (0, nsParent.Frame.Top + (nsParent.Frame.Height - nsChild.Frame.Height) / 2)); + nsChild.SetFrame (new CoreGraphics.CGRect (x, y, nsChild.Frame.Width, nsChild.Frame.Height), true); + } + return; } - Gtk.Window child = childControl; - Gtk.Window parent = parentControl; - child.Child.Show (); - int w, h, winw, winh, x, y, winx, winy; - child.GetSize (out w, out h); - parent.GetSize (out winw, out winh); - parent.GetPosition (out winx, out winy); - x = Math.Max (0, (winw - w) /2) + winx; - y = Math.Max (0, (winh - h) /2) + winy; - child.Move (x, y); +#endif + if (gtkChild != null) { + gtkChild.Child.Show (); + int x, y; + gtkChild.GetSize (out var w, out var h); + if (gtkParent != null) { + gtkParent.GetSize (out var winw, out var winh); + gtkParent.GetPosition (out var winx, out var winy); + x = Math.Max (0, (winw - w) / 2) + winx; + y = Math.Max (0, (winh - h) / 2) + winy; + gtkChild.Move (x, y); +#if MAC + } else if (nsParent != null) { + x = (int)(Math.Max (0, nsParent.Frame.Left + (nsParent.Frame.Width - w) / 2)); + y = (int)(Math.Max (0, nsParent.Frame.Top + (nsParent.Frame.Height - h) / 2)); + gtkChild.Move (x, y); +#endif + } else { + gtkChild.SetPosition (Gtk.WindowPosition.Center); + } + } } public static AlertButton GenericAlert (string icon, string primaryText, string secondaryText, params AlertButton[] buttons) @@ -560,7 +559,7 @@ namespace MonoDevelop.Ide return messageService.GetTextResponse (parent, question, caption, initialValue, isPassword); } - #region Internal GUI object +#region Internal GUI object static InternalMessageService mso; static InternalMessageService messageService { @@ -577,7 +576,7 @@ namespace MonoDevelop.Ide public AlertButton GenericAlert (Window parent, MessageDescription message) { var dialog = new AlertDialog (message) { - TransientFor = parent ?? GetDefaultModalParent () + TransientFor = parent ?? IdeServices.DesktopService.GetParentForModalWindow () }; return dialog.Run (); } @@ -589,14 +588,14 @@ namespace MonoDevelop.Ide Caption = caption, Value = initialValue, IsPassword = isPassword, - TransientFor = parent ?? GetDefaultModalParent () + TransientFor = parent ?? IdeServices.DesktopService.GetParentForModalWindow () }; if (dialog.Run ()) return dialog.Value; return null; } } - #endregion +#endregion } public class MessageDescription diff --git a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide/ProjectOperations.cs b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide/ProjectOperations.cs index e20d881b26..d9d578d147 100644 --- a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide/ProjectOperations.cs +++ b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide/ProjectOperations.cs @@ -1,4 +1,4 @@ -//
+//
// ProjectOperations.cs
//
// Author:
@@ -49,22 +49,23 @@ using MonoDevelop.Projects; using MonoDevelop.Projects.MSBuild; using ExecutionContext = MonoDevelop.Projects.ExecutionContext; using MonoDevelop.Ide.Gui.Documents;
+using MonoDevelop.Ide.Projects.OptionPanels;
namespace MonoDevelop.Ide {
/// <summary> /// This is the basic interface to the workspace. - /// </summary>
+ /// </summary> [DefaultServiceImplementation] public partial class ProjectOperations: Service {
- RootWorkspace workspace;
- - AsyncOperation<BuildResult> currentBuildOperation = new AsyncOperation<BuildResult> (Task.FromResult (BuildResult.CreateSuccess ()), null); + static AsyncOperation<BuildResult> DefaultBuildOperation = new AsyncOperation<BuildResult> (Task.FromResult (BuildResult.CreateSuccess ()), null); + AsyncOperation<BuildResult> currentBuildOperation = DefaultBuildOperation; MultipleAsyncOperation currentRunOperation = MultipleAsyncOperation.CompleteMultipleOperation; IBuildTarget currentBuildOperationOwner; List<IBuildTarget> currentRunOperationOwners = new List<IBuildTarget> (); - + RootWorkspace workspace;
+ SelectReferenceDialog selDialog = null; internal ProjectOperations () @@ -148,7 +149,13 @@ namespace MonoDevelop.Ide public AsyncOperation CurrentRunOperation { get { return currentRunOperation; } set { AddRunOperation (value); } - } + }
+
+ void ResetCurrentBuildOperation () + {
+ currentBuildOperation = DefaultBuildOperation;
+ currentBuildOperationOwner = null; + }
public void AddRunOperation (AsyncOperation runOperation) { @@ -340,17 +347,18 @@ namespace MonoDevelop.Ide public async void Export (IMSBuildFileObject item, MSBuildFileFormat format) { - ExportSolutionDialog dlg = new ExportSolutionDialog (item, format); - + ExportSolutionDialog dlg = null; try { + dlg = new ExportSolutionDialog (item, format); + if (MessageService.RunCustomDialog (dlg) == (int) Gtk.ResponseType.Ok) { using (ProgressMonitor monitor = IdeServices.ProgressMonitorManager.GetToolOutputProgressMonitor (true)) { await Services.ProjectService.Export (monitor, item.FileName, dlg.TargetFolder, dlg.Format); } } } finally { - dlg.Destroy (); - dlg.Dispose (); + dlg?.Destroy (); + dlg?.Dispose (); } } @@ -654,6 +662,35 @@ namespace MonoDevelop.Ide } } } +
+ public async void ShowRunConfiguration (Solution solution, MultiItemSolutionRunConfiguration runConfiguration) + { + var optionsDialog = new CombineOptionsDialog (IdeApp.Workbench.RootWindow, solution); + optionsDialog.CurrentConfig = IdeApp.Workspace.ActiveConfigurationId; + try {
+ optionsDialog.SelectPanel ("Run"); + if (runConfiguration != null) {
+ void shownCallback (object sender, EventArgs args) + { + var panel = optionsDialog.GetPanel<SolutionRunConfigurationsPanel> ("General");
+ if (panel != null) {
+ panel.ShowConfiguration (runConfiguration);
+ }
+ optionsDialog.Shown -= shownCallback; + } + + optionsDialog.Shown += shownCallback; + }
+ + if (MessageService.RunCustomDialog (optionsDialog) == (int)Gtk.ResponseType.Ok) { + await SaveAsync (solution); + await IdeApp.Workspace.SavePreferences (solution); + } + } finally { + optionsDialog.Destroy (); + optionsDialog.Dispose (); + } + }
public Task<bool> NewSolution () { @@ -1167,21 +1204,17 @@ namespace MonoDevelop.Ide var t = CleanAsync (entry, monitor, tt, false, operationContext); - t = t.ContinueWith (ta => { - currentBuildOperationOwner = null; - return ta.Result; - }); - var op = new AsyncOperation<BuildResult> (t, cs); currentBuildOperation = op; - currentBuildOperationOwner = entry; + currentBuildOperationOwner = entry;
+
+ t.ContinueWith (ta => { ResetCurrentBuildOperation (); });
+ return op; } catch { tt.End (); throw; } - - return currentBuildOperation; } async Task<BuildResult> CleanAsync (IBuildTarget entry, ProgressMonitor monitor, ITimeTracker tt, bool isRebuilding, OperationContext operationContext) @@ -1306,14 +1339,13 @@ namespace MonoDevelop.Ide ProgressMonitor monitor = IdeServices.ProgressMonitorManager.GetRebuildProgressMonitor ().WithCancellationSource (cs); var t = RebuildAsync (entry, monitor, operationContext); - t = t.ContinueWith (ta => { - currentBuildOperationOwner = null; - return ta.Result; - }); - - var op = new AsyncOperation<BuildResult> (t, cs); - return currentBuildOperation = op; + var op = new AsyncOperation<BuildResult> (t, cs);
+ currentBuildOperation = op;
+ currentBuildOperationOwner = entry;
+
+ t.ContinueWith (ta => { ResetCurrentBuildOperation (); });
+ return op;
} async Task<BuildResult> RebuildAsync (IBuildTarget entry, ProgressMonitor monitor, OperationContext operationContext) @@ -1604,15 +1636,19 @@ namespace MonoDevelop.Ide cs = CancellationTokenSource.CreateLinkedTokenSource (cs.Token, cancellationToken.Value); ProgressMonitor monitor = IdeServices.ProgressMonitorManager.GetBuildProgressMonitor ().WithCancellationSource (cs); BeginBuild (monitor, tt, false); - var t = BuildSolutionItemAsync (entry, monitor, tt, skipPrebuildCheck, operationContext); - currentBuildOperation = new AsyncOperation<BuildResult> (t, cs); - currentBuildOperationOwner = entry; - t.ContinueWith ((ta) => currentBuildOperationOwner = null); +
+ var t = BuildSolutionItemAsync (entry, monitor, tt, skipPrebuildCheck, operationContext);
+
+ var op = new AsyncOperation<BuildResult> (t, cs); + currentBuildOperation = op; + currentBuildOperationOwner = entry;
+
+ t.ContinueWith (ta => { ResetCurrentBuildOperation (); });
+ return op; } catch { tt.End (); throw; } - return currentBuildOperation; } async Task<BuildResult> BuildSolutionItemAsync (IBuildTarget entry, ProgressMonitor monitor, ITimeTracker tt, bool skipPrebuildCheck = false, OperationContext operationContext = null) @@ -1739,7 +1775,6 @@ namespace MonoDevelop.Ide tt.Trace ("Begin reporting build result"); try { if (result != null) { - var lastResult = result; monitor.Log.WriteLine (); var msg = GettextCatalog.GetString (
@@ -1759,7 +1794,7 @@ namespace MonoDevelop.Ide if (monitor.CancellationToken.IsCancellationRequested) { monitor.ReportError (GettextCatalog.GetString ("Build canceled."), null); - } else if (result.ErrorCount == 0 && result.WarningCount == 0 && lastResult.FailedBuildCount == 0) { + } else if (result.ErrorCount == 0 && result.WarningCount == 0 && result.FailedBuildCount == 0) { monitor.ReportSuccess (GettextCatalog.GetString ("Build successful.")); } else if (result.ErrorCount == 0 && result.WarningCount > 0) { monitor.ReportWarning(GettextCatalog.GetString("Build: ") + errorString + ", " + warningString); @@ -1769,7 +1804,7 @@ namespace MonoDevelop.Ide monitor.ReportError(GettextCatalog.GetString("Build failed."), null); } tt.Trace ("End build event"); - OnEndBuild (monitor, lastResult.FailedBuildCount == 0, lastResult, entry as SolutionFolderItem); + OnEndBuild (monitor, result.FailedBuildCount == 0, result, entry as SolutionFolderItem); } else { tt.Trace ("End build event"); OnEndBuild (monitor, false); @@ -2550,13 +2585,14 @@ namespace MonoDevelop.Ide public void AddOperation (AsyncOperation op) { Operations.Add (op); - op.Task.ContinueWith (CheckForCompletion); + op.Task.ContinueWith (t => CheckForCompletion (t)); } - void CheckForCompletion (Task obj) + void CheckForCompletion (Task obj)
{ - if (Operations.All (op => op.IsCompleted)) - TaskCompletionSource.SetResult (0); + if (Operations.All (op => op.IsCompleted)) { + TaskCompletionSource.SetResult (0);
+ } } void MultiCancel () diff --git a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide/Services.cs b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide/Services.cs index dd7d7e6166..87f4b79ef3 100644 --- a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide/Services.cs +++ b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide/Services.cs @@ -60,10 +60,7 @@ namespace MonoDevelop.Ide internal static TimerCounter SaveAllTimer = InstrumentationService.CreateTimerCounter ("Save all documents", "IDE", id:"Ide.Shell.SaveAll"); internal static TimerCounter CloseWorkspaceTimer = InstrumentationService.CreateTimerCounter ("Workspace closed", "IDE", id:"Ide.Shell.CloseWorkspace"); internal static Counter<StartupMetadata> Startup = InstrumentationService.CreateCounter<StartupMetadata> ("IDE Startup", "IDE", id:"Ide.Startup"); - internal static TimerCounter CompositionAddinLoad = InstrumentationService.CreateTimerCounter ("MEF Composition Addin Load", "IDE", id: "Ide.Startup.Composition.ExtensionLoad"); - internal static TimerCounter CompositionDiscovery = InstrumentationService.CreateTimerCounter ("MEF Composition From Discovery", "IDE", id:"Ide.Startup.Composition.Discovery"); - internal static TimerCounter CompositionCacheControl = InstrumentationService.CreateTimerCounter ("MEF Composition Control Cache", "IDE", id: "Ide.Startup.Composition.ControlCache"); - internal static TimerCounter CompositionCache = InstrumentationService.CreateTimerCounter ("MEF Composition From Cache", "IDE", id: "Ide.Startup.Composition.Cache"); + internal static TimerCounter<CompositionLoadMetadata> CompositionLoad = InstrumentationService.CreateTimerCounter<CompositionLoadMetadata> ("MEF Composition Load", "IDE", id: "Ide.Startup.Composition.Load"); internal static TimerCounter CompositionSave = InstrumentationService.CreateTimerCounter ("MEF Composition Save", "IDE", id: "Ide.CompositionSave"); internal static TimerCounter AnalysisTimer = InstrumentationService.CreateTimerCounter ("Code Analysis", "IDE", id:"Ide.CodeAnalysis"); internal static TimerCounter ProcessCodeCompletion = InstrumentationService.CreateTimerCounter ("Process Code Completion", "IDE", id: "Ide.ProcessCodeCompletion", logMessages:false); @@ -153,6 +150,11 @@ namespace MonoDevelop.Ide get => GetProperty<Dictionary<string, long>> (); set => SetProperty (value); } + + public OnStartupBehaviour StartupBehaviour { + get => GetProperty<OnStartupBehaviour> (); + set => SetProperty (value); + } } class TimeToCodeMetadata : CounterMetadata @@ -210,5 +212,32 @@ namespace MonoDevelop.Ide set => SetProperty (value); } } + + class CompositionLoadMetadata : CounterMetadata + { + public CompositionLoadMetadata () + { + } + + public CompositionLoadMetadata (Dictionary<string, long> timings) + { + Timings = timings; + } + + public Dictionary<string, long> Timings { + get => GetProperty<Dictionary<string, long>> (); + set => SetProperty (value); + } + + public bool ValidCache { + get => GetProperty<bool> (); + set => SetProperty (value); + } + + public long Duration { + get => GetProperty<long> (); + set => SetProperty (value); + } + } } diff --git a/main/src/core/MonoDevelop.Ide/gtkrc b/main/src/core/MonoDevelop.Ide/gtkrc index 0144ec125d..1dbc980702 100644 --- a/main/src/core/MonoDevelop.Ide/gtkrc +++ b/main/src/core/MonoDevelop.Ide/gtkrc @@ -1,5 +1,5 @@ # Xamarin Studio GTK Theme -# Copyright 2012-2016 Xamarin Inc. +# Copyright 2012-2019 Xamarin Inc. # Authors: # Christian Kellner <christian.kellner@lanedo.com> # Carlos Garnacho <carlos.garnacho@lanedo.com> @@ -13,6 +13,7 @@ gtk-color-scheme = fg_color: #000 base_color: #fff text_color: #000 +dim_color: #767676 selected_bg_color: #649dd6 selected_fg_color: #fff tooltip_bg_color: #fff9e5 @@ -42,13 +43,13 @@ style "default" { fg[NORMAL] = @fg_color fg[PRELIGHT] = @fg_color fg[SELECTED] = @selected_fg_color - fg[INSENSITIVE] = darker (@bg_color) + fg[INSENSITIVE] = @dim_color fg[ACTIVE] = @fg_color text[NORMAL] = @fg_color text[PRELIGHT] = @fg_color text[SELECTED] = @selected_fg_color - text[INSENSITIVE] = darker (@bg_color) + text[INSENSITIVE] = @dim_color text[ACTIVE] = @fg_color base[NORMAL] = @base_color diff --git a/main/src/core/MonoDevelop.Ide/gtkrc.mac b/main/src/core/MonoDevelop.Ide/gtkrc.mac index fbfc7736b6..84972e73f9 100644 --- a/main/src/core/MonoDevelop.Ide/gtkrc.mac +++ b/main/src/core/MonoDevelop.Ide/gtkrc.mac @@ -1,5 +1,5 @@ # Xamarin Studio Light Mac GTK Theme -# Copyright 2012-2016 Xamarin Inc. +# Copyright 2012-2019 Xamarin Inc. # Authors: # Christian Kellner <christian.kellner@lanedo.com> # Carlos Garnacho <carlos.garnacho@lanedo.com> @@ -14,7 +14,8 @@ fg_color: #272727 base_color: #fff text_color: #272727 link_color: #175fde -selected_bg_color: #5189ed +dim_color: #767676 +selected_bg_color: #2862d9 selected_fg_color: #fff tooltip_bg_color: #f2f2f2 tooltip_fg_color: #272727 @@ -49,13 +50,13 @@ style "default" { fg[NORMAL] = @fg_color fg[PRELIGHT] = @fg_color fg[SELECTED] = @selected_fg_color - fg[INSENSITIVE] = darker (@bg_color) + fg[INSENSITIVE] = @dim_color fg[ACTIVE] = @fg_color text[NORMAL] = @fg_color text[PRELIGHT] = @fg_color text[SELECTED] = @selected_fg_color - text[INSENSITIVE] = darker (@bg_color) + text[INSENSITIVE] = @dim_color text[ACTIVE] = @fg_color base[NORMAL] = @base_color diff --git a/main/src/core/MonoDevelop.Ide/gtkrc.mac-dark b/main/src/core/MonoDevelop.Ide/gtkrc.mac-dark index f107bcc673..687ae9b48a 100644 --- a/main/src/core/MonoDevelop.Ide/gtkrc.mac-dark +++ b/main/src/core/MonoDevelop.Ide/gtkrc.mac-dark @@ -1,5 +1,5 @@ # Xamarin Studio Dark Mac GTK Theme -# Copyright 2012-2016 Xamarin Inc. +# Copyright 2012-2019 Xamarin Inc. # Authors: # Christian Kellner <christian.kellner@lanedo.com> # Carlos Garnacho <carlos.garnacho@lanedo.com> @@ -14,8 +14,8 @@ fg_color: #d7d7d7 base_color: #404040 text_color: #d7d7d7 link_color: #ace2ff -dim_color: #777777 -selected_bg_color: #5189ed +dim_color: #ababab +selected_bg_color: #2257c9 selected_fg_color: #fff tooltip_bg_color: #5a5a5a tooltip_fg_color: #d2d5cd diff --git a/main/src/core/MonoDevelop.Ide/gtkrc.win32 b/main/src/core/MonoDevelop.Ide/gtkrc.win32 index f0e6db4d26..5ebcd9b1db 100644 --- a/main/src/core/MonoDevelop.Ide/gtkrc.win32 +++ b/main/src/core/MonoDevelop.Ide/gtkrc.win32 @@ -1,5 +1,5 @@ # Xamarin Studio Light Windows GTK Theme -# Copyright 2012-2016 Xamarin Inc. +# Copyright 2012-2019 Xamarin Inc. # Authors: # Christian Kellner <christian.kellner@lanedo.com> # Carlos Garnacho <carlos.garnacho@lanedo.com> @@ -14,6 +14,7 @@ fg_color: #000 base_color: #fff text_color: #000 link_color: #175fde +dim_color: #767676 selected_bg_color: #cce8ff selected_fg_color: #000 tooltip_bg_color: #f2f2f2 @@ -48,13 +49,13 @@ style "default" { fg[NORMAL] = @fg_color fg[PRELIGHT] = @fg_color fg[SELECTED] = @selected_fg_color - fg[INSENSITIVE] = darker (@bg_color) + fg[INSENSITIVE] = @dim_color fg[ACTIVE] = @fg_color text[NORMAL] = @fg_color text[PRELIGHT] = @fg_color text[SELECTED] = @selected_fg_color - text[INSENSITIVE] = darker (@bg_color) + text[INSENSITIVE] = @dim_color text[ACTIVE] = @fg_color base[NORMAL] = @base_color diff --git a/main/src/core/MonoDevelop.Ide/gtkrc.win32-dark b/main/src/core/MonoDevelop.Ide/gtkrc.win32-dark index 35836699c5..074f97a9d8 100644 --- a/main/src/core/MonoDevelop.Ide/gtkrc.win32-dark +++ b/main/src/core/MonoDevelop.Ide/gtkrc.win32-dark @@ -1,5 +1,5 @@ # Xamarin Studio Dark Windows GTK Theme -# Copyright 2012-2016 Xamarin Inc. +# Copyright 2012-2019 Xamarin Inc. # Authors: # Christian Kellner <christian.kellner@lanedo.com> # Carlos Garnacho <carlos.garnacho@lanedo.com> @@ -14,7 +14,7 @@ fg_color: #d7d7d7 base_color: #404040 text_color: #d7d7d7 link_color: #ace2ff -dim_color: #777777 +dim_color: #ababab selected_bg_color: #4c5e6e selected_fg_color: #bfbfbf tooltip_bg_color: #5a5a5a diff --git a/main/src/core/MonoDevelop.Projects.Formats.MSBuild/MonoDevelop.MSBuildResolver/Resolver.cs b/main/src/core/MonoDevelop.Projects.Formats.MSBuild/MonoDevelop.MSBuildResolver/Resolver.cs index 5deb40613d..d899b0fcf1 100644 --- a/main/src/core/MonoDevelop.Projects.Formats.MSBuild/MonoDevelop.MSBuildResolver/Resolver.cs +++ b/main/src/core/MonoDevelop.Projects.Formats.MSBuild/MonoDevelop.MSBuildResolver/Resolver.cs @@ -82,7 +82,7 @@ namespace MonoDevelop.Projects.MSBuild // Pick the SDK with the highest version foreach (var sdk in sdkFetcher ()) { - if (sdk.Name == sdkReference.Name) { + if (StringComparer.OrdinalIgnoreCase.Equals (sdk.Name, sdkReference.Name)) { if (sdk.Version != null) { // If the sdk has a version, it must satisfy the min version requirement if (minVersion != null && sdk.Version < minVersion) diff --git a/main/src/core/MonoDevelop.Startup/app.config b/main/src/core/MonoDevelop.Startup/app.config index 7f78d4ebec..2744d6fdba 100644 --- a/main/src/core/MonoDevelop.Startup/app.config +++ b/main/src/core/MonoDevelop.Startup/app.config @@ -160,7 +160,7 @@ </dependentAssembly> <dependentAssembly> <assemblyIdentity name="Microsoft.VisualStudio.Threading" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" /> - <bindingRedirect oldVersion="0.0.0.0-15.6.0.0" newVersion="15.6.0.0" /> + <bindingRedirect oldVersion="0.0.0.0-15.8.0.0" newVersion="15.8.0.0" /> </dependentAssembly> <dependentAssembly> <assemblyIdentity name="Microsoft.VisualStudio.Validation" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" /> diff --git a/main/src/tools/ExtensionTools/AddinDependencyTreeWidget.cs b/main/src/tools/ExtensionTools/AddinDependencyTreeWidget.cs new file mode 100644 index 0000000000..311f852b83 --- /dev/null +++ b/main/src/tools/ExtensionTools/AddinDependencyTreeWidget.cs @@ -0,0 +1,144 @@ +// +// AddinDependencyTreeWidget.cs +// +// Author: +// Marius Ungureanu <maungu@microsoft.com> +// +// Copyright (c) 2018 Microsoft Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +using System; +using System.Collections.Generic; +using Mono.Addins; +using Mono.Addins.Description; +using Xwt; +namespace MonoDevelop.ExtensionTools +{ + class AddinDependencyTreeWidget : Widget + { + readonly TreeStore treeStore; + readonly TreeView treeView; + readonly DataField<string> labelField = new DataField<string> (); + readonly Label summary = new Label (); + + public AddinDependencyTreeWidget (Addin[] addins = null) + { + addins = addins ?? AddinManager.Registry.GetAllAddins (); + + treeStore = new TreeStore (labelField); + treeView = new TreeView (treeStore); + + var col = treeView.Columns.Add ("Name", labelField); + col.Expands = true; + + FillData (addins); + treeView.ExpandAll (); + + var vbox = new VBox (); + vbox.PackStart (summary); + vbox.PackStart (treeView, true); + Content = vbox; + } + + void FillData(Addin[] addins) + { + var roots = MakeDependencyTree (addins); + var node = treeStore.AddNode (); + + node.SetValue (labelField, "Root"); + int depth = BuildTree (node, roots, new HashSet<AddinNode> (), 1); + + summary.Text = $"Depth: {depth}"; + } + + int BuildTree (TreeNavigator currentPosition, IEnumerable<AddinNode> addins, HashSet<AddinNode> visited, int currentDepth) + { + int maxDepth = currentDepth; + + foreach (var addinNode in addins) { + if (!visited.Add (addinNode)) + continue; + + var node = currentPosition.Clone ().AddChild (); + node.SetValue (labelField, addinNode.Label); + + var childDepth = BuildTree (node, addinNode.Children, visited, currentDepth + 1); + maxDepth = Math.Max (maxDepth, childDepth); + } + + return maxDepth; + } + + List<AddinNode> MakeDependencyTree (Addin[] addins) + { + var cache = new Dictionary<Addin, AddinNode> (); + var roots = new List<AddinNode> (); + + foreach (var addin in addins) { + var addinNode = GetOrCreateNode (addin); + + if (addin.Description.IsRoot) + roots.Add (addinNode); + } + + foreach (var kvp in cache) { + var addin = kvp.Key; + var addinNode = kvp.Value; + + // TODO: handle optional dependencies and other modules + foreach (Dependency dep in addin.Description.MainModule.Dependencies) { + if (dep is AddinDependency adep) { + string adepid = Addin.GetFullId (addin.Namespace, adep.AddinId, adep.Version); + + var addinDep = AddinManager.Registry.GetAddin (adepid); + + cache [addinDep].Children.Add (addinNode); + } + } + } + + return roots; + + AddinNode GetOrCreateNode (Addin addin) + { + if (!cache.TryGetValue (addin, out var addinNode)) { + var addinLabel = Addin.GetIdName (addin.Id); + cache [addin] = addinNode = new AddinNode (addinLabel); + } + return addinNode; + } + } + + class AddinNode : IEquatable<AddinNode> + { + public HashSet<AddinNode> Children { get; } = new HashSet<AddinNode> (); + public string Label { get; } + + public AddinNode (string label) + { + Label = label; + } + + public bool Equals (AddinNode other) + { + return Label == other.Label; + } + } + } +} diff --git a/main/src/tools/ExtensionTools/AddinInfo.cs b/main/src/tools/ExtensionTools/AddinInfo.cs new file mode 100644 index 0000000000..2ca91262e8 --- /dev/null +++ b/main/src/tools/ExtensionTools/AddinInfo.cs @@ -0,0 +1,15 @@ + +using System; +using Mono.Addins; +using Mono.Addins.Description; + +[assembly:Addin ("ExtensionTool", + Namespace = "MonoDevelop", + Version = MonoDevelop.BuildInfo.Version, + Category = "MonoDevelop Core")] + +[assembly:AddinName ("Extension Developer Tools")] +[assembly:AddinDescription ("Tools used to analyze the extension model")] +[assembly:AddinFlags (AddinFlags.Hidden)] + +[assembly:AddinDependency ("Core", MonoDevelop.BuildInfo.Version)] diff --git a/main/src/tools/ExtensionTools/AddinListWidget.cs b/main/src/tools/ExtensionTools/AddinListWidget.cs new file mode 100644 index 0000000000..d842be1e14 --- /dev/null +++ b/main/src/tools/ExtensionTools/AddinListWidget.cs @@ -0,0 +1,68 @@ +// +// AddinListWidget.cs +// +// Author: +// Marius Ungureanu <maungu@microsoft.com> +// +// Copyright (c) 2018 Microsoft Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +using System; +using Mono.Addins; +using Xwt; + +namespace MonoDevelop.ExtensionTools +{ + class AddinListWidget : Widget + { + readonly ListStore listStore; + readonly ListView listView; + readonly DataField<string> labelField = new DataField<string> (); + readonly Label summary = new Label (); + + public AddinListWidget (Addin[] addins = null) + { + addins = addins ?? AddinManager.Registry.GetAllAddins (x => x.Name); + + listStore = new ListStore (labelField); + listView = new ListView (listStore); + + var col = listView.Columns.Add ("Name", labelField); + col.Expands = true; + + FillData (addins); + + var vbox = new VBox (); + vbox.PackStart (summary, false); + vbox.PackStart (listView, true); + Content = vbox; + } + + void FillData (Addin[] addins) + { + summary.Text = $"Count: {addins.Length}"; + + foreach (var addin in addins) { + int row = listStore.AddRow (); + listStore.SetValue (row, labelField, addin.Name); + } + // TODO: clicking a node should open addin info tab + } + } +} diff --git a/main/src/tools/ExtensionTools/AddinRegistryExtensions.cs b/main/src/tools/ExtensionTools/AddinRegistryExtensions.cs new file mode 100644 index 0000000000..493c421d59 --- /dev/null +++ b/main/src/tools/ExtensionTools/AddinRegistryExtensions.cs @@ -0,0 +1,64 @@ +// +// AddinRegistryExtensions.cs +// +// Author: +// Marius Ungureanu <maungu@microsoft.com> +// +// Copyright (c) 2018 Microsoft Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +using System; +using System.Collections.Generic; +using Mono.Addins; + +namespace MonoDevelop.ExtensionTools +{ + static class AddinRegistryExtensions + { + public static Addin[] GetAllAddins (this AddinRegistry registry, Func<Addin, string> sortItemSelector = null) + { + if (sortItemSelector == null) + sortItemSelector = x => x.Id; + + var array = registry.GetModules (AddinSearchFlags.IncludeAll | AddinSearchFlags.LatestVersionsOnly); + + var comparer = new NameComparer (sortItemSelector); + Array.Sort (array, comparer); + + return array; + } + + class NameComparer : IComparer<Addin> + { + readonly Func<Addin, string> selector; + + public NameComparer (Func<Addin, string> selector) + { + this.selector = selector; + } + + public int Compare (Addin x, Addin y) + { + var xString = selector (x); + var yString = selector (y); + return string.Compare (xString, yString, StringComparison.Ordinal); + } + } + } +} diff --git a/main/src/tools/ExtensionTools/Application.cs b/main/src/tools/ExtensionTools/Application.cs new file mode 100644 index 0000000000..7403015edb --- /dev/null +++ b/main/src/tools/ExtensionTools/Application.cs @@ -0,0 +1,97 @@ +// +// MyClass.cs +// +// Author: +// Marius Ungureanu <maungu@microsoft.com> +// +// Copyright (c) 2018 Microsoft Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +using System; +using System.Collections.Generic; +using System.IO; +using System.Reflection; +using System.Threading; +using System.Threading.Tasks; +using Mono.Addins; +using MonoDevelop.Core; +using Xwt; + +namespace MonoDevelop.ExtensionTools +{ + public class Application: IApplication + { + // HACK: Make this proper + internal static LazyNotebook MainNotebook; + + public Task<int> Run (string[] arguments) + { + Xwt.Application.Initialize (ToolkitType.Gtk); +#if MAC + var dir = Path.GetDirectoryName (typeof (Application).Assembly.Location); + if (ObjCRuntime.Dlfcn.dlopen (Path.Combine (dir, "libxammac.dylib"), 0) == IntPtr.Zero) { + LoggingService.LogFatalError ("Unable to load libxammac"); + return Task.FromResult (1); + } +#endif + LoggingService.LogInfo ("Initialized toolkit"); + + Xwt.Toolkit.NativeEngine.Invoke (() => { + using (CreateWindow ()) { + LoggingService.LogInfo ("Showing main window"); + Xwt.Application.Run (); + } + }); + + return Task.FromResult (0); + } + + static Window CreateWindow () + { + var window = new Window { + Content = CreateWindowContent (), + Width = 800, + Height = 800, + }; + + window.Closed += (_, __) => Xwt.Application.Exit (); + window.Show (); + + return window; + } + + static Widget CreateWindowContent () + { + var nb = MainNotebook = new LazyNotebook (); + + foreach (var (widgetFunc, title) in GetTabs ()) { + nb.Add (widgetFunc, title); + } + + return nb; + } + + static IEnumerable<(Func<Widget> widgetFunc, string title)> GetTabs () + { + yield return (() => new AddinListWidget (), "List"); + yield return (() => new AddinDependencyTreeWidget (), "Dependency Tree"); + yield return (() => new ExtensionPointsWidget (), "Extension Points"); + } + } +} diff --git a/main/src/tools/ExtensionTools/ExtensionNodesWidget.cs b/main/src/tools/ExtensionTools/ExtensionNodesWidget.cs new file mode 100644 index 0000000000..d8a63369cc --- /dev/null +++ b/main/src/tools/ExtensionTools/ExtensionNodesWidget.cs @@ -0,0 +1,120 @@ +// +// ExtensionNodesWidget.cs +// +// Author: +// Marius Ungureanu <maungu@microsoft.com> +// +// Copyright (c) 2018 Microsoft Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +using System; +using System.Collections.Generic; +using System.Linq; +using Mono.Addins; +using Mono.Addins.Description; +using Xwt; + +namespace MonoDevelop.ExtensionTools +{ + class ExtensionNodesWidget : Widget + { + readonly TreeStore treeStore; + readonly TreeView treeView; + readonly DataField<string> labelField = new DataField<string> (); + readonly Label summary = new Label (); + + public ExtensionNodesWidget (string path, Addin[] addins = null) + { + addins = addins ?? AddinManager.Registry.GetAllAddins (); + + treeStore = new TreeStore (labelField); + treeView = new TreeView (treeStore); + + var col = treeView.Columns.Add ("Name", labelField); + col.Expands = true; + + FillData (path, addins); + treeView.ExpandAll (); + + var vbox = new VBox (); + vbox.PackStart (summary, false); + vbox.PackStart (treeView, true); + Content = vbox; + } + + void FillData (string path, Addin[] addins) + { + // TODO: add group by addin support. + var allNodes = addins + .SelectMany (x => x.Description.AllModules) + .SelectMany (x => x.Extensions) + .Where (x => x.Path == path) + .Select (x => x.ExtensionNodes); + + var nav = treeStore.AddNode (); + + int maxDepth = 0; + int count = 0; + foreach (var node in allNodes) { + int depth = BuildTree (nav, node, 1, ref count); + maxDepth = Math.Max (maxDepth, depth); + } + + summary.Text = $"'{path}' Count: {count} Depth: {maxDepth}"; + } + + int BuildTree (TreeNavigator currentPosition, ExtensionNodeDescriptionCollection nodes, int currentDepth, ref int count) + { + int maxDepth = currentDepth; + + // TODO: insertbefore/after + + foreach (ExtensionNodeDescription node in nodes) { + count++; + var pos = currentPosition.Clone ().AddChild (); + + var label = GetLabelForNode (node); + pos.SetValue (labelField, label); + + var childDepth = BuildTree (pos, node.ChildNodes, currentDepth + 1, ref count); + maxDepth = Math.Max (maxDepth, childDepth); + } + + return maxDepth; + } + + string GetLabelForNode (ExtensionNodeDescription node) + { + if (node.IsCondition) { + var value = node.GetAttribute ("value"); + if (!string.IsNullOrEmpty (value)) + return $"Condition: {node.Id} == {value}"; + return $"Condition: {node.Id}"; + } + + var type = node.GetAttribute ("class"); + if (!string.IsNullOrEmpty (type)) + return type; + + return node.Id; + } + + // TODO: add full attribute visualization + } +} diff --git a/main/src/tools/ExtensionTools/ExtensionPointsWidget.cs b/main/src/tools/ExtensionTools/ExtensionPointsWidget.cs new file mode 100644 index 0000000000..0784222b45 --- /dev/null +++ b/main/src/tools/ExtensionTools/ExtensionPointsWidget.cs @@ -0,0 +1,97 @@ +// +// ExtensionPointsWidget.cs +// +// Author: +// Marius Ungureanu <maungu@microsoft.com> +// +// Copyright (c) 2018 Microsoft Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +using System; +using System.Collections.Generic; +using System.Linq; +using Mono.Addins; +using Mono.Addins.Description; +using Xwt; +namespace MonoDevelop.ExtensionTools +{ + class ExtensionPointsWidget : Widget + { + readonly ListStore listStore; + readonly ListView listView; + readonly DataField<string> labelField = new DataField<string> (); + readonly Label summary = new Label (); + + public ExtensionPointsWidget (Addin[] addins = null) + { + addins = addins ?? AddinManager.Registry.GetAllAddins (); + + listStore = new ListStore (labelField); + listView = new ListView (listStore); + listView.RowActivated += ListView_RowActivated; + + var col = listView.Columns.Add ("Name", labelField); + col.Expands = true; + + FillData (addins); + + var vbox = new VBox (); + vbox.PackStart (summary, false); + vbox.PackStart (listView, true); + Content = vbox; + } + + void ListView_RowActivated (object sender, ListViewRowEventArgs e) + { + var value = listStore.GetValue (e.RowIndex, labelField); + + var window = new Window { + Content = new ExtensionNodesWidget (value), + Height = 800, + Width = 600, + }; + window.Show (); + } + + void FillData (Addin[] addins) + { + var points = GatherExtensionPoints (addins); + + summary.Text = $"Count: {points.Length}"; + + foreach (var point in points) { + int row = listStore.AddRow (); + listStore.SetValue (row, labelField, point); + } + } + + string[] GatherExtensionPoints (Addin[] addins) + { + var points = new HashSet<string> (); + + foreach (var addin in addins) { + foreach (ExtensionPoint extensionPoint in addin.Description.ExtensionPoints) { + points.Add (extensionPoint.Path); + } + } + + return points.ToSortedArray (); + } + } +} diff --git a/main/src/tools/ExtensionTools/ExtensionTools.csproj b/main/src/tools/ExtensionTools/ExtensionTools.csproj new file mode 100644 index 0000000000..5d45e69fc3 --- /dev/null +++ b/main/src/tools/ExtensionTools/ExtensionTools.csproj @@ -0,0 +1,56 @@ +<?xml version="1.0" encoding="utf-8"?> +<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> + <Import Project="..\..\..\MonoDevelop.props" /> + <PropertyGroup> + <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> + <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform> + <ProjectGuid>{E33F7901-AA45-4C25-9868-DBB6CC8DC40C}</ProjectGuid> + <TargetFrameworkVersion>$(MDFrameworkVersion)</TargetFrameworkVersion> + <OutputPath>..\..\..\build\bin</OutputPath> + <RootNamespace>MonoDevelop.ExtensionTools</RootNamespace> + </PropertyGroup> + <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' " /> + <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'DebugMac|AnyCPU' " /> + <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' " /> + <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'ReleaseMac|AnyCPU' " /> + <ItemGroup> + <Reference Include="System" /> + <Reference Include="Xamarin.Mac" Condition=" '$(Configuration)' == 'DebugMac' OR '$(Configuration)' == 'ReleaseMac' "> + <HintPath>..\..\..\build\bin\Xamarin.Mac.dll</HintPath> + <Private>False</Private> + </Reference> + </ItemGroup> + <ItemGroup> + <Compile Include="Application.cs" /> + <Compile Include="Properties\AssemblyInfo.cs" /> + <Compile Include="AddinInfo.cs" /> + <Compile Include="AddinDependencyTreeWidget.cs" /> + <Compile Include="AddinListWidget.cs" /> + <Compile Include="AddinRegistryExtensions.cs" /> + <Compile Include="ExtensionPointsWidget.cs" /> + <Compile Include="LazyNotebook.cs" /> + <Compile Include="ExtensionNodesWidget.cs" /> + <Compile Include="HashSetExtensions.cs" /> + </ItemGroup> + <ItemGroup> + <ProjectReference Include="..\..\core\MonoDevelop.Core\MonoDevelop.Core.csproj"> + <Project>{7525BB88-6142-4A26-93B9-A30C6983390A}</Project> + <Name>MonoDevelop.Core</Name> + <Private>False</Private> + </ProjectReference> + <ProjectReference Include="..\..\..\external\mono-addins\Mono.Addins\Mono.Addins.csproj"> + <Project>{91DD5A2D-9FE3-4C3C-9253-876141874DAD}</Project> + <Name>Mono.Addins</Name> + <Private>False</Private> + </ProjectReference> + <ProjectReference Include="..\..\..\external\xwt\Xwt\Xwt.csproj"> + <Project>{92494904-35FA-4DC9-BDE9-3A3E87AC49D3}</Project> + <Name>Xwt</Name> + <Private>False</Private> + </ProjectReference> + </ItemGroup> + <ItemGroup> + <EmbeddedResource Include="MonoDevelop.ExtensionTools.addin.xml" /> + </ItemGroup> + <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" /> +</Project>
\ No newline at end of file diff --git a/main/src/tools/ExtensionTools/HashSetExtensions.cs b/main/src/tools/ExtensionTools/HashSetExtensions.cs new file mode 100644 index 0000000000..138ee4c378 --- /dev/null +++ b/main/src/tools/ExtensionTools/HashSetExtensions.cs @@ -0,0 +1,41 @@ +// +// HashSetExtensions.cs +// +// Author: +// Marius Ungureanu <maungu@microsoft.com> +// +// Copyright (c) 2018 Microsoft Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +using System; +using System.Collections.Generic; +using System.Linq; + +namespace MonoDevelop.ExtensionTools +{ + static class HashSetExtensions + { + public static T[] ToSortedArray<T> (this HashSet<T> set) + { + var arr = set.ToArray (); + Array.Sort (arr); + return arr; + } + } +} diff --git a/main/src/tools/ExtensionTools/LazyNotebook.cs b/main/src/tools/ExtensionTools/LazyNotebook.cs new file mode 100644 index 0000000000..bdba85338c --- /dev/null +++ b/main/src/tools/ExtensionTools/LazyNotebook.cs @@ -0,0 +1,71 @@ +// +// LazyNotebook.cs +// +// Author: +// Marius Ungureanu <maungu@microsoft.com> +// +// Copyright (c) 2018 Microsoft Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +using System; +using Xwt; + +namespace MonoDevelop.ExtensionTools +{ + class LazyNotebook : Notebook + { + public void Add (Func<Widget> createWidget, string label) + { + var createWidgetChain = OnAdd (createWidget); + var widget = new LazyWidget (createWidgetChain); + Add (widget, label); + } + + protected override void OnCurrentTabChanged (EventArgs e) + { + base.OnCurrentTabChanged (e); + Xwt.Application.TimeoutInvoke (0, () => { + Toolkit.NativeEngine.Invoke (() => { + var tab = Tabs [CurrentTabIndex]; + if (tab.Child is LazyWidget lazy) + lazy.CreateContent (); + }); + return false; + }); + } + + protected virtual Func<Widget> OnAdd (Func<Widget> createWidget) => createWidget; + + class LazyWidget : Widget + { + readonly Func<Widget> createWidget; + + public LazyWidget (Func<Widget> createWidget) + { + this.createWidget = createWidget; + } + + public void CreateContent () + { + if (Content == null) + Content = createWidget (); + } + } + } +} diff --git a/main/src/tools/ExtensionTools/MonoDevelop.ExtensionTools.addin.xml b/main/src/tools/ExtensionTools/MonoDevelop.ExtensionTools.addin.xml new file mode 100644 index 0000000000..25998713d8 --- /dev/null +++ b/main/src/tools/ExtensionTools/MonoDevelop.ExtensionTools.addin.xml @@ -0,0 +1,7 @@ +<ExtensionModel> + + <Extension path = "/MonoDevelop/Core/Applications"> + <Application id = "extension-tool" class = "MonoDevelop.ExtensionTools.Application" description = "Developer overview over the extension model"/> + </Extension> + +</ExtensionModel> diff --git a/main/src/tools/ExtensionTools/Properties/AssemblyInfo.cs b/main/src/tools/ExtensionTools/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000..061f89aac8 --- /dev/null +++ b/main/src/tools/ExtensionTools/Properties/AssemblyInfo.cs @@ -0,0 +1,51 @@ +// +// AssemblyInfo.cs +// +// Author: +// Marius Ungureanu <maungu@microsoft.com> +// +// Copyright (c) 2018 Microsoft Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +using System.Reflection; +using System.Runtime.CompilerServices; + +// Information about this assembly is defined by the following attributes. +// Change them to the values specific to your project. + +[assembly: AssemblyTitle ("ExtensionTools")] +[assembly: AssemblyDescription ("")] +[assembly: AssemblyConfiguration ("")] +[assembly: AssemblyCompany ("Microsoft")] +[assembly: AssemblyProduct ("")] +[assembly: AssemblyCopyright ("Microsoft Inc.")] +[assembly: AssemblyTrademark ("")] +[assembly: AssemblyCulture ("")] + +// The assembly version has the format "{Major}.{Minor}.{Build}.{Revision}". +// The form "{Major}.{Minor}.*" will automatically update the build and revision, +// and "{Major}.{Minor}.{Build}.*" will update just the revision. + +[assembly: AssemblyVersion ("1.0.0")] + +// The following attributes are used to specify the signing key for the assembly, +// if desired. See the Mono documentation for more information about signing. + +//[assembly: AssemblyDelaySign(false)] +//[assembly: AssemblyKeyFile("")] |