How to handle copy & paste in ncurses? - paste

It seems ncurses handles paste (from copy & paste) by inserting one character at a time from the text that was pasted, which can be slow if the handler for each character is slow.
I'd like to handle paste events myself, when a 'bracketed paste' sequence is detected, starting with ESC[200~ (see http://www.xfree86.org/current/ctlseqs.html#Bracketed%20Paste%20Mode).
How can I implement this in ncurses?

Here some code illustrating 'bracketed paste mode' (using Ruby curses).
There is only three key things:
0.) Make sure your terminal supports bracketed paste mode (such as for instance Windows 10 Terminal since about 1 month, see https://github.com/microsoft/terminal/releases/tag/v1.7.572.0)
1.) Switch on bracketed paste mode in the terminal by sending the ?2004h CSI.
print("\x1b[?2004h")
2.) When a paste occurs, recognize that you received \x1b[200~ to start parsing the pasted text and \x1b[201~ to recognize that the pasted text is finished.
# /usr/bin/ruby2.7
require "curses"
include Curses
def main_loop
init_screen
noecho # Disable Echoing of character presses
# Switch on braketed paste mode
print("\x1b[?2004h")
addstr("Please paste something into this terminal (make sure it supports braketed paste!) or press 'q' to quit.\n")
loop do
c = get_char2
case c
in 'q' # use q to quit
return
in csi: "200~" # Bracketed paste started
pasted = ""
loop do
d = get_char2
case d
in csi: "201~" # Bracketed paste ended
break
else
pasted += d
end
end
addstr("You pasted: #{pasted.inspect}\n")
else
addstr("You didn't paste something, you entered: #{c.inspect} #{c.class.name}\n")
end
end
ensure
close_screen
end
#
# For CSI, or "Control Sequence Introducer" commands,
# the ESC [ is followed by
# 1.) any number (including none) of "parameter bytes" in the range
# 0x30–0x3F (ASCII 0–9:;<=>?), then by
# 2.) any number of "intermediate bytes" in the range
# 0x20–0x2F (ASCII space and !"#$%&'()*+,-./), then finally by
# 3.) a single "final byte" in the range
# 0x40–0x7E (ASCII #A–Z[\]^_`a–z{|}~).
#
# From: https://handwiki.org/wiki/ANSI_escape_code
def get_csi
result = ""
loop do
c = get_char
result += c
if c.ord >= 0x40 && c.ord <= 0x7E
return result
end
end
end
# Just like get_char, but will read \x1b[<csi> and return it as a hash { csi: ... }, everything else is just returned as-is
def get_char2
c = get_char
case c
when "\e" # ESC
case get_char
when '['
return { csi: get_csi }
else
raise "¯\_(ツ)_/¯"
end
else
return c
end
end
main_loop()

I figured it works after enabling bracketed paste mode, but this needs to be done with raw terminal sequences since ncurses doesn't provide support for that by itself.

Related

How do I write test for homebrew formula?

I made an homebrew formula which is now accessible only on my local taps. I want to send pull request to homebrew-core. Now I am required to write test for my formula. How to write that based on example below?
test do
output = shell_output("#{bin}/balance 2>&1", 64)
assert_match "this is balance #{version}", output
end
My formula
#!/usr/bin/env ruby
def match
files = Dir.glob("*")
if ARGV.length == 0
puts "usage: match <keyword>"
return
end
files.each { |x|
if File.directory?(x)
puts "#{x}_ found directory"
puts "***"
next
end
found = false
File.open(x).each_line.with_index do |line, index|
if line.include? ARGV[0]
puts "#{x}_ #{index+1} #{line}"
found = true
end
end
puts "***" if found
}
end
match
Brew formula
class Match < Formula
desc "Browse all files inside any directory for a keyword"
homepage "https://github.com/aatalyk/homebrew-match"
url ""
sha256 ""
def install
bin.install "match"
end
end
Tests for shell commands in Homebrew formulae usually usually follow this scenario:
create a context usable by the command : a git repository, a directories hierarchy, a sample file, etc.
run the command
assert the result is correct
In your case since match is a grep -R -like you could create a bunch of files with some content, then run match <something> and ensure it finds the correct files.
You can use any Ruby code in your tests as well as Homebrew utilities such as shell_output("...command...") to get the output of a command.
Here is an example of test you could write:
class Match < Formula
# ...
test do
# Create two dummy files
(testpath/"file1").write "foo\nbar\nqux"
(testpath/"file2").write "bar\nabc"
# Ensure `match bar` finds both files
assert_match "file1_ 2 bar\n***\nfile2_ 1 bar",
shell_output("#{bin}/match bar")
# Ensure `match abc` finds the second file
assert_match "file2_ 2 abc", shell_output("#{bin}/match abc")
# Ensure `match idontmatchanything` doesn’t match any of the files
assert_not_match(/file[12]/,
shell_output("#{bin}/match idontmatchanything"))
end
end
assert_match "something", shell_output("command") ensures that (1) command runs successfully and (2) its output contains "something".

yowsup2 receives incomplete messages and strange characters

I am try Yowsup2 with demo EchoClient:
yowsup-cli demos -c config.example -e
I receive messages, but they are incomplete and contain strange characters at the end of each text.
For example: I send "What is your name?" from my mobile phone to Yowsup2 number, and Yowsup2 receive (and print in the terminal):
Echoing hat is your name?������������������������������������������������������������������������������������������������������������������������������������������������������������������������
Any idea?
I was working on a project using this library and i got stuck at this issue, i was waiting for someone to fix it and since no has i tried and got mine to work, this is what i did
clone the repository from github
tested to work on python3.5
in yowsup/layers/axolotl/layer.py
replace line 192 which is
191 padded.extend(self.encodeInt7bit(len(plaintext)))
192 padded.extend(plaintext) # this is the line. replace it
193 padded.append(ord("\x01"))
with this
padded.extend(plaintext.encode() if isinstance(plaintext,str) else plaintext)
add #jlguardi fix in this thread but i had to modify it a bit to work for me
def decodeInt7bit(self, string):
idx = 0
while string[idx] >= 128:
idx += 1
consumedBytes = idx + 1
value = 0
while idx >= 0:
value <<= 7
value += string[idx] % 128
idx -= 1
return value, consumedBytes
def unpadV2Plaintext(self, v2plaintext):
print(v2plaintext)
v2plaintext=bytearray(v2plaintext,'utf8') if isinstance(v2plaintext,str) else v2plaintext
end = (-(v2plaintext[-1])) # length of the left padding
length,consumed = self.decodeInt7bit(v2plaintext[1:])
return v2plaintext[1+consumed:end]
Looks clean on the client side
The garbled text though still appears on the server
not install setup.py install
Hope it works for you

file seek in wlst / Jython 2.2.1 fails for lines longer than 8091 characters

For a CSV file generated in WLST / Jython 2.2.1 i want to update the header, the first line of the output file, when new metrics have been detected. This works fine by using seek to go to the first line and overwriting the line. But it fails when the number of characters of the first line exceeds 8091 characters.
I made simplified script which does reproduce the issue i am facing here.
#!/usr/bin/python
#
import sys
global maxheaderlength
global initheader
maxheaderlength=8092
logFilename = "test.csv"
# Create (overwrite existing) file
logfileAppender = open(logFilename,"w",0)
logfileAppender.write("." * maxheaderlength)
logfileAppender.write("\n")
logfileAppender.close()
# Append some lines
logfileAppender = open(logFilename,"a",0)
logfileAppender.write("2nd line\n")
logfileAppender.write("3rd line\n")
logfileAppender.write("4th line\n")
logfileAppender.write("5th line\n")
logfileAppender.close()
# Seek back to beginning of file and add data
logfileAppender = open(logFilename,"r+",0)
logfileAppender.seek(0) ;
header = "New Header Line" + "." * maxheaderlength
header = header[:maxheaderlength]
logfileAppender.write(header)
logfileAppender.close()
When maxheaderlength is 8091 or lower i do get the results as expected. The file test.csv starts with “New Header Line" followed by 8076 dots and
followed by the lines
2nd line
3rd line
4th line
5th line
When maxheaderlength is 8092> the test.csv results as a file starting with 8092 dots followed by "New Header Line" and then followed by 8077 dots. The 2nd ... 5th line are now show, probably overwritten by the dots.
Any idea how to work around or fix this ?
I too was able to reproduce this extremely odd behaviour and indeed it works correctly in Jython 2.5.3 so I think we can safely say this is a bug in 2.2.1 (which unfortunately you're stuck with for WLST).
My usual recourse in these circumstances is to fall back to using native Java methods. Changing the last block of code as follows seems to work as expected :-
# Seek back to beginning of file and add data
from java.io import RandomAccessFile
logfileAppender = RandomAccessFile(logFilename, "rw")
logfileAppender.seek(0) ;
header = "New Header Line" + "." * maxheaderlength
header = header[:maxheaderlength]
logfileAppender.writeBytes(header)
logfileAppender.close()

Why doesn't io:write() write to the output file?

I'm writing a short script in Lua to replicate Search/Replace functionality. The goal is to enter a search term and a replacement term, and it will comb through all the files of a given extension (not input-determined yet) and replace the Search term with the Replacement term.
Everything seems to do what it's supposed to, except the files are not actually written to. My Lua interpreter (compiled by myself in Pelles-C) does not throw any errors or exit abnormally; the script completes as if it worked.
At first I didn't have i:flush(), but I added it after reading that it is supposed to save any written data to the file (see LUA docs). It didn't change anything, and files are still not written to.
I think it might have something to do with how I'm opening the file to edit it, since the "w" option works (but overwrites everything in my test files).
Source:
io.write("Enter your search term:")
term = io.read()
io.write("Enter your replace term:")
replacement = io.read()
io.stdin:read()
t = {}
for z in io.popen('dir /b /a-d'):lines() do
if string.match(string.lower(z), "%.txt$") then
print(z)
table.insert(t, z)
end
end
print("Second loop")
for _, w in pairs(t) do
print(w)
i = io.open(w, "r+")
print(i)
--i:seek("set", 6)
--i:write("cheese")
--i:flush()
for y in i:lines() do
print(y)
p, count = string.gsub(y, term, replacement, 1)
print(p)
i:write(p)
i:flush()
io.stdin:read()
end
i:close()
end
This is the output I get (which is what I want to happen), but in reality isn't being written to the file:
There was one time where it wrote output to a file, but it only output to one file and after that write my script crashed with the message: No error. The line number was at the for y in i:lines() do line, but I don't know why it broke there. I've noticed file:lines() will break if the file itself has nothing in it and give an odd/gibberish error, but there are things in my text files.
Edit1
I tried do this in my for loop:
for y in i:lines() do
print(y)
p, count = string.gsub(y, term, replacement, 1)
print(p)
i:write(p)
i:seek("set", 3) --New
i:write("TESTESTTEST") --New
i:flush()
io.stdin:read()
end
in order to see if I could force it to write regular text. It does but then it crashes with No error and still doesn't write the replacement string (just TESTESTTEST). I don't know what the problem could be.
I guess, one can't write to file while traversing its lines
for y in i:lines() do
i:write(p)
i:flush()
end

twisted server, nc client

Ill demonstrate the problem I am facing with a small example.
class TestProtocol(basic.LineReceiver):
def lineReceived(self, line):
print line
Everything works fine as long as I use the telnet client to connect to the server. However, the line is not received connect and send the data using netcat. I have a feeling that this has something to do with the default delimiter being "\r\n" in twisted.
How could I make a server such that both the clients(telnet and nc) would behave in a similar manner when connecting to the client?
LineReceiver only supports one delimiter. You can specify it, but there can only be one at a time. In general, if you want to support multiple delimiters, you'll need to implement a new protocol that supports that. You could take a look at the implementation of LineReceiver for some ideas about how a line-based protocol is implemented.
netcat sends whatever you type, so the delimiter is often \n (but it may vary from platform to platform and terminal emulator to terminal emulator). For the special case of \n, which is a substring of the default LineReceiver delimiter \r\n, there's another trick you can use. Set the TestProtocol.delimiter to "\n" and then strip the "\r" off the end of the line passed to lineReceived if there is one.
class TestProtocol(basic.LineReceiver):
delimiter = "\n"
def lineReceived(self, line):
print line.rstrip("\r")
Another workaround is to use nc with the -C switch.
From the manual:
-C Send CRLF as line-ending
or as #CraigMcQueen suggested:
socket with -c switch (Ubuntu package).
Twisted's LineReceiver and LineOnlyReceiver only support one line ending delimiter.
Here is code for UniversalLineReceiver and UniversalLineOnlyReceiver, which override the dataReceived() method with support for universal line endings (any combination of CR+LF, CR or LF). The line breaks are detected with the regular expression object delimiter_re.
Note, they override functions with more code in them than I'd like, so there's a chance that they may break if the underlying Twisted implementation changes. I've tested they work with Twisted 13.2.0. The essential change is the use of delimiter_re.split() from the re module.
# Standard Python packages
import re
# Twisted framework
from twisted.protocols.basic import LineReceiver, LineOnlyReceiver
class UniversalLineReceiver(LineReceiver):
delimiter_re = re.compile(br"\r\n|\r|\n")
def dataReceived(self, data):
"""
Protocol.dataReceived.
Translates bytes into lines, and calls lineReceived (or
rawDataReceived, depending on mode.)
"""
if self._busyReceiving:
self._buffer += data
return
try:
self._busyReceiving = True
self._buffer += data
while self._buffer and not self.paused:
if self.line_mode:
try:
line, remainder = self.delimiter_re.split(self._buffer, 1)
except ValueError:
if len(self._buffer) > self.MAX_LENGTH:
line, self._buffer = self._buffer, b''
return self.lineLengthExceeded(line)
return
else:
lineLength = len(line)
if lineLength > self.MAX_LENGTH:
exceeded = self._buffer
self._buffer = b''
return self.lineLengthExceeded(exceeded)
self._buffer = remainder
why = self.lineReceived(line)
if (why or self.transport and
self.transport.disconnecting):
return why
else:
data = self._buffer
self._buffer = b''
why = self.rawDataReceived(data)
if why:
return why
finally:
self._busyReceiving = False
class UniversalLineOnlyReceiver(LineOnlyReceiver):
delimiter_re = re.compile(br"\r\n|\r|\n")
def dataReceived(self, data):
"""
Translates bytes into lines, and calls lineReceived.
"""
lines = self.delimiter_re.split(self._buffer+data)
self._buffer = lines.pop(-1)
for line in lines:
if self.transport.disconnecting:
# this is necessary because the transport may be told to lose
# the connection by a line within a larger packet, and it is
# important to disregard all the lines in that packet following
# the one that told it to close.
return
if len(line) > self.MAX_LENGTH:
return self.lineLengthExceeded(line)
else:
self.lineReceived(line)
if len(self._buffer) > self.MAX_LENGTH:
return self.lineLengthExceeded(self._buffer)