[CSharp][Roslyn] InteractiveCS

Hace unos días tuve la oportunidad de ver a un colega que usa una maquina MAC, todo iba bien hasta que el hombre lanzó una de esas IDEs que me cautivan, que son tan extremadamente sencillas e “improductivas” (pero llamativas para mi) por su estilo de “hacker“. Esta IDE no era más que una Consola REPL con cierta transparencia y letras verdes donde se escribía código y se tenía una respuesta inmediata. No es la primera vez que veo interfaces con este estilo tan “cautivante“, las he visto en varias máquinas pero nunca he trabajado con IDEs así. En el tiempo que llevo escribiendo código he tenido la oportunidad de interactuar con varias herramientas y variados lenguajes, hasta que un día llegué a Visual Studio :D, realmente adoro este IDE, así como adoro el lenguaje C# (Momento fanboy). Visual Studio es de seguro el mejor IDE que ha creado la humanidad, desde la versión 2010 de Visual Studio no se necesitan más herramientas para gestionar todo el ciclo de vida de un proyecto. Poderosas herramientas para hacer debug, refactoring, todo un set para trabajar con metodologías agiles… ¡en fin! Visual Studio es genial! Pero no puedo desconocer que siento cierto encanto por la consola y escribir código como lo hizo aquel desarrollador en sus demostraciones… así que me di a la tarea de escribir mi propia REPL para escribir código C#. En este post voy a describir como lo tengo pensado 😛

Esto no es posible con C#!

Todos sabemos que escribir un programa con C# debe ser compilado, si quieres hacerlo por fuera de Visual Studio puedes escribir desde tu editor de texto preferido y emplear el Microsoft C# Compiler (csc.exe), como resultado tendremos un .exe si el código fuente tiene un punto de entrada (método main). Entonces ¿Cómo conseguir una interacción inmediata con el usuario que desea escribir código C# por fuera de Visual Studio?

Roslyn al rescate

Ya llevo algunos meses trabajando con el proyecto Roslyn, es realmente maravilloso poder tener el compilador como servicio y en este caso me iba a servir bastante. El proyecto Roslyn expone tres conjuntos de APIs, entre las que está el Scrpting API que hace parte del Compiler API, esta nos expone un contexto de ejecución de código y ScriptEngine que permite la evaluación de expresiones. Con esto entonces soluciono mi problema de la interpretación del código C# 😀 ya no lo compilo, lo interpreto como cualquier lenguaje de script 😛

InteractiveCS

Así pues cierta envidia me motivo a tener una consola con ese estilo y el estilo es que más me llama la atención, así que decidí escribir un proyecto WPF con una única ventana y cierta transparencia que le da ese aspecto tan genial. Todo el código XAML que necesite:

<Window x:Class="Interactivecs.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow"
        WindowStartupLocation="CenterScreen"
        WindowStyle="None"
        AllowsTransparency="True"
        Height="600"
        Width="800"
        Background="Black"
        Opacity="0.9"
        >
    <Grid>
        <RichTextBox Foreground="Green"
                     Background="Transparent"
                     CaretBrush="White"
                     FontSize="18"
                     FontFamily="Consolas"
                     x:Name="richTextBoxPrincipal"
                     PreviewKeyDown="RichTextBoxPreviewKeyDown"
                     Cursor="None"/>

    </Grid>
</Window>

En el código fuente en su mayoría es para controlar la interacción con el usuario… donde realmente ocurre la magia es con el uso de:

using Roslyn.Compilers;
using Roslyn.Scripting;
using Roslyn.Scripting.CSharp;

Que nos permite hacer uso de ScriptEngine y Session para poder procesar el código que el usuario ingresa:

        private void ProccessCommand(string currentCode)
        {
            try
            {
                _result = _session.Execute(currentCode);
            }
            catch (CompilationErrorException errorException)
            {
                var run = new Run(errorException.Message) {Foreground = new SolidColorBrush(Colors.Red)};
                _flowDocument.Blocks.Add(new Paragraph(run));
            }
            if (_result == null)
            {
                AddStartContentLine();
                return;
            }
            _flowDocument.Blocks.Add(new Paragraph(new Run(_result.ToString())));
            AddStartContentLine();
        }

El código completo:

    public partial class MainWindow
    {
        private readonly ScriptEngine _scriptEngine;
        private readonly Session _session;
        private string _currentDirectory;
        private FlowDocument _flowDocument;
        private object _result;

        public MainWindow()
        {
            _scriptEngine = new ScriptEngine();
            _session = _scriptEngine.CreateSession();
            _scriptEngine.CreateSession();
            InitializeComponent();
            Loaded += MainWindowsLoaded;
        }

        private void MainWindowsLoaded(object sender, RoutedEventArgs e)
        {
            _currentDirectory = string.Format("{0}>",
                                              Environment.GetFolderPath(Environment.SpecialFolder.UserProfile));
            _flowDocument = new FlowDocument();
            _flowDocument.Blocks.Add(new Paragraph(new Run(_currentDirectory)));
            richTextBoxPrincipal.Document = _flowDocument;
        }

        private void RichTextBoxPreviewKeyDown(object sender, KeyEventArgs e)
        {
            switch (e.Key)
            {
                case Key.Back:
                    {
                        TextPointer start = richTextBoxPrincipal.CaretPosition;
                        string text = start.GetTextInRun(LogicalDirection.Backward);
                        if (text == _currentDirectory)
                            e.Handled = true;
                    }
                    break;
                case Key.Enter:
                    string currentCode = richTextBoxPrincipal.CaretPosition.GetTextInRun(LogicalDirection.Backward);
                    ProccessCommand(currentCode);
                    e.Handled = true;
                    break;
                case Key.Left:
                    {
                        TextPointer start = richTextBoxPrincipal.CaretPosition;
                        string text = start.GetTextInRun(LogicalDirection.Backward);
                        if (text == _currentDirectory || text == string.Empty)
                            e.Handled = true;
                    }
                    break;
            }
        }

        private void ProccessCommand(string currentCode)
        {
            try
            {
                _result = _session.Execute(currentCode);
            }
            catch (CompilationErrorException errorException)
            {
                var run = new Run(errorException.Message) {Foreground = new SolidColorBrush(Colors.Red)};
                _flowDocument.Blocks.Add(new Paragraph(run));
            }
            if (_result == null)
            {
                AddStartContentLine();
                return;
            }
            _flowDocument.Blocks.Add(new Paragraph(new Run(_result.ToString())));
            AddStartContentLine();
        }

        private void AddStartContentLine()
        {
            _flowDocument.Blocks.Add(new Paragraph(new Run(_currentDirectory)));
            richTextBoxPrincipal.Document = _flowDocument;
            richTextBoxPrincipal.CaretPosition = richTextBoxPrincipal.CaretPosition.DocumentEnd;
        }
    }

Descarga la versión 0

He subido este proyecto a GitHub si quieren descargarlo, probarlo y mejorarlo :D, cualquier mejora/feedback será bienvenido. https://github.com/nicolocodev/interactivecs

Como siempre… cualquier duda, comentario o sugerencia es bienvenido.

Hasta el próximo post.

Anuncios
[CSharp][Roslyn] InteractiveCS

4 comentarios en “[CSharp][Roslyn] InteractiveCS

Responder

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Cerrar sesión / Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Cerrar sesión / Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Cerrar sesión / Cambiar )

Google+ photo

Estás comentando usando tu cuenta de Google+. Cerrar sesión / Cambiar )

Conectando a %s