diff --git a/sys/apis/entry.lua b/sys/apis/entry.lua index fd77af9..7379a09 100644 --- a/sys/apis/entry.lua +++ b/sys/apis/entry.lua @@ -30,6 +30,19 @@ local function nextWord(line, cx) end 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 +end + function Entry:updateScroll() if self.pos - self.scroll > self.width then self.scroll = self.pos - (self.width) @@ -38,109 +51,198 @@ function Entry:updateScroll() end end +local function moveLeft(entry) + if entry.pos > 0 then + entry.pos = math.max(entry.pos - 1, 0) + 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) + return true + end +end + +local function moveStart(entry) + if entry.pos ~= 0 then + entry.pos = 0 + return true + end +end + +local function moveEnd(entry) + if entry.pos ~= #tostring(entry.value) then + entry.pos = #tostring(entry.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 + 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 + 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 + 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 + 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 + end + 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 + 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) + 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 + end +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, +} + function Entry:process(ie) - local updated = false + local action = mappings[ie.code] + local updated - if ie.code == 'left' then - if self.pos > 0 then - self.pos = math.max(self.pos - 1, 0) - updated = true - end - - elseif ie.code == 'right' then - local input = tostring(self.value) - if self.pos < #input then - self.pos = math.min(self.pos + 1, #input) - updated = true - end - - elseif ie.code == 'home' then - if self.pos ~= 0 then - self.pos = 0 - updated = true - end - - elseif ie.code == 'end' then - if self.pos ~= #tostring(self.value) then - self.pos = #tostring(self.value) - updated = true - end - - elseif ie.code == '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 - updated = true - end - - elseif ie.code == 'control-right' then - local nx = nextWord(self.value, self.pos + 1) - if nx then - self.pos = math.min(nx - 1, #self.value) - elseif self.pos < #self.value then - self.pos = #self.value - end - updated = true - - elseif ie.code == 'control-left' then - if self.pos ~= 0 then - local lx = 1 - while true do - local nx = nextWord(self.value, lx) - if not nx or nx >= self.pos then - break - end - lx = nx - end - if not lx then - self.pos = 0 - else - self.pos = lx - 1 - end - updated = true - end - - elseif ie.code == '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.update = true - updated = true - end - - elseif ie.code == 'char' then - local input = tostring(self.value) - if #input < self.limit then - self.value = input:sub(1, self.pos) .. ie.ch .. input:sub(self.pos + 1) - self.pos = self.pos + 1 - self.update = true - updated = true - end - - elseif ie.code == 'copy' then - os.queueEvent('clipboard_copy', self.value) - - elseif ie.code == 'paste' then - local input = tostring(self.value) - if #input + #ie.text > self.limit then - ie.text = ie.text:sub(1, self.limit-#input) - end - self.value = input:sub(1, self.pos) .. ie.text .. input:sub(self.pos + 1) - self.pos = self.pos + #ie.text - updated = true - - elseif ie.code == 'mouse_click' then - -- need starting x passed in instead of hardcoding 3 - self.pos = math.max(0, math.min(ie.x - 3 + self.scroll, #self.value)) - updated = true - - elseif ie.code == 'mouse_rightclick' then - local input = tostring(self.value) - if #input > 0 then - self:reset() - updated = true - end + if action then + updated = action(self, ie) end self:updateScroll() diff --git a/sys/apis/terminal.lua b/sys/apis/terminal.lua index 950741e..dc10311 100644 --- a/sys/apis/terminal.lua +++ b/sys/apis/terminal.lua @@ -6,6 +6,25 @@ local _gsub = string.gsub local Terminal = { } +local mapColorToGray = { + [ colors.white ] = colors.white, + [ colors.orange ] = colors.lightGray, + [ colors.magenta ] = colors.lightGray, + [ colors.lightBlue ] = colors.lightGray, + [ colors.yellow ] = colors.lightGray, + [ colors.lime ] = colors.lightGray, + [ colors.pink ] = colors.lightGray, + [ colors.gray ] = colors.gray, + [ colors.lightGray ] = colors.lightGray, + [ colors.cyan ] = colors.lightGray, + [ colors.purple ] = colors.gray, + [ colors.blue ] = colors.gray, + [ colors.brown ] = colors.gray, + [ colors.green ] = colors.lightGray, + [ colors.red ] = colors.gray, + [ colors.black ] = colors.black, +} + -- Replacement for window api with scrolling and buffering function Terminal.window(parent, sx, sy, w, h, isVisible) isVisible = isVisible ~= false @@ -243,32 +262,17 @@ function Terminal.getContents(win, parent) return lines end -function Terminal.toGrayscale(ct) - local scolors = { - [ colors.white ] = colors.white, - [ colors.orange ] = colors.lightGray, - [ colors.magenta ] = colors.lightGray, - [ colors.lightBlue ] = colors.lightGray, - [ colors.yellow ] = colors.lightGray, - [ colors.lime ] = colors.lightGray, - [ colors.pink ] = colors.lightGray, - [ colors.gray ] = colors.gray, - [ colors.lightGray ] = colors.lightGray, - [ colors.cyan ] = colors.lightGray, - [ colors.purple ] = colors.gray, - [ colors.blue ] = colors.gray, - [ colors.brown ] = colors.gray, - [ colors.green ] = colors.lightGray, - [ colors.red ] = colors.gray, - [ colors.black ] = colors.black, - } +function Terminal.colorToGrayscale(c) + return mapColorToGray[c] +end +function Terminal.toGrayscale(ct) local methods = { 'setBackgroundColor', 'setBackgroundColour', 'setTextColor', 'setTextColour' } for _,v in pairs(methods) do local fn = ct[v] ct[v] = function(c) - fn(scolors[c]) + fn(mapColorToGray[c]) end end diff --git a/sys/apps/Overview.lua b/sys/apps/Overview.lua index 58c65b8..eba0bfd 100644 --- a/sys/apps/Overview.lua +++ b/sys/apps/Overview.lua @@ -1,5 +1,3 @@ -_G.requireInjector(_ENV) - local class = require('class') local Config = require('config') local Event = require('event') diff --git a/sys/apps/shell.lua b/sys/apps/shell.lua index 8946e0a..05978a7 100644 --- a/sys/apps/shell.lua +++ b/sys/apps/shell.lua @@ -362,6 +362,7 @@ local Config = require('config') local Entry = require('entry') local History = require('history') local Input = require('input') +local Terminal = require('terminal') local colors = _G.colors local os = _G.os @@ -372,22 +373,12 @@ local oldTerm local terminal = term.current() if not terminal.scrollUp then - local Terminal = require('terminal') terminal = Terminal.window(term.current()) terminal.setMaxScroll(200) oldTerm = term.redirect(terminal) end local config = { - standard = { - textColor = colors.white, - commandTextColor = colors.lightGray, - directoryTextColor = colors.gray, - directoryBackgroundColor = colors.black, - promptTextColor = colors.gray, - promptBackgroundColor = colors.black, - directoryColor = colors.gray, - }, color = { textColor = colors.white, commandTextColor = colors.yellow, @@ -396,15 +387,20 @@ local config = { promptTextColor = colors.blue, promptBackgroundColor = colors.black, directoryColor = colors.green, + fileColor = colors.white, + backgroundColor = colors.black, }, displayDirectory = true, } Config.load('shellprompt', config) -local _colors = config.standard -if term.isColor() then - _colors = config.color +local _colors = config.color +if not term.isColor() then + _colors = { } + for k, v in pairs(config.color) do + _colors[k] = Terminal.colorToGrayscale(v) + end end local function autocompleteArgument(program, words) @@ -536,9 +532,9 @@ local function autocomplete(line) end if #tDirs > 0 then - textutils.tabulate(_colors.directoryColor, tDirs, colors.white, tFiles) + textutils.tabulate(_colors.directoryColor, tDirs, _colors.fileColor, tFiles) else - textutils.tabulate(colors.white, tFiles) + textutils.tabulate(_colors.fileColor, tFiles) end term.setTextColour(_colors.promptTextColor) @@ -546,7 +542,7 @@ local function autocomplete(line) term.write("$ " ) term.setTextColour(_colors.commandTextColor) - term.setBackgroundColor(colors.black) + term.setBackgroundColor(_colors.backgroundColor) return line end end @@ -593,8 +589,9 @@ local function shellRead(history) elseif ie.code == 'enter' then break - elseif ie.code == 'up' or ie.code == 'down' then - if ie.code == 'up' then + elseif ie.code == 'up' or ie.code == 'control-p' or + ie.code == 'down' or ie.code == 'control-n' then + if ie.code == 'up' or ie.code == 'control-p' then entry.value = history:back() or '' else entry.value = history:forward() or '' @@ -634,6 +631,9 @@ end local history = History.load('usr/.shell_history', 25) +term.setBackgroundColor(_colors.backgroundColor) +term.clear() + while not bExit do if config.displayDirectory then term.setTextColour(_colors.directoryTextColor) @@ -644,7 +644,7 @@ while not bExit do term.setBackgroundColor(_colors.promptBackgroundColor) term.write("$ " ) term.setTextColour(_colors.commandTextColor) - term.setBackgroundColor(colors.black) + term.setBackgroundColor(_colors.backgroundColor) local sLine = shellRead(history) if bExit then -- terminated break diff --git a/sys/apps/system/cloud.lua b/sys/apps/system/cloud.lua index d766f4d..434a16c 100644 --- a/sys/apps/system/cloud.lua +++ b/sys/apps/system/cloud.lua @@ -27,16 +27,16 @@ if _G.http.websocket then event = 'update_key', }, labelText = UI.TextArea { - x = 2, ex = -2, y = 6, + x = 3, ex = -3, y = 6, textColor = colors.yellow, - marginRight = 0, + marginLeft = 0, marginRight = 0, value = string.format( - [[Use a non-changing cloud key. Note that only a single computer can use this session at one time. - To obtain a key, visit: - %shttps://cloud-catcher.squiddev.cc%s then bookmark: - %shttps://cloud-catcher.squiddev.cc/?id=%sKEY +[[Use a non-changing cloud key. Note that only a single computer can use this session at one time. +To obtain a key, visit: +%shttps://cloud-catcher.squiddev.cc%s then bookmark: +%shttps://cloud-catcher.squiddev.cc/?id=KEY ]], - Ansi.white, Ansi.reset, Ansi.white, Ansi.white), + Ansi.white, Ansi.reset, Ansi.white), }, } diff --git a/sys/apps/system/colors.lua b/sys/apps/system/colors.lua new file mode 100644 index 0000000..3bdefda --- /dev/null +++ b/sys/apps/system/colors.lua @@ -0,0 +1,136 @@ +local Config = require('config') +local UI = require('ui') +local Util = require('util') + +local colors = _G.colors +local os = _G.os + +local config = Config.load('shellprompt') + +local allColors = { } +for k,v in pairs(colors) do + if type(v) == 'number' then + table.insert(allColors, { name = k, value = v }) + end +end + +local defaults = { + textColor = colors.white, + commandTextColor = colors.yellow, + directoryTextColor = colors.orange, + directoryBackgroundColor = colors.black, + promptTextColor = colors.blue, + promptBackgroundColor = colors.black, + directoryColor = colors.green, + fileColor = colors.white, + backgroundColor = colors.black, +} +local _colors = config.color or Util.shallowCopy(defaults) + +local allSettings = { } +for k, v in pairs(defaults) do + table.insert(allSettings, { name = k }) +end + +local tab = UI.Tab { + tabTitle = 'Shell', + description = 'Shell option', + grid1 = UI.ScrollingGrid { + y = 2, ey = -10, x = 3, ex = -16, + disableHeader = true, + columns = { { key = 'name' } }, + values = allSettings, + sortColumn = 'name', + }, + grid2 = UI.ScrollingGrid { + y = 2, ey = -10, x = -14, ex = -3, + disableHeader = true, + columns = { { key = 'name' } }, + values = allColors, + sortColumn = 'name', + }, + directoryLabel = UI.Text { + x = 2, y = -2, + value = 'Display directory', + }, + directory = UI.Checkbox { + x = 20, y = -2, + value = config.displayDirectory + }, + reset = UI.Button { + x = -18, y = -2, + text = 'Reset', + event = 'reset', + }, + button = UI.Button { + x = -9, y = -2, + text = 'Update', + event = 'update', + }, + display = UI.Window { + x = 3, ex = -3, y = -8, height = 5, + }, +} + +function tab.grid2:getRowTextColor(row) + local selected = tab.grid1:getSelected() + if _colors[selected.name] == row.value then + return colors.yellow + end + return UI.Grid.getRowTextColor(self, row) +end + +function tab.display:draw() + self:clear(_colors.backgroundColor) + local offset = 0 + if config.displayDirectory then + self:write(1, 1, + '==' .. os.getComputerLabel() .. ':/dir/etc', + _colors.directoryBackgroundColor, _colors.directoryTextColor) + offset = 1 + end + + self:write(1, 1 + offset, '$ ', + _colors.promptBackgroundColor, _colors.promptTextColor) + + self:write(3, 1 + offset, 'ls /', + _colors.backgroundColor, _colors.commandTextColor) + + self:write(1, 2 + offset, 'sys usr', + _colors.backgroundColor, _colors.directoryColor) + + self:write(1, 3 + offset, 'startup', + _colors.backgroundColor, _colors.fileColor) +end + +function tab:eventHandler(event) + if event.type =='checkbox_change' then + config.displayDirectory = not not event.checked + self.display:draw() + + elseif event.type == 'grid_focus_row' and event.element == self.grid1 then + self.grid2:draw() + + elseif event.type == 'grid_select' and event.element == self.grid2 then + _colors[tab.grid1:getSelected().name] = event.selected.value + self.display:draw() + self.grid2:draw() + + elseif event.type == 'reset' then + config.color = defaults + config.displayDirectory = true + self.directory.value = true + _colors = Util.shallowCopy(defaults) + + Config.update('shellprompt', config) + self:draw() + + elseif event.type == 'update' then + config.color = _colors + Config.update('shellprompt', config) + + end + return UI.Tab.eventHandler(self, event) +end + +return tab diff --git a/sys/apps/system/launcher.lua b/sys/apps/system/launcher.lua new file mode 100644 index 0000000..25e5db6 --- /dev/null +++ b/sys/apps/system/launcher.lua @@ -0,0 +1,81 @@ +local Config = require('config') +local UI = require('ui') + +local colors = _G.colors +local fs = _G.fs + +local config = Config.load('multishell') + +local tab = UI.Tab { + tabTitle = 'Launcher', + description = 'Set the application launcher', + launcherLabel = UI.Text { + x = 3, y = 2, + value = 'Launcher', + }, + launcher = UI.Chooser { + x = 13, y = 2, width = 12, + choices = { + { name = 'Overview', value = 'sys/apps/Overview.lua' }, + { name = 'Shell', value = 'sys/apps/shell.lua' }, + { name = 'Custom', value = 'custom' }, + }, + }, + custom = UI.TextEntry { + x = 13, ex = -3, y = 3, + limit = 128, + shadowText = 'File name', + }, + button = UI.Button { + x = 3, y = 5, + text = 'Update', + event = 'update', + }, + labelText = UI.TextArea { + x = 3, ex = -3, y = 7, + textColor = colors.yellow, + value = 'Choose an application launcher', + }, +} + +function tab:enable() + local launcher = config.launcher and 'custom' or 'sys/apps/Overview.lua' + + for _, v in pairs(self.launcher.choices) do + if v.value == config.launcher then + launcher = v.value + break + end + end + + UI.Tab.enable(self) + + self.launcher.value = launcher + self.custom.enabled = launcher == 'custom' +end + +function tab:eventHandler(event) + if event.type == 'choice_change' then + self.custom.enabled = event.value == 'custom' + self:draw() + + elseif event.type == 'update' then + local launcher + + if self.launcher.value ~= 'custom' then + launcher = self.launcher.value + elseif fs.exists(self.custom.value) and not fs.isDir(self.custom.value) then + launcher = self.custom.value + end + + if launcher then + config.launcher = launcher + Config.update('multishell', config) + self:emit({ type = 'success_message', message = 'Updated' }) + else + self:emit({ type = 'error_message', message = 'Invalid file' }) + end + end +end + +return tab diff --git a/sys/apps/system/path.lua b/sys/apps/system/path.lua index 7e3abff..504c7e1 100644 --- a/sys/apps/system/path.lua +++ b/sys/apps/system/path.lua @@ -20,7 +20,7 @@ local tab = UI.Tab { disableHeader = true, columns = { { key = 'value' } }, autospace = true, - sort = 'index', + sortColumn = 'index', help = 'double-click to remove, shift-arrow to move', accelerators = { delete = 'remove', diff --git a/sys/apps/system/requires.lua b/sys/apps/system/requires.lua index dfa7a02..2bd97cf 100644 --- a/sys/apps/system/requires.lua +++ b/sys/apps/system/requires.lua @@ -20,7 +20,7 @@ local tab = UI.Tab { disableHeader = true, columns = { { key = 'value' } }, autospace = true, - sort = 'index', + sortColumn = 'index', help = 'double-click to remove, shift-arrow to move', accelerators = { delete = 'remove', diff --git a/sys/etc/apps.db b/sys/etc/apps.db index edd19bd..d7ea0dd 100644 --- a/sys/etc/apps.db +++ b/sys/etc/apps.db @@ -46,7 +46,7 @@ iconExt = "\031f\128\0313\152\131\131\132\031f\128\ \0313\139\159\129\0303\031f\159\129\139\ \031f\128\0313\136\0303\031f\143\143\030f\0313\134\031f\128", - run = "update", + run = "update update", }, c47ae15370cfe1ed2781eedc1dc2547d12d9e972 = { title = "Help", diff --git a/sys/extensions/1.device.lua b/sys/extensions/1.device.lua index 5c57761..3d25083 100644 --- a/sys/extensions/1.device.lua +++ b/sys/extensions/1.device.lua @@ -75,7 +75,7 @@ end) local modifiers = Util.transpose { keys.leftCtrl, keys.rightCtrl, keys.leftShift, keys.rightShift, - --keys.leftAlt, keys.rightAlt, + keys.leftAlt, keys.rightAlt, } kernel.hook({ 'key', 'key_up', 'char', 'paste' }, function(event, eventData) diff --git a/sys/extensions/4.user.lua b/sys/extensions/4.user.lua index 169155d..e795997 100644 --- a/sys/extensions/4.user.lua +++ b/sys/extensions/4.user.lua @@ -44,6 +44,6 @@ end shell.setPath(table.concat(path, ':')) _G.LUA_PATH = config.lua_path -_G.settings.set('require.path', config.lua_path) +_G.settings.set('mbs.shell.require_path', config.lua_path) fs.loadTab('usr/config/fstab') diff --git a/sys/extensions/7.multishell.lua b/sys/extensions/7.multishell.lua index 974afe4..9d48ce0 100644 --- a/sys/extensions/7.multishell.lua +++ b/sys/extensions/7.multishell.lua @@ -332,11 +332,15 @@ kernel.hook('mouse_scroll', function(_, eventData) end) kernel.hook('kernel_ready', function() + local env = Util.shallowCopy(shell.getEnv()) + _G.requireInjector(env) + overviewId = multishell.openTab({ - path = 'sys/apps/Overview.lua', + path = config.launcher or 'sys/apps/Overview.lua', isOverview = true, focused = true, title = '+', + env = env, }) multishell.openTab({