diff --git a/RetailCoder.VBE/Inspections/Abstract/IInspectionResult.cs b/RetailCoder.VBE/Inspections/Abstract/IInspectionResult.cs index 3733384ee5..baf6a44c6a 100644 --- a/RetailCoder.VBE/Inspections/Abstract/IInspectionResult.cs +++ b/RetailCoder.VBE/Inspections/Abstract/IInspectionResult.cs @@ -11,5 +11,6 @@ public interface IInspectionResult : IComparable, IComparable QualifiedSelection QualifiedSelection { get; } IInspection Inspection { get; } object[] ToArray(); + string ToClipboardString(); } } diff --git a/RetailCoder.VBE/Inspections/Abstract/InspectionResultBase.cs b/RetailCoder.VBE/Inspections/Abstract/InspectionResultBase.cs index 60191463b2..88c59c4924 100644 --- a/RetailCoder.VBE/Inspections/Abstract/InspectionResultBase.cs +++ b/RetailCoder.VBE/Inspections/Abstract/InspectionResultBase.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.IO; using System.Linq; using Antlr4.Runtime; using Rubberduck.Inspections.Resources; @@ -87,18 +88,33 @@ public virtual int CompareTo(IInspectionResult other) return Inspection.CompareTo(other.Inspection); } - //public override string ToString() - //{ - // var module = QualifiedSelection.QualifiedName; - // return string.Format( - // InspectionsUI.QualifiedSelectionInspection, - // Inspection.Severity, - // Description, - // "(" + module.ProjectDisplayName + ")", - // module.ProjectName, - // module.ComponentName, - // QualifiedSelection.Selection.StartLine); - //} + /// + /// WARNING: This property can have side effects. It can change the ActiveVBProject if the result has a null Declaration, + /// which causes a flicker in the VBE. This should only be called if it is *absolutely* necessary. + /// + public string ToClipboardString() + { + var module = QualifiedSelection.QualifiedName; + var documentName = _target != null ? _target.ProjectDisplayName : string.Empty; + if (string.IsNullOrEmpty(documentName)) + { + var component = module.Component; + documentName = component != null ? component.ParentProject.ProjectDisplayName : string.Empty; + } + if (string.IsNullOrEmpty(documentName)) + { + documentName = Path.GetFileName(module.ProjectPath); + } + + return string.Format( + InspectionsUI.QualifiedSelectionInspection, + Inspection.Severity, + Description, + "(" + documentName + ")", + module.ProjectName, + module.ComponentName, + QualifiedSelection.Selection.StartLine); + } public virtual NavigateCodeEventArgs GetNavigationArgs() { diff --git a/RetailCoder.VBE/Rubberduck.csproj b/RetailCoder.VBE/Rubberduck.csproj index a6b38e5f48..4603d3ece4 100644 --- a/RetailCoder.VBE/Rubberduck.csproj +++ b/RetailCoder.VBE/Rubberduck.csproj @@ -232,6 +232,9 @@ True + + ..\packages\AvalonEdit.5.0.3\lib\Net40\ICSharpCode.AvalonEdit.dll + ..\libs\Infralution.Localization.Wpf.dll @@ -504,6 +507,7 @@ + LinkButton.xaml @@ -1155,6 +1159,7 @@ Designer + diff --git a/RetailCoder.VBE/UI/About/AboutDialog.cs b/RetailCoder.VBE/UI/About/AboutDialog.cs index f4fa115826..16d313530f 100644 --- a/RetailCoder.VBE/UI/About/AboutDialog.cs +++ b/RetailCoder.VBE/UI/About/AboutDialog.cs @@ -21,5 +21,19 @@ private AboutControlViewModel ViewModel AboutControl.DataContext = _viewModel; } } + + private void AboutDialog_Load(object sender, System.EventArgs e) + { + + } + protected override bool ProcessCmdKey(ref Message msg, Keys keyData) + { + if (keyData == Keys.Escape) + { + this.Close(); + return true; + } + return base.ProcessCmdKey(ref msg, keyData); + } } } diff --git a/RetailCoder.VBE/UI/Controls/BindableTextEditor.cs b/RetailCoder.VBE/UI/Controls/BindableTextEditor.cs new file mode 100644 index 0000000000..fa973cbcb8 --- /dev/null +++ b/RetailCoder.VBE/UI/Controls/BindableTextEditor.cs @@ -0,0 +1,76 @@ +using System; +using System.ComponentModel; +using System.Reflection; +using System.Windows; +using System.Windows.Media; +using System.Xml; +using ICSharpCode.AvalonEdit; +using ICSharpCode.AvalonEdit.Highlighting; +using ICSharpCode.AvalonEdit.Highlighting.Xshd; + +namespace Rubberduck.UI.Controls +{ + //see http://stackoverflow.com/a/20823917/4088852 + public class BindableTextEditor : TextEditor, INotifyPropertyChanged + { + public BindableTextEditor() + { + WordWrap = false; + + var highlighter = LoadHighlighter("Rubberduck.UI.Controls.vba.xshd"); + SyntaxHighlighting = highlighter; + + //Style hyperlinks so they look like comments. Note - this needs to move if used for user code. + TextArea.TextView.LinkTextUnderline = false; + TextArea.TextView.LinkTextForegroundBrush = new SolidColorBrush(Colors.Green); + Options.RequireControlModifierForHyperlinkClick = false; + Options.EnableHyperlinks = true; + Options.EnableEmailHyperlinks = true; + } + + public new string Text + { + get { return base.Text; } + set { base.Text = value; } + } + + public static readonly DependencyProperty TextProperty = + DependencyProperty.Register("Text", typeof(string), typeof(BindableTextEditor), new PropertyMetadata((obj, args) => + { + var target = (BindableTextEditor)obj; + target.Text = (string)args.NewValue; + })); + + protected override void OnTextChanged(EventArgs e) + { + RaisePropertyChanged("Text"); + base.OnTextChanged(e); + } + + public void RaisePropertyChanged(string property) + { + if (PropertyChanged != null) + { + PropertyChanged(this, new PropertyChangedEventArgs(property)); + } + } + + public event PropertyChangedEventHandler PropertyChanged; + + private static IHighlightingDefinition LoadHighlighter(string resource) + { + var assembly = Assembly.GetExecutingAssembly(); + using (var stream = assembly.GetManifestResourceStream(resource)) + { + if (stream == null) + { + return null; + } + using (var reader = new XmlTextReader(stream)) + { + return HighlightingLoader.Load(reader, HighlightingManager.Instance); + } + } + } + } +} diff --git a/RetailCoder.VBE/UI/Controls/vba.xshd b/RetailCoder.VBE/UI/Controls/vba.xshd new file mode 100644 index 0000000000..f47e0ea4a9 --- /dev/null +++ b/RetailCoder.VBE/UI/Controls/vba.xshd @@ -0,0 +1,230 @@ + + + + + + + + + + + + + + + + + + + + + + + " + " + + + + + + + (?<=(^\s*))\# + + + + (?<!(^\s*))\# + \# + + + + '|^\s*Rem + (?<!\s_)$ + + + + Assert + Debug + Print + Stop + + + + Any + Boolean + Byte + Currency + Date + Decimal + Double + Integer + Long + LongLong + LongPtr + Object + Short + Single + String + Variant + + + + AddressOf + And + Eqv + Imp + Is + Like + Mod + New + Not + Or + TypeOf + Xor + + + + Empty + False + Nothing + Null + True + + + + CBool + CByte + CCur + CDate + CDbl + CDec + CInt + CLng + CLngLng + CLngPtr + CObj + CSng + CStr + CVar + CVErr + + + + Alias + Append + As + ByRef + ByVal + Call + Case + Close + Const + Command + Declare + DefBool + DefByte + DefCur + DefDate + DefDbl + DefInt + DefLng + DefObj + DefSng + DefStr + DefVar + Dim + Do + Each + Else + ElseIf + End + EndIf + Enum + EOF + Erase + Error + Event + Exit + For + Friend + Function + Get + Global + GoSub + GoTo + If + Implements + In + Input + Let + Lib + Line + LOC + Lock + LOF + Loop + LSet + Name + New + Next + On + Open + Optional + Output + ParamArray + Private + Property + Public + Put + RaiseEvent + Random + Read + ReDim + Resume + Return + RSet + Seek + Select + Set + Shared + Spc + Static + Step + Sub + Tab + Then + To + Type + Unlock + Until + Wend + While + Width + With + WithEvents + Write + + + + Base + Binary + Compare + Database + Explicit + Module + Option + Preserve + Text + + + + + + Const + Else + ElseIf + End + If + + + + diff --git a/RetailCoder.VBE/UI/FindSymbol/FindSymbolDialog.cs b/RetailCoder.VBE/UI/FindSymbol/FindSymbolDialog.cs index 254a9e7e07..4fdbf62fd4 100644 --- a/RetailCoder.VBE/UI/FindSymbol/FindSymbolDialog.cs +++ b/RetailCoder.VBE/UI/FindSymbol/FindSymbolDialog.cs @@ -29,5 +29,15 @@ public FindSymbolDialog() Text = string.Format("Rubberduck - {0}", RubberduckUI.FindSymbolDialog_Caption); } + + protected override bool ProcessCmdKey(ref Message msg, Keys keyData) + { + if (keyData == Keys.Escape) + { + Close(); + return true; + } + return base.ProcessCmdKey(ref msg, keyData); + } } } diff --git a/RetailCoder.VBE/UI/Inspections/InspectionResultsViewModel.cs b/RetailCoder.VBE/UI/Inspections/InspectionResultsViewModel.cs index c74616fcbe..5aa386dbf3 100644 --- a/RetailCoder.VBE/UI/Inspections/InspectionResultsViewModel.cs +++ b/RetailCoder.VBE/UI/Inspections/InspectionResultsViewModel.cs @@ -430,7 +430,7 @@ private void ExecuteCopyResultsCommand(object parameter) var title = string.Format(resource, DateTime.Now.ToString(CultureInfo.InvariantCulture), _results.Count); - var textResults = title + Environment.NewLine + string.Join("", _results.Select(result => result.ToString() + Environment.NewLine).ToArray()); + var textResults = title + Environment.NewLine + string.Join("", _results.Select(result => result.ToClipboardString() + Environment.NewLine).ToArray()); var csvResults = ExportFormatter.Csv(aResults, title,ColumnInfos); var htmlResults = ExportFormatter.HtmlClipboardFragment(aResults, title,ColumnInfos); var rtfResults = ExportFormatter.RTF(aResults, title); diff --git a/RetailCoder.VBE/UI/RubberduckUI.Designer.cs b/RetailCoder.VBE/UI/RubberduckUI.Designer.cs index 59896802e5..04b4ec2212 100644 --- a/RetailCoder.VBE/UI/RubberduckUI.Designer.cs +++ b/RetailCoder.VBE/UI/RubberduckUI.Designer.cs @@ -1920,6 +1920,15 @@ public static string IndenterSettings_AlignmentOptionsLabel { } } + /// + /// Looks up a localized string similar to Indented Code Sample. + /// + public static string IndenterSettings_CodeSampleHeader { + get { + return ResourceManager.GetString("IndenterSettings_CodeSampleHeader", resourceCulture); + } + } + /// /// Looks up a localized string similar to Enable Options. /// diff --git a/RetailCoder.VBE/UI/RubberduckUI.resx b/RetailCoder.VBE/UI/RubberduckUI.resx index af2a771331..82043fb2e2 100644 --- a/RetailCoder.VBE/UI/RubberduckUI.resx +++ b/RetailCoder.VBE/UI/RubberduckUI.resx @@ -1911,4 +1911,7 @@ Would you like to import them to Rubberduck? runtime expression + + Indented Code Sample + \ No newline at end of file diff --git a/RetailCoder.VBE/UI/Settings/IndenterSettings.xaml b/RetailCoder.VBE/UI/Settings/IndenterSettings.xaml index d0f4acaf74..8e38f8ea22 100644 --- a/RetailCoder.VBE/UI/Settings/IndenterSettings.xaml +++ b/RetailCoder.VBE/UI/Settings/IndenterSettings.xaml @@ -1,243 +1,320 @@ - + - - - - + + + + MethodName="GetValues" + ObjectType="{x:Type core:Enum}"> - + - - - - - - - - - - - - - - - - - - + + + + + + + + + + + \ No newline at end of file diff --git a/RetailCoder.VBE/packages.config b/RetailCoder.VBE/packages.config index c006f9277c..4e03da4174 100644 --- a/RetailCoder.VBE/packages.config +++ b/RetailCoder.VBE/packages.config @@ -2,6 +2,7 @@ + diff --git a/Rubberduck.Parsing/Symbols/ProjectDeclaration.cs b/Rubberduck.Parsing/Symbols/ProjectDeclaration.cs index 99b19931c1..eb5a852559 100644 --- a/Rubberduck.Parsing/Symbols/ProjectDeclaration.cs +++ b/Rubberduck.Parsing/Symbols/ProjectDeclaration.cs @@ -1,5 +1,4 @@ -using System.Text.RegularExpressions; -using Rubberduck.Parsing.ComReflection; +using Rubberduck.Parsing.ComReflection; using Rubberduck.VBEditor; using System.Collections.Generic; using System.Linq; @@ -72,9 +71,6 @@ public void AddProjectReference(string referencedProjectId, int priority) _projectReferences.Add(new ProjectReference(referencedProjectId, priority)); } - private static readonly Regex CaptionProjectRegex = new Regex(@"^(?:[^-]+)(?:\s-\s)(?.+)(?:\s-\s.*)?$"); - private static readonly Regex OpenModuleRegex = new Regex(@"^(?.+)(?\s-\s\[.*\((Code|UserForm)\)\])$"); - private string _displayName; /// /// WARNING: This property has side effects. It changes the ActiveVBProject, which causes a flicker in the VBE. @@ -88,39 +84,8 @@ public override string ProjectDisplayName { return _displayName; } - - if (_project == null) - { - _displayName = string.Empty; - return _displayName; - } - - var vbe = _project.VBE; - var activeProject = vbe.ActiveVBProject; - var mainWindow = vbe.MainWindow; - { - try - { - if (_project.HelpFile != activeProject.HelpFile) - { - vbe.ActiveVBProject = _project; - } - - var caption = mainWindow.Caption; - if (CaptionProjectRegex.IsMatch(caption)) - { - caption = CaptionProjectRegex.Matches(caption)[0].Groups["project"].Value; - _displayName = OpenModuleRegex.IsMatch(caption) - ? OpenModuleRegex.Matches(caption)[0].Groups["project"].Value - : caption; - } - } - catch - { - _displayName = string.Empty; - } - return _displayName; - } + _displayName = _project != null ? _project.ProjectDisplayName : string.Empty; + return _displayName; } } } diff --git a/Rubberduck.SmartIndenter/AbsoluteCodeLine.cs b/Rubberduck.SmartIndenter/AbsoluteCodeLine.cs index 39b5b49d25..19d746e85b 100644 --- a/Rubberduck.SmartIndenter/AbsoluteCodeLine.cs +++ b/Rubberduck.SmartIndenter/AbsoluteCodeLine.cs @@ -10,10 +10,8 @@ namespace Rubberduck.SmartIndenter internal class AbsoluteCodeLine { private const string StupidLineEnding = ": _"; - private const char StringPlaceholder = '\a'; - private const char BracketPlaceholder = '\x2'; - private static readonly Regex StringReplaceRegex = new Regex(StringPlaceholder.ToString(CultureInfo.InvariantCulture)); - private static readonly Regex BracketReplaceRegex = new Regex(BracketPlaceholder.ToString(CultureInfo.InvariantCulture)); + private static readonly Regex StringReplaceRegex = new Regex(StringLiteralAndBracketEscaper.StringPlaceholder.ToString(CultureInfo.InvariantCulture)); + private static readonly Regex BracketReplaceRegex = new Regex(StringLiteralAndBracketEscaper.BracketPlaceholder.ToString(CultureInfo.InvariantCulture)); private static readonly Regex LineNumberRegex = new Regex(@"^(?(-?\d+)|(&H[0-9A-F]{1,8}))\s+(?.*)", RegexOptions.ExplicitCapture); private static readonly Regex EndOfLineCommentRegex = new Regex(@"^(?!(Rem\s)|('))(?[^']*)(\s(?'.*))$", RegexOptions.ExplicitCapture); private static readonly Regex ProcedureStartRegex = new Regex(@"^(Public\s|Private\s|Friend\s)?(Static\s)?(Sub|Function|Property\s(Let|Get|Set))\s"); @@ -34,8 +32,9 @@ internal class AbsoluteCodeLine private string _code; private readonly bool _stupidLineEnding; private readonly string[] _segments; - private List _strings; - private List _brackets; + private readonly StringLiteralAndBracketEscaper _escaper; + //private List _strings; + //private List _brackets; public AbsoluteCodeLine(string code, IIndenterSettings settings) : this(code, settings, null) { } @@ -56,72 +55,17 @@ public AbsoluteCodeLine(string code, IIndenterSettings settings, AbsoluteCodeLin Original = code; - ExtractStringLiteralsAndBrackets(); + _escaper = new StringLiteralAndBracketEscaper(_code); + _code = _escaper.EscapedString; + ExtractLineNumber(); ExtractEndOfLineComment(); - _code = Regex.Replace(_code, StringPlaceholder + "+", StringPlaceholder.ToString(CultureInfo.InvariantCulture)); - _code = Regex.Replace(_code, BracketPlaceholder + "+", BracketPlaceholder.ToString(CultureInfo.InvariantCulture)).Trim(); + _code = Regex.Replace(_code, StringLiteralAndBracketEscaper.StringPlaceholder + "+", StringLiteralAndBracketEscaper.StringPlaceholder.ToString(CultureInfo.InvariantCulture)); + _code = Regex.Replace(_code, StringLiteralAndBracketEscaper.BracketPlaceholder + "+", StringLiteralAndBracketEscaper.BracketPlaceholder.ToString(CultureInfo.InvariantCulture)).Trim(); _segments = _code.Split(new[] { ": " }, StringSplitOptions.None); } - //TODO: This should be a class. - private void ExtractStringLiteralsAndBrackets() - { - _strings = new List(); - _brackets = new List(); - - var chars = _code.ToCharArray(); - var quoted = false; - var bracketed = false; - var ins = 0; - var strpos = 0; - var brkpos = 0; - for (var c = 0; c < chars.Length; c++) - { - if (chars[c] == '"' && !bracketed) - { - if (!quoted) - { - strpos = c; - quoted = true; - continue; - } - if (c + 1 < chars.Length && chars[c] == '"') - { - c++; - } - quoted = false; - _strings.Add(new string(chars.Skip(strpos).Take(c - strpos).ToArray())); - for (var e = strpos; e < c; e++) - { - chars[e] = StringPlaceholder; - } - } - else if (!quoted && !bracketed && chars[c] == '[') - { - bracketed = true; - brkpos = c; - ins++; - } - else if (!quoted && bracketed && chars[c] == ']') - { - ins--; - if (ins != 0) - { - continue; - } - bracketed = false; - _brackets.Add(new string(chars.Skip(brkpos).Take(c - brkpos).ToArray())); - for (var e = brkpos; e < c; e++) - { - chars[e] = BracketPlaceholder; - } - } - } - _code = new string(chars); - } - private void ExtractLineNumber() { if (Previous == null || !Previous.HasContinuation) @@ -162,15 +106,18 @@ public string Escaped { get { + // ReSharper disable LoopCanBeConvertedToQuery var output = Original; - foreach (var item in _strings) + foreach (var item in _escaper.EscapedStrings) + { - output = output.Replace(item, new string(StringPlaceholder, item.Length)); + output = output.Replace(item, new string(StringLiteralAndBracketEscaper.StringPlaceholder, item.Length)); } - foreach (var item in _brackets) + foreach (var item in _escaper.EscapedBrackets) { - output = output.Replace(item, new string(BracketPlaceholder, item.Length)); + output = output.Replace(item, new string(StringLiteralAndBracketEscaper.BracketPlaceholder, item.Length)); } + // ReSharper restore LoopCanBeConvertedToQuery return output; } } @@ -320,13 +267,13 @@ public string Indent(int indents, bool atProcStart, bool absolute = false) } var code = string.Join(": ", _segments); - if (_strings.Any()) + if (_escaper.EscapedStrings.Any()) { - code = _strings.Aggregate(code, (current, literal) => StringReplaceRegex.Replace(current, literal, 1)); + code = _escaper.EscapedStrings.Aggregate(code, (current, literal) => StringReplaceRegex.Replace(current, literal, 1)); } - if (_brackets.Any()) + if (_escaper.EscapedBrackets.Any()) { - code = _brackets.Aggregate(code, (current, expr) => BracketReplaceRegex.Replace(current, expr, 1)); + code = _escaper.EscapedBrackets.Aggregate(code, (current, expr) => BracketReplaceRegex.Replace(current, expr, 1)); } code = string.Join(string.Empty, number, new string(' ', gap), code); diff --git a/Rubberduck.SmartIndenter/LogicalCodeLine.cs b/Rubberduck.SmartIndenter/LogicalCodeLine.cs index dcbe9ab617..6a2f0e8820 100644 --- a/Rubberduck.SmartIndenter/LogicalCodeLine.cs +++ b/Rubberduck.SmartIndenter/LogicalCodeLine.cs @@ -7,7 +7,7 @@ namespace Rubberduck.SmartIndenter { internal class LogicalCodeLine { - private List _lines = new List(); + private readonly List _lines = new List(); private AbsoluteCodeLine _rebuilt; private readonly IIndenterSettings _settings; @@ -186,6 +186,7 @@ public override string ToString() //The splitNamed parameter is a straight up hack for fixing https://github.com/rubberduck-vba/Rubberduck/issues/2402 private int FunctionAlign(string line, bool splitNamed) { + line = new StringLiteralAndBracketEscaper(line).EscapedString; var stackPos = _alignment.Count; for (var index = StartIgnoreRegex.Match(line).Length + 1; index <= line.Length; index++) @@ -194,10 +195,8 @@ private int FunctionAlign(string line, bool splitNamed) switch (character) { case "\a": - while (!line.Substring(index++, 1).Equals("\a")) { } - break; case "\x2": - while (!line.Substring(index++, 1).Equals("\x2")) { } + index++; break; case "(": //Start of another function => remember this position diff --git a/Rubberduck.SmartIndenter/Rubberduck.SmartIndenter.csproj b/Rubberduck.SmartIndenter/Rubberduck.SmartIndenter.csproj index dbea8f5ac5..901240fb30 100644 --- a/Rubberduck.SmartIndenter/Rubberduck.SmartIndenter.csproj +++ b/Rubberduck.SmartIndenter/Rubberduck.SmartIndenter.csproj @@ -57,6 +57,7 @@ + diff --git a/Rubberduck.SmartIndenter/StringLiteralAndBracketEscaper.cs b/Rubberduck.SmartIndenter/StringLiteralAndBracketEscaper.cs new file mode 100644 index 0000000000..7b98f8a26e --- /dev/null +++ b/Rubberduck.SmartIndenter/StringLiteralAndBracketEscaper.cs @@ -0,0 +1,76 @@ +using System.Collections.Generic; +using System.Linq; + +namespace Rubberduck.SmartIndenter +{ + internal class StringLiteralAndBracketEscaper + { + public const char StringPlaceholder = '\a'; + public const char BracketPlaceholder = '\x2'; + + private readonly List _strings = new List(); + private readonly List _brackets = new List(); + private readonly string _unescaped; + private readonly string _escaped; + + public string EscapedString { get { return _escaped; } } + public string OriginalString { get { return _unescaped; } } + public IEnumerable EscapedStrings { get { return _strings; } } + public IEnumerable EscapedBrackets { get { return _brackets; } } + + public StringLiteralAndBracketEscaper(string code) + { + _unescaped = code; + + var chars = _unescaped.ToCharArray(); + var quoted = false; + var bracketed = false; + var ins = 0; + var strpos = 0; + var brkpos = 0; + for (var c = 0; c < chars.Length; c++) + { + if (chars[c] == '"' && !bracketed) + { + if (!quoted) + { + strpos = c; + quoted = true; + continue; + } + if (c + 1 < chars.Length && chars[c] == '"') + { + c++; + } + quoted = false; + _strings.Add(new string(chars.Skip(strpos).Take(c - strpos).ToArray())); + for (var e = strpos; e < c; e++) + { + chars[e] = StringPlaceholder; + } + } + else if (!quoted && !bracketed && chars[c] == '[') + { + bracketed = true; + brkpos = c; + ins++; + } + else if (!quoted && bracketed && chars[c] == ']') + { + ins--; + if (ins != 0) + { + continue; + } + bracketed = false; + _brackets.Add(new string(chars.Skip(brkpos).Take(c - brkpos).ToArray())); + for (var e = brkpos; e < c; e++) + { + chars[e] = BracketPlaceholder; + } + } + } + _escaped = new string(chars); + } + } +} diff --git a/Rubberduck.VBEEditor/QualifiedModuleName.cs b/Rubberduck.VBEEditor/QualifiedModuleName.cs index 0469810768..d04c0a5403 100644 --- a/Rubberduck.VBEEditor/QualifiedModuleName.cs +++ b/Rubberduck.VBEEditor/QualifiedModuleName.cs @@ -101,7 +101,7 @@ public QualifiedMemberName QualifyMemberName(string member) public string Name { get { return ToString(); } } private readonly string _projectName; - public string ProjectName { get { return _projectName; } } + public string ProjectName { get { return _projectName ?? string.Empty; } } private readonly string _projectPath; public string ProjectPath { get { return _projectPath; } } diff --git a/Rubberduck.VBEEditor/SafeComWrappers/Abstract/IVBComponent.cs b/Rubberduck.VBEEditor/SafeComWrappers/Abstract/IVBComponent.cs index 346e66d5b3..7242677ceb 100644 --- a/Rubberduck.VBEEditor/SafeComWrappers/Abstract/IVBComponent.cs +++ b/Rubberduck.VBEEditor/SafeComWrappers/Abstract/IVBComponent.cs @@ -20,5 +20,7 @@ public interface IVBComponent : ISafeComWrapper, IEquatable void Activate(); void Export(string path); string ExportAsSourceFile(string folder); + + IVBProject ParentProject { get; } } } \ No newline at end of file diff --git a/Rubberduck.VBEEditor/SafeComWrappers/Abstract/IVBProject.cs b/Rubberduck.VBEEditor/SafeComWrappers/Abstract/IVBProject.cs index 3187dead57..4683cbf37c 100644 --- a/Rubberduck.VBEEditor/SafeComWrappers/Abstract/IVBProject.cs +++ b/Rubberduck.VBEEditor/SafeComWrappers/Abstract/IVBProject.cs @@ -28,6 +28,7 @@ public interface IVBProject : ISafeComWrapper, IEquatable void SaveAs(string fileName); void MakeCompiledFile(); void ExportSourceFiles(string folder); + string ProjectDisplayName { get; } IReadOnlyList ComponentNames(); } diff --git a/Rubberduck.VBEEditor/SafeComWrappers/VB6/VBComponent.cs b/Rubberduck.VBEEditor/SafeComWrappers/VB6/VBComponent.cs index 83f828a8cc..d46d63afca 100644 --- a/Rubberduck.VBEEditor/SafeComWrappers/VB6/VBComponent.cs +++ b/Rubberduck.VBEEditor/SafeComWrappers/VB6/VBComponent.cs @@ -132,6 +132,8 @@ public string ExportAsSourceFile(string folder) return fullPath; } + public IVBProject ParentProject { get; private set; } + private void ExportUserFormModule(string path) { // VBIDE API inserts an extra newline when exporting a UserForm module. diff --git a/Rubberduck.VBEEditor/SafeComWrappers/VB6/VBProject.cs b/Rubberduck.VBEEditor/SafeComWrappers/VB6/VBProject.cs index 684225d8f8..3327589ca3 100644 --- a/Rubberduck.VBEEditor/SafeComWrappers/VB6/VBProject.cs +++ b/Rubberduck.VBEEditor/SafeComWrappers/VB6/VBProject.cs @@ -167,5 +167,6 @@ public void ExportSourceFiles(string folder) } } + public string ProjectDisplayName { get; private set; } } } \ No newline at end of file diff --git a/Rubberduck.VBEEditor/SafeComWrappers/VBA/VBComponent.cs b/Rubberduck.VBEEditor/SafeComWrappers/VBA/VBComponent.cs index e97ab44b1e..d632b50c1e 100644 --- a/Rubberduck.VBEEditor/SafeComWrappers/VBA/VBComponent.cs +++ b/Rubberduck.VBEEditor/SafeComWrappers/VBA/VBComponent.cs @@ -129,6 +129,14 @@ public string ExportAsSourceFile(string folder) return fullPath; } + public IVBProject ParentProject + { + get + { + return Collection != null ? Collection.Parent : null; + } + } + private void ExportUserFormModule(string path) { // VBIDE API inserts an extra newline when exporting a UserForm module. diff --git a/Rubberduck.VBEEditor/SafeComWrappers/VBA/VBProject.cs b/Rubberduck.VBEEditor/SafeComWrappers/VBA/VBProject.cs index 4ea33e995d..af89ce15e8 100644 --- a/Rubberduck.VBEEditor/SafeComWrappers/VBA/VBProject.cs +++ b/Rubberduck.VBEEditor/SafeComWrappers/VBA/VBProject.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using System.Linq; +using System.Text.RegularExpressions; using Rubberduck.VBEditor.SafeComWrappers.Abstract; using VB = Microsoft.Vbe.Interop; @@ -180,5 +181,55 @@ public void ExportSourceFiles(string folder) } } + private static readonly Regex CaptionProjectRegex = new Regex(@"^(?:[^-]+)(?:\s-\s)(?.+)(?:\s-\s.*)?$"); + private static readonly Regex OpenModuleRegex = new Regex(@"^(?.+)(?\s-\s\[.*\((Code|UserForm)\)\])$"); + private string _displayName; + /// + /// WARNING: This property has side effects. It changes the ActiveVBProject, which causes a flicker in the VBE. + /// This should only be called if it is *absolutely* necessary. + /// + public string ProjectDisplayName + { + get + { + if (_displayName != null) + { + return _displayName; + } + + if (IsWrappingNullReference) + { + _displayName = string.Empty; + return _displayName; + } + + var vbe = VBE; + var activeProject = vbe.ActiveVBProject; + var mainWindow = vbe.MainWindow; + { + try + { + if (Target.HelpFile != activeProject.HelpFile) + { + vbe.ActiveVBProject = this; + } + + var caption = mainWindow.Caption; + if (CaptionProjectRegex.IsMatch(caption)) + { + caption = CaptionProjectRegex.Matches(caption)[0].Groups["project"].Value; + _displayName = OpenModuleRegex.IsMatch(caption) + ? OpenModuleRegex.Matches(caption)[0].Groups["project"].Value + : caption; + } + } + catch + { + _displayName = string.Empty; + } + return _displayName; + } + } + } } } \ No newline at end of file diff --git a/RubberduckTests/SmartIndenter/MiscAndCornerCaseTests.cs b/RubberduckTests/SmartIndenter/MiscAndCornerCaseTests.cs index 37f8181b93..347e023475 100644 --- a/RubberduckTests/SmartIndenter/MiscAndCornerCaseTests.cs +++ b/RubberduckTests/SmartIndenter/MiscAndCornerCaseTests.cs @@ -745,5 +745,43 @@ public void BracketedIdentifiersWork() var actual = indenter.Indent(code); Assert.IsTrue(expected.SequenceEqual(actual)); } + + //https://github.com/rubberduck-vba/Rubberduck/issues/2604 + [TestMethod] + [TestCategory("Indenter")] + public void AlignmentAnchorsInStringLiteralsAreIgnored() + { + var code = new[] + { + "Sub Test()", + "Dim LoremIpsum As String", + @"LoremIpsum = ""Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam dictum,"" & vbCrLf _", + @"& ""felis in tempor finibus, arcu lectus molestie urna, eget interdum turpis"" & vbCrLf _", + @"& ""tellus ac diam. Nulla mauris lectus, vulputate et fringilla ac, iaculis eget urna."" & vbCrLf _", + @"& ""Ut feugiat felis lacinia eros vestibulum facilisis. Ut euismod dapibus augue,"" & vbCrLf _", + @"& ""lacinia elementum elit dictum in. Nam in imperdiet tortor. Curabitur efficitur libero"" & vbCrLf _", + @"& ""lacus, et placerat metus sodales sit amet.""", + "Debug.Print LoremIpsum", + "End Sub" + }; + + var expected = new[] + { + "Sub Test()", + " Dim LoremIpsum As String", + @" LoremIpsum = ""Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam dictum,"" & vbCrLf _", + @" & ""felis in tempor finibus, arcu lectus molestie urna, eget interdum turpis"" & vbCrLf _", + @" & ""tellus ac diam. Nulla mauris lectus, vulputate et fringilla ac, iaculis eget urna."" & vbCrLf _", + @" & ""Ut feugiat felis lacinia eros vestibulum facilisis. Ut euismod dapibus augue,"" & vbCrLf _", + @" & ""lacinia elementum elit dictum in. Nam in imperdiet tortor. Curabitur efficitur libero"" & vbCrLf _", + @" & ""lacus, et placerat metus sodales sit amet.""", + " Debug.Print LoremIpsum", + "End Sub" + }; + + var indenter = new Indenter(null, () => IndenterSettingsTests.GetMockIndenterSettings()); + var actual = indenter.Indent(code); + Assert.IsTrue(expected.SequenceEqual(actual)); + } } }