mayerwin / vs-customize-window-title

Customize Visual Studio Window Title - Visual Studio Extension
https://marketplace.visualstudio.com/items?itemName=mayerwin.RenameVisualStudioWindowTitle
MIT License
108 stars 30 forks source link

String Operators to simplify title? #8

Closed sercanparan closed 3 years ago

sercanparan commented 7 years ago

I have a huge solution and to identify the project i am working on i need to pull more data than just the solution name or project path. These features are available but at the end title is not readable for me to identify. Can you add string operators like replace, substring, or at least if operator :). i dont know the technical side but it would be great. Thanks.

mayerwin commented 7 years ago

Can you give me examples of titles you would ideally want to see? If what you are seeking to achieve is already doable with the current feature set, better not to add more complexity to the pattern vocabulary,

sercanparan commented 7 years ago

For example my solution name is ZIRAAT_AG_KATILIM_BANKACILIGI and my branch name is DEVELOPMENT and my workspace name is INTERFACE. So i wanna see it like ZIR_DEV_INT.

mayerwin commented 7 years ago

Did you try using solution-specific configuration files? This would allow you to hardcode the solution name only (or anything in the title) for this specific solution.

sercanparan commented 7 years ago

That might solve the problem but i wish i could show you my workspace. That is ridiculously massive. It won't be applicable for me to change all those solutions. I work in an international IT company with over 900 employees who work for the most of the banks in Turkey and some major Finance companies and banks in Europe, Russia and Asia. I told your extension to my collegues and noone hesistate to use it. Instantly shared between all software developers, with the questions of "Can we use Substring()?", "Does Left() work?", "Is it possible to Replace()". Then i wrote to you :) That was the story :D

mayerwin commented 7 years ago

Thanks for the story! Always interesting and motivating to hear how much it helps.

The problem with more complex patterns (like those supporting string operators) is that they tend not to produce desired titles across different kinds of solutions. This is why solution-specific configurations are available (also, in the Global settings overrides file, wildcards are supported so you can apply a config to solution paths matching a common wildcard).

I am not at all against implementing more features but then we either have to go for full scripting (which will make the extension much much heavier), or implement operators one by one and define a syntax that plays nicely with the current one. I welcome any suggestion on the latter (if you already ha something in mind). The syntax is meant to be future-proof so better to take the time to make wise choices. For example it could be interesting to use a simple argument-based syntax similar to the one for [documentPath:X:Y]: [String.Replace:[solutionName]:DEVELOPMENT:DEV].

In the meantime, you can try the following extension and let me know if it solves your specific needs: https://marketplace.visualstudio.com/items?itemName=IstvanPasztor.VisualStudioWindowTitleChanger It is less simple to use out of the box but does provide a more complete scripting language. It is also free an open source.

JvanderStad commented 7 years ago

A user defined PowerShell script can do this job:

Open this C# with LinqPad to see it in action

<Query Kind="Program">
  <Reference>&lt;ProgramFilesX86&gt;\Reference Assemblies\Microsoft\WindowsPowerShell\3.0\System.Management.Automation.dll</Reference>
  <Namespace>System.Management.Automation</Namespace>
  <Namespace>System.Management.Automation.Runspaces</Namespace>
</Query>

private void RunPowerShell(Runspace runspace, string powerShellScript)
{
    using (var psInstance = PowerShell.Create())
    {
        psInstance.Runspace = runspace;

        psInstance.Streams.Error.DataAdded += (sender, args) =>
        {
            var data = (PSDataCollection<ErrorRecord>)sender;
            Console.WriteLine("PowerShell Error: {0}", data[args.Index]);
        };
        psInstance.Streams.Information.DataAdded += (sender, args) =>
        {
            var data = (PSDataCollection<InformationRecord>)sender;
            Console.WriteLine("PowerShell Information: {0}", data[args.Index]);
        };
        psInstance.Streams.Debug.DataAdded += (sender, args) =>
        {
            var data = (PSDataCollection<DebugRecord>)sender;
            Console.WriteLine("PowerShell Debug: {0}", data[args.Index]);
        };
        psInstance.Streams.Verbose.DataAdded += (sender, args) =>
        {
            var data = (PSDataCollection<VerboseRecord>)sender;
            Console.WriteLine("PowerShell Verbose: {0}", data[args.Index]);
        };
        psInstance.Streams.Warning.DataAdded += (sender, args) =>
        {
            var data = (PSDataCollection<WarningRecord>)sender;
            Console.WriteLine("PowerShell Warning: {0}", data[args.Index]);
        };
        psInstance.AddScript(powerShellScript);

        var outputCollection = new PSDataCollection<PSObject>();
        var result = psInstance.BeginInvoke<PSObject, PSObject>(null, outputCollection);

        var counter = 0;
        while (result.IsCompleted == false && counter < 1000)
        {
            counter++;
            Thread.Sleep(100);
        }

        foreach (var item in outputCollection)
        {
            Console.WriteLine("PowerShell Object: {0}", item);
        }

        try
        {           
            psInstance.EndInvoke(result);
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex);
        }

    }
}

