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 table = table
|
||||||
local math = math
|
local math = math
|
||||||
local ipairs = ipairs
|
local ipairs = ipairs
|
||||||
local pcall = pcall
|
|
||||||
local capi =
|
local capi =
|
||||||
{
|
{
|
||||||
selection = selection
|
selection = selection
|
||||||
|
@ -258,6 +257,11 @@ local function history_add(id, command)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
local function have_multibyte_char_at(text, position)
|
||||||
|
return text:sub(position, position):wlen() == -1
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
--- Draw the prompt text with a cursor.
|
--- Draw the prompt text with a cursor.
|
||||||
-- @tparam table args The table of arguments.
|
-- @tparam table args The table of arguments.
|
||||||
-- @field text The text.
|
-- @field text The text.
|
||||||
|
@ -285,10 +289,14 @@ local function prompt_text_with_cursor(args)
|
||||||
text_start = gstring.xml_escape(text)
|
text_start = gstring.xml_escape(text)
|
||||||
text_end = ""
|
text_end = ""
|
||||||
else
|
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 = " "
|
spacer = " "
|
||||||
text_start = gstring.xml_escape(text:sub(1, args.cursor_pos - 1))
|
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
|
end
|
||||||
|
|
||||||
local cursor_color = gcolor.ensure_pango_color(args.cursor_color)
|
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()
|
local function update()
|
||||||
textbox:set_font(font)
|
textbox:set_font(font)
|
||||||
textbox:set_markup(prompt_text_with_cursor{
|
textbox:set_markup(prompt_text_with_cursor{
|
||||||
text = command, text_color = inv_col, cursor_color = cur_col,
|
text = command, text_color = inv_col, cursor_color = cur_col,
|
||||||
cursor_pos = cur_pos, cursor_ul = cur_ul, selectall = selectall,
|
cursor_pos = cur_pos, cursor_ul = cur_ul, selectall = selectall,
|
||||||
prompt = prettyprompt, highlighter = highlighter })
|
prompt = prettyprompt, highlighter = highlighter })
|
||||||
end
|
end
|
||||||
|
|
||||||
grabber = keygrabber.run(
|
grabber = keygrabber.run(
|
||||||
|
@ -663,6 +671,9 @@ function prompt.run(args, textbox, exe_callback, completion_callback,
|
||||||
elseif key == "b" then
|
elseif key == "b" then
|
||||||
if cur_pos > 1 then
|
if cur_pos > 1 then
|
||||||
cur_pos = cur_pos - 1
|
cur_pos = cur_pos - 1
|
||||||
|
if have_multibyte_char_at(command, cur_pos) then
|
||||||
|
cur_pos = cur_pos - 1
|
||||||
|
end
|
||||||
end
|
end
|
||||||
elseif key == "d" then
|
elseif key == "d" then
|
||||||
if cur_pos <= #command then
|
if cur_pos <= #command then
|
||||||
|
@ -711,12 +722,20 @@ function prompt.run(args, textbox, exe_callback, completion_callback,
|
||||||
end
|
end
|
||||||
elseif key == "f" then
|
elseif key == "f" then
|
||||||
if cur_pos <= #command 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
|
end
|
||||||
elseif key == "h" then
|
elseif key == "h" then
|
||||||
if cur_pos > 1 then
|
if cur_pos > 1 then
|
||||||
command = command:sub(1, cur_pos - 2) .. command:sub(cur_pos)
|
local offset = 0
|
||||||
cur_pos = cur_pos - 1
|
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
|
end
|
||||||
elseif key == "k" then
|
elseif key == "k" then
|
||||||
command = command:sub(1, cur_pos - 1)
|
command = command:sub(1, cur_pos - 1)
|
||||||
|
@ -844,8 +863,12 @@ function prompt.run(args, textbox, exe_callback, completion_callback,
|
||||||
cur_pos = #command + 1
|
cur_pos = #command + 1
|
||||||
elseif key == "BackSpace" then
|
elseif key == "BackSpace" then
|
||||||
if cur_pos > 1 then
|
if cur_pos > 1 then
|
||||||
command = command:sub(1, cur_pos - 2) .. command:sub(cur_pos)
|
local offset = 0
|
||||||
cur_pos = cur_pos - 1
|
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
|
end
|
||||||
elseif key == "Delete" then
|
elseif key == "Delete" then
|
||||||
command = command:sub(1, cur_pos - 1) .. command:sub(cur_pos + 1)
|
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
|
selectall = nil
|
||||||
end
|
end
|
||||||
|
|
||||||
local success = pcall(update)
|
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
|
|
||||||
|
|
||||||
if changed_callback then
|
if changed_callback then
|
||||||
changed_callback(command)
|
changed_callback(command)
|
||||||
end
|
end
|
||||||
|
|
|
@ -41,9 +41,20 @@ describe('helper functions', function()
|
||||||
assert.are_equal('<span property1="foo">', get_tag(sample_markup))
|
assert.are_equal('<span property1="foo">', get_tag(sample_markup))
|
||||||
end)
|
end)
|
||||||
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)
|
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, 'press')
|
||||||
callback({}, char, 'release')
|
callback({}, char, 'release')
|
||||||
end
|
end
|
||||||
|
@ -62,7 +73,20 @@ insulate('main', function ()
|
||||||
}
|
}
|
||||||
-- luacheck: globals string
|
-- luacheck: globals string
|
||||||
function string.wlen(self)
|
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
|
end
|
||||||
local keygrabber = require("awful.keygrabber")
|
local keygrabber = require("awful.keygrabber")
|
||||||
package.loaded['awful.keygrabber'] = mock(keygrabber, true)
|
package.loaded['awful.keygrabber'] = mock(keygrabber, true)
|
||||||
|
@ -171,6 +195,40 @@ insulate('main', function ()
|
||||||
prompt_callback({}, 'Left', 'press')
|
prompt_callback({}, 'Left', 'press')
|
||||||
assert_prompt_text('comman', 'd', ' ')
|
assert_prompt_text('comman', 'd', ' ')
|
||||||
end)
|
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()
|
it('backspace', function()
|
||||||
prompt.run{
|
prompt.run{
|
||||||
textbox = atextbox,
|
textbox = atextbox,
|
||||||
|
@ -192,6 +250,70 @@ insulate('main', function ()
|
||||||
prompt_callback({}, 'BackSpace', 'press')
|
prompt_callback({}, 'BackSpace', 'press')
|
||||||
assert_prompt_text('o', 'm', 'an ')
|
assert_prompt_text('o', 'm', 'an ')
|
||||||
end)
|
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()
|
it('delete', function()
|
||||||
prompt.run{
|
prompt.run{
|
||||||
textbox = atextbox,
|
textbox = atextbox,
|
||||||
|
|
Loading…
Reference in New Issue