+
+ +
+

Console

+
+

Interactive Console

+_images/console.png +
+
+class rpw.ui.Console(stack_level=1, stack_info=False)
+

REPL Console for Inspecting Stack

+
>>> from rpw.forms import Console
+>>> Console()
+
+
+ +++ + + + +
Parameters:
    +
  • stack_level (int, optional) – Stack Level. Default is 1.
  • +
  • stack_info (bool) – Print Stack Call info. Default is False.
  • +
+
+
+ +
+
+
+

Implementation

+
import os
+import inspect
+import logging
+import tempfile
+from collections import defaultdict
+
+# This is only so forms.py to be executed on console for easier testing and dev
+# `ipy.exe forms.py` and ipy -X:FullFrames console.py
+
+try:
+    from forms import *
+except ImportError:
+    from rpw.ui.forms import *
+# logger.verbose(True)
+
+class Console(Window):
+    """ REPL Console for Inspecting Stack
+
+    >>> from rpw.forms import Console
+    >>> Console()
+
+    Args:
+        stack_level (int, optional): Stack Level. Default is 1.
+        stack_info (bool): Print Stack Call info. Default is False.
+
+    """
+    LAYOUT = """
+                <Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
+                        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+                        Title="DeployWindow" Height="400" Width="800" SnapsToDevicePixels="True"
+                        UseLayoutRounding="True" WindowState="Normal" WindowStartupLocation="CenterScreen">
+            	<Window.Resources>
+            		<Style TargetType="{x:Type MenuItem}">
+            			<Setter Property="FontFamily" Value="Consolas"/>
+            			<Setter Property="FontSize" Value="12.0"/>
+            		</Style>
+            	</Window.Resources>
+                <Grid>
+                    <Grid.ColumnDefinitions>
+                        <ColumnDefinition Width="*"></ColumnDefinition>
+                    </Grid.ColumnDefinitions>
+                <Grid.RowDefinitions>
+                    <RowDefinition Height="0"></RowDefinition>
+                    <RowDefinition Height="*"></RowDefinition>
+                </Grid.RowDefinitions>
+                <TextBox Grid.Column="1" Grid.Row="1"  HorizontalAlignment="Stretch"
+                         KeyDown="OnKeyDownHandler" KeyUp="OnKeyUpHandler"
+                         Name="tbox" Margin="6,6,6,6" VerticalAlignment="Stretch"
+                         AcceptsReturn="True" VerticalScrollBarVisibility="Auto"
+                         TextWrapping="Wrap"
+                         FontFamily="Consolas" FontSize="12.0"
+                         />
+                </Grid>
+                </Window>
+    """
+    # <Button Grid.Column="1" Content="Deploy" Height="30" Width="100" HorizontalAlignment="Left" Margin="10,10,10,10" Name="deployButton" Cursor="Hand" />
+
+    CARET = '>>> '
+
+    def __init__(self, stack_level=1, stack_info=False):
+        """ RPW Console
+
+        >>> rpw.ui.Console()
+        # Execution will stop, and a Console will start with the stack variable
+        loaded
+
+        Args:
+            stack_level (int): Default is 1. 0 Is the Console stack, 1 is the
+                               caller; 2 is previous to that, etc.
+            stack_info (bool): Display info about where call came from.
+                               Will print filename name,  line no. and Caller
+                               name.
+        Tips:
+            Press `UP` + `DOWN` to scroll through persistant history.
+            Press tab to autocomplete based on local variables.
+        """
+
+        # History Helper
+        tempdir = tempfile.gettempdir()
+        filename = 'rpw-history'
+        self.history_file = os.path.join(tempdir, filename)
+
+        # Stack Info
+        # stack = inspect.currentframe().f_back
+        stack_frame = inspect.stack()[stack_level][0] # Finds Calling Stack
+
+        self.stack_locals = stack_frame.f_locals
+        self.stack_globals = stack_frame.f_globals
+        stack_code = stack_frame.f_code
+        # logger.debug('Local vars: ' + str(self.stack_locals))
+        # logger.debug('Global vars: ' + str(self.stack_globals))
+
+        stack_filename = os.path.basename(stack_code.co_filename)
+        stack_lineno = stack_code.co_firstlineno
+        stack_caller = stack_code.co_name
+
+        # Form Setup
+        self.ui = wpf.LoadComponent(self, StringReader(Console.LAYOUT))
+        self.ui.Title = 'RevitPythonWrapper Console'
+        self.PreviewKeyDown += self.KeyPressPreview
+
+        # Form Init
+        self.ui.tbox.Focus()
+        if stack_info:
+            self.write_line('Caller: {} [line:{}] | File: {}'.format(stack_caller, stack_lineno, stack_filename))
+        else:
+            self.tbox.Text = Console.CARET
+
+        self.ui.tbox.CaretIndex = len(self.tbox.Text)
+
+        # Vars
+        self.history_index = 0
+        self.ac_options = defaultdict(int)
+
+        self.ShowDialog()
+
+    def get_line(self, index):
+        line = self.tbox.GetLineText(index).replace('\r\n','')
+        if line.startswith(Console.CARET):
+            line = line[len(Console.CARET):]
+        logger.debug('Get Line: {}'.format(line))
+        return line
+
+    def get_last_line(self):
+        try:
+            last_line = self.get_lines()[-1]
+        except IndexError:
+            last_line = self.get_line(0)
+        logger.debug('Last Line: {}'.format(last_line))
+        return last_line
+
+    def get_last_entered_line(self):
+        try:
+            last_line = self.get_lines()[-2]
+        except IndexError:
+            last_line = self.get_line(0)
+        logger.debug('Last Line: {}'.format(last_line))
+        return last_line
+
+    def get_lines(self):
+        last_line_index = self.tbox.LineCount
+        lines = []
+        for index in range(0, last_line_index):
+            line = self.get_line(index)
+            lines.append(line)
+        logger.debug('Lines: {}'.format(lines))
+        return lines
+
+    def OnKeyUpHandler(self, sender, args):
+        """ Need to use this to be able to override ENTER """
+        if self.tbox.LineCount == 1:
+            return
+        if args.Key == Key.Enter:
+            entered_line = self.get_last_entered_line()
+            if entered_line == '':
+                self.write_line(None)
+                return
+            output = self.evaluate(entered_line)
+            self.append_history(entered_line)
+            self.history_index = 0
+            self.write_line(output)
+
+    def evaluate(self, line):
+
+        try:
+            output = eval(line, self.stack_globals, self.stack_locals)
+        except SyntaxError as errmsg:
+            try:
+                exec(line, self.stack_globals, self.stack_locals)
+                return
+            except Exception as errmsg:
+                output = errmsg
+        except Exception as errmsg:
+            output = errmsg
+        return str(output)
+
+    def OnKeyDownHandler(self, sender, args):
+        pass
+
+    def reset_caret(self):
+        self.tbox.CaretIndex = self.tbox.Text.rfind(Console.CARET) + len(Console.CARET)
+
+    def KeyPressPreview(self, sender, e):
+        e.Handled = False
+        if self.tbox.CaretIndex < self.tbox.Text.rfind(Console.CARET):
+            self.tbox.CaretIndex = len(self.tbox.Text)
+        if e.Key == Key.Up:
+            self.history_up()
+            e.Handled = True
+        if e.Key == Key.Down:
+            self.history_down()
+            e.Handled = True
+        if e.Key == Key.Left or e.Key == Key.Back:
+            if self.ui.tbox.CaretIndex == self.tbox.Text.rfind(Console.CARET) + len(Console.CARET):
+                e.Handled = True
+        if e.Key == Key.Home:
+            self.reset_caret()
+            e.Handled = True
+        if e.Key == Key.Tab:
+            self.autocomplete()
+            e.Handled = True
+        if e.Key == Key.Enter:
+            self.tbox.CaretIndex = len(self.tbox.Text)
+
+
+    def autocomplete(self):
+        # TODO: Add recursive dir() attribute suggestions
+
+        last_line = self.get_last_line()
+        cursor_line_index = self.tbox.CaretIndex - self.tbox.Text.rfind(Console.CARET) - len(Console.CARET)
+        text = last_line[0:cursor_line_index]
+        possibilities = set(self.stack_locals.keys() + self.stack_globals.keys()) # + self.get_all_history()
+        suggestions = [p for p in possibilities if p.lower().startswith(text.lower())]
+
+        logger.debug('Text: {}'.format(text))
+        logger.debug('Sug: {}'.format(suggestions))
+
+        if not suggestions:
+            return None
+        # Create Dictionary to Track iteration over suggestion
+        index = self.ac_options[text]
+        try:
+            suggestion = suggestions[index]
+        except IndexError:
+            self.ac_options[text] = 0
+            suggestion = suggestions[0]
+        self.ac_options[text] += 1
+
+        if suggestion is not None:
+            caret_index = self.tbox.CaretIndex
+            self.write_text(suggestion)
+            self.tbox.CaretIndex = caret_index
+
+    def write_line(self, line=None):
+        if line:
+            self.tbox.AppendText(line)
+            self.tbox.AppendText(NewLine)
+        self.tbox.AppendText(Console.CARET)
+
+    def write_text(self, line):
+        last_new_line = self.tbox.Text.rfind(Console.CARET)
+        self.tbox.Text = self.tbox.Text[0:last_new_line]
+        self.tbox.AppendText(Console.CARET)
+        self.tbox.AppendText(line)
+        self.ui.tbox.CaretIndex = len(self.ui.tbox.Text)
+
+    def get_all_history(self):
+        """ Retrieves all lines from history file """
+        with open(self.history_file) as fp:
+            lines = [l for l in fp.read().split('\n') if l != '']
+            return lines
+
+    def history_up(self):
+        self.history_index += 1
+        line = self.history_iter()
+        if line is not None:
+            self.write_text(line)
+
+    def history_down(self):
+        self.history_index -= 1
+        line = self.history_iter()
+        if line is not None:
+            self.write_text(line)
+
+    def append_history(self, line):
+        logger.debug('Adding Line to History:' + repr(line))
+        with open(self.history_file, 'a') as fp:
+            fp.write(line + '\n')
+
+    def history_iter(self):
+        lines = self.get_all_history()
+        logger.debug('Lines: {}'.format(lines))
+        try:
+            line = lines[::-1][self.history_index -1]
+        # Wrap around lines to loop and up down infinetly.
+        except IndexError:
+            if len(lines) == 0:
+                return None
+            if len(lines) < 0:
+                self.history_index += len(lines)
+            if len(lines) > 0:
+                self.history_index -= len(lines)
+            line = lines[0]
+        return line
+
+    def __repr__(self):
+        '<rpw:Console stack_level={}>'.format(self.stack_level)
+
+
+# if __name__ == '__main__':
+#     # def test():
+#     #     Console()
+#     # test()
+
+
+
+
+ + +
+
+ +
+