public class WindowInfo
{
    public string DocumentName { get; set;}
    public string ProjectName { get; set;}
    public string StartupProjectsNames { get; set; }
    public string DocumentProjectName { get; set; }

    public void SetTitle(string title)
    {
        // UPDATE WINDOW TITLE
        Console.WriteLine($"Title: {title}");
    }
}

void Main()
{
    var runspaceConfiguration = RunspaceConfiguration.Create();
    using (var runspace = RunspaceFactory.CreateRunspace(runspaceConfiguration))
    {
        runspace.Open();

        var info = new WindowInfo();
            info.DocumentName = "Document.cs";
            info.ProjectName = "Project";
            info.StartupProjectsNames = "Startup project";
            info.DocumentProjectName = "Document project name";

        runspace.SessionStateProxy.SetVariable("windowInfo", info);

        var script = 
        $@"
            # Create A Guid (.NET object)
            $myId = [Guid]::NewGuid().ToString(""D"")

            # Call Split, take first from array
            $myId = $myId.Split('-')[0]

            $windowInfo.SetTitle(""$($windowInfo.DocumentName) on project '$($windowInfo.ProjectName)' #$myId"")
        ";

        //show member variable
        RunPowerShell(runspace, script);

        runspace.Close();
    }
}

An enduser can enter any powershell script to change the window title. For example, if a user wants to create a title with the document name, projectname and a random id based on the most-left part of a splitted Guid, eg:

Document.cs on project 'Project' #58d0a818

he/she can use a script like this:

# Create A Guid (.NET object), call ToString on it
$myId = [Guid]::NewGuid().ToString("D") 

# Call Split, take first from array, assign to $myId
$myId = $myId.Split('-')[0]

#Update window title
$windowInfo.SetTitle("$($windowInfo.DocumentName) on project '$($windowInfo.ProjectName)' #$myId")
lexx9999 commented 7 years ago

Just a suggestion.. I wouldn't introduce operators, .. with c# it is possible to compile and execute code.

Once I've created a very basic test form, where it is possible to code some class and compile and execute it. The concept: One new placeholder [custom:param] (like env)

Then a edit form where users (programmers) can enter code, like this:

class CustomAttributeProvider
{
  public  string Calculate(IMatchResolver resolver, string param)
  {
    if(resolver.TryResolve("[parent:99:0]", out string path))
    {
      // do some magic stuff
      return path.SubString(3,9); 
    }
    return param; // for simplicity just return the param
  }
}
 public interface IMatchResolver {
        bool TryResolve(string tag, out string s);
    }

IMatchResolver.TryResolve does the same as GetNewTitle internally with the tag, but excluding custom.

Giving some rough idea about the calling code:

string txResultText="return value", param="the param of [custom:param]", code="the code from above";

     CodeSnippetCompileUnit cu=new CodeSnippetCompileUnit(code);
            CompilerInfo compilerInfo=CodeDomProvider.GetCompilerInfo("C#");
            CompilerParameters compilerParameters=compilerInfo.CreateDefaultCompilerParameters();
            compilerParameters.ReferencedAssemblies.Add("System.dll");
            compilerParameters.ReferencedAssemblies.Add("System.Windows.Forms.dll");
//            compilerParameters.ReferencedAssemblies.Add("System.Core.dll");
            compilerParameters.ReferencedAssemblies.Add("System.Xml.dll");
            CodeDomProvider codeDomProvider=compilerInfo.CreateProvider();

            CompilerResults compilerResults=codeDomProvider.CompileAssemblyFromDom(compilerParameters, cu);
            if(!compilerResults.Errors.HasErrors)
            {
                txResultText=null;
                Assembly assembly=compilerResults.CompiledAssembly;
                object X= assembly.CreateInstance("CustomAttributeProvider");
                if(X!=null)
                {
                    var run=X.GetType().GetMethod("Calculate");
                    object result=run.Invoke(X, new object[] { resolver, param });
                    if(result!=null)
                        txResultText=result.ToString();
                }
            } else
            {
                StringBuilder sb=new StringBuilder();
                foreach(var err in compilerResults.Errors)
                    sb.AppendLine(err.ToString());
                txResultText=sb.ToString();
            }

BTW: AvailableInfo could be also provided as parameter to Calculate, but this would allow modifications to some members, which could be undesired.

mayerwin commented 7 years ago

That's an excellent idea. I like the possibility of falling back to this kind of Turing-complete support, to take care of the long tail of use cases where custom tags would not be the right solution. Do you want to submit a PR?

lexx9999 commented 7 years ago

Sorry, no PR at the moment, maybe later .. sorry, I don't know yet. While thinking I recognized some possible issues:

Even more powerful (any reference possible) could be to let the user specify a dll to load which can be created by a normal vs project, which would likely be less work to support and provides easier testing.

JvanderStad commented 7 years ago

I updated my https://github.com/mayerwin/vs-customize-window-title/issues/8#issuecomment-310623523 , it has error handling, a proxy class you can expose data/methods and all the .Net objects you want to call.