cleanup + undo overhaul
This commit is contained in:
343
common/edit.lua
343
common/edit.lua
@@ -1,5 +1,6 @@
|
|||||||
local fuzzy = require('opus.fuzzy')
|
local fuzzy = require('opus.fuzzy')
|
||||||
local UI = require('opus.ui')
|
local UI = require('opus.ui')
|
||||||
|
local Util = require('opus.util')
|
||||||
|
|
||||||
local colors = _G.colors
|
local colors = _G.colors
|
||||||
local fs = _G.fs
|
local fs = _G.fs
|
||||||
@@ -9,6 +10,14 @@ local shell = _ENV.shell
|
|||||||
local term = _G.term
|
local term = _G.term
|
||||||
local textutils = _G.textutils
|
local textutils = _G.textutils
|
||||||
|
|
||||||
|
local _format = string.format
|
||||||
|
local _rep = string.rep
|
||||||
|
local _sub = string.sub
|
||||||
|
local _concat = table.concat
|
||||||
|
local _insert = table.insert
|
||||||
|
local _remove = table.remove
|
||||||
|
local _unpack = table.unpack
|
||||||
|
|
||||||
local x, y = 1, 1
|
local x, y = 1, 1
|
||||||
local w, h = term.getSize()
|
local w, h = term.getSize()
|
||||||
local scrollX = 0
|
local scrollX = 0
|
||||||
@@ -21,7 +30,7 @@ local lastSave
|
|||||||
local dirty = { y = 1, ey = h }
|
local dirty = { y = 1, ey = h }
|
||||||
local mark = { }
|
local mark = { }
|
||||||
local searchPattern
|
local searchPattern
|
||||||
local undo = { chain = { }, pointer = 0 }
|
local undo = { chain = { } }
|
||||||
|
|
||||||
h = h - 1
|
h = h - 1
|
||||||
|
|
||||||
@@ -47,9 +56,9 @@ local keyMapping = {
|
|||||||
down = 'down',
|
down = 'down',
|
||||||
left = 'left',
|
left = 'left',
|
||||||
right = 'right',
|
right = 'right',
|
||||||
pageUp = 'pageUp',
|
pageUp = 'page_up',
|
||||||
[ 'control-b' ] = 'pageUp',
|
[ 'control-b' ] = 'page_up',
|
||||||
pageDown = 'pageDown',
|
pageDown = 'page_down',
|
||||||
home = 'home',
|
home = 'home',
|
||||||
[ 'end' ] = 'toend',
|
[ 'end' ] = 'toend',
|
||||||
[ 'control-home' ] = 'top',
|
[ 'control-home' ] = 'top',
|
||||||
@@ -77,6 +86,7 @@ local keyMapping = {
|
|||||||
[ 'shift-home' ] = 'mark_home',
|
[ 'shift-home' ] = 'mark_home',
|
||||||
[ 'mouse_down' ] = 'mark_anchor',
|
[ 'mouse_down' ] = 'mark_anchor',
|
||||||
[ 'mouse_doubleclick' ] = 'mark_current_word',
|
[ 'mouse_doubleclick' ] = 'mark_current_word',
|
||||||
|
[ 'mouse_tripleclick' ] = 'mark_line',
|
||||||
|
|
||||||
-- editing
|
-- editing
|
||||||
delete = 'delete',
|
delete = 'delete',
|
||||||
@@ -91,6 +101,7 @@ local keyMapping = {
|
|||||||
-- copy/paste
|
-- copy/paste
|
||||||
[ 'control-x' ] = 'cut',
|
[ 'control-x' ] = 'cut',
|
||||||
[ 'control-c' ] = 'copy',
|
[ 'control-c' ] = 'copy',
|
||||||
|
[ 'control-y' ] = 'paste_internal',
|
||||||
|
|
||||||
-- file
|
-- file
|
||||||
[ 'control-s' ] = 'save',
|
[ 'control-s' ] = 'save',
|
||||||
@@ -126,7 +137,7 @@ local page = UI.Page {
|
|||||||
{ text = 'Edit', dropdown = {
|
{ text = 'Edit', dropdown = {
|
||||||
{ text = 'Cut ^x', event = 'menu_action', action = 'cut' },
|
{ text = 'Cut ^x', event = 'menu_action', action = 'cut' },
|
||||||
{ text = 'Copy ^c', event = 'menu_action', action = 'copy' },
|
{ text = 'Copy ^c', event = 'menu_action', action = 'copy' },
|
||||||
{ text = 'Paste ^V', event = 'menu_action', action = 'paste_internal' },
|
{ text = 'Paste ^y,^V', event = 'menu_action', action = 'paste_internal' },
|
||||||
{ spacer = true },
|
{ spacer = true },
|
||||||
{ text = 'Find... ^f', event = 'menu_action', action = 'find_prompt' },
|
{ text = 'Find... ^f', event = 'menu_action', action = 'find_prompt' },
|
||||||
{ text = 'Find Next ^n', event = 'menu_action', action = 'find_next' },
|
{ text = 'Find Next ^n', event = 'menu_action', action = 'find_next' },
|
||||||
@@ -220,7 +231,7 @@ local page = UI.Page {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
show = function(self)
|
show = function(self)
|
||||||
self.filename.value = fileInfo.abspath
|
self.filename:setValue(fileInfo.abspath)
|
||||||
self.filename:setPosition(#self.filename.value)
|
self.filename:setPosition(#self.filename.value)
|
||||||
UI.MiniSlideOut.show(self)
|
UI.MiniSlideOut.show(self)
|
||||||
end,
|
end,
|
||||||
@@ -295,11 +306,11 @@ local page = UI.Page {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
grid = UI.ScrollingGrid {
|
grid = UI.ScrollingGrid {
|
||||||
x = 2, y = 4, ex = -2, ey = -4,
|
x = 2, y = 3, ex = -2, ey = -4,
|
||||||
sortColumn = 'name',
|
disableHeader = true,
|
||||||
columns = {
|
columns = {
|
||||||
{ heading = 'Name', key = 'name' },
|
{ key = 'name' },
|
||||||
{ heading = 'Dir', key = 'dir' },
|
{ key = 'dir', textColor = colors.lightGray },
|
||||||
},
|
},
|
||||||
accelerators = {
|
accelerators = {
|
||||||
grid_select = 'accept',
|
grid_select = 'accept',
|
||||||
@@ -308,7 +319,7 @@ local page = UI.Page {
|
|||||||
cancel = UI.Button {
|
cancel = UI.Button {
|
||||||
x = -9, y = -2,
|
x = -9, y = -2,
|
||||||
text = 'Cancel',
|
text = 'Cancel',
|
||||||
event = 'quick_cancel',
|
event = 'slide_hide',
|
||||||
},
|
},
|
||||||
apply_filter = function(self, filter)
|
apply_filter = function(self, filter)
|
||||||
local t = { }
|
local t = { }
|
||||||
@@ -320,17 +331,16 @@ local page = UI.Page {
|
|||||||
for _,v in pairs(self.listing) do
|
for _,v in pairs(self.listing) do
|
||||||
v.score = fuzzy(v.lname, filter)
|
v.score = fuzzy(v.lname, filter)
|
||||||
if v.score then
|
if v.score then
|
||||||
table.insert(t, v)
|
_insert(t, v)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
self.grid.sortColumn = 'name'
|
self.grid.sortColumn = 'lname'
|
||||||
self.grid.inverseSort = false
|
self.grid.inverseSort = false
|
||||||
t = self.listing
|
t = self.listing
|
||||||
end
|
end
|
||||||
|
|
||||||
self.grid:setValues(t)
|
self.grid:setValues(t)
|
||||||
self.grid:update()
|
|
||||||
self.grid:setIndex(1)
|
self.grid:setIndex(1)
|
||||||
end,
|
end,
|
||||||
show = function(self)
|
show = function(self)
|
||||||
@@ -340,9 +350,9 @@ local page = UI.Page {
|
|||||||
for _,f in ipairs(files) do
|
for _,f in ipairs(files) do
|
||||||
local fullName = fs.combine(dir, f)
|
local fullName = fs.combine(dir, f)
|
||||||
if fs.native.isDir(fullName) then
|
if fs.native.isDir(fullName) then
|
||||||
recurse(fullName)
|
if f ~= '.git' then recurse(fullName) end
|
||||||
else
|
else
|
||||||
table.insert(listing, {
|
_insert(listing, {
|
||||||
name = f,
|
name = f,
|
||||||
dir = dir,
|
dir = dir,
|
||||||
lname = f:lower(),
|
lname = f:lower(),
|
||||||
@@ -371,9 +381,6 @@ local page = UI.Page {
|
|||||||
self:hide()
|
self:hide()
|
||||||
end
|
end
|
||||||
|
|
||||||
elseif event.type == 'quick_cancel' then
|
|
||||||
self:hide()
|
|
||||||
|
|
||||||
elseif event.type == 'text_change' then
|
elseif event.type == 'text_change' then
|
||||||
self:apply_filter(event.text)
|
self:apply_filter(event.text)
|
||||||
self.grid:draw()
|
self.grid:draw()
|
||||||
@@ -395,7 +402,7 @@ local page = UI.Page {
|
|||||||
accelerators = {
|
accelerators = {
|
||||||
[ ' ' ] = 'down',
|
[ ' ' ] = 'down',
|
||||||
backspace = 'slide_hide',
|
backspace = 'slide_hide',
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
show = function(self, values)
|
show = function(self, values)
|
||||||
local m = 12
|
local m = 12
|
||||||
@@ -443,7 +450,7 @@ local page = UI.Page {
|
|||||||
UI.Window.resize(self)
|
UI.Window.resize(self)
|
||||||
|
|
||||||
w, h = self.width, self.height
|
w, h = self.width, self.height
|
||||||
actions.setCursor(x, y)
|
actions.set_cursor(x, y)
|
||||||
actions.dirty_all()
|
actions.dirty_all()
|
||||||
actions.redraw()
|
actions.redraw()
|
||||||
end,
|
end,
|
||||||
@@ -461,7 +468,6 @@ local page = UI.Page {
|
|||||||
|
|
||||||
elseif ie.code == "mouse_click" or
|
elseif ie.code == "mouse_click" or
|
||||||
ie.code == 'mouse_drag' or
|
ie.code == 'mouse_drag' or
|
||||||
--ie.code == 'mouse_up' or
|
|
||||||
ie.code == 'shift-mouse_click' or
|
ie.code == 'shift-mouse_click' or
|
||||||
ie.code == 'mouse_down' or
|
ie.code == 'mouse_down' or
|
||||||
ie.code == 'mouse_doubleclick' then
|
ie.code == 'mouse_doubleclick' then
|
||||||
@@ -528,121 +534,50 @@ local function getFileInfo(path)
|
|||||||
return fi
|
return fi
|
||||||
end
|
end
|
||||||
|
|
||||||
local function setStatus(pattern, ...)
|
local keywords = Util.transpose {
|
||||||
page.notification:info(string.format(pattern, ...))
|
'and', 'break', 'do', 'else', 'elseif', 'end', 'false', 'for', 'function', 'if',
|
||||||
end
|
'in', 'local', 'nil', 'not', 'or', 'repeat', 'return', 'then', 'true', 'until', 'while'
|
||||||
|
|
||||||
local function setError(pattern, ...)
|
|
||||||
page.notification:error(string.format(pattern, ...))
|
|
||||||
end
|
|
||||||
|
|
||||||
local function save( _sPath )
|
|
||||||
-- Create intervening folder
|
|
||||||
local sDir = _sPath:sub(1, _sPath:len() - fs.getName(_sPath):len() )
|
|
||||||
if not fs.exists( sDir ) then
|
|
||||||
fs.makeDir( sDir )
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Save
|
|
||||||
local file = nil
|
|
||||||
local function innerSave()
|
|
||||||
file = fs.open( _sPath, "w" )
|
|
||||||
if file then
|
|
||||||
for _,sLine in ipairs( tLines ) do
|
|
||||||
file.write(sLine .. "\n")
|
|
||||||
end
|
|
||||||
else
|
|
||||||
error( "Failed to open ".._sPath )
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
local ok, err = pcall( innerSave )
|
|
||||||
if file then
|
|
||||||
file.close()
|
|
||||||
end
|
|
||||||
return ok, err
|
|
||||||
end
|
|
||||||
|
|
||||||
local function split(str, pattern)
|
|
||||||
pattern = pattern or "(.-)\n"
|
|
||||||
local t = {}
|
|
||||||
local function helper(line) table.insert(t, line) return "" end
|
|
||||||
helper((str:gsub(pattern, helper)))
|
|
||||||
return t
|
|
||||||
end
|
|
||||||
|
|
||||||
local tKeywords = {
|
|
||||||
["and"] = true,
|
|
||||||
["break"] = true,
|
|
||||||
["do"] = true,
|
|
||||||
["else"] = true,
|
|
||||||
["elseif"] = true,
|
|
||||||
["end"] = true,
|
|
||||||
["false"] = true,
|
|
||||||
["for"] = true,
|
|
||||||
["function"] = true,
|
|
||||||
["if"] = true,
|
|
||||||
["in"] = true,
|
|
||||||
["local"] = true,
|
|
||||||
["nil"] = true,
|
|
||||||
["not"] = true,
|
|
||||||
["or"] = true,
|
|
||||||
["repeat"] = true,
|
|
||||||
["return"] = true,
|
|
||||||
["then"] = true,
|
|
||||||
["true"] = true,
|
|
||||||
["until"]= true,
|
|
||||||
["while"] = true,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
local function writeHighlighted(sLine, ny, dy)
|
local function writeHighlighted(sLine, ny, dy)
|
||||||
local buffer = {
|
local buffer = { fg = { }, text = { } }
|
||||||
fg = '',
|
|
||||||
text = '',
|
|
||||||
}
|
|
||||||
|
|
||||||
local function tryWrite(line, regex, fgcolor)
|
local function tryWrite(line, regex, fgcolor)
|
||||||
local match = line:match(regex)
|
local match = line:match(regex)
|
||||||
if match then
|
if match then
|
||||||
local fg = type(fgcolor) == "string" and fgcolor or fgcolor(match)
|
local fg = type(fgcolor) == "string" and fgcolor or fgcolor(match)
|
||||||
buffer.text = buffer.text .. match
|
_insert(buffer.text, match)
|
||||||
buffer.fg = buffer.fg .. string.rep(fg, #match)
|
_insert(buffer.fg, _rep(fg, #match))
|
||||||
return line:sub(#match + 1)
|
return _sub(line, #match + 1)
|
||||||
end
|
end
|
||||||
return nil
|
return nil
|
||||||
end
|
end
|
||||||
|
|
||||||
while #sLine > 0 do
|
while #sLine > 0 do
|
||||||
sLine =
|
sLine =
|
||||||
|
-- tryWrite(sLine, "^[%\26]", '7' ) or
|
||||||
tryWrite(sLine, "^%-%-%[%[.-%]%]", color.commentColor ) or
|
tryWrite(sLine, "^%-%-%[%[.-%]%]", color.commentColor ) or
|
||||||
tryWrite(sLine, "^%-%-.*", color.commentColor ) or
|
tryWrite(sLine, "^%-%-.*", color.commentColor ) or
|
||||||
tryWrite(sLine, "^\".-[^\\]\"", color.stringColor ) or
|
tryWrite(sLine, "^\".-[^\\]\"", color.stringColor ) or
|
||||||
tryWrite(sLine, "^\'.-[^\\]\'", color.stringColor ) or
|
tryWrite(sLine, "^\'.-[^\\]\'", color.stringColor ) or
|
||||||
tryWrite(sLine, "^%[%[.-%]%]", color.stringColor ) or
|
tryWrite(sLine, "^%[%[.-%]%]", color.stringColor ) or
|
||||||
tryWrite(sLine, "^[%w_]+", function(match)
|
tryWrite(sLine, "^[%w_]+", function(match)
|
||||||
if tKeywords[match] then
|
return keywords[match] and color.keywordColor or color.textColor
|
||||||
return color.keywordColor
|
|
||||||
end
|
|
||||||
return color.textColor
|
|
||||||
end) or
|
end) or
|
||||||
tryWrite(sLine, "^[^%w_]", color.textColor)
|
tryWrite(sLine, "^[^%w_]", color.textColor)
|
||||||
end
|
end
|
||||||
|
|
||||||
buffer.fg = buffer.fg .. '7'
|
buffer.fg = _concat(buffer.fg) .. '7'
|
||||||
buffer.text = buffer.text .. '\183'
|
buffer.text = _concat(buffer.text) .. '\183'
|
||||||
|
|
||||||
if mark.active and ny >= mark.y and ny <= mark.ey then
|
if mark.active and ny >= mark.y and ny <= mark.ey then
|
||||||
local sx = ny == mark.y and mark.x or 1
|
local sx = ny == mark.y and mark.x or 1
|
||||||
local ex = #buffer.text
|
local ex = ny == mark.ey and mark.ex or #buffer.text
|
||||||
if ny == mark.ey then
|
buffer.bg = _rep('f', sx - 1) ..
|
||||||
ex = mark.ex
|
_rep('7', ex - sx) ..
|
||||||
end
|
_rep('f', #buffer.text - ex + 1)
|
||||||
buffer.bg = string.rep('f', sx - 1) ..
|
|
||||||
string.rep('7', ex - sx) ..
|
|
||||||
string.rep('f', #buffer.text - ex + 1)
|
|
||||||
|
|
||||||
else
|
else
|
||||||
buffer.bg = string.rep('f', #buffer.text)
|
buffer.bg = _rep('f', #buffer.text)
|
||||||
end
|
end
|
||||||
|
|
||||||
page.editor:blit(1 - scrollX, dy, buffer.text, buffer.bg, buffer.fg)
|
page.editor:blit(1 - scrollX, dy, buffer.text, buffer.bg, buffer.fg)
|
||||||
@@ -651,7 +586,6 @@ end
|
|||||||
local function redraw()
|
local function redraw()
|
||||||
if dirty.y > 0 then
|
if dirty.y > 0 then
|
||||||
for dy = 1, h do
|
for dy = 1, h do
|
||||||
|
|
||||||
local sLine = tLines[dy + scrollY]
|
local sLine = tLines[dy + scrollY]
|
||||||
if sLine ~= nil then
|
if sLine ~= nil then
|
||||||
if dy + scrollY >= dirty.y and dy + scrollY <= dirty.ey then
|
if dy + scrollY >= dirty.y and dy + scrollY <= dirty.ey then
|
||||||
@@ -665,7 +599,7 @@ local function redraw()
|
|||||||
end
|
end
|
||||||
|
|
||||||
local modifiedIndicator = undo.chain[#undo.chain] == lastSave and ' ' or '*'
|
local modifiedIndicator = undo.chain[#undo.chain] == lastSave and ' ' or '*'
|
||||||
page.menuBar.status.value = string.format(' %d:%d%s', y, x, modifiedIndicator)
|
page.menuBar.status.value = _format('%d:%d%s', y, x, modifiedIndicator)
|
||||||
page.menuBar.status:draw()
|
page.menuBar.status:draw()
|
||||||
|
|
||||||
if page.editor.focused then
|
if page.editor.focused then
|
||||||
@@ -688,36 +622,34 @@ local function nextWord(line, cx)
|
|||||||
end
|
end
|
||||||
|
|
||||||
actions = {
|
actions = {
|
||||||
|
info = function(pattern, ...)
|
||||||
|
page.notification:info(_format(pattern, ...))
|
||||||
|
end,
|
||||||
|
|
||||||
|
error = function(pattern, ...)
|
||||||
|
page.notification:error(_format(pattern, ...))
|
||||||
|
end,
|
||||||
|
|
||||||
undo = function()
|
undo = function()
|
||||||
local last = table.remove(undo.chain)
|
local last = _remove(undo.chain)
|
||||||
if last then
|
if last then
|
||||||
undo.active = true
|
undo.active = true
|
||||||
actions[last.action](table.unpack(last.args))
|
for i = #last, 1, -1 do
|
||||||
|
local u = last[i]
|
||||||
|
actions[u.action](_unpack(u.args))
|
||||||
|
end
|
||||||
undo.active = false
|
undo.active = false
|
||||||
else
|
else
|
||||||
setStatus('Already at oldest change')
|
actions.info('already at oldest change')
|
||||||
end
|
end
|
||||||
end,
|
end,
|
||||||
|
|
||||||
addUndo = function(entry)
|
undo_add = function(entry)
|
||||||
local last = undo.chain[#undo.chain]
|
local last = undo.chain[#undo.chain]
|
||||||
if last and last.action == entry.action then
|
if last and undo.continue then
|
||||||
if last.action == 'deleteText' then
|
table.insert(last, entry)
|
||||||
if last.args[3] == entry.args[1] and
|
|
||||||
last.args[4] == entry.args[2] then
|
|
||||||
last.args = {
|
|
||||||
last.args[1], last.args[2], entry.args[3], entry.args[4],
|
|
||||||
last.args[5] .. entry.args[5]
|
|
||||||
}
|
|
||||||
else
|
|
||||||
table.insert(undo.chain, entry)
|
|
||||||
end
|
|
||||||
else
|
|
||||||
-- insertText (need to finish)
|
|
||||||
table.insert(undo.chain, entry)
|
|
||||||
end
|
|
||||||
else
|
else
|
||||||
table.insert(undo.chain, entry)
|
_insert(undo.chain, { entry })
|
||||||
end
|
end
|
||||||
end,
|
end,
|
||||||
|
|
||||||
@@ -726,7 +658,7 @@ actions = {
|
|||||||
local results = sLine and textutils.complete(sLine, _ENV) or { }
|
local results = sLine and textutils.complete(sLine, _ENV) or { }
|
||||||
|
|
||||||
if #results == 0 then
|
if #results == 0 then
|
||||||
setError('No completions available')
|
actions.error('no completions available')
|
||||||
|
|
||||||
elseif #results == 1 then
|
elseif #results == 1 then
|
||||||
actions.insertText(x, y, results[1])
|
actions.insertText(x, y, results[1])
|
||||||
@@ -746,7 +678,7 @@ actions = {
|
|||||||
refresh = function()
|
refresh = function()
|
||||||
actions.dirty_all()
|
actions.dirty_all()
|
||||||
mark.continue = mark.active
|
mark.continue = mark.active
|
||||||
setStatus('refreshed')
|
actions.info('refreshed')
|
||||||
end,
|
end,
|
||||||
|
|
||||||
goto_line = function()
|
goto_line = function()
|
||||||
@@ -763,7 +695,7 @@ actions = {
|
|||||||
local nx = tLines[ny]:lower():find(pattern, sx, true)
|
local nx = tLines[ny]:lower():find(pattern, sx, true)
|
||||||
if nx then
|
if nx then
|
||||||
if ny < y or ny == y and nx <= x then
|
if ny < y or ny == y and nx <= x then
|
||||||
setStatus('search hit BOTTOM, continuing at TOP')
|
actions.info('search hit BOTTOM, continuing at TOP')
|
||||||
end
|
end
|
||||||
actions.go_to(nx, ny)
|
actions.go_to(nx, ny)
|
||||||
actions.mark_to(nx + #pattern, ny)
|
actions.mark_to(nx + #pattern, ny)
|
||||||
@@ -772,7 +704,7 @@ actions = {
|
|||||||
end
|
end
|
||||||
sx = 1
|
sx = 1
|
||||||
end
|
end
|
||||||
setError('Pattern not found')
|
actions.error('pattern not found')
|
||||||
end,
|
end,
|
||||||
|
|
||||||
find_next = function()
|
find_next = function()
|
||||||
@@ -812,7 +744,7 @@ actions = {
|
|||||||
|
|
||||||
open = function(filename)
|
open = function(filename)
|
||||||
if not actions.load(filename) then
|
if not actions.load(filename) then
|
||||||
setError('Unable to load file')
|
actions.error('unable to load file')
|
||||||
end
|
end
|
||||||
end,
|
end,
|
||||||
|
|
||||||
@@ -825,39 +757,51 @@ actions = {
|
|||||||
x, y = 1, 1
|
x, y = 1, 1
|
||||||
scrollX, scrollY = 0, 0
|
scrollX, scrollY = 0, 0
|
||||||
lastPos = { x = 1, y = 1 }
|
lastPos = { x = 1, y = 1 }
|
||||||
lastSave = nil
|
lastSave = nil
|
||||||
dirty = { y = 1, ey = h }
|
dirty = { y = 1, ey = h }
|
||||||
mark = { }
|
mark = { }
|
||||||
undo = { chain = { }, pointer = 0 }
|
undo = { chain = { } }
|
||||||
|
|
||||||
tLines = { }
|
tLines = Util.readLines(fileInfo.abspath) or { }
|
||||||
if fs.exists(fileInfo.abspath) then
|
if #tLines == 0 then
|
||||||
local file = io.open(fileInfo.abspath, "r")
|
_insert(tLines, '')
|
||||||
local sLine = file:read()
|
|
||||||
while sLine do
|
|
||||||
table.insert(tLines, sLine)
|
|
||||||
sLine = file:read()
|
|
||||||
end
|
|
||||||
file:close()
|
|
||||||
end
|
end
|
||||||
|
|
||||||
if #tLines == 0 then
|
--[[
|
||||||
table.insert(tLines, '')
|
local function detabify(l)
|
||||||
|
return l:gsub('\26\26', '\9'):gsub('\26', '\9')
|
||||||
|
end ]]
|
||||||
|
|
||||||
|
-- since we can't handle tabs, convert them to spaces :(
|
||||||
|
local t1, t2 = ' ', ' '
|
||||||
|
local function tabify(l)
|
||||||
|
repeat
|
||||||
|
local i = l:find('\9')
|
||||||
|
if i then
|
||||||
|
local tabs = (i - 1) % 2 == 0 and t2 or t1
|
||||||
|
l = l:sub(1, i - 1) .. tabs .. l:sub(i + 1)
|
||||||
|
end
|
||||||
|
until not i
|
||||||
|
return l
|
||||||
|
end
|
||||||
|
|
||||||
|
for k, v in pairs(tLines) do
|
||||||
|
tLines[k] = tabify(v)
|
||||||
end
|
end
|
||||||
|
|
||||||
local name = fileInfo.path
|
local name = fileInfo.path
|
||||||
if fileInfo.isNew then
|
if fileInfo.isNew then
|
||||||
if not fileInfo.dirExists then
|
if not fileInfo.dirExists then
|
||||||
setStatus('"%s" [New DIRECTORY]', name)
|
actions.info('"%s" [New DIRECTORY]', name)
|
||||||
else
|
else
|
||||||
setStatus('"%s" [New File]', name)
|
actions.info('"%s" [New File]', name)
|
||||||
end
|
end
|
||||||
elseif fileInfo.isReadOnly then
|
elseif fileInfo.isReadOnly then
|
||||||
setStatus('"%s" [readonly] %dL, %dC',
|
actions.info('"%s" [readonly] %dL, %dC',
|
||||||
name, #tLines, fs.getSize(fileInfo.abspath))
|
name, #tLines, fs.getSize(fileInfo.abspath))
|
||||||
else
|
else
|
||||||
setStatus('"%s" %dL, %dC',
|
actions.info('"%s" %dL, %dC',
|
||||||
name, #tLines, fs.getSize(fileInfo.abspath))
|
name, #tLines, fs.getSize(fileInfo.abspath))
|
||||||
end
|
end
|
||||||
|
|
||||||
return true
|
return true
|
||||||
@@ -866,17 +810,22 @@ actions = {
|
|||||||
save = function(filename)
|
save = function(filename)
|
||||||
filename = filename or fileInfo.abspath
|
filename = filename or fileInfo.abspath
|
||||||
if fs.isReadOnly(filename) then
|
if fs.isReadOnly(filename) then
|
||||||
setError("Access denied")
|
actions.error("access denied")
|
||||||
else
|
else
|
||||||
local ok = save(filename)
|
local s, m = pcall(function()
|
||||||
if ok then
|
if not Util.writeLines(filename, tLines) then
|
||||||
|
error("Failed to open " .. filename)
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|
||||||
|
if s then
|
||||||
lastSave = undo.chain[#undo.chain]
|
lastSave = undo.chain[#undo.chain]
|
||||||
fileInfo = getFileInfo(filename)
|
fileInfo = getFileInfo(filename)
|
||||||
setStatus('"%s" %dL, %dC written',
|
actions.info('"%s" %dL, %dC written',
|
||||||
fileInfo.path, #tLines, fs.getSize(fileInfo.abspath))
|
fileInfo.path, #tLines, fs.getSize(fileInfo.abspath))
|
||||||
return true
|
return true
|
||||||
else
|
else
|
||||||
setError("Error saving to %s", filename)
|
actions.error(m)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end,
|
end,
|
||||||
@@ -894,26 +843,34 @@ actions = {
|
|||||||
end,
|
end,
|
||||||
|
|
||||||
run = function()
|
run = function()
|
||||||
--input:reset()
|
if undo.chain[#undo.chain] == lastSave then
|
||||||
local sTempPath = "/.temp"
|
local nTask = shell.openTab(fileInfo.abspath)
|
||||||
local ok = save(sTempPath)
|
|
||||||
if ok then
|
|
||||||
local nTask = shell.openTab(sTempPath)
|
|
||||||
if nTask then
|
if nTask then
|
||||||
shell.switchTab(nTask)
|
shell.switchTab(nTask)
|
||||||
else
|
else
|
||||||
setError("Error starting Task")
|
actions.error("error starting Task")
|
||||||
end
|
end
|
||||||
os.sleep(0)
|
|
||||||
fs.delete(sTempPath)
|
|
||||||
else
|
else
|
||||||
setError("Error saving to %s", sTempPath)
|
local fn, msg = load(_concat(tLines, '\n'), fileInfo.abspath)
|
||||||
|
if fn then
|
||||||
|
multishell.openTab({
|
||||||
|
fn = fn,
|
||||||
|
focused = true,
|
||||||
|
title = fs.getName(fileInfo.abspath),
|
||||||
|
})
|
||||||
|
else
|
||||||
|
local ln = msg:match(':(%d+):')
|
||||||
|
if ln and tonumber(ln) then
|
||||||
|
actions.go_to(1, tonumber(ln))
|
||||||
|
end
|
||||||
|
actions.error(msg)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end,
|
end,
|
||||||
|
|
||||||
status = function()
|
status = function()
|
||||||
local modified = undo.chain[#undo.chain] == lastSave and '' or '[Modified] '
|
local modified = undo.chain[#undo.chain] == lastSave and '' or '[Modified] '
|
||||||
setStatus('"%s" %s%d lines --%d%%--',
|
actions.info('"%s" %s%d lines --%d%%--',
|
||||||
fileInfo.abspath, modified, #tLines,
|
fileInfo.abspath, modified, #tLines,
|
||||||
math.floor((y - 1) / (#tLines - 1) * 100))
|
math.floor((y - 1) / (#tLines - 1) * 100))
|
||||||
end,
|
end,
|
||||||
@@ -1019,6 +976,14 @@ actions = {
|
|||||||
actions.mark_finish()
|
actions.mark_finish()
|
||||||
end,
|
end,
|
||||||
|
|
||||||
|
mark_line = function()
|
||||||
|
actions.home()
|
||||||
|
actions.mark_begin()
|
||||||
|
actions.toend()
|
||||||
|
actions.right()
|
||||||
|
actions.mark_finish()
|
||||||
|
end,
|
||||||
|
|
||||||
mark_word = function()
|
mark_word = function()
|
||||||
actions.mark_begin()
|
actions.mark_begin()
|
||||||
actions.word()
|
actions.word()
|
||||||
@@ -1074,7 +1039,7 @@ actions = {
|
|||||||
actions.dirty_all()
|
actions.dirty_all()
|
||||||
end,
|
end,
|
||||||
|
|
||||||
setCursor = function()
|
set_cursor = function()
|
||||||
lastPos.x = x
|
lastPos.x = x
|
||||||
lastPos.y = y
|
lastPos.y = y
|
||||||
|
|
||||||
@@ -1128,11 +1093,11 @@ actions = {
|
|||||||
actions.insertText(x, y, ' ')
|
actions.insertText(x, y, ' ')
|
||||||
end,
|
end,
|
||||||
|
|
||||||
pageUp = function()
|
page_up = function()
|
||||||
actions.go_to(x, y - h)
|
actions.go_to(x, y - h)
|
||||||
end,
|
end,
|
||||||
|
|
||||||
pageDown = function()
|
page_down = function()
|
||||||
actions.go_to(x, y + h)
|
actions.go_to(x, y + h)
|
||||||
end,
|
end,
|
||||||
|
|
||||||
@@ -1208,21 +1173,21 @@ actions = {
|
|||||||
actions.dirty_line(y)
|
actions.dirty_line(y)
|
||||||
x = x + #text
|
x = x + #text
|
||||||
else
|
else
|
||||||
local lines = split(text)
|
local lines = Util.split(text)
|
||||||
local remainder = sLine:sub(x)
|
local remainder = sLine:sub(x)
|
||||||
tLines[y] = sLine:sub(1, x - 1) .. lines[1]
|
tLines[y] = sLine:sub(1, x - 1) .. lines[1]
|
||||||
actions.dirty_range(y, #tLines + #lines)
|
actions.dirty_range(y, #tLines + #lines)
|
||||||
x = x + #lines[1]
|
x = x + #lines[1]
|
||||||
for k = 2, #lines do
|
for k = 2, #lines do
|
||||||
y = y + 1
|
y = y + 1
|
||||||
table.insert(tLines, y, lines[k])
|
_insert(tLines, y, lines[k])
|
||||||
x = #lines[k] + 1
|
x = #lines[k] + 1
|
||||||
end
|
end
|
||||||
tLines[y] = tLines[y]:sub(1, x) .. remainder
|
tLines[y] = tLines[y]:sub(1, x) .. remainder
|
||||||
end
|
end
|
||||||
|
|
||||||
if not undo.active then
|
if not undo.active then
|
||||||
actions.addUndo(
|
actions.undo_add(
|
||||||
{ action = 'deleteText', args = { sx, sy, x, y, text } })
|
{ action = 'deleteText', args = { sx, sy, x, y, text } })
|
||||||
end
|
end
|
||||||
end,
|
end,
|
||||||
@@ -1233,14 +1198,14 @@ actions = {
|
|||||||
|
|
||||||
if not undo.active then
|
if not undo.active then
|
||||||
local text = actions.copyText(sx, sy, ex, ey)
|
local text = actions.copyText(sx, sy, ex, ey)
|
||||||
actions.addUndo(
|
actions.undo_add(
|
||||||
{ action = 'insertText', args = { sx, sy, text } })
|
{ action = 'insertText', args = { sx, sy, text } })
|
||||||
end
|
end
|
||||||
|
|
||||||
local front = tLines[sy]:sub(1, sx - 1)
|
local front = tLines[sy]:sub(1, sx - 1)
|
||||||
local back = tLines[ey]:sub(ex, #tLines[ey])
|
local back = tLines[ey]:sub(ex, #tLines[ey])
|
||||||
for _ = 2, ey - sy + 1 do
|
for _ = 2, ey - sy + 1 do
|
||||||
table.remove(tLines, y + 1)
|
_remove(tLines, y + 1)
|
||||||
end
|
end
|
||||||
tLines[y] = front .. back
|
tLines[y] = front .. back
|
||||||
if sy ~= ey then
|
if sy ~= ey then
|
||||||
@@ -1267,10 +1232,10 @@ actions = {
|
|||||||
end
|
end
|
||||||
local str = line:sub(cx, ex)
|
local str = line:sub(cx, ex)
|
||||||
count = count + #str
|
count = count + #str
|
||||||
table.insert(lines, str)
|
_insert(lines, str)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
return table.concat(lines, '\n'), count
|
return _concat(lines, '\n'), count
|
||||||
end,
|
end,
|
||||||
|
|
||||||
delete = function()
|
delete = function()
|
||||||
@@ -1304,7 +1269,7 @@ actions = {
|
|||||||
if mark.active then
|
if mark.active then
|
||||||
actions.delete()
|
actions.delete()
|
||||||
end
|
end
|
||||||
actions.insertText(x, y, '\n' .. string.rep(' ', spaces))
|
actions.insertText(x, y, '\n' .. _rep(' ', spaces))
|
||||||
end,
|
end,
|
||||||
|
|
||||||
char = function(ch)
|
char = function(ch)
|
||||||
@@ -1317,7 +1282,7 @@ actions = {
|
|||||||
copy_marked = function()
|
copy_marked = function()
|
||||||
local text = actions.copyText(mark.x, mark.y, mark.ex, mark.ey)
|
local text = actions.copyText(mark.x, mark.y, mark.ex, mark.ey)
|
||||||
os.queueEvent('clipboard_copy', text)
|
os.queueEvent('clipboard_copy', text)
|
||||||
setStatus('shift-^v to paste')
|
actions.info('shift-^v to paste')
|
||||||
end,
|
end,
|
||||||
|
|
||||||
cut = function()
|
cut = function()
|
||||||
@@ -1340,9 +1305,9 @@ actions = {
|
|||||||
end
|
end
|
||||||
if text then
|
if text then
|
||||||
actions.insertText(x, y, text)
|
actions.insertText(x, y, text)
|
||||||
setStatus('%d chars added', #text)
|
actions.info('%d chars added', #text)
|
||||||
else
|
else
|
||||||
setStatus('Clipboard empty')
|
actions.info('clipboard empty')
|
||||||
end
|
end
|
||||||
end,
|
end,
|
||||||
|
|
||||||
@@ -1384,10 +1349,16 @@ actions = {
|
|||||||
local wasMarking = mark.continue
|
local wasMarking = mark.continue
|
||||||
mark.continue = false
|
mark.continue = false
|
||||||
|
|
||||||
|
-- for undo purposes, treat tab and enter as char actions
|
||||||
|
local a = (action == 'tab' or action == 'enter') and 'char' or action
|
||||||
|
undo.continue = a == undo.lastAction
|
||||||
|
|
||||||
actions[action](...)
|
actions[action](...)
|
||||||
|
|
||||||
|
undo.lastAction = a
|
||||||
|
|
||||||
if x ~= lastPos.x or y ~= lastPos.y then
|
if x ~= lastPos.x or y ~= lastPos.y then
|
||||||
actions.setCursor()
|
actions.set_cursor()
|
||||||
end
|
end
|
||||||
if not mark.continue and wasMarking then
|
if not mark.continue and wasMarking then
|
||||||
actions.unmark()
|
actions.unmark()
|
||||||
|
|||||||
Reference in New Issue
Block a user