Use terminal binary as a base for Terminal

openid
Marcin Kulik 11 years ago
parent e537c1b78a
commit 0e2c1784c6

@ -22,8 +22,8 @@ gem 'dotenv-rails', '~> 0.8'
gem 'sass-rails', '~> 4.0.0'
gem 'coffee-rails', '~> 4.0.0'
gem 'uglifier', '>= 2.1.2'
gem 'tsm', :git => 'git://github.com/sickill/tsm.git'
gem 'sinatra', :require => false
gem 'sinatra', '~> 1.4.3', :require => false
gem 'oj', '~> 2.1.4'
group :development do
gem 'quiet_assets', '~> 1.0.1'

@ -1,10 +1,3 @@
GIT
remote: git://github.com/sickill/tsm.git
revision: 6959bcecdb9061607fd41e9aee34f25e65c11a2d
specs:
tsm (0.1.0)
ffi (~> 1.8)
GEM
remote: http://rubygems.org/
specs:
@ -172,6 +165,7 @@ GEM
jwt (~> 0.1.4)
multi_json (~> 1.0)
rack (~> 1.2)
oj (2.1.4)
omniauth (1.1.4)
hashie (>= 1.2, < 3)
rack
@ -338,6 +332,7 @@ DEPENDENCIES
jasmine-rails (~> 0.4.5)
jquery-rails (~> 2.2.0)
kaminari (~> 0.14.1)
oj (~> 2.1.4)
omniauth (~> 1.1.4)
omniauth-github (~> 1.1.0)
omniauth-twitter (~> 0.0.16)
@ -357,10 +352,9 @@ DEPENDENCIES
sidekiq (~> 2.13)
simple_form (~> 3.0.0.rc)
simplecov (~> 0.7.1)
sinatra
sinatra (~> 1.4.3)
slim (~> 2.0.0)
thin (~> 1.5.0)
tsm!
uglifier (>= 2.1.2)
unicorn (~> 4.6.2)
zeus (= 0.13.4.pre2)

@ -1,67 +1,59 @@
require 'open3'
class Terminal
BINARY_PATH = "/home/kill/code/ascii.io-converter/terminal"
def initialize(width, height)
@screen = TSM::Screen.new(width, height)
@vte = TSM::Vte.new(@screen)
@process = Process.new("#{BINARY_PATH} #{width} #{height}")
end
def feed(data)
vte.input(data)
process.write("d\n#{data.bytesize}\n#{data}")
end
def snapshot
lines = []
screen.draw do |x, y, char, screen_attribute|
assign_cell(lines, x, y, char, screen_attribute)
end
process.write("p\n")
lines = Oj.load(process.read_line)
Snapshot.build(lines)
end
def cursor
Cursor.new(screen.cursor_x, screen.cursor_y, screen.cursor_visible?)
process.write("c\n")
c = Oj.load(process.read_line)
Cursor.new(c['x'], c['y'], c['visible'])
end
def release
screen.release
vte.release
process.stop
end
private
attr_reader :screen, :vte
attr_reader :process
def assign_cell(lines, x, y, char, screen_attribute)
line = lines[y] ||= []
line[x] = [sanitize_char(char), attributes_hash(screen_attribute)]
end
class Process
def sanitize_char(char)
char.
encode('UTF-16', :invalid => :replace, :undef => :replace,
:replace => "\001").
encode('UTF-8').gsub(/\001+/, '?').
first
end
def attributes_hash(screen_attribute)
attrs = {}
[:fg, :bg, :bold?, :underline?, :inverse?, :blink?].each do |name|
assign_attr(attrs, screen_attribute, name)
def initialize(command)
@stdin, @stdout, @thread = Open3.popen2(command)
end
attrs
end
def write(data)
raise "terminal died" unless @thread.alive?
@stdin.write(data)
end
def assign_attr(attrs, screen_attribute, name)
value = screen_attribute.public_send(name)
def read_line
raise "terminal died" unless @thread.alive?
@stdout.readline.strip
end
if value
key = name.to_s.sub('?', '').to_sym
attrs[key] = value
def stop
@stdin.close
end
end
end

@ -0,0 +1 @@
Oj.default_options = { mode: :strict }

