From c9ef150605082b28288f3d781eb7097779620216 Mon Sep 17 00:00:00 2001 From: "kepler155c@gmail.com" Date: Fri, 29 Mar 2019 09:56:56 -0400 Subject: [PATCH] better text entry --- sys/apis/entry.lua | 477 +++++++++++++++++---------- sys/apis/input.lua | 6 + sys/apis/ui.lua | 11 +- sys/apis/ui/components/TextEntry.lua | 144 +++----- sys/apps/Lua.lua | 11 +- sys/apps/shell.lua | 49 ++- sys/apps/system/launcher.lua | 3 + 7 files changed, 405 insertions(+), 296 deletions(-) diff --git a/sys/apis/entry.lua b/sys/apis/entry.lua index 7379a09..e19f55c 100644 --- a/sys/apis/entry.lua +++ b/sys/apis/entry.lua @@ -8,246 +8,379 @@ function Entry:init(args) self.pos = 0 self.scroll = 0 self.value = '' - self.width = args.width - self.limit = 1024 + self.width = args.width or 256 + self.limit = args.limit or 1024 + self.mark = { } + self.offset = args.offset or 1 end function Entry:reset() self.pos = 0 self.scroll = 0 self.value = '' + self.mark = { } end -local function nextWord(line, cx) - local result = { line:find("(%w+)", cx) } - if #result > 1 and result[2] > cx then - return result[2] + 1 - elseif #result > 0 and result[1] == cx then - result = { line:find("(%w+)", result[2] + 1) } - if #result > 0 then - return result[1] - end - end +function Entry:nextWord() + return select(2, self.value:find("[%s%p]?%w[%s%p]", self.pos + 1)) or #self.value end -local function prevWord(line, cx) - local nOffset = 1 - while nOffset <= #line do - local nNext = line:find("%W%w", nOffset) - if nNext and nNext < cx then - nOffset = nNext + 1 - else - break - end - end - return nOffset - 1 < cx and nOffset - 1 +function Entry:prevWord() + local x = #self.value - (self.pos - 1) + local _, n = self.value:reverse():find("[%s%p]?%w[%s%p]", x) + return n and #self.value - n + 1 or 0 end function Entry:updateScroll() + local ps = self.scroll + if self.pos > #self.value then + self.pos = #self.value + self.scroll = 0 -- ?? + end if self.pos - self.scroll > self.width then - self.scroll = self.pos - (self.width) + self.scroll = self.pos - self.width elseif self.pos < self.scroll then self.scroll = self.pos end + if ps ~= self.scroll then + self.textChanged = true + end end -local function moveLeft(entry) - if entry.pos > 0 then - entry.pos = math.max(entry.pos - 1, 0) +function Entry:copyText(cx, ex) + return self.value:sub(cx + 1, ex) +end + +function Entry:insertText(x, text) + if #self.value + #text > self.limit then + text = text:sub(1, self.limit-#self.value) + end + self.value = self.value:sub(1, x) .. text .. self.value:sub(x + 1) + self.pos = self.pos + #text +end + +function Entry:deleteText(sx, ex) + local front = self.value:sub(1, sx) + local back = self.value:sub(ex + 1, #self.value) + self.value = front .. back + self.pos = sx +end + +function Entry:moveLeft() + if self.pos > 0 then + self.pos = self.pos - 1 return true end end -local function moveRight(entry) - local input = tostring(entry.value) - if entry.pos < #input then - entry.pos = math.min(entry.pos + 1, #input) +function Entry:moveRight() + if self.pos < #self.value then + self.pos = self.pos + 1 return true end end -local function moveStart(entry) - if entry.pos ~= 0 then - entry.pos = 0 +function Entry:moveHome() + if self.pos ~= 0 then + self.pos = 0 return true end end -local function moveEnd(entry) - if entry.pos ~= #tostring(entry.value) then - entry.pos = #tostring(entry.value) +function Entry:moveEnd() + if self.pos ~= #self.value then + self.pos = #self.value return true end end -local function backspace(entry) - if entry.pos > 0 then - local input = tostring(entry.value) - entry.value = input:sub(1, entry.pos - 1) .. input:sub(entry.pos + 1) - entry.pos = entry.pos - 1 +function Entry:moveTo(ie) + self.pos = math.max(0, math.min(ie.x + self.scroll - self.offset, #self.value)) +end + +function Entry:backspace() + if self.mark.active then + self:delete() + elseif self:moveLeft() then + self:delete() + end +end + +function Entry:moveWordRight() + if self.pos < #self.value then + self.pos = self:nextWord(self.value, self.pos + 1) return true end end -local function moveWordRight(entry) - local nx = nextWord(entry.value, entry.pos + 1) - if nx then - entry.pos = math.min(nx - 1, #entry.value) - elseif entry.pos < #entry.value then - entry.pos = #entry.value +function Entry:moveWordLeft() + if self.pos > 0 then + self.pos = self:prevWord(self.value, self.pos - 1) or 0 + return true end - return true end -local function moveWordLeft(entry) - if entry.pos ~= 0 then - local lx = 1 - while true do - local nx = nextWord(entry.value, lx) - if not nx or nx >= entry.pos then - break - end - lx = nx - end - if not lx then - entry.pos = 0 +function Entry:delete() + if self.mark.active then + self:deleteText(self.mark.x, self.mark.ex) + elseif self.pos < #self.value then + self:deleteText(self.pos, self.pos + 1) + end +end + +function Entry:cutFromStart() + if self.pos > 0 then + local text = self:copyText(1, self.pos) + self:deleteText(1, self.pos) + os.queueEvent('clipboard_copy', text) + end +end + +function Entry:cutToEnd() + if self.pos < #self.value then + local text = self:copyText(self.pos, #self.value) + self:deleteText(self.pos, #self.value) + os.queueEvent('clipboard_copy', text) + end +end + +function Entry:cutNextWord() + if self.pos < #self.value then + local ex = self:nextWord(self.value, self.pos) + local text = self:copyText(self.pos, ex) + self:deleteText(self.pos, ex) + os.queueEvent('clipboard_copy', text) + end +end + +function Entry:cutPrevWord() + if self.pos > 0 then + local sx = self:prevWord(self.value, self.pos) + local text = self:copyText(sx, self.pos) + self:deleteText(sx, self.pos) + os.queueEvent('clipboard_copy', text) + end +end + +function Entry:insertChar(ie) + if self.mark.active then + self:delete() + end + self:insertText(self.pos, ie.ch) +end + +function Entry:copy() + if #self.value > 0 then + self.mark.continue = true + if self.mark.active then + self:copyMarked() else - entry.pos = lx - 1 - end - return true - end -end - -local function delete(entry) - local input = tostring(entry.value) - if entry.pos < #input then - entry.value = input:sub(1, entry.pos) .. input:sub(entry.pos + 2) - entry.update = true - return true - end -end - --- credit for cut functions to: https://github.com/SquidDev-CC/mbs/blob/master/lib/readline.lua -local function cutFromStart(entry) - if entry.pos > 0 then - local input = tostring(entry.value) - os.queueEvent('clipboard_copy', input:sub(1, entry.pos)) - entry.value = input:sub(entry.pos + 1) - entry.pos = 0 - return true - end -end - -local function cutToEnd(entry) - local input = tostring(entry.value) - if entry.pos < #input then - os.queueEvent('clipboard_copy', input:sub(entry.pos + 1)) - entry.value = input:sub(1, entry.pos) - return true - end -end - -local function cutNextWord(entry) - local input = tostring(entry.value) - if entry.pos < #input then - local ex = nextWord(entry.value, entry.pos) - if ex then - os.queueEvent('clipboard_copy', input:sub(entry.pos + 1, ex)) - entry.value = input:sub(1, entry.pos) .. input:sub(ex + 1) - return true + os.queueEvent('clipboard_copy', self.value) end end end -local function cutPrevWord(entry) - if entry.pos > 0 then - local sx = prevWord(entry.value, entry.pos) - if sx then - local input = tostring(entry.value) - os.queueEvent('clipboard_copy', input:sub(sx + 1, entry.pos)) - entry.value = input:sub(1, sx) .. input:sub(entry.pos + 1) - entry.pos = sx - return true +function Entry:cut() + if self.mark.active then + self:copyMarked() + self:delete() + end +end + +function Entry:copyMarked() + local text = self:copyText(self.mark.x, self.mark.ex) + os.queueEvent('clipboard_copy', text) +end + +function Entry:paste(ie) + if #ie.text > 0 then + if self.mark.active then + self:delete() end + self:insertText(self.pos, ie.text) end end -local function insertChar(entry, ie) - local input = tostring(entry.value) - if #input < entry.limit then - entry.value = input:sub(1, entry.pos) .. ie.ch .. input:sub(entry.pos + 1) - entry.pos = entry.pos + 1 - entry.update = true - return true +function Entry:clearLine() + if #self.value > 0 then + self:reset() end end -local function copy(entry) - os.queueEvent('clipboard_copy', entry.value) -end - -local function paste(entry, ie) - local input = tostring(entry.value) - if #input + #ie.text > entry.limit then - ie.text = ie.text:sub(1, entry.limit-#input) +function Entry:markBegin() + if not self.mark.active then + self.mark.active = true + self.mark.anchor = { x = self.pos } end - entry.value = input:sub(1, entry.pos) .. ie.text .. input:sub(entry.pos + 1) - entry.pos = entry.pos + #ie.text - return true end -local function moveCursor(entry, ie) - -- need starting x passed in instead of hardcoding 3 - entry.pos = math.max(0, math.min(ie.x - 3 + entry.scroll, #entry.value)) - return true -end - -local function clearLine(entry) - local input = tostring(entry.value) - if #input > 0 then - entry:reset() - return true +function Entry:markFinish() + if self.pos == self.mark.anchor.x then + self.mark.active = false + else + self.mark.x = math.min(self.mark.anchor.x, self.pos) + self.mark.ex = math.max(self.mark.anchor.x, self.pos) end + self.textChanged = true + self.mark.continue = self.mark.active +end + +function Entry:unmark() + if self.mark.active then + self.textChanged = true + self.mark.active = false + end +end + +function Entry:markAnchor(ie) + self:unmark() + self:moveTo(ie) + self:markBegin() + self:markFinish() +end + +function Entry:markLeft() + self:markBegin() + if self:moveLeft() then + self:markFinish() + end +end + +function Entry:markRight() + self:markBegin() + if self:moveRight() then + self:markFinish() + end +end + +function Entry:markWord(ie) + local index = 1 + self:moveTo(ie) + while true do + local s, e = self.value:find('%w+', index) + if not s or s - 1 > self.pos then + break + end + if self.pos >= s - 1 and self.pos < e then + self.pos = s - 1 + self:markBegin() + self.pos = e + self:markFinish() + self:moveTo(ie) + break + end + index = e + 1 + end +end + +function Entry:markNextWord() + self:markBegin() + if self:moveWordRight() then + self:markFinish() + end +end + +function Entry:markPrevWord() + self:markBegin() + if self:moveWordLeft() then + self:markFinish() + end +end + +function Entry:markAll() + if #self.value > 0 then + self.mark.anchor = { x = 1 } + self.mark.active = true + self.mark.continue = true + self.mark.x = 0 + self.mark.ex = #self.value + self.textChanged = true + end +end + +function Entry:markHome() + self:markBegin() + if self:moveHome() then + self:markFinish() + end +end + +function Entry:markEnd() + self:markBegin() + if self:moveEnd() then + self:markFinish() + end +end + +function Entry:markTo(ie) + self:markBegin() + self:moveTo(ie) + self:markFinish() end local mappings = { - [ 'left' ] = moveLeft, - [ 'control-b' ] = moveLeft, - [ 'right' ] = moveRight, - [ 'control-f' ] = moveRight, - [ 'home' ] = moveStart, - [ 'control-a' ] = moveStart, - [ 'end' ] = moveEnd, - [ 'control-e' ] = moveEnd, - [ 'backspace' ] = backspace, - [ 'control-right' ] = moveWordRight, - [ 'alt-f' ] = moveWordRight, - [ 'control-left' ] = moveWordLeft, - [ 'alt-b' ] = moveWordLeft, - [ 'delete' ] = delete, - [ 'control-u' ] = cutFromStart, - [ 'control-k' ] = cutToEnd, - [ 'control-d' ] = cutNextWord, - [ 'control-w' ] = cutPrevWord, - [ 'char' ] = insertChar, - [ 'copy' ] = copy, - [ 'paste' ] = paste, - [ 'control-y' ] = paste, - [ 'mouse_click' ] = moveCursor, - [ 'mouse_rightclick' ] = clearLine, + [ 'left' ] = Entry.moveLeft, + [ 'control-b' ] = Entry.moveLeft, + [ 'right' ] = Entry.moveRight, + [ 'control-f' ] = Entry.moveRight, + [ 'home' ] = Entry.moveHome, + [ 'end' ] = Entry.moveEnd, + [ 'control-e' ] = Entry.moveEnd, + [ 'mouse_click' ] = Entry.moveTo, + [ 'backspace' ] = Entry.backspace, + [ 'control-right' ] = Entry.moveWordRight, + [ 'alt-f' ] = Entry.moveWordRight, + [ 'control-left' ] = Entry.moveWordLeft, + [ 'alt-b' ] = Entry.moveWordLeft, + [ 'mouse_doubleclick' ] = Entry.markWord, + [ 'delete' ] = Entry.delete, + [ 'control-u' ] = Entry.cutFromStart, + [ 'control-k' ] = Entry.cutToEnd, + --[ 'control-d' ] = Entry.cutNextWord, + [ 'control-w' ] = Entry.cutPrevWord, + [ 'char' ] = Entry.insertChar, + [ 'control-c' ] = Entry.copy, + [ 'control-x' ] = Entry.cut, + [ 'paste' ] = Entry.paste, + [ 'control-y' ] = Entry.paste, + [ 'mouse_rightclick' ] = Entry.clearLine, + + [ 'shift-left' ] = Entry.markLeft, + [ 'shift-right' ] = Entry.markRight, + [ 'mouse_down' ] = Entry.markAnchor, + [ 'mouse_drag' ] = Entry.markTo, + [ 'shift-mouse_click' ] = Entry.markTo, + [ 'control-a' ] = Entry.markAll, + [ 'control-shift-right' ] = Entry.markNextWord, + [ 'control-shift-left' ] = Entry.markPrevWord, + [ 'shift-end' ] = Entry.markEnd, + [ 'shift-home' ] = Entry.markHome, } function Entry:process(ie) local action = mappings[ie.code] - local updated + + self.textChanged = false if action then - updated = action(self, ie) + local pos = self.pos + local line = self.value + + local wasMarking = self.mark.continue + self.mark.continue = false + + action(self, ie) + + self.textChanged = self.textChanged or self.value ~= line + self.posChanged = pos ~= self.pos + self:updateScroll() + + if not self.mark.continue and wasMarking then + self:unmark() + end + + return true end - - self:updateScroll() - - return updated end return Entry diff --git a/sys/apis/input.lua b/sys/apis/input.lua index a47213a..a1e91a6 100644 --- a/sys/apis/input.lua +++ b/sys/apis/input.lua @@ -119,6 +119,12 @@ function input:translate(event, code, p1, p2) local buttons = { 'mouse_click', 'mouse_rightclick' } self.mch = buttons[code] self.mfired = nil + return { + code = input:toCode('mouse_down', 255), + button = code, + x = p1, + y = p2, + } elseif event == 'mouse_drag' then self.mfired = true diff --git a/sys/apis/ui.lua b/sys/apis/ui.lua index 5a3b257..e4a373a 100644 --- a/sys/apis/ui.lua +++ b/sys/apis/ui.lua @@ -45,7 +45,7 @@ function Manager:init() if ie and currentPage then local target = currentPage.focused or currentPage self:inputEvent(target, - { type = 'key', key = ie.code == 'char' and ie.ch or ie.code, element = target }) + { type = 'key', key = ie.code == 'char' and ie.ch or ie.code, element = target, ie = ie }) currentPage:sync() end end @@ -97,7 +97,7 @@ function Manager:init() end, mouse_click = function(_, button, x, y) - Input:translate('mouse_click', button, x, y) + local ie = Input:translate('mouse_click', button, x, y) local currentPage = self:getActivePage() if currentPage then @@ -107,6 +107,7 @@ function Manager:init() currentPage:setFocus(event.element) currentPage:sync() end + self:click(currentPage, ie.code, button, x, y) end end end, @@ -136,14 +137,15 @@ function Manager:init() if ie and currentPage then local event = currentPage:pointToChild(x, y) event.type = ie.code + event.ie = ie self:inputEvent(event.element, event) currentPage:sync() end end, paste = function(_, text) - Input:translate('paste') - self:emitEvent({ type = 'paste', text = text }) + local ie = Input:translate('paste', text) + self:emitEvent({ type = 'paste', text = text, ie = ie }) self:getActivePage():sync() end, } @@ -259,6 +261,7 @@ function Manager:click(target, code, button, x, y) clickEvent.button = button clickEvent.type = code clickEvent.key = code + clickEvent.ie = { code = code, x = x, y = y } if clickEvent.element.focus then target:setFocus(clickEvent.element) diff --git a/sys/apis/ui/components/TextEntry.lua b/sys/apis/ui/components/TextEntry.lua index 384c098..e8df5d2 100644 --- a/sys/apis/ui/components/TextEntry.lua +++ b/sys/apis/ui/components/TextEntry.lua @@ -1,9 +1,9 @@ local class = require('class') +local entry = require('entry') local UI = require('ui') local Util = require('util') local colors = _G.colors -local os = _G.os local _rep = string.rep UI.TextEntry = class(UI.Window) @@ -18,41 +18,31 @@ UI.TextEntry.defaults = { backgroundFocusColor = colors.black, --lightGray, height = 1, limit = 6, - pos = 0, accelerators = { [ 'control-c' ] = 'copy', } } function UI.TextEntry:postInit() self.value = tostring(self.value) + self.entry = entry({ limit = self.limit, offset = 2 }) +end + +function UI.TextEntry:layout() + UI.Window.layout(self) + self.entry.width = self.width - 2 end function UI.TextEntry:setValue(value) self.value = value + self.entry:unmark() + self.entry.value = value + self.entry:updateScroll() end function UI.TextEntry:setPosition(pos) - self.pos = pos -end - -function UI.TextEntry:updateScroll() - if not self.scroll then - self.scroll = 0 - end - - if not self.pos then - self.pos = #tostring(self.value) - self.scroll = 0 - elseif self.pos > #tostring(self.value) then - self.pos = #tostring(self.value) - self.scroll = 0 - end - - if self.pos - self.scroll > self.width - 2 then - self.scroll = self.pos - (self.width - 2) - elseif self.pos < self.scroll then - self.scroll = self.pos - end + self.entry.pos = pos + self.entry.value = self.value + self.entry:updateScroll() end function UI.TextEntry:draw() @@ -62,11 +52,10 @@ function UI.TextEntry:draw() bg = self.backgroundFocusColor end - self:updateScroll() local text = tostring(self.value) if #text > 0 then - if self.scroll and self.scroll > 0 then - text = text:sub(1 + self.scroll) + if self.entry.scroll > 0 then + text = text:sub(1 + self.entry.scroll) end if self.mask then text = _rep('*', #text) @@ -77,21 +66,32 @@ function UI.TextEntry:draw() end self:write(1, 1, ' ' .. Util.widthify(text, self.width - 2) .. ' ', bg, tc) + + if self.entry.mark.active then + local tx = math.max(self.entry.mark.x - self.entry.scroll, 0) + local tex = self.entry.mark.ex - self.entry.scroll + + if tex > self.width - 2 then -- unsure about this + tex = self.width - 2 - tx + end + + if tx ~= tex then + self:write(tx + 2, 1, text:sub(tx + 1, tex), colors.gray, tc) + end + end if self.focused then - self:setCursorPos(self.pos-self.scroll+2, 1) + self:setCursorPos(self.entry.pos - self.entry.scroll + 2, 1) end end function UI.TextEntry:reset() - self.pos = 0 - self.value = '' + self.entry:reset() self:draw() self:updateCursor() end function UI.TextEntry:updateCursor() - self:updateScroll() - self:setCursorPos(self.pos-self.scroll+2, 1) + self:setCursorPos(self.entry.pos - self.entry.scroll + 2, 1) end function UI.TextEntry:focus() @@ -103,86 +103,20 @@ function UI.TextEntry:focus() end end ---[[ - A few lines below from theoriginalbit - http://www.computercraft.info/forums2/index.php?/topic/16070-read-and-limit-length-of-the-input-field/ ---]] function UI.TextEntry:eventHandler(event) - if event.type == 'key' then - local ch = event.key - if ch == 'left' then - if self.pos > 0 then - self.pos = math.max(self.pos-1, 0) - self:draw() - end - elseif ch == 'right' then - local input = tostring(self.value) - if self.pos < #input then - self.pos = math.min(self.pos+1, #input) - self:draw() - end - elseif ch == 'home' then - self.pos = 0 + local text = self.value + self.entry.value = text + if event.ie and self.entry:process(event.ie) then + if self.entry.textChanged then + self.value = self.entry.value self:draw() - elseif ch == 'end' then - self.pos = #tostring(self.value) - self:draw() - elseif ch == 'backspace' then - if self.pos > 0 then - local input = tostring(self.value) - self.value = input:sub(1, self.pos-1) .. input:sub(self.pos+1) - self.pos = self.pos - 1 - self:draw() + if text ~= self.value then self:emit({ type = 'text_change', text = self.value, element = self }) end - elseif ch == 'delete' then - local input = tostring(self.value) - if self.pos < #input then - self.value = input:sub(1, self.pos) .. input:sub(self.pos+2) - self:draw() - self:emit({ type = 'text_change', text = self.value, element = self }) - end - elseif #ch == 1 then - local input = tostring(self.value) - if #input < self.limit then - self.value = input:sub(1, self.pos) .. ch .. input:sub(self.pos+1) - self.pos = self.pos + 1 - self:draw() - self:emit({ type = 'text_change', text = self.value, element = self }) - end - else - return false - end - return true - - elseif event.type == 'copy' then - os.queueEvent('clipboard_copy', self.value) - - elseif event.type == 'paste' then - local input = tostring(self.value) - local text = event.text - if #input + #text > self.limit then - text = text:sub(1, self.limit-#input) - end - self.value = input:sub(1, self.pos) .. text .. input:sub(self.pos+1) - self.pos = self.pos + #text - self:draw() - self:updateCursor() - self:emit({ type = 'text_change', text = self.value, element = self }) - return true - - elseif event.type == 'mouse_click' then - if self.focused and event.x > 1 then - self.pos = event.x + self.scroll - 2 + elseif self.entry.posChanged then self:updateCursor() - return true - end - elseif event.type == 'mouse_rightclick' then - local input = tostring(self.value) - if #input > 0 then - self:reset() - self:emit({ type = 'text_change', text = self.value, element = self }) end + return true end return false diff --git a/sys/apps/Lua.lua b/sys/apps/Lua.lua index 12bc072..5627d6c 100644 --- a/sys/apps/Lua.lua +++ b/sys/apps/Lua.lua @@ -34,7 +34,7 @@ local page = UI.Page { prompt = UI.TextEntry { y = 2, shadowText = 'enter command', - limit = 256, + limit = 1024, accelerators = { enter = 'command_enter', up = 'history_back', @@ -72,12 +72,11 @@ page.output = page.tabs[2].output function page:setPrompt(value, focus) self.prompt:setValue(value) - self.prompt.scroll = 0 - self.prompt:setPosition(#value) - self.prompt:updateScroll() if value:sub(-1) == ')' then self.prompt:setPosition(#value - 1) + else + self.prompt:setPosition(#value) end self.prompt:draw() @@ -155,8 +154,8 @@ function page:eventHandler(event) elseif event.type == 'autocomplete' then local sz = #self.prompt.value - local pos = self.prompt.pos - self:setPrompt(autocomplete(sandboxEnv, self.prompt.value, self.prompt.pos)) + local pos = self.prompt.entry.pos + self:setPrompt(autocomplete(sandboxEnv, self.prompt.value, self.prompt.entry.pos)) self.prompt:setPosition(pos + #self.prompt.value - sz) self.prompt:updateCursor() diff --git a/sys/apps/shell.lua b/sys/apps/shell.lua index a138fe6..5c9ecea 100644 --- a/sys/apps/shell.lua +++ b/sys/apps/shell.lua @@ -370,7 +370,9 @@ local term = _G.term local textutils = _G.textutils local oldTerm -local terminal = term.current() +local terminal = term.current() +local _rep = string.rep +local _sub = string.sub if not terminal.scrollUp then terminal = Terminal.window(term.current()) @@ -402,11 +404,19 @@ if not _colors.backgroundColor then _colors.fileColor = colors.white end +local palette = { } +for n = 1, 16 do + palette[2 ^ (n - 1)] = _sub("0123456789abcdef", n, n) +end + if not term.isColor() then _colors = { } for k, v in pairs(config.color) do _colors[k] = Terminal.colorToGrayscale(v) end + for n = 1, 16 do + palette[2 ^ (n - 1)] = _sub("088888878877787f", n, n) + end end local function autocompleteArgument(program, words) @@ -556,12 +566,17 @@ end local function shellRead(history) local lastLen = 0 local entry = Entry({ - width = term.getSize() - 3 + width = term.getSize() - 3, + offset = 3, }) history:reset() term.setCursorBlink(true) + local function updateCursor() + term.setCursorPos(3 + entry.pos - entry.scroll, select(2, term.getCursorPos())) + end + local function redraw() if terminal.scrollBottom then terminal.scrollBottom() @@ -571,9 +586,18 @@ local function shellRead(history) local filler = #entry.value < lastLen and string.rep(' ', lastLen - #entry.value) or '' - local str = string.sub(entry.value, entry.scroll + 1) - term.write(string.sub(str, 1, entry.width) .. filler) - term.setCursorPos(3 + entry.pos - entry.scroll, cy) + local str = string.sub(entry.value, entry.scroll + 1, entry.width + entry.scroll) .. filler + local fg = _rep(palette[_colors.commandTextColor], #str) + local bg = _rep(palette[_colors.backgroundColor], #str) + if entry.mark.active then + local sx = entry.mark.x - entry.scroll + 1 + local ex = entry.mark.ex - entry.scroll + 1 + bg = string.rep('f', sx - 1) .. + string.rep('7', ex - sx) .. + string.rep('f', #str - ex + 1) + end + term.blit(str, fg, bg) + updateCursor() lastLen = #entry.value end @@ -597,13 +621,13 @@ local function shellRead(history) elseif ie.code == 'up' or ie.code == 'control-p' or ie.code == 'down' or ie.code == 'control-n' then + entry:reset() if ie.code == 'up' or ie.code == 'control-p' then entry.value = history:back() or '' else entry.value = history:forward() or '' end - entry.pos = string.len(entry.value) - entry.scroll = 0 + entry.pos = #entry.value entry:updateScroll() redraw() @@ -613,17 +637,24 @@ local function shellRead(history) if cline then entry.value = cline entry.pos = #entry.value + entry:unmark() entry:updateScroll() redraw() end end - elseif entry:process(ie) then - redraw() + else + entry:process(ie) + if entry.textChanged then + redraw() + elseif entry.posChanged then + updateCursor() + end end elseif event == "term_resize" then entry.width = term.getSize() - 3 + entry:updateScroll() redraw() end end diff --git a/sys/apps/system/launcher.lua b/sys/apps/system/launcher.lua index 25e5db6..a2fb843 100644 --- a/sys/apps/system/launcher.lua +++ b/sys/apps/system/launcher.lua @@ -57,6 +57,9 @@ end function tab:eventHandler(event) if event.type == 'choice_change' then self.custom.enabled = event.value == 'custom' + if self.custom.enabled then + self.custom.value = config.launcher + end self:draw() elseif event.type == 'update' then