2

I want to open a file in predefined text editor (like notepad for Windows or vim for Linux) using .NET Core. Basically that's my code:

var process = new Process();
process.StartInfo.FileName = file;
process.StartInfo.Arguments = argument;
process.StartInfo.UseShellExecute = true;
process.Start();

On Windows everything goes fine, I can specify editor as FileName and file which I want to open as Arguments.

But on Linux I can only open file by default if I set its name as FileName. If I set vi/vim/less as FileName and file as Arguments, nothing happens. (Though they are installed in the system and I can do the same from terminal)

How can I open the file in a predefined program in a cross-platform way or just so it works on Linux (since I have the working code for Windows)?

romainl
  • 186,200
  • 21
  • 280
  • 313
demonplus
  • 5,613
  • 12
  • 49
  • 68
  • [This](https://stackoverflow.com/questions/30662334/how-to-get-list-of-programs-which-can-open-a-particular-file-extension-in-linux) might be related to your question. Imho there is no simple way, you need to handle it per OS. – Eldar Feb 13 '23 at 07:17
  • @Eldar Platform specific was is fine, what I want to know how to handle it for Linux – demonplus Feb 13 '23 at 08:10
  • The link includes the method of how you handle it in Linux. You need to start `xdg-open` process. VS code handles it like this. – Eldar Feb 13 '23 at 08:12
  • @Eldar Not sure how this helps. I don't need to open with a default app, I want to specify what app should be used to open the file – demonplus Feb 13 '23 at 11:27
  • Have you tried setting UseShellExecute to false? So FileName="vim"; Arguments = your_file; UseShellExecute = false – Matteo Umili Feb 13 '23 at 11:56
  • @MatteoUmili For .NET Core UseShellExecute = false by default. I've tried to set it to true :) But that didn't help. Actually there is no difference in my case – demonplus Feb 13 '23 at 13:10
  • Vim is getting executed in the same terminal session that your app is executed... To open it in a new window you need to start a new terminal and then run vim there. Something like `StartInfo.FileName = "xterm";` `StartInfo.Arguments = $"-e vim \"{yourFile}\""` This assumes that xterm is installed in your system. There is no standard way to automatically detect the default terminal emulator (that may be konsole, gnome-terminal, etc...) – Matteo Umili Feb 14 '23 at 08:38
  • You could also look for an `$EDITOR` or `$VISUAL` environment variable to execute. – Jeremy Lakeman Feb 22 '23 at 06:06

2 Answers2

0

You can simply run a terminal instance, and then open the file via commandline

using System.Diagnostics;

var process = new Process();
const string program = "nano";
const string file = "test.txt";
if (OperatingSystem.IsWindows())
{
    process.StartInfo.FileName = program;
    process.StartInfo.Arguments = file;
}
else if (OperatingSystem.IsLinux())
{
    process.StartInfo.FileName = "/bin/bash";
    process.StartInfo.Arguments = $"-c \"{program} {file}\"";
}

process.Start();
Console.ReadLine();

I tried and tested this on Manjaro Linux, and it seems to work fine for me.

(Note starting it in a terminal editor such as vim seemed to cause issues for me since the program would open it and then immediately stop the program, to fix this I had to add the Console.ReadLine() so it doesn't immediately close)

JDChris100
  • 146
  • 9
0

My solution explained.

1. Try to find the command, checking for the existence of the file using all paths stored in the "path" Environment variable.

The function "CommandExists" it returns command if command file exists, or empty.

Thanks to this autors Taking inspiration from this post

e.g. CommandExists("notepad") it returns "C:\Windows\system32\notepad.exe".

2. Start process with command and arguments.

if command is command line app. Process needs to be waited for finish.

3. Text editor is opened with file content.


Code

You can check/test/clone this code from Github https://github.com/JomaStackOverflowAnswers/RunDefaultEditor

Main program

using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Runtime.InteropServices;

Console.WriteLine("███ Opening file with text Editor");
string editor = string.Empty;
string parameters = string.Empty;
while (!editor.Equals("exit!"))
{
    Console.WriteLine(@"Type the editor command/filename OR exit [exit]:");
    editor = Console.ReadLine() ?? string.Empty;
    if (editor.Equals("exit"))
    {
        break;
    }
    Console.WriteLine(@"Type [True] OR [False] to set if the editor is command line app:");
    _ = bool.TryParse(Console.ReadLine(), out bool wait);
    Console.WriteLine(@"Type additional parameters:");
    parameters = Console.ReadLine() ?? string.Empty;

    await EditorLauncher.LaunchAsync(new EditorInfo { Command = editor, IsCommandLineApp = wait, Parameters = parameters }, "File.txt", verbose: true);
}
//Try to Launch multiple editors.
//await EditorLauncher.LaunchAsync(EditorLauncher.NotepadEditor, "File.txt", true);
//await EditorLauncher.LaunchAsync(EditorLauncher.VisualStudioEditorWindows, "File.txt", true);
//await EditorLauncher.LaunchAsync(EditorLauncher.NotepadPlusPlusEditor, "File.txt", true);
//await EditorLauncher.LaunchAsync(EditorLauncher.SublimeTextEditorWindows, "File.txt", true);
//await EditorLauncher.LaunchAsync(EditorLauncher.NotepadPlusPlusEditorFullPath, "File.txt", true);
//await EditorLauncher.LaunchAsync(EditorLauncher.SublimeTextEditorWindowsFullPath, "File.txt", true);
//await EditorLauncher.LaunchAsync(EditorLauncher.CodeEditor, "File.txt", true);
//await EditorLauncher.LaunchAsync(EditorLauncher.SublimeTextEditorUnix, "File.txt", true);
//await EditorLauncher.LaunchAsync(EditorLauncher.NanoEditor, "File.txt", true);
//await EditorLauncher.LaunchAsync(EditorLauncher.VimEditor, "File.txt", true);
//await EditorLauncher.LaunchAsync(EditorLauncher.ViEditor, "File.txt", true);
//await EditorLauncher.LaunchAsync(EditorLauncher.OpenDefaultEditorMacOS, "File.txt", true);
//await EditorLauncher.LaunchAsync(EditorLauncher.OpenTextEditEditorMacOS, "File.txt", true);
//await EditorLauncher.LaunchAsync(new EditorInfo { Command ="gedit", WaitForExit = false, Parameters = "-s"}, "File.txt", true);

EditorInfo class

public class EditorInfo
{
    public string Command { get; init; } = null!;
    public bool IsCommandLineApp { get; init; } = false;
    public string Parameters { get; set; } = string.Empty;
}

EditorLauncher class

public class EditorLauncher
{
    public static readonly EditorInfo NotepadEditor = new() { Command = "notepad" };
    public static readonly EditorInfo VisualStudioEditorWindows = new() { Command = "devenv.exe" };
    public static readonly EditorInfo NotepadPlusPlusEditor = new() { Command = "notepad++.exe" };
    public static readonly EditorInfo SublimeTextEditorWindows = new() { Command = "subl.exe" };
    public static readonly EditorInfo NotepadPlusPlusEditorFullPath = new() { Command = "C:/Program Files/Notepad++/notepad++.exe" };
    public static readonly EditorInfo SublimeTextEditorWindowsFullPath = new() { Command = "C:/Program Files/Sublime Text/sublime_text.exe" };
    public static readonly EditorInfo CodeEditor = new() { Command = "code" };
    public static readonly EditorInfo SublimeTextEditorUnix = new() { Command = "subl" };
    public static readonly EditorInfo NanoEditor = new() { Command = "nano", IsCommandLineApp = true };
    public static readonly EditorInfo VimEditor = new() { Command = "vim", IsCommandLineApp = true };
    public static readonly EditorInfo ViEditor = new() { Command = "vi", IsCommandLineApp = true };
    public static readonly EditorInfo OpenDefaultEditorMacOS = new() { Command = "open", IsCommandLineApp = true, Parameters = @"-t" };
    public static readonly EditorInfo OpenTextEditEditorMacOS = new() { Command = "open", IsCommandLineApp = true, Parameters = @"-e" };

    private static readonly ReadOnlyDictionary<string, EditorInfo> WindowsEditors = new ReadOnlyDictionary<string, EditorInfo>(
        new Dictionary<string, EditorInfo>
        {
            [NotepadEditor.Command] = NotepadEditor,
            [CodeEditor.Command] = CodeEditor,
            [NotepadPlusPlusEditor.Command] = NotepadPlusPlusEditor,
            [SublimeTextEditorWindows.Command] = SublimeTextEditorWindows,
            [NotepadPlusPlusEditorFullPath.Command] = NotepadPlusPlusEditor,
            [SublimeTextEditorWindowsFullPath.Command] = SublimeTextEditorWindows,
            [VisualStudioEditorWindows.Command] = VisualStudioEditorWindows,
            [VimEditor.Command] = VimEditor
        });

    private static readonly ReadOnlyDictionary<string, EditorInfo> LinuxEditors = new ReadOnlyDictionary<string, EditorInfo>(
        new Dictionary<string, EditorInfo>
        {
            [nameof(NanoEditor)] = NanoEditor,
            [nameof(VimEditor)] = VimEditor,
            [nameof(ViEditor)] = ViEditor,
            [nameof(CodeEditor)] = CodeEditor,
            [nameof(SublimeTextEditorUnix)] = SublimeTextEditorUnix
        });

    private static readonly ReadOnlyDictionary<string, EditorInfo> MacOSEditors = new ReadOnlyDictionary<string, EditorInfo>(
        new Dictionary<string, EditorInfo>
        {
            [nameof(OpenDefaultEditorMacOS)] = OpenDefaultEditorMacOS,
            [nameof(OpenTextEditEditorMacOS)] = OpenTextEditEditorMacOS,
            [nameof(NanoEditor)] = NanoEditor,
            [nameof(VimEditor)] = VimEditor,
            [nameof(ViEditor)] = ViEditor,
            [nameof(CodeEditor)] = CodeEditor,
            [nameof(SublimeTextEditorUnix)] = SublimeTextEditorUnix
        });

    private static async Task OpenProcessAsync(EditorInfo editor, string textFilename, bool verbose)
    {
        if (!string.IsNullOrWhiteSpace(editor.Command))
        {
            if (verbose)
            {
                Console.WriteLine($@"Opening ""{textFilename}"" with ""{editor.Command}"" {(editor.IsCommandLineApp? "command line " : string.Empty)}editor.");
            }
            var process = new Process();
            process.StartInfo.UseShellExecute = true;
            process.StartInfo.FileName = editor.Command;
            process.StartInfo.Arguments = @$"{editor.Parameters} ""{textFilename}""";
            process.Start();
            if (editor.IsCommandLineApp)
            {
                await process.WaitForExitAsync();
            }
            return;
        }
        Console.WriteLine(@$"Invalid command.");
    }

    /// <summary>
    /// Launchs the specified editor. Using Process class, to start new process of PowerShell(pwsh) with the script as parameter and the script required parameters. If editor specified is not found, it try to launch the default.
    /// </summary>
    /// <param name="textFilename">Path of text file.</param>
    /// <param name="editor">The editor command.</param>
    /// <param name="tryVsCode">If Visual Studio Code is available. The file is opened with VSCode. Editor value is ignored.</param>
    /// <param name="verbose">Prints parameters.</param>
    public static async Task LaunchAsync(EditorInfo editor, string textFilename, bool tryVsCode = false, bool verbose = false)
    {
        string command;
        if (tryVsCode)
        {
            command = CommandExists(CodeEditor.Command);
            if (!string.IsNullOrWhiteSpace(command))
            {
                await OpenProcessAsync(CodeEditor, textFilename, verbose);
                return;
            }
        }

        command = CommandExists(editor.Command);
        if (!string.IsNullOrWhiteSpace(command))
        {
            await OpenProcessAsync(editor, textFilename, verbose);
            return;
        }
        string message = @$"No editor was found for file ""{textFilename}"".";
        if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
        {
            foreach (var cmd in WindowsEditors)
            {
                command = CommandExists(cmd.Value.Command);
                if (!string.IsNullOrWhiteSpace(command))
                {
                    await OpenProcessAsync(cmd.Value, textFilename, verbose);
                    return;
                }
            }
            Console.WriteLine(message);
        }

        if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
        {
            foreach (var cmd in LinuxEditors)
            {
                command = CommandExists(cmd.Value.Command);
                if (!string.IsNullOrWhiteSpace(command))
                {
                    await OpenProcessAsync(cmd.Value, textFilename, verbose);
                    return;
                }
            }
            Console.WriteLine(message);
        }

        if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
        {
            foreach (var cmd in MacOSEditors)
            {
                command = CommandExists(cmd.Value.Command);
                if (!string.IsNullOrWhiteSpace(command))
                {
                    await OpenProcessAsync(cmd.Value, textFilename, verbose);
                    return;
                }
            }
            Console.WriteLine(message);
        }
        throw new NotSupportedException(RuntimeInformation.OSDescription);
    }


    private static string CommandExists(string filename)
    {
        if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
        {
            var paths = new[] { Environment.CurrentDirectory }
                    .Concat(Environment.GetEnvironmentVariable("PATH")!.Split(';'))
                    .Where(p => !string.IsNullOrWhiteSpace(p));

            var extensions = new[] { String.Empty }
                    .Concat(Environment.GetEnvironmentVariable("PATHEXT")!.Split(';')
                               .Where(e => e.StartsWith(".")));

            var combinations = paths.SelectMany(x => extensions,
                    (path, extension) => Path.Combine(path, filename + extension));
            return combinations.FirstOrDefault(File.Exists) ?? string.Empty;
        }
        if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
        {
            var paths = new[] { Environment.CurrentDirectory }
                    .Concat(Environment.GetEnvironmentVariable("PATH")!.Split(':'))
                    .Where(p => !string.IsNullOrWhiteSpace(p)).Select(p => Path.Combine(p, filename));
            return paths.FirstOrDefault(File.Exists) ?? string.Empty;
        }

        if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
        {
            var paths = new[] { Environment.CurrentDirectory }
                    .Concat(Environment.GetEnvironmentVariable("PATH")!.Split(':'))
                    .Where(p => !string.IsNullOrWhiteSpace(p)).Select(p => Path.Combine(p, filename));
            return paths.FirstOrDefault(File.Exists) ?? string.Empty;
        }
        throw new NotSupportedException(RuntimeInformation.OSDescription);
    }

}


Screenshots

Windows

All ok. IsCommandLineApp = true, for vim editor.

Windows

Windows

Windows

MacOS

All ok. IsCommandLineApp = true, for vim, vi, nano editors.

MacOS

MacOS

MacOS

MacOS

MacOS

Linux - Ubuntu - WSL

All ok. IsCommandLineApp = true for command line apps (vi, vim, nano).

It need to be tested in real Linux OS.

Ubuntu WSL

Ubuntu WSL

Joma
  • 3,520
  • 1
  • 29
  • 32