-- a text editor require("keys") require("logger") function inc(val,min,max) if val == max then return min end if val < min then return min end return val + 1 end function dec(val,min,max) if val == min then return max end if val > max then return max end return val - 1 end button = {} button.__index = button function button.create(caption,x,y,font,w,h) local b = {} setmetatable(b,button) b.font = font if b.font == nil then b.font = FONT.MED_LARGE end b.caption = caption b.pad = 5 b.left = x b.top = y b.width = w b.height = h b.border = COLOR.WHITE b.foreground = COLOR.WHITE b.background = COLOR.BLACK b.highlight = COLOR.BLUE b.disabled_color = COLOR.DARK_GRAY b.disabled_background = COLOR.gray(5) b.focused = false b.disabled = false if h == nil then b.height = b.font.height + b.pad * 2 end if w == nil then b.width = b.font:width(b.caption) + b.pad * 2 end return b end function button:draw() local bg = self.background local fg = self.foreground if self.disabled then fg = self.disabled_color end if self.focused then if self.disabled then bg = self.disabled_background else bg = self.highlight end end display.rect(self.left,self.top,self.width,self.height,self.border,bg) local x = self.width / 2 - self.font:width(self.caption) / 2 display.print(self.caption,self.left + x,self.top + self.pad,self.font,fg,bg) end function button:handle_key(k) if k == KEY.SET and self.focused and self.disabled == false then return self.caption end end scrollbar = {} scrollbar.__index = scrollbar function scrollbar.create(step,min,max,x,y,w,h) local sb = {} setmetatable(sb,scrollbar) sb.step = step sb.min = min sb.max = max sb.value = min sb.top = y sb.left = x sb.width = w sb.foreground = COLOR.BLUE -- fixme: neither global width nor sb.w appear to be used anywhere -- if width == nil then sb.w = 2 end sb.height = h if h == nil then sb.height = display.height - y end return sb end function scrollbar:draw() --update max automatically from 'table' if self.table ~= nil then self.max = #(self.table) end --don't draw if we are not needed if (self.max - self.min + 1) * self.step <= self.height then return end local total_height = (self.max - self.min + 1) * self.step local thumb_height = self.height * self.height / total_height local offset = (self.value - self.min) * self.step * self.height / total_height display.rect(self.left,self.top + offset,self.width,thumb_height,self.foreground,self.foreground) end function scrollbar:up() self.value = dec(self.value,self.min,self.max) end function scrollbar:down() self.value = inc(self.value,self.min,self.max) end textbox = {} textbox.__index = textbox function textbox.create(value,x,y,w,font,h) local tb = {} setmetatable(tb,textbox) tb.font = font if tb.font == nil then tb.font = FONT.MED_LARGE end tb.min_char = 32 tb.max_char = 127 tb.value = value tb.pad = 5 tb.left = x tb.top = y tb.width = w tb.height = h tb.border = COLOR.WHITE tb.foreground = COLOR.WHITE tb.background = COLOR.BLACK tb.focused_background = COLOR.BLUE tb.col = 1 if h == nil then tb.height = tb.font.height + 10 end return tb end function textbox:draw() local bg = self.background if self.focused then bg = self.focused_background end display.rect(self.left,self.top,self.width,self.height,self.border,bg) display.print(self.value,self.left + self.pad,self.top + self.pad,self.font,self.foreground,bg) local w = self.font:width(self.value:sub(1,self.col - 1)) if self.col == 1 then w = 0 end local ch = self.value:sub(self.col,self.col) display.print(ch,self.left + w + self.pad,self.top + self.pad,self.font,self.background,self.foreground) end function textbox:handle_key(k) if k == KEY.RIGHT then self.col = inc(self.col,1,#(self.value) + 1) elseif k == KEY.LEFT then self.col = dec(self.col,1,#(self.value) + 1) elseif k == KEY.WHEEL_RIGHT then --mod char local l = self.value if self.col < #l then local ch = l:byte(self.col) ch = inc(ch,self.min_char,self.max_char) self.value = string.format("%s%s%s",l:sub(1,self.col - 1),string.char(ch),l:sub(self.col + 1)) else self.value = l..string.char(self.min_char) end elseif k == KEY.WHEEL_LEFT then --mod char local l = self.value if self.col < #l then local ch = l:byte(self.col) ch = dec(ch,self.min_char,self.max_char) self.value = string.format("%s%s%s",l:sub(1,self.col - 1),string.char(ch),l:sub(self.col + 1)) else self.value = l..string.char(self.max_char) end elseif k == KEY.PLAY then --insert local l = self.value self.value = string.format("%s %s",l:sub(1,self.col),l:sub(self.col + 1)) self.col = self.col + 1 elseif k == KEY.TRASH then --delete local l = self.value if self.col <= #l then self.value = string.format("%s%s",l:sub(1,self.col - 1),l:sub(self.col + 1)) end end end filedialog = {} filedialog.__index = filedialog function filedialog.create() local fd = {} setmetatable(fd,filedialog) fd.font = FONT.MED_LARGE fd.top = 60 fd.height = 380 fd.left = 100 fd.width = 520 fd:createcontrols() return fd end function filedialog:createcontrols() self.save_box = textbox.create("",self.left,self.top + self.height - self.font.height - 10,self.width) --limit chars to those needed for filenames self.save_box.min_char = 46 self.save_box.max_char = 95 local w = self.width / 2 self.ok_button = button.create("OK",self.left,self.top+self.height,self.font,w) self.cancel_button = button.create("Cancel",self.left + w,self.top+self.height,self.font,w) local title_height = 20 + FONT.LARGE.height self.scrollbar = scrollbar.create(self.font.height,0,1,self.left + self.width - 6,self.top + title_height, 2, self.height - title_height - self.save_box.height) end function filedialog:updatefiles() self.item_count = 0 local status,items = xpcall(self.current.children,debug.traceback,self.current) if status == false then handle_error(items) end if status then self.children = items self.item_count = #items table.sort(self.children, function(d1,d2) return d1.path < d2.path end) else self.children = nil end status,items = xpcall(self.current.files,debug.traceback,self.current) if status == false then handle_error(items) end if status then self.files = items self.item_count = self.item_count + #items table.sort(self.files) else self.files = nil end self.scrollbar.max = self.item_count end function filedialog:scroll_into_view() if self.selected < self.scrollbar.value then self.scrollbar.value = self.selected elseif self.selected >= self.scrollbar.value + (self.height - 20 - FONT.LARGE.height) / self.font.height - 3 then self.scrollbar.value = self.selected - ((self.height - 20 - FONT.LARGE.height) / self.font.height - 3) end end function filedialog:focus_next() if self.save_mode then self.focused_index = inc(self.focused_index,1,4) else self.focused_index = inc(self.focused_index,1,3) end self:update_focus() end function filedialog:update_focus() self.focused = (self.focused_index == 1) if self.save_mode then self.save_box.focused = (self.focused_index == 2) self.ok_button.focused = (self.focused_index == 3) self.cancel_button.focused = (self.focused_index == 4) else self.ok_button.focused = (self.focused_index == 2) self.cancel_button.focused = (self.focused_index == 3) end end function filedialog:handle_key(k) if k == KEY.Q then self:focus_next() elseif self.save_mode and self.focused_index == 2 then return self.save_box:handle_key(k) elseif self.focused_index == 2 then return self.ok_button:handle_key(k) elseif self.save_mode and self.focused_index == 3 then return self.ok_button:handle_key(k) elseif self.focused_index == 3 then return self.cancel_button:handle_key(k) elseif self.focused_index == 4 then return self.cancel_button:handle_key(k) elseif k == KEY.UP or k == KEY.WHEEL_UP then self.selected = dec(self.selected,0,self.item_count) self:scroll_into_view() elseif k == KEY.DOWN or k == KEY.WHEEL_DOWN then self.selected = inc(self.selected,0,self.item_count) self:scroll_into_view() elseif k == KEY.SET then if self.selected == 0 then if self.current.parent ~= nil then self.current = self.current.parent self.selected = 1 self.scrollbar.value = 0 self:updatefiles() end elseif self.is_dir_selected and self.selected_value ~= nil then self.current = self.selected_value self.selected = 1 self.scrollbar.value = 0 self:updatefiles() elseif self.save_mode then local found = self.current.path:find("/[^/]+$") self.save_box.value = self.current.path:sub(found) else return self.selected_value end end end function filedialog:save(default_name) self.save_mode = true if default_name ~= nil then self.save_box.value = default_name end if self.save_box.value == nil then self.save_box.value = "UNTITLED" end return self:show() end function filedialog:open() self.save_mode = false return self:show() end function filedialog:show() if self.current == nil then self.current = dryos.directory("ML/SCRIPTS") self.selected = 1 self.scrollbar.value = 0 end self.focused_index = 1 self:update_focus() local w = self.width/2 self:updatefiles() self:draw() local started = keys:start() while true do local key = keys:getkey() if key ~= nil then -- process all keys in the queue (until getkey() returns nil), then redraw while key ~= nil do local result = self:handle_key(key) if result == "Cancel" then if started then keys:stop() end return nil elseif result == "OK" then if started then keys:stop() end if self.save_mode then return self.current.path..self.save_box.value else return self.selected_value end elseif result ~= nil then if started then keys:stop() end return result end key = keys:getkey() end self:draw() end task.yield(100) end end function filedialog:draw() display.draw(function() self:draw_main() self.scrollbar:draw() if self.save_mode then self.save_box:draw() else self.ok_button.disabled = self.is_dir_selected end self.ok_button:draw() self.cancel_button:draw() end) end function filedialog:draw_main() display.rect(self.left, self.top, self.width, self.height, COLOR.WHITE, COLOR.BLACK) display.rect(self.left, self.top, self.width, 20 + FONT.LARGE.height, COLOR.WHITE, COLOR.gray(10)) if self.save_mode then display.print(string.format("Save | %s",self.current.path), self.left + 10, self.top + 10, FONT.LARGE,COLOR.WHITE,COLOR.gray(10)) else display.print(string.format("Open | %s",self.current.path), self.left + 10, self.top + 10, FONT.LARGE,COLOR.WHITE,COLOR.gray(10)) end local pos = self.top + 20 + FONT.LARGE.height display.line(self.left, pos, self.width - self.left, pos, COLOR.WHITE) local x = self.left + 10 local r = self.left + self.width pos = pos + 10 local dir_count = #(self.children) local status,items local sel_color = COLOR.DARK_GRAY if self.focused then sel_color = COLOR.BLUE end self.is_dir_selected = false if self.current.exists ~= true then return end if self.scrollbar.value == 0 then if self.selected == 0 then display.rect(self.left + 1,pos,self.width - 2,self.font.height,sel_color,sel_color) display.print("..", x, pos, self.font, COLOR.WHITE, sel_color) else display.print("..", x, pos, self.font) end pos = pos + self.font.height end if self.children ~= nil then for i,v in ipairs(self.children) do if i >= self.scrollbar.value then if i == self.selected then self.is_dir_selected = true self.selected_value = v display.rect(self.left + 1,pos,self.width - 2,self.font.height,sel_color,sel_color) display.print(v.path, x, pos, self.font, COLOR.WHITE, sel_color) else display.print(v.path, x, pos, self.font) end pos = pos + self.font.height if (pos + self.font.height) > (self.top + self.height) then return end end end end if self.files ~= nil then for i,v in ipairs(self.files) do if dir_count + i >= self.scrollbar.value then if dir_count + i == self.selected then self.selected_value = v display.rect(self.left + 1,pos,self.width - 2,self.font.height,sel_color,sel_color) display.print(v, x, pos, self.font, COLOR.WHITE, sel_color) elseif self.save_mode then display.print(v, x, pos, self.font, COLOR.GRAY, COLOR.BLACK) else display.print(v, x, pos, self.font) end pos = pos + self.font.height if (pos + self.font.height) > (self.top + self.height) then return end end end end end editor = { running = false, first_run = true, min_char = 32, max_char = 126, show_line_numbers = true, menu = { { name = "File", items = {"New","Open","Save","Save As","Exit"}, }, { name = "Edit", items = {"Cut","Copy","Paste","Select All"}, }, { name = "Debug", items = {"Run","Step Into","Stacktrace","Locals","Detach"}, }, { name = "Font", items = {} } }, filedialog = filedialog.create(), menu_index = 1, submenu_index = 1, font = FONT.MONO_20, debugging = false, time = 0 } for k,v in pairs(FONT) do table.insert(editor.menu[4].items,k) end table.sort(editor.menu[4].items) editor.lines_per_page = (display.height - 20 - FONT.LARGE.height) / editor.font.height / 2 editor.scrollbar = scrollbar.create(editor.font.height,1,1,display.width - 2,20 + FONT.LARGE.height,2) editor.mlmenu = menu.new { name = "Text Editor", help = "Edit text files or debug Lua scripts", icon_type = ICON_TYPE.ACTION, select = function(this) task.create(function() editor:run() end) end, update = function(this) if editor.filename ~= nil then return editor.filename else return "" end end } -- The main program loop function editor:run() local status, error = xpcall(function() self.running = true menu.block(true) display.clear() if self.first_run then self:new() self.first_run = false else self.menu_open = false end self:main_loop() end, debug.traceback) if status == false then debug.sethook() self.debugging = false handle_error(error) end keys:stop() menu.block(false) self.running = false end function editor:main_loop() menu.block(true) self:draw() keys:start() local exit = false while not exit do if menu.visible == false then break end local key = keys:getkey() if key ~= nil then -- process all keys in the queue (until getkey() returns nil), then redraw while key ~= nil do if self.menu_open then if self:handle_menu_key(key) == false then exit = true break end elseif self.debugging then if self:handle_debug_key(key) == false then exit = true break end else self:handle_key(key) end key = keys:getkey() end self:draw() end editor.time = editor.time + 1 task.yield(100) end keys:stop() if self.running == false then menu.block(false) end end function editor:handle_key(k) if k == KEY.Q then self.menu_open = true elseif k == KEY.WHEEL_DOWN then self.scrollbar:down() elseif k == KEY.WHEEL_UP then self.scrollbar:up() elseif k == KEY.DOWN then self.line = inc(self.line,1,#(self.lines)) self:scroll_into_view() elseif k == KEY.UP then self.line = dec(self.line,1,#(self.lines)) self:scroll_into_view() elseif k == KEY.RIGHT then self.col = inc(self.col,1,#(self.lines[self.line]) + 1) if self.col == 1 then self.line = inc(self.line,1,#(self.lines)) end self:scroll_into_view() elseif k == KEY.LEFT then if self.col == 1 then self.line = dec(self.line,1,#(self.lines)) end self.col = dec(self.col,1,#(self.lines[self.line]) + 1) self:scroll_into_view() elseif k == KEY.WHEEL_RIGHT then --mod char self:update_title(true) local l = self.lines[self.line] if self.col < #l then local ch = l:byte(self.col) ch = inc(ch,self.min_char,self.max_char) self.lines[self.line] = string.format("%s%s%s",l:sub(1,self.col - 1),string.char(ch),l:sub(self.col + 1)) else self.lines[self.line] = l..string.char(self.min_char) end self:scroll_into_view() elseif k == KEY.WHEEL_LEFT then --mod char self:update_title(true) local l = self.lines[self.line] if self.col < #l then local ch = l:byte(self.col) ch = dec(ch,self.min_char,self.max_char) self.lines[self.line] = string.format("%s%s%s",l:sub(1,self.col - 1),string.char(ch),l:sub(self.col + 1)) else self.lines[self.line] = l..string.char(self.max_char) end self:scroll_into_view() elseif k == KEY.TRASH then --delete self:update_title(true) if self.selection_start ~= nil and self.selection_end ~= nil then self:delete_selection() else local l = self.lines[self.line] if #l == 0 then if #(self.lines) > 1 then table.remove(self.lines,self.line) end elseif self.col > #l and self.line < #(self.lines) then self.lines[self.line] = l..self.lines[self.line + 1] table.remove(self.lines,self.line + 1) else self.lines[self.line] = string.format("%s%s",l:sub(1,self.col - 1),l:sub(self.col + 1)) end end self:scroll_into_view() elseif k == KEY.LV or k == KEY.REC then self:toggle_breakpoint(self.line) elseif k == KEY.SET then --insert char self:update_title(true) local l = self.lines[self.line] self.lines[self.line] = string.format("%s %s",l:sub(1,self.col),l:sub(self.col + 1)) self.col = self.col + 1 self:scroll_into_view() elseif k == KEY.PLAY then --insert line return self:update_title(true) local l = self.lines[self.line] self.lines[self.line] = l:sub(1,self.col) table.insert(self.lines, self.line + 1, l:sub(self.col + 1)) self.line = self.line + 1 self.col = 1 self:scroll_into_view() elseif k == KEY.INFO then if self.selection_start == nil or self.selection_end ~= nil then self.selection_start = {self.line,self.col} self.selection_end = nil else if self.selection_start[1] > self.line or (self.selection_start[1] == self.line and self.selection_start[2] > self.col) then self.selection_end = self.selection_start self.selection_start = {self.line,self.col} else self.selection_end = {self.line,self.col} end end end end function editor:scroll_into_view() if self.line < self.scrollbar.value then self.scrollbar.value = self.line elseif self.line > (self.scrollbar.value + self.lines_per_page) then self.scrollbar.value = self.line - self.lines_per_page + 1 end end function editor:update_title(mod, force) if self.mod ~= mod or force == true then self.mod = mod local name = self.filename if name == nil then name = "untitled" end if mod then self.title = string.format("Text Editor [%s*]",name) else self.title = string.format("Text Editor [%s]",name) end end end function editor:handle_menu_key(k) if k == KEY.Q then self.menu_open = false elseif k == KEY.LEFT or k == KEY.WHEEL_LEFT then self.menu_index = dec(self.menu_index, 1, #(self.menu)) self.submenu_index = 1 elseif k == KEY.RIGHT or k == KEY.WHEEL_RIGHT then self.menu_index = inc(self.menu_index, 1, #(self.menu)) self.submenu_index = 1 elseif k == KEY.DOWN or k == KEY.WHEEL_DOWN then local m = self.menu[self.menu_index] self.submenu_index = inc(self.submenu_index,1,#(m.items)) elseif k == KEY.UP or k == KEY.WHEEL_UP then local m = self.menu[self.menu_index] self.submenu_index = dec(self.submenu_index,1,#(m.items)) elseif k == KEY.SET then local m = self.menu[self.menu_index].items[self.submenu_index] if self:menu_enabled(m) then if m == "Exit" then return false elseif m == "Save" then self:save(self.filename) elseif m == "Save As" then self:save() elseif m == "New" then self:new() elseif m == "Open" then self:open() elseif m == "Cut" then self:copy() self:delete_selection() elseif m == "Copy" then self:copy() elseif m == "Paste" then self:paste() elseif m == "Select All" then self.selection_start = {1,1} self.selection_end = {#(self.lines),#(self.lines[self.line])} elseif m == "Run" then if self:save(self.filename) then self:debug() end elseif m == "Step Into" then if self:save(self.filename) then self:debug(true) end elseif m == "Detach" then self.debugging = false debug.sethook() elseif m == "Stacktrace" then self:draw_text(self.stacktrace) elseif m == "Locals" then self:draw_text(self.locals) elseif FONT[m] ~= nil then self.font = FONT[m] self.scrollbar.step = self.font.height end end end return true end function editor:menu_enabled(m) if self.debugging then if m == "Exit" or m == "Copy" or m == "Detach" or m =="Stacktrace" or m == "Locals" or m == "Globals" then return true else return false end else if m == "Detach" or m =="Stacktrace" or m == "Locals" or m == "Globals" then return false else return true end end end function editor:open() local f = self.filedialog:open() if f ~= nil then self.filename = f self:update_title(false, true) self:draw_status("Loading...") local file = io.open(f,"r") --this is much faster than io.lines b/c the file io is all done in one large read request self.lines = logger.tolines(file:read("*a")) file:close() self.line = 1 self.col = 1 self.scrollbar.table = self.lines self.scrollbar.value = 1 self.breakpoints = {} end self.menu_open = false end function editor:new() self.filename = nil self:update_title(true, true) self.lines = {""} self.menu_open = false self.line = 1 self.col = 1 self.scrollbar.table = self.lines self.scrollbar.value = 1 self.breakpoints = {} end function editor:save(filename) if filename == nil then local result = self.filedialog:save("UNTITLED.LUA") if result ~= nil then return self:save(result) else return false end else self:draw_status("Saving...") local f = io.open(filename,"w") for i,v in ipairs(self.lines) do f:write(v,"\n") end f:close() self.filename = filename self:update_title(false, true) self.menu_open = false return true end end function editor:copy() if self.selection_start ~= nil and self.selection_end ~= nil then self.clipboard = {} if self.selection_start[1] == self.selection_end[1] then --single line selection self.clipboard[1] = self.lines[self.selection_start[1]]:sub(self.selection_start[2], self.selection_end[2]) else --multiline selection self.clipboard[1] = self.lines[self.selection_start[1]]:sub(self.selection_start[2]) for i = self.selection_start[1] + 1,self.selection_end[1] - 1,1 do table.insert(self.clipboard,self.lines[i]) end table.insert(self.clipboard,self.lines[self.selection_end[1]]:sub(1,self.selection_end[2])) end else editor:message("Error: Nothing Selected!") end self.menu_open = false end function editor:paste() if self.clipboard ~= nil and #(self.clipboard) > 0 then local c = #(self.clipboard) if c == 1 then local l = self.lines[self.line] self.lines[self.line] = string.format("%s%s%s",l:sub(1,self.col),self.clipboard[1],l:sub(self.col + 1)) else local l = self.lines[self.line] self.lines[self.line] = string.format("%s%s",l:sub(1,self.col),self.clipboard[1]) for i = 2,c - 1,1 do table.insert(self.lines,self.line + i - 1,self.clipboard[i]) end table.insert(self.lines,self.line + c - 1, string.format("%s%s",self.clipboard[c],l:sub(self.col + 1))) end self:update_title(true) else editor:message("Error: Clipboard Empty!") end self.menu_open = false end function editor.traceback(msg) editor.debug_error_msg = msg editor.debug_error_info = debug.getinfo(2,"lS") editor:capture_locals(3) return debug.traceback(msg,2) end function editor:debug(step_into) if self.filename ~= nil then self.debugging = true self.debug_error = false self.debug_line = -1 self:draw() keys:stop() self.step_over = step_into self.debug_call = true debug.sethook(function(event,line) self:debug_step(event,line) end, "c") local status,error = xpcall(dofile, editor.traceback, self.filename) keys:start() if status == false then debug.sethook() self.debug_error = true self.stacktrace = error if self.debug_error_info ~= nil then if self.filename == self.debug_error_info.short_src then self.error_line = self.debug_error_info.currentline if type(self.error_line) == "number" then self.line = self.error_line self:scroll_into_view() end end end return false end return true end return false end function editor:capture_locals(level) local name,value local i = 1 self.locals = "" while true do name,value = debug.getlocal(level,i) if name == nil then break end if value == nil then self.locals = string.format("%s\n%s=(nil)",self.locals,name) elseif type(value) == "number" then self.locals = string.format("%s\n%s=%d",self.locals,name,value) elseif type(value) == "string" then self.locals = string.format("%s\n%s='%s'",self.locals,name,value) else self.locals = string.format("%s\n%s=%s",self.locals,name,type(value)) end i = i + 1 end end function editor:debug_step(event,line) local info = debug.getinfo(3,"S") if info.short_src == self.filename then if self.debug_call then --we've entered user code, switch back to line mode so as to catch breakpoints debug.sethook() debug.sethook(function(event,line) self:debug_step(event,line) end, "l") self.debug_call = false elseif self.step_over or self.breakpoints[line] then self.stacktrace = debug.traceback(nil,3) self:capture_locals(4) self.line = line self.col = 1 self.debug_line = line self:scroll_into_view() self:main_loop() self.debug_line = -1 self:draw() end elseif self.debug_call == false then --switch to "c" hook mode, so that our own code runs faster --it will never happen that there would be a switch to user code, w/o a function call first debug.sethook() debug.sethook(function(event,line) self:debug_step(event,line) end, "c") self.debug_call = true end end function editor:toggle_breakpoint(line) if self.breakpoints[line] then self.breakpoints[line] = false else self.breakpoints[line] = true end end function editor:handle_debug_key(k) if k == KEY.Q then self.menu_open = true elseif k == KEY.WHEEL_DOWN then self.scrollbar:down() elseif k == KEY.WHEEL_UP then self.scrollbar:up() elseif k == KEY.DOWN then self.line = inc(self.line,1,#(self.lines)) self:scroll_into_view() elseif k == KEY.UP then self.line = dec(self.line,1,#(self.lines)) self:scroll_into_view() elseif k == KEY.RIGHT then self.col = inc(self.col,1,#(self.lines[self.line]) + 1) if self.col == 1 then self.line = inc(self.line,1,#(self.lines)) end self:scroll_into_view() elseif k == KEY.LEFT then if self.col == 1 then self.line = dec(self.line,1,#(self.lines)) end self.col = dec(self.col,1,#(self.lines[self.line]) + 1) self:scroll_into_view() elseif k == KEY.SET then --step over self.step_over = true return false elseif k == KEY.LV or k == KEY.REC then self:toggle_breakpoint(self.line) elseif k == KEY.PLAY then --continue self.step_over = false return false end end function editor:delete_selection() if self.selection_start ~= nil and self.selection_end ~= nil then if self.selection_start[1] == self.selection_end[1] then --single line selection self.lines[self.selection_start[1]] = self.lines[self.selection_start[1]]:sub(1,self.selection_start[2] - 1).. self.lines[self.selection_start[1]]:sub(self.selection_end[2] + 1) else --multiline selection self.lines[self.selection_start[1]] = self.lines[self.selection_start[1]]:sub(1,self.selection_start[2] - 1) self.lines[self.selection_end[1]] = self.lines[self.selection_end[1]]:sub(self.selection_end[2] + 1) for i = self.selection_start[1] + 1,self.selection_end[1] - 1,1 do table.remove(self.lines,self.selection_start[1] + 1) end end self:update_title(true) self.line = self.selection_start[1] self.selection_start = nil self.selection_end = nil end end function editor:message(msg) self:draw_status(msg) beep() keys:anykey() end function editor:draw_status(msg) local h = FONT.LARGE.height + 40 local w = FONT.LARGE:width(msg) + 40 local x = 360 - w / 2 local y = 240 - h / 2 display.rect(x,y,w,h,COLOR.WHITE,COLOR.BLACK) display.print(msg,x+20,y+20,FONT.LARGE,COLOR.WHITE,COLOR.BLACK) end function editor:draw() display.draw(function() self.drawing = true self:draw_main() self.scrollbar:draw() if self.menu_open then self:draw_menu() end if self.debugging then self:draw_debug_error() end self.drawing = false end) end function editor:draw_debug_error() if self.debug_error and self.debug_error_msg ~= nil then display.rect(0,display.height - self.font.height * 2 - 10,display.width,self.font.height*2 + 10,COLOR.RED,COLOR.BLACK) local clipped = display.print(self.debug_error_msg,10,display.height - self.font.height*2 - 5,self.font,COLOR.RED,COLOR.BLACK) if clipped ~= nil then display.print(clipped,10,display.height - self.font.height - 5,self.font,COLOR.RED,COLOR.BLACK) end end end function editor:draw_text(text) self.menu_open = false local pos = self:draw_title() display.rect(0,pos,display.width,display.height-pos,COLOR.BLACK,COLOR.BLACK) for line in text:gmatch("[^\r\n]+") do local clipped = display.print(line,10,pos,self.font) while clipped ~= nil do pos = pos + self.font.height clipped = display.print(clipped,10,pos,self.font) end pos = pos + self.font.height end keys:anykey() self.menu_open = true end function editor:draw_title() local w = FONT.LARGE:width("Q") + 20 local h = 20 + FONT.LARGE.height local bg = COLOR.gray(5) local fg = COLOR.GRAY if self.debugging then if self.debug_error then bg = COLOR.RED else bg = COLOR.DARK_GREEN1_MOD end end display.rect(0,0,display.width,h,fg,bg) if self.menu_open then display.rect(0,0,w,h,fg,COLOR.BLUE) display.print("Q",10,10,FONT.LARGE,COLOR.WHITE,COLOR.BLUE) else display.rect(0,0,w,h,fg,bg) display.print("Q",10,10,FONT.LARGE,COLOR.WHITE,bg) end display.print(self.title,w + 10,10,FONT.LARGE,COLOR.WHITE,bg) return h end function editor:draw_submenu(m,x,y) local bg = COLOR.gray(5) local fg = COLOR.GRAY local f = FONT.LARGE local h = #m * f.height + 10 local w = 200 display.rect(x,y,w,h,fg,bg) x = x + 5 y = y + 5 for i,v in ipairs(m) do if self:menu_enabled(v) then if i == self.submenu_index then display.rect(x,y,w-10,f.height,COLOR.BLUE,COLOR.BLUE) display.print(v,x,y,f,COLOR.WHITE,COLOR.BLUE) else display.print(v,x,y,f,COLOR.WHITE,bg) end else if i == self.submenu_index then display.rect(x,y,w-10,f.height,COLOR.DARK_GRAY,COLOR.DARK_GRAY) display.print(v,x,y,f,COLOR.GRAY,COLOR.DARK_GRAY) else display.print(v,x,y,f,COLOR.DARK_GRAY,bg) end end y = y + f.height end end function editor:draw_menu() local bg = COLOR.gray(5) local fg = COLOR.GRAY local f = FONT.LARGE local h = f.height + 10 local x = 0 local y = self:draw_title() for i,v in ipairs(self.menu) do local w = f:width(v.name) + 20 if i == self.menu_index then display.rect(x,y,w,h,fg,COLOR.BLUE) display.print(v.name,x + 10,y + 5,f,COLOR.WHITE,COLOR.BLUE) self:draw_submenu(v.items,x,y+h) else display.rect(x,y,w,h,fg,bg) display.print(v.name,x + 10,y + 5,f,COLOR.WHITE,bg) end x = x + w end end function editor:draw_selection(line_num,line,x,y,sublines) if self.selection_start ~= nil and self.selection_end ~= nil then if self.selection_start[1] <= line_num and self.selection_end[1] >=line_num then local start_offset = 1 if self.selection_start[1] == line_num then start_offset = self.selection_start[2] end local end_offset = #line if self.selection_end[1] == line_num then end_offset = self.selection_end[2] end if start_offset > 1 then x = x + self.font:width(line:sub(1,start_offset - 1)) end local s = line:sub(start_offset,end_offset) display.print(s,x,y,self.font,COLOR.WHITE,COLOR.BLUE) end end end function editor:draw_main() display.rect(0,0,display.width,display.height,COLOR.BLACK,COLOR.BLACK) local pos = self:draw_title() pos = pos + 10 local pad = 10 local h = self.font.height if self.show_line_numbers then pad = pad + self.font:width("0000") display.line(pad-5,pos,pad-5,display.height,COLOR.BLUE) end if self.lines == nil then return end local scroll = self.scrollbar.value for i,v in ipairs(self.lines) do if i >= scroll then if self.show_line_numbers then if self.breakpoints[i] then display.rect(0,pos,pad - 5,h,COLOR.RED,COLOR.RED) display.print(string.format("%4d",i),0,pos,self.font,COLOR.WHITE,COLOR.RED) else display.print(string.format("%4d",i),0,pos,self.font,COLOR.BLUE,COLOR.BLACK) end end local bg = COLOR.BLACK if self.debugging then if i == self.error_line then bg = COLOR.RED elseif i == self.debug_line then bg = COLOR.GREEN1 end end local clipped = display.print(v,pad,pos,self.font,COLOR.WHITE,bg) local actual_pos = pos local sublines = {} if clipped ~= nil then table.insert(sublines,v:sub(1,#v - #clipped)) end while clipped ~= nil do pos = pos + h local prev = clipped clipped = display.print(clipped,pad,pos,self.font,COLOR.WHITE,bg) if clipped ~= nil then table.insert(sublines,prev:sub(1,#prev - #clipped)) else table.insert(sublines,prev) end end self:draw_selection(i,v,pad,pos,sublines) if i == self.line then if self.col > #v then local x = pad + self.font:width(v) if #sublines > 0 then x = pad + self.font:width(sublines[#sublines]) end display.print(" ",x,pos,self.font,COLOR.BLACK,COLOR.WHITE) else local x = pad local actual_col = self.col local actual_line = v if #sublines > 0 then --figure out what subline we should be on for si,sv in ipairs(sublines) do if actual_col <= #sv then break end actual_pos = actual_pos + self.font.height actual_col = actual_col - #sv actual_line = sv end end if actual_col > 1 then x = x + self.font:width(actual_line:sub(1,actual_col - 1)) end local ch = v:sub(self.col,self.col) display.print(ch,x,actual_pos,self.font,COLOR.BLACK,COLOR.WHITE) end end pos = pos + h if pos >= display.height then return end end end end function handle_error(error) if error == nil then error = "Unknown Error!\n" end local f = FONT.MONO_20 display.rect(0,0,display.width,display.height,COLOR.RED,COLOR.BLACK) local pos = 10 for line in error:gmatch("[^\r\n]+") do local clipped = display.print(line,10,pos,f) while clipped ~= nil do pos = pos + f.height clipped = display.print(clipped,10,pos,f) end pos = pos + f.height end local log = logger("EDITOR.ERR") log:write(error) log:close() keys:anykey() end