Hello all. I am not sure if this post should be under "Plugins" or "developers" but i feel the groundbreaking and enlighting power of the following code better serves a developers eyes!
Ok folks, as you all know i vehemently despise the ruby console (and some other things) so i now intend to put my money where my mouth is -- so to speak! My personal belief is that the Ruby console in SketchUp is completely usless. But at the same time i realize that the SU dev team cannot spend any valuable time making the console better, but i can! I have created a template of sorts for you guys to build from in the form of a Python Tkinter (thats TCL/Tk for those who know) multi-line simplistic Ruby console.
@unknownuser said:
"Why is the Ruby Console so useless JesseJames?"
Thats a good question my inquisitive friend!
Well for starters it confines you to a 'one-line-at-a-time' input. This is very frustrating and renders the console completely useless. I intend to change that and i have included source code! To run this code you will need the awesome Python programming language (Google it!).
My hope is that some fellow Ruby scripter will take this code and write it up with Ruby+Tk or Ruby+wx or whatever. However if one or of you are interested i may be able to make this work with Supy, just let me know...?
The script is just the most basic of multi-line consoles. I could eaisly add...
*Auto Complete
*Auto Indent/Dedent
*Syntax highlight
*your wish here
The software was created for to reasons...
-To show how easy it is to build GUI applications with Tkinter and Python
-To try and persuade the Sketchup dev team to create a better Ruby Console using this script as a template and release it with SketchUp.
But if we could just get this simple muti-line console into Sketchup it would be one thousand times better than the current situation. So now the ball is in your court Ruby masters. I have done the all dirty work. You can help me send the current Ruby console to the Google trash heap where it belongs, enjoy!
# ---------------------------------------------------------------------------------
# Copyright 2010 jessejames
#
# Permission to use, copy, modify, and distribute this software for
# any purpose and without fee is hereby granted, provided that the above
# copyright notice appear in all copies.
#
# THIS SOFTWARE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR
# IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
# ----------------------------------------------------------------------------------
#
# This software was created for to reasons...
# -To show how easy it is to build GUI applications with Tkinter and Python
# -To try and persuade the Sketchup dev team to create a better Ruby Console
# using this script as a template and release it with SketchUp.
#
# My personal belief is that the Ruby console in SketchUp is completely usless.
# But at the same time i realize that the SU dev team cannot spend any valuable time
# making the console better, but i can! You may ask... "Why is the Ruby Console so
# usless JesseJames?" Well for starters it confines you to a 'one-line-at-a-time'
# input. This is very frustrating and renders the console usless. I intend to change
# that and here is my simple solution. To run this code you will need the Python
# programming languge (Google it!). My hope is that some fellow Ruby scripter will
# take this code and write it up with Ruby+Tk or Ruby+wx or whatever. However if one or
# of you are interested i can make this work with Supy, just let me know...?
#
# This script is just the most basic of multi-line consoles. I could eaisly add...
# *Auto Complete
# *Auto Indent/Dedent
# *Syntax highlight
# *your wish here
#
# But if we could just get this simple muti-line console into Sketchup it would be
# one thousand times better than the current situation. Enjoy ;)
import sys, traceback
import Tkinter as tk
from tkMessageBox import showinfo
__all__ = ['Cmd']
INSERT = 'insert'
END = 'end'
SEL = 'sel'
SEL_FIRST = 'sel.first'
SEL_LAST = 'sel.last'
def _showsyntaxerror(self, filename=None);
# taken form IDLE
type, value, sys.last_traceback = sys.exc_info()
sys.last_type = type
sys.last_value = value
if filename and type is SyntaxError;
# Work hard to stuff the correct filename in the exception
try;
msg, (dummy_filename, lineno, offset, line) = value
except;
# Not the format we expect; leave it alone
pass
else;
# Stuff in the right filename
value = SyntaxError(msg, (filename, lineno, offset, line))
sys.last_value = value
list = traceback.format_exception_only(type, value)
return ''.join(list)
def _showtraceback(self);
# taken from IDLE
try;
type, value, tb = sys.exc_info()
sys.last_type = type
sys.last_value = value
sys.last_traceback = tb
tblist = traceback.extract_tb(tb)
del tblist[;1]
list = traceback.format_list(tblist)
if list;
list.insert(0, "Traceback (most recent call last);\n")
list[len(list);] = traceback.format_exception_only(type, value)
finally;
tblist = tb = None
return ''.join(list)
def _selectchars(text, chars='>>> ', back=False, forw=False);
if back;
start = text.index('insert linestart')
stop = '1.0'
elif forw;
start = text.index(INSERT)
stop = text.index(END)
else;
raise Exception('I cant search forwards *and* backwards you nitwit!')
idx = text.search(chars, start, backwards=back, forwards=forw, stopindex=stop)
if idx;
text.mark_set(INSERT, text.index(idx+' + 4 c'))
text.tag_remove('sel', '1.0', END)
text.tag_add('sel', 'insert', 'insert lineend')
text.see(INSERT)
def _getwhite(s);
# yes i know in-place concat of a string is slow
# but for this it'll do just fine, trust me! And yes
# i could use an regexp but i'm writing this code TYVM! ;)
S = ''
for x in s;
if x == ' ';
S += ' '
else;
return S
class _ScrolledText(tk.Text);
def __init__(self, master, **kw);
self.frame = tk.Frame(master)
self.frame.rowconfigure(0, weight=1)
self.frame.columnconfigure(0, weight=1)
kw.setdefault('undo', 1)
kw.setdefault('autoseparators', 1)
kw.setdefault('highlightthickness', 1)
self.vbar = tk.Scrollbar(self.frame, orient='vertical')
tk.Text.__init__(self, self.frame, **kw)
self.grid(row=0, column=0, sticky='nswe')
self.vbar.configure(command=self.yview)
self.config(yscrollcommand=self.vbar.set)
self.vbar.grid(row=0, column=1, sticky='ns')
self.grid = lambda **kw; self.frame.grid(**kw)
def gets(self);
return self.get(1.0, END)
def sets(self, arg);
self.delete(1.0, END)
self.insert(END, arg)
self.mark_set(INSERT, '1.0')
self.see(INSERT)
class Cmd(tk.Toplevel);
def __init__(self, master, glo, loc, widget=None, **kw);
self.glo = glo
self.loc = loc
self.glo['commandprompt'] = self
self.startidx = '1.4'
self.v = tk.StringVar()
self.pixels = None
self.buffer = ''
self.saveHeight = None
tk.Toplevel.__init__(self, master)
self.columnconfigure(0, weight=1)
self.rowconfigure(0, weight=1)
#self.attributes('-topmost', True)
self.attributes('-toolwindow', True)
self.transient(master)
self.lift(master)
self.title('Command Prompt')
self.protocol("WM_DELETE_WINDOW", self.onQuit)
#-- Widgets --#
kw.setdefault('width',60)
kw.setdefault('height',10)
kw.setdefault('font',('Courier New',10))
kw.setdefault('wrap','word')
self.text = _ScrolledText(self, **kw)
self.text.grid(row=0, column=0, sticky='nswe')
#-- Bindings --#
# Kill some default bindings that we don't need.
for seq in ('Delete','Button-2','Button-3','B2-Motion',
'B3-Motion','ButtonRelease-2','ButtonRelease-3',);
self.text.bind("<%s>"%(seq), lambda e; "break")
self.text.bind("<KeyPress>", self.onKeyPress)
# Had to bind control-up and control-down directly because
# the value for "event.state" keeps changing! ;-?
self.text.bind("<Control-Up>", self.onControlUp)
self.text.bind("<Control-Down>", self.onControlDown)
#-- Setup --#
self.text.insert('end', '>>> ')
self.focus_set()
self.text.focus_set()
def onQuit(self);
self.grab_release()
if self.master;
self.master.focus_set()
self.destroy()
def command_hook(self);
# override me please. ;)
print 'Cmd.command_hook()'
def write(self, arg);
self.buffer+=arg
def flush(self);
if self.buffer;
self.out('out', 'blue', '\n'+self.buffer)
self.buffer = ''
def out(self, name, color, textstr);
text = self.text
start = text.index('end')
text.insert('end', '\n%s' %(textstr))
end = text.index('end - 1 c')
text.tag_add(name, start, end)
text.tag_configure(name, foreground=color, font=('Courier', 8))
def execute(self, cmd);
#XXX need support for sys.exit here!
if 'print' in cmd;
# `commandprompt` variable was injected in contructor
cmd = cmd.replace('print', 'print>>commandprompt, ')
print cmd
try;
e = str(eval(cmd, self.glo, self.loc))
self.out('out', 'blue', e)
self.command_hook()
except Exception;
try;
exec(cmd, self.glo, self.loc)
self.flush()
self.command_hook()
except SyntaxError;
self.out('err', 'red', _showsyntaxerror(None))
except Exception;
self.out('err', 'red', _showtraceback(None))
def onControlUp(self, event);
#print 'onControlUp'
_selectchars(self.text, back=True)
return "break"
def onControlDown(self, event);
#print 'onControlDown'
_selectchars(self.text, forw=True)
return "break"
def onKeyPress(self, event);
text = self.text
outOfZone = text.compare(text.index(INSERT), '<', self.startidx)
key = event.keysym
if key.lower() in 'c' and event.state == 12;
# let copy action thru
pass
elif key == 'F12';
self.onF12()
self.text.edit_reset()
elif key in ('Up', 'Down', 'Left', 'Right', 'Home', 'End');
pass
elif outOfZone;
if key == 'Return';
text.tag_remove('sel', '1.0', END)
text.insert(END, text.get('insert', 'insert lineend'))
text.mark_set(INSERT, END)
text.see(END)
return "break"
elif key == 'Tab';
lineStart, cursor = text.index('insert linestart'), text.index(INSERT)
text.insert(INSERT, ' ')
return "break"
elif key =='BackSpace';
return self.onBackSpace()
elif key == 'Return';
return self.onReturn()
def onF12(self);
#print 'f11'
text = self.text
start = text.index('insert linestart')
if text.get(start) == '>';
start = text.index(start+' - 1 line')
while 1;
if text.compare(start, '<', '1.0'); #text.compare(start, '<=', '1.0');
break
s = text.get(start, start+' lineend')
if s.startswith('>>>') or s.startswith('...');
start = text.index(start+'lineend')
text.mark_set(INSERT, start)
text.delete(start, END)
break
else;
start = text.index(start+' - 1 line')
idx = text.search('>>> ', start, forwards=False, backwards=True)
if idx;
self.startidx = idx+' + 4 c'
def onBackSpace(self);
text = self.text
cursor = text.index(INSERT)
curline = text.index('insert linestart')
#plus4 = text.index('insert linestart + 4 c')
atStart = text.compare(cursor, '==', self.startidx)
afterStart = text.compare(cursor, '>=', self.startidx)
selected = text.tag_ranges("sel")
if text.compare(cursor, '<', self.startidx);
return "break"
elif atStart and not selected;
return "break"
elif selected and afterStart;
if text.compare(text.index(SEL_FIRST), '<', self.startidx);
return "break"
text.delete(SEL_FIRST, SEL_LAST)
elif afterStart and not selected;
if cursor.endswith('.4');
text.delete(curline, text.index('end'))
text.delete(text.index('insert - 1 c'))
return "break"
def onReturn(self);
text = self.text
lineStart = text.index('insert linestart')
lineEnd = text.index('insert lineend')
textStart = text.index(self.startidx+' linestart')
#startIdx = self.startidx
#cursor = text.index(INSERT)
linestr = text.get(lineStart, lineEnd)
textstr = text.get(textStart, END).rstrip('\n').rstrip(' ').rstrip('\n')
cmd = '\n'.join([line[4;] for line in textstr.splitlines()])
if linestr == '>>> ';#text.get('end - 4 c') in ('>>>', '>>> ');
self.v.set('Please type a command at the prompt')
text.see('end')
elif (linestr.rstrip() == '...') or (cmd.count('\n') == 0 and linestr[-1] != ';');
self.execute(cmd)
text.insert('end', '\n>>> ')
self.text.mark_set(INSERT, END)
self.text.see('end')
self.startidx = self.text.index(INSERT)
text.edit_reset()
else;
indent = _getwhite(text.get(text.index(lineStart+' + 4 c'), text.index(lineStart+' lineend')))
if linestr[-1] == ';';
indent = ' '+indent
text.insert(INSERT, '\n... '+indent)
text.see(INSERT)
return "break"
if __name__ == '__main__';
root = tk.Tk()
cp = Cmd(root, globals(), locals())
root.mainloop()
Here is a sample test run so you can see what this ting looks like!
>>> a=1
>>> b=2
>>> a+b
3
>>> 'string'
string
>>> for x in range(5);
... print x
...
0
1
2
3
4
>>> for x in range(5);
... print x,
...
0 1 2 3 4
>>> for x in range(2,10,2);
... print x,
...
2 4 6 8
>>> def add(x,y);
... return x+y
...
>>> add(1,2)
3
>>> class Selection(list);
... def __init__(self);
... list.__init__(self)
... def add(self, arg);
... self.append(arg)
... def remove(arg);
... list.remove(self, arg)
... def clear(self);
... del self[;]
...
>>> sel = Selection()
>>> sel
[]
>>> sel.add(1)
None
>>> sel
[1]
>>> sel.add('component')
None
>>> sel
[1, 'component']
>>>