Merge pull request #3362 from actionless/prompt-multibyte-hack
Fix the hack for multibyte characters in prompt (fixes #3308)
This commit is contained in:
commit
832483dd60
|
@ -114,7 +114,6 @@ local io = io
|
|||
local table = table
|
||||
local math = math
|
||||
local ipairs = ipairs
|
||||
local pcall = pcall
|
||||
local capi =
|
||||
{
|
||||
selection = selection
|
||||
|
@ -258,6 +257,11 @@ local function history_add(id, command)
|
|||
end
|
||||
|
||||
|
||||
local function have_multibyte_char_at(text, position)
|
||||
return text:sub(position, position):wlen() == -1
|
||||
end
|
||||
|
||||
|
||||
--- Draw the prompt text with a cursor.
|
||||
-- @tparam table args The table of arguments.
|
||||
-- @field text The text.
|
||||
|
@ -285,10 +289,14 @@ local function prompt_text_with_cursor(args)
|
|||
text_start = gstring.xml_escape(text)
|
||||
text_end = ""
|
||||
else
|
||||
char = gstring.xml_escape(text:sub(args.cursor_pos, args.cursor_pos))
|
||||
local offset = 0
|
||||
if have_multibyte_char_at(text, args.cursor_pos) then
|
||||
offset = 1
|
||||
end
|
||||
char = gstring.xml_escape(text:sub(args.cursor_pos, args.cursor_pos + offset))
|
||||
spacer = " "
|
||||
text_start = gstring.xml_escape(text:sub(1, args.cursor_pos - 1))
|
||||
text_end = gstring.xml_escape(text:sub(args.cursor_pos + 1))
|
||||
text_end = gstring.xml_escape(text:sub(args.cursor_pos + 1 + offset))
|
||||
end
|
||||
|
||||
local cursor_color = gcolor.ensure_pango_color(args.cursor_color)
|
||||
|
@ -544,9 +552,9 @@ function prompt.run(args, textbox, exe_callback, completion_callback,
|
|||
local function update()
|
||||
textbox:set_font(font)
|
||||
textbox:set_markup(prompt_text_with_cursor{
|
||||
text = command, text_color = inv_col, cursor_color = cur_col,
|
||||
cursor_pos = cur_pos, cursor_ul = cur_ul, selectall = selectall,
|
||||
prompt = prettyprompt, highlighter = highlighter })
|
||||
text = command, text_color = inv_col, cursor_color = cur_col,
|
||||
cursor_pos = cur_pos, cursor_ul = cur_ul, selectall = selectall,
|
||||
prompt = prettyprompt, highlighter = highlighter })
|
||||
end
|
||||
|
||||
grabber = keygrabber.run(
|
||||
|
@ -663,6 +671,9 @@ function prompt.run(args, textbox, exe_callback, completion_callback,
|
|||
elseif key == "b" then
|
||||
if cur_pos > 1 then
|
||||
cur_pos = cur_pos - 1
|
||||
if have_multibyte_char_at(command, cur_pos) then
|
||||
cur_pos = cur_pos - 1
|
||||
end
|
||||
end
|
||||
elseif key == "d" then
|
||||
if cur_pos <= #command then
|
||||
|
@ -711,12 +722,20 @@ function prompt.run(args, textbox, exe_callback, completion_callback,
|
|||
end
|
||||
elseif key == "f" then
|
||||
if cur_pos <= #command then
|
||||
cur_pos = cur_pos + 1
|
||||
if have_multibyte_char_at(command, cur_pos) then
|
||||
cur_pos = cur_pos + 2
|
||||
else
|
||||
cur_pos = cur_pos + 1
|
||||
end
|
||||
end
|
||||
elseif key == "h" then
|
||||
if cur_pos > 1 then
|
||||
command = command:sub(1, cur_pos - 2) .. command:sub(cur_pos)
|
||||
cur_pos = cur_pos - 1
|
||||
local offset = 0
|
||||
if have_multibyte_char_at(command, cur_pos - 1) then
|
||||
offset = 1
|
||||
end
|
||||
command = command:sub(1, cur_pos - 2 - offset) .. command:sub(cur_pos)
|
||||
cur_pos = cur_pos - 1 - offset
|
||||
end
|
||||
elseif key == "k" then
|
||||
command = command:sub(1, cur_pos - 1)
|
||||
|
@ -844,8 +863,12 @@ function prompt.run(args, textbox, exe_callback, completion_callback,
|
|||
cur_pos = #command + 1
|
||||
elseif key == "BackSpace" then
|
||||
if cur_pos > 1 then
|
||||
command = command:sub(1, cur_pos - 2) .. command:sub(cur_pos)
|
||||
cur_pos = cur_pos - 1
|
||||
local offset = 0
|
||||
if have_multibyte_char_at(command, cur_pos - 1) then
|
||||
offset = 1
|
||||
end
|
||||
command = command:sub(1, cur_pos - 2 - offset) .. command:sub(cur_pos)
|
||||
cur_pos = cur_pos - 1 - offset
|
||||
end
|
||||
elseif key == "Delete" then
|
||||
command = command:sub(1, cur_pos - 1) .. command:sub(cur_pos + 1)
|
||||
|
@ -889,22 +912,7 @@ function prompt.run(args, textbox, exe_callback, completion_callback,
|
|||
selectall = nil
|
||||
end
|
||||
|
||||
local success = pcall(update)
|
||||
while not success do
|
||||
-- TODO UGLY HACK TODO
|
||||
-- Setting the text failed. Most likely reason is that the user
|
||||
-- entered a multibyte character and pressed backspace which only
|
||||
-- removed the last byte. Let's remove another byte.
|
||||
if cur_pos <= 1 then
|
||||
-- No text left?!
|
||||
break
|
||||
end
|
||||
|
||||
command = command:sub(1, cur_pos - 2) .. command:sub(cur_pos)
|
||||
cur_pos = cur_pos - 1
|
||||
success = pcall(update)
|
||||
end
|
||||
|
||||
update()
|
||||
if changed_callback then
|
||||
changed_callback(command)
|
||||
end
|
||||
|
|
|
@ -41,9 +41,20 @@ describe('helper functions', function()
|
|||
assert.are_equal('<span property1="foo">', get_tag(sample_markup))
|
||||
end)
|
||||
end)
|
||||
describe('helper functions multibyte', function()
|
||||
local sample_markup = 'Сперва<span property1="foo">Высокоосвещенный</span>Конечный'
|
||||
it('main', function()
|
||||
assert.are_equal('Сперва', get_first_part(sample_markup))
|
||||
assert.are_equal('Высокоосвещенный', get_highlighted_part(sample_markup))
|
||||
assert.are_equal('Конечный', get_last_part(sample_markup))
|
||||
assert.are_equal('СперваВысокоосвещенныйКонечный', get_prompt_text(sample_markup))
|
||||
assert.are_equal('<span property1="foo">', get_tag(sample_markup))
|
||||
end)
|
||||
end)
|
||||
|
||||
|
||||
local function enter_text(callback, text)
|
||||
for char in string.gmatch(text, '.') do
|
||||
for char in string.gmatch(text, '([%z\1-\127\194-\244][\128-\191]*)') do
|
||||
callback({}, char, 'press')
|
||||
callback({}, char, 'release')
|
||||
end
|
||||
|
@ -62,7 +73,20 @@ insulate('main', function ()
|
|||
}
|
||||
-- luacheck: globals string
|
||||
function string.wlen(self)
|
||||
return #self
|
||||
local _, string_length = string.gsub(self, "[^\128-\193]", "")
|
||||
local byte = string.byte(self)
|
||||
if (
|
||||
#self > 0 and string_length == 0
|
||||
) or (
|
||||
byte and
|
||||
(
|
||||
(byte >= 194 and byte <= 244)
|
||||
) and
|
||||
string_length == 1 and #self == 1
|
||||
) then
|
||||
return -1
|
||||
end
|
||||
return string_length
|
||||
end
|
||||
local keygrabber = require("awful.keygrabber")
|
||||
package.loaded['awful.keygrabber'] = mock(keygrabber, true)
|
||||
|
@ -171,6 +195,40 @@ insulate('main', function ()
|
|||
prompt_callback({}, 'Left', 'press')
|
||||
assert_prompt_text('comman', 'd', ' ')
|
||||
end)
|
||||
it('moving cursor readline', function()
|
||||
prompt.run{
|
||||
textbox = atextbox,
|
||||
}
|
||||
enter_text(prompt_callback, 'command')
|
||||
prompt_callback({'Control'}, 'a', 'press')
|
||||
assert_prompt_text('', 'c', 'ommand ')
|
||||
|
||||
prompt_callback({'Control'}, 'f', 'press')
|
||||
assert_prompt_text('c', 'o', 'mmand ')
|
||||
|
||||
prompt_callback({'Control'}, 'e', 'press')
|
||||
assert_prompt_text('command', ' ', '')
|
||||
|
||||
prompt_callback({'Control'}, 'b', 'press')
|
||||
assert_prompt_text('comman', 'd', ' ')
|
||||
end)
|
||||
it('moving cursor readline multibyte', function()
|
||||
prompt.run{
|
||||
textbox = atextbox,
|
||||
}
|
||||
enter_text(prompt_callback, 'кокаинум')
|
||||
prompt_callback({'Control'}, 'a', 'press')
|
||||
assert_prompt_text('', 'к', 'окаинум ')
|
||||
|
||||
prompt_callback({'Control'}, 'f', 'press')
|
||||
assert_prompt_text('к', 'о', 'каинум ')
|
||||
|
||||
prompt_callback({'Control'}, 'e', 'press')
|
||||
assert_prompt_text('кокаинум', ' ', '')
|
||||
|
||||
prompt_callback({'Control'}, 'b', 'press')
|
||||
assert_prompt_text('кокаину', 'м', ' ')
|
||||
end)
|
||||
it('backspace', function()
|
||||
prompt.run{
|
||||
textbox = atextbox,
|
||||
|
@ -192,6 +250,70 @@ insulate('main', function ()
|
|||
prompt_callback({}, 'BackSpace', 'press')
|
||||
assert_prompt_text('o', 'm', 'an ')
|
||||
end)
|
||||
it('backspace multibyte', function()
|
||||
prompt.run{
|
||||
textbox = atextbox,
|
||||
}
|
||||
enter_text(prompt_callback, 'кокаинум')
|
||||
prompt_callback({}, 'BackSpace', 'press')
|
||||
assert_prompt_text('кокаину', ' ', '')
|
||||
|
||||
prompt_callback({}, 'Home', 'press')
|
||||
prompt_callback({}, 'BackSpace', 'press')
|
||||
assert_prompt_text('', 'к', 'окаину ')
|
||||
|
||||
--@TODO: Left/Right not yet implemented for multibyte chars
|
||||
--prompt_callback({}, 'Right', 'press')
|
||||
--prompt_callback({}, 'BackSpace', 'press')
|
||||
--assert_prompt_text('', 'о', 'каину ')
|
||||
|
||||
--prompt_callback({}, 'Right', 'press')
|
||||
--prompt_callback({}, 'Right', 'press')
|
||||
--prompt_callback({}, 'BackSpace', 'press')
|
||||
--assert_prompt_text('о', 'а', 'ину ')
|
||||
end)
|
||||
it('backspace readline', function()
|
||||
prompt.run{
|
||||
textbox = atextbox,
|
||||
}
|
||||
enter_text(prompt_callback, 'command')
|
||||
prompt_callback({'Control'}, 'h', 'press')
|
||||
assert_prompt_text('comman', ' ', '')
|
||||
|
||||
prompt_callback({'Control'}, 'a', 'press')
|
||||
prompt_callback({'Control'}, 'h', 'press')
|
||||
assert_prompt_text('', 'c', 'omman ')
|
||||
|
||||
prompt_callback({'Control'}, 'f', 'press')
|
||||
prompt_callback({'Control'}, 'h', 'press')
|
||||
assert_prompt_text('', 'o', 'mman ')
|
||||
|
||||
prompt_callback({'Control'}, 'f', 'press')
|
||||
prompt_callback({'Control'}, 'f', 'press')
|
||||
prompt_callback({'Control'}, 'h', 'press')
|
||||
assert_prompt_text('o', 'm', 'an ')
|
||||
end)
|
||||
it('backspace readline multibyte', function()
|
||||
prompt.run{
|
||||
textbox = atextbox,
|
||||
}
|
||||
enter_text(prompt_callback, 'кокаинум')
|
||||
prompt_callback({'Control'}, 'h', 'press')
|
||||
assert_prompt_text('кокаину', ' ', '')
|
||||
|
||||
prompt_callback({'Control'}, 'a', 'press')
|
||||
prompt_callback({'Control'}, 'h', 'press')
|
||||
assert_prompt_text('', 'к', 'окаину ')
|
||||
|
||||
prompt_callback({'Control'}, 'f', 'press')
|
||||
prompt_callback({'Control'}, 'h', 'press')
|
||||
assert_prompt_text('', 'о', 'каину ')
|
||||
|
||||
prompt_callback({'Control'}, 'f', 'press')
|
||||
prompt_callback({'Control'}, 'f', 'press')
|
||||
prompt_callback({'Control'}, 'h', 'press')
|
||||
assert_prompt_text('о', 'а', 'ину ')
|
||||
end)
|
||||
it('delete', function()
|
||||
prompt.run{
|
||||
textbox = atextbox,
|
||||
|
|
Loading…
Reference in New Issue