shoes / shoes3

a tiny graphical app kit for ruby
http://walkabout.mvmanila.com
Other
180 stars 19 forks source link

Better console #236

Closed ccoupe closed 7 years ago

ccoupe commented 8 years ago

The new console in Shoes 3.2 is kind of miserable - particularly on OSX but the tesi/gtk is not a shining example of perfection either. Windows is OK. The nix's use a pty that talks to a secondary event poll in Shoes. What if we could create a subprocess that runs a terminal and communicates over a PTY. Turns out there is a Ruby sdtlib for this.

$ irb
2.2.4 :001 > require 'pty'
 => true 
2.2.4 :002 > PTY.spawn('gnome-terminal')
 => [#<File:/dev/pts/4>, #<File:/dev/pts/4>, 16575] 
2.2.4 :003 > ^C
2.2.4 :003 > 

That doesn't wire up stdout<->stdin between processes but the API and we don't really want bash in the subprocess but lets wave our hands and pretend that is something we can control (probably)

Shoes goes odd if you require 'pty'

Shoes.app do
  stack do
    para "Demonstrate Pty.spawn with new Terminal"
    button "Try It" do
      #require 'pty'
      if RUBY_PLATFORM =~ /darwin/
        puts 'darwin'
        `/Applications/Utilities/Terminal.app/Contents/MacOS/Terminal`
      else
        puts "linux"
        system('gnome-terminal')
      end
    end
  end
end

Yet if you use the Shoes irb (alt-=) you can type the same IRB commands above without error.

OSX is more like Windows. If you start a terminal from an app and you have a terminal on screen it uses that one. If you have no terminals open then it does create a new one (with a error message on it) I don't understand why you can't `PTY,spawn(...) in a Shoes script. Some sort of scope/method missing magic?

passenger94 commented 8 years ago

Nice idea ! PTY,spawn works for me (in your example) in linux, though... well, i don't have a good understanding of all this term/tty/pty thing Don't we need a IO.pipe like in the official doc of PTY to interact with gnome_terminal ?

otherwise, i can send a 'ls' command, for example, that way and get back the result in a para

PTY.spawn('ls') do |output, input, pid|
        input.write "hi\n"
        output.each { |line| @ret.text = @ret.text + line }
end

There is also this, don't know if it's useful, (for the present case , i mean) http://ruby-doc.org/stdlib-2.0.0/libdoc/io/console/rdoc/IO.html

Shell library too

IanTrudel commented 8 years ago

I had many attempts using such techniques on Windows to integrate the real IRB in Shoes but without success.

passenger94 commented 8 years ago

I've done something maybe similar, connecting Shoes, IRB and some Gimp input/output : https://github.com/passenger94/Gimp-Ruby/blob/master/lib/plug-ins/irbconsole.rb https://github.com/passenger94/Gimp-Ruby/blob/master/lib/plug-ins/shoes_console.rb. Works nice on my system

ccoupe commented 8 years ago

A bit long winded.

Actually, the Windows code for the new console is pretty nice as is - one could re write the Shoes.irb to use it (but read on before doing that)

Remember that the console works by replacing the low level file_descriptors for stdin/stdout/stderr so they point one side of pty (think one side some pipes) and the other side of the pty(pipes) is a terminal emulator. For nix/osx, that is a poorly written emulator in the same process. Clever but still a hack.

Updated sample:

Shoes.app do
  stack do
    para "Demonstrate Pty.spawn with new Terminal"
    button "Try It" do
      if RUBY_PLATFORM =~ /darwin/
        puts 'darwin'
        `/Applications/Utilities/Terminal.app/Contents/MacOS/Terminal`
      else
        require 'pty.so'
        puts "linux stdin = #{$stdin.fileno} stdout = #{$stdout.fileno}"
        rd, wr, pid = PTY.spawn("gnome-terminal")
        wr.puts "new #{pid}  pty?"
      end
    end
  end
end

Sadly, gnome-terminal has some problems used this way. It runs a shell (and .bashrc.) and changes it's stdin/out/err to that shell process so it's no longer talking to our pty. OSX would have the same problem (probably) What would be better for the nix's is when shoes.show_console is called that the code does a fork() and the new (subprocess) runs a terminal emulator. (which is pretty close to the code above, if gnome-terminal worked here) There's also an issue that gnome-terminal is not in all Linux installs.

conceptually in C,

long pty[3] = create_pty(); 
char *[] command_line(args)
 if (pid = fork()) {
     // in child subprocess
     start_terminal(pty[0], pty[1], pty[2], command_line)  // something we wrote or the user already has
    // never returns
}

For now that's the tesi based 'thing' but now it's running in a different process and doesn't have to call back to Shoes/ruby to display things. It couldn't because its a different process We'd have to manage the subprocess pid at quit time.

There are several use case to consider: (1) test-unit/rspec, (2) byebug/remote, (3) other gems that think they have a terminal on stdin/out/err, (4) Shoes/irb

1 and 3 are satisfied by this proposal. (2) Byebug/remote is too complicated. Let's create a 'shoesbug' method (which starts the byebug remote in the Shoes process and then creates the console with the byebug command to connect to the (parent process). Details left undiscovered and they are important. We do have Shoes & ruby in the console subprocess so we can execute the ruby code to make the server connection. Irb(4) use case: Unless it has some sort of server mode, we can't poke inside the parent process BUT the existing implementation could just read/write stdin/stdout (if irb uses readline) after creating a console. No need to manage keystrokes in Shoes or call Shoes to display things. Again, details matter. Also, we don't have to do anything for Irb.

There are many terminal emulators in the OSS world we so can get better code than tesi. (search terminal in syntapic) For linux , we also have gtk's vte. On OSX, there are not as many OSS emulators (lots of binary only shareware) and they seem designed to replace Apples terminal which is a lot more code than we need. There are other OSS state machines for handling the vt102/vt220 escapes instead of tesi and we do have OSX code to create a window, set the font, draw characters arriving from the pty and send key strokes to it. Don't have the color stuff or bold or ...

That said, the difference between what we have now and this proposal is a lot of C/Obj-C code. The difference between that start_terminal and a full app with menus .... is

main(argc,argv)  {
  start_terminal(stdin, stdout, stderr, "/bin/sh")
}

Not much different except we don't need to create or handle lots of menus or font selection, and we control what the command line args are.

ccoupe commented 8 years ago

I'm leaning towards using https://github.com/kika/libtsm because it could/should work with OSX as well as Linux and it has a gtk example we can start from. It's not documented in a way most of us would like -- aka misleading and confusing - pty/tty is already a deep dive for the young folk ;^). It's much better than the tesi crap.

Clone it and cd in

./autoconf.sh
./configure --enable-gtktsm
make
passenger94 commented 8 years ago

The Kids Are Alright !! :stuck_out_tongue:

indeed more complete library !

ccoupe commented 8 years ago

Actually the way to build libtsm is

./autoconf.sh
./configure
./make 
./make install
./make gtktsm

gtktsm is a properly written program. Much can be learned. I suspect 90% can be copied for Shoes purposes. libtsm (in /usr/local here) or /usr if installed from apt-get) is a new dependency and a new lib to copy into Tight Shoes. Getting gtktsm/libtsm working in OSX will be the larger challenge. I've created a remote branch 'tsm_con' since it's going to change things a lot and we don't know if it's worth it.

ccoupe commented 8 years ago

Now for the problems. There are many. Deal Breakers? The gtktsm sample was not written to handle pty the way we need. I knew that from the beginning but it runs deep in the sample. Secondly, on OSX, the rules for fork() are pretty strict - its needs to be followed by an exec..(path_to_executable) before the child does anything to confuse cocoa. This is true of Gtk apps as well. We need a real executable - not some functions in the duped code. This is a big issue for OSX (new app and build and all the rake files). It also turns out that the gtktsm sample doesn't handle scrolling (and the backing buffer may not be handled). That's a lot of work just to discover it might not work well in a separate process for all the use cases.

Perhaps the current design isn't all that bad, just incomplete (can't handle multi char escapes for setting colors...) There is a lot to like about using NSTextView and GtkTextView if we could handle insert/delete char/line/range which is suspiciously like issue #144 requirements only called from the terminal state machine(tesi or libtsm) -> shoes_nativetextedit set_font_color(). That would be a lot of methods to add to text_edit but that's the point, text_edit is not edit_box and new methods are desired. And, it wouldn't change the windows implementation of show_console since Windows will never call the pty state machine - no ptys on Windows. (and that's OK in this scheme).

The tesi/llibtsm/vt102/xterm escape sequences can help define what the Shoes native methods need to be for the text_edit widget. Tonight, there's a lot to like about this approach.

passenger94 commented 8 years ago

too much problems ?

ccoupe commented 8 years ago

What the shoes console code does is backwards to what gtktsm and tesi was writtten for - their assumption is they a collecting key strokes and pass that to /bin/sh is a subprocess. and output from the subprocess is routed to gtk display methods. What Shoes wants is different.

Tesi is not impossible to use or to understand. It does needs as serious clean up and better comments plus function names that fit the purpose (libtsm has that problem too when used in the Shoes way).

Parsing all and responding to all escape sequences in the xterm-256 definition would be very difficult and useless since most will never be used. I short circuited tesi handling at tesi.c:54 and duped some processing into haveChar in gtk-terminal/cocoa-terminal. That hack needs to be undone. Use the 'tesi', Luke!

passenger94 commented 8 years ago

i've made some basic escape sequence parsing for a ri based Ruby doc Shoes app https://github.com/passenger94/Dance_floor/blob/master/ruby_doc.rb#L87

A Jedi craves not these things !

ccoupe commented 8 years ago

This is even harder than I thought. Tesi had bugs and never could parse xterm-color escapes in a useful manner. On OSX, with it's missing stdout, pty related things are going to be much different. Tesi may not be used for OSX - too soon to say.

gtk-terminal.c (only used on Linux) may switch to using Gtk vte instead of gtk_text_view (because VTE does all the missing stuff and seems to be part of every Linux except Raspbian which is a low value target when downloads are counted and they are used to do sudo apt-get install libvte-2.90

One last thing. I'm going to call it Shoes Terminal, visually, verbally and in the code to avoid confusion with Shoes Console in log.rb

ccoupe commented 8 years ago

Gtk terminal has an icon, just like osx! Windows can't play that game. Bonus points to use the packaged app name instead of "Shoes". Here's samples/expert-console.rb screenshot from 2016-04-16 00 45 54

ccoupe commented 8 years ago

There is a lot to like with VTE - except it's a moving target. The api changes and Shoes has no idea if the destination has the API Shoe was compiled against and the internet doc is pathetic. The /usr/share/gtk-doc is better but version specific. I'll commit the gtk-terminal.c code for VTE (version 0.34 api)- who knows what your libvte-2.90 implements. Maybe someone else wants to deal with it.

screenshot from 2016-04-16 22 17 57

passenger94 commented 8 years ago

term

vte 2.90.34

but no luck with tests in colorized output (like tests for vlc, here testing Shoes::Color)

term_test

ccoupe commented 8 years ago

There are some important things missing in the VTE version. (like scrolling and keyboard input). VTE is the core of gnome-terminal so it parses all the escape sequences of an xterm and responds as an xterm. That also means every Linux out there has a slightly different version of 2.90 with slightly different incompatible .so

passenger94 commented 8 years ago

is it a lost cause then ? looks better ...

ccoupe commented 8 years ago

We would have to include libvte.so with Shoes (might work) and doesn't screw up gtk3 on the running system. Odds are high the x86 linux build machine has an older vte than either of us and much older than the guy running 15.10 and the color picker.

Font's and colors can be set in the older terminal and I believe we can support attributes like color, bold, an so on. Just a matter of predefining text tags for them and then creative use of iterators to mark the start and end. Then apply the tag to the text buffer. And then something similar but different on osx.

ccoupe commented 8 years ago

Here's the Gtk_TextView version of the console: screenshot from 2016-04-17 22 25 33

There's much to be done but if we're clever I think we can work in all the attributes and quite likely the very odd scroll regions and xterm cursor movement (aka ncurses)

ccoupe commented 8 years ago

The gtk_text_view terminal continues to improve. screenshot from 2016-04-18 21 47 00

As noted in the commit, there is a tesi bug dealing with ; in combined sequences (set bold and red for example) and they don't span properly - like underline, green,text,red, text,end_underline. That will take a more clever data structure - I'm not sure its worth the effort.

passenger94 commented 8 years ago

:high_heel: :high_heel: :high_heel: Yes, sir ! (with some intended failure) term_test3

ccoupe commented 8 years ago

After much thinking and tinkering, It's hard to do xterm cursor movement in gkt_text_view. Not impossible, just difficult (and probably not very useful when working). Lines in gtk_text_buffers are variable length, null terminated. Asking for a character position past the end of a line segfaults (or returns the last character at best) -- not a lot of flexibility for moving the insertion point on the X axis. Worst of all there's no way to detect that you aren't really pointing to where you asked.

One path I started down was to convert the first 24/25 lines to have 80 characters each when involved in a cursor based escape sequence but that's difficult too, However, we could have two gtk_text_buffers and switch them in the gtk_text_view, the log_buffer that @passenger94 shows as working above this comment, and a 'legacy_buffer' which is just 24|25 lines of 80 spaces to be moved to and replaced. Since there can be only one Shoes Terminal because there's only one stdin/stdout/stderr device, these buffers can be global vars (they are just C pointers) and gtk_text_view * could be stored a global too since it will never change. The terminal would start in log mode by default and attempts to move the cursor with with escape sequences (and clear screen, erase line. etc) would be ignored.

We should have a way for the Shoes scriptwriter to select which mode to start in. Might as well also specifying the terminal size (row, columns), background and foreground colors - because some people like black backgrounds and the window title (like we for dialogs). The redundant 'Shoes Terminal' in the 'menu panel' could be replaced with 'legacy_mode' checkbox that could switch the buffers and mode setting. If Windows can't do all those changes (it probably can't), so be it. If OSX can't all of if, thats OK too. Please advise.

passenger94 commented 8 years ago

From your comments i understand that gtk_text_view don't play well with escape sequence related to cursor movement (they require a well defined buffer?), so looks like a good plan to separate both ...

Looks like gtk_text_view is doing his royal picky huge kludge Princess, separation of concern is a mantra in development area, i've been told !

ccoupe commented 8 years ago
Shoes.app do 
  stack do
    para "Terminal test"
    button "do it" do
      #Shoes.show_console
      Shoes.terminal columns: 64, rows: 12, fontsize: 9, title: "Bug236",
        fg: "yellow", bg: "black"
      #if RUBY_PLATFORM =~ /darwin/
      #  $stderr.puts "Filenums #{STDOUT.fileno} #{STDERR.fileno}"
      #  $stdout = $stderr
      #end
      $stderr.puts "STDERR OK"
      $stdout.puts "STDOUT OK" 
      puts "Way to go!"
      puts "\033[32mGood in green?\033[00m or is \033\[35mthis better\033\[00m"
      puts "\033[31m\033[40mRed on Black\033[0m OK? \033[01mBold?\033[0m"
      puts "And \033[04;33;46mUnderline joy?\033\[0m"
    end
  end
end

Produces screenshot from 2016-04-24 00 40 18

passenger94 commented 8 years ago

:high_heel: :high_heel: :high_heel: :high_heel: :high_heel: :high_heel: Nice

Windows is totally oblivious to all of the Linux fun

haha

we now have Shoes.terminal in lieu of Shoes.console, right ?

ccoupe commented 8 years ago

We have both Shoes.show_console (old) and Shoes.terminal {hash} (new). Either one works, only one has {args}. Command line -w calls Shoes.show_console for backwards compatibility.

Edit: I should also mention that Windows won't need a game/log mode since it kind of does the right thing if you send the "correct" escape sequences which may not be xterm. Can't fix that. Windows will also display stderr before buffered stdout. Can't fix that either.

passenger94 commented 8 years ago

Tried with videovlc tests and it launches a bunch of terminal now ? Single test are ok, seems something allows to have several terminal windows.

ccoupe commented 8 years ago

app.c:628 is the controlling variable and the code is just below that. Is it multiple consoles? I can see a potential for that. If it's multiple terminals then I'm missing something.

ccoupe commented 8 years ago

app.c lines 640 & 641 shoukd look like this

    shoes_native_terminal(dir_path, 1, 80, 24, 10, "black", "white", "Shoes Terminal");
    shoes_global_terminal = 1;
passenger94 commented 8 years ago

not in sync with those line numbers (though i just pull from terminal) and don't match ! i'm going to retry a pull/fetch yes terminal i mean (from Shoes.show_console, haha, confusing now !)

passenger94 commented 8 years ago

shoes_native_terminal(dir_path, 1, 80, 24, 10, "black", "white", "Shoes Terminal"); shoes_global_terminal = 1;

no i have the same thing than in terminal branch here at github https://github.com/Shoes3/shoes3/blob/terminal/shoes/app.c#L640 and https://github.com/Shoes3/shoes3/blob/terminal/shoes/app.c#L678

and git says i'm in synch ... difference in line numbers is simply because there isn't the app_finished commit incorporated (so this is expected)

passenger94 commented 8 years ago

it's fine with Shoes.terminal though (not Shoes.show_terminal, didn't pay attention at first try)

ccoupe commented 8 years ago

those are the lines to insert in your copy. shoes_native_terminal is void() so you have to set the shoes_global_terminal to 1. Mine is fixed but not committed yet.

passenger94 commented 8 years ago

ok, fixed ! :-)

ccoupe commented 8 years ago

I'm working on the 'game' mode for the terminal (cursor placement) and as you should expect from me, I may have gone down the wrong rabbit hole and missed some warning signs like there is one more line in the gtk_text_buffer than you create. The newline at the end is part of the line width, I could have set 'overwrite' mode and I should use text_view instead of text_buffer for some things, but not others. It's a good learning experience and doesn't affect the log mode which does continue to work.

ccoupe commented 8 years ago

Gaining ground (gtk linux)

Shoes.app do 
  stack do
    para "Game Mode Terminal test"
    button "do it" do
      columns = 80
      rows = 24
      Shoes.terminal columns: columns, rows: rows, fontsize: 12, title: "Test Cursor Movements",
        mode: 'game'
      ruler1 = ''
      ruler2 = ''
      (1..columns).each do |i|
        tens = i/10
        ones = i%10
        ruler1 << (tens == 0 ? ' ' : tens.to_s)
        ruler2 << ones.to_s
      end
      puts ruler1
      puts ruler2
      (3..rows+2).each {|row| puts row }
      (3..rows).each { |ln| $stdout.write "\033[#{ln};1H#{ln}"; $stdout.flush }
      $stdout.write "\033[10;10H10,10\033[20;20H20,20"
      $stdout.flush
      $stdout.write "\033[24;79HEND\033[1;1H"
      $stdout.flush
    end
  end
end

You might notice that line numbers 3..2x are drawn twice in two different ways. Part of the test. Last one wins, because that's how old school terminals work. screenshot from 2016-04-26 22 01 07

ccoupe commented 8 years ago

Getting closer. cap236

Now that I almost know what I'm doing on OSX, setting colors from escape sequences should be relatively easy except I should never have used two classes in Cocoa when one would do and I need to change code names from console to terminal.

ccoupe commented 8 years ago

There is still much to do. attr

Clear eyes will notice there is no bold. On OSX thats a font change thing and not so easily done, yet. Keydown doesn't work and there are mamy strange problems with Tests/test_video_vlc.rb only works in certain situations and without color and have #224 issues.

ccoupe commented 8 years ago

Warning! I'm likely to merge the terminal branch into master soon. OSX may still have some issues but the branch is much better than what's in master.

If any one is bored and would like to write some ruby, we need an sample/expert-console.rb that uses some of the things found here

ccoupe commented 8 years ago

Just when I was almost done with this issue, I got to thinking about Windows (and it's too ugly for Shoes) console. Windows has pipes, and handles and even explicit calls to set/redirect stdin/stdout/stderr. We already have the xterm behavior in tesi/gtk-terminal. We just need to periodically move bytes from stdout pipe to gtk-terminal in a similar way to the osx implementation (probably much easier to do in Windows, BTW just add a gtk idle routine?. Like linux and choes on osx, choes.exe has a launch terminal which works fine (except for xterm escapes). A call to Shoes.terminal would bring up a Shoes window instead of (another) DOS console. Cross platform.

ccoupe commented 8 years ago

The win-terminal branch may have hit a dead end. Without a tty/pty, readline just won't work. if you look at the code for the pure ruby readline (rb-readline gem) you'll see that it too depends on external things (infocmp command, and tty calls) that would be damn near impossible to implement in Shoes. Particularly on Windows.

The good new is that it should be possible to use the g_io_channel structure to monitor the pty fds for stdout/stderr which is noticeably faster on Linux compared to the timer method.

ccoupe commented 7 years ago

Closed in Shoes 3.3.2