@ -4,43 +4,23 @@ require 'spec_helper'
describe Terminal do
let(:terminal) { Terminal.new(20, 10) }
let(:tsm_screen) { double('tsm_screen', :draw => nil) }
let(:tsm_vte) { double('tsm_vte', :input => nil) }
let(:terminal) { Terminal.new(4, 3) }
let(:first_line_text) { subject.as_json.first.map(&:first).join.strip }
before do
allow(TSM::Screen).to receive(:new).with(20, 10) { tsm_screen }
allow(TSM::Vte).to receive(:new).with(tsm_screen) { tsm_vte }
data.each do |chunk|
terminal.feed(chunk)
end
end
describe '#feed' do
subject { terminal.feed('foo') }
it 'feeds the vte with the data' do
subject
expect(tsm_vte).to have_received(:input).with('foo')
end
after do
terminal.release
end
describe '#snapshot' do
subject { terminal.snapshot }
def make_attr(attrs = {})
TSM::ScreenAttribute.new.tap do |screen_attribute|
attrs.each { |name, value| screen_attribute[name] = value }
end
end
before do
allow(tsm_screen).to receive(:draw).
and_yield(0, 0, 'f', make_attr(fg: 1)).
and_yield(1, 0, 'o', make_attr(bg: 2)).
and_yield(0, 1, 'o', make_attr(bold?: true)).
and_yield(1, 1, 'ś', make_attr(fg: 2, bg: 3,
bold?: true, underline?: true,
inverse?: true, blink?: true))
end
let(:data) { ["f\e[31mo\e[42mo\n", "\rb\e[0ma\e[1;4;5;7ms"] }
it 'returns an instance of Snapshot' do
expect(subject).to be_kind_of(Snapshot)
@ -49,45 +29,120 @@ describe Terminal do
it "returns each screen cell with its character attributes" do
expect(subject.as_json).to eq([
[
['f', fg: 1],
['o', bg: 2]
['f', {}], ['o', fg: 1], ['o', fg: 1, bg: 2], [' ', {}],
],
[
['b', fg: 1, bg: 2], ['a', {}], ['s', bold: true, underline: true,
inverse: true, blink: true],
[' ', inverse: true] # <- cursor here
],
[
['o', bold: true],
['ś', fg: 2, bg: 3, bold: true, underline: true, inverse: true,
blink: true]
[' ', {}], [' ', {}], [' ', {}], [' ', {}]
]
])
end
describe 'utf-8 characters handling' do
let(:terminal) { Terminal.new(20, 1) }
context "when polish national characters given" do
let(:data) { ['żółć'] }
it 'returns proper utf-8 string' do
expect(first_line_text).to eq('żółć')
end
end
context "when chinese national characters given" do
let(:data) { ['雞機基積'] }
it 'returns proper utf-8 string' do
expect(first_line_text).to eq('雞機基積')
end
end
end
context "when invalid utf-8 character is yielded by tsm_screen" do
before do
allow(tsm_screen).to receive(:draw).
and_yield(0, 0, "\xc3\xff\xaa", make_attr(fg: 1))
let(:terminal) { Terminal.new(3, 1) }
let(:data) { ["A\xc3\xff\xaaZ"] }
it 'gets replaced with "<22>"' do
expect(first_line_text).to eq('A<>Z')
end
end
context "when double quote character ...." do
let(:terminal) { Terminal.new(6, 1) }
let(:data) { ['"a"b"'] }
it 'works' do
expect(first_line_text).to eq('"a"b"')
end
end
context "when backslash character..." do
let(:terminal) { Terminal.new(6, 1) }
let(:data) { ['a\\b'] }
it 'works' do
expect(first_line_text).to eq('a\\b')
end
end
it 'gets replaced with "?"' do
expect(subject.as_json).to eq([[['?', fg: 1]]])
describe 'with a 256-color mode foreground color' do
subject { terminal.snapshot.as_json.first.first.last[:fg] }
let(:data) { ["\x1b[38;5;#{color_code}mX"] }
(1..255).each do |n|
context "of value #{n}" do
let(:color_code) { n }
it { should eq(n) }
end
end
end
describe 'with a 256-color mode background color' do
subject { terminal.snapshot.as_json.first.first.last[:bg] }
let(:data) { ["\x1b[48;5;#{color_code}mX"] }
(1..255).each do |n|
context "of value #{n}" do
let(:color_code) { n }
it { should eq(n) }
end
end
end
end
describe '#cursor' do
let(:tsm_screen) { double('tsm_screen', :cursor_x => 3, :cursor_y => 5,
:cursor_visible? => false) }
subject { terminal.cursor }
let(:data) { ["foo\n\rba"] }
it 'gets its x position from the screen' do
expect(subject.x).to eq(3)
expect(subject.x).to eq(2)
end
it 'gets its y position from the screen' do
expect(subject.y).to eq(5)
expect(subject.y).to eq(1)
end
it 'gets its visibility from the screen' do
expect(subject.visible).to eq(false)
expect(subject.visible).to eq(true)
end
context "when cursor was hidden" do
before do
terminal.feed("\e[?25l")
end
it 'gets its visibility from the screen' do
expect(subject.visible).to eq(false)
end
end
end

Loading…
Cancel
Save