about summary refs log tree commit diff stats
path: root/main.lua
diff options
context:
space:
mode:
authorKartik K. Agaram <vc@akkartik.com>2022-09-03 14:13:22 -0700
committerKartik K. Agaram <vc@akkartik.com>2022-09-03 14:13:22 -0700
commite1c5a42f311fdafd88506726bbe480f3fcc2d1a3 (patch)
tree6628729cc55947d0bd5d306704e88b57680c3514 /main.lua
parent9c72ff1bb4fc1ba08acfb0324079da6fe49f3a4a (diff)
downloadlines.love-e1c5a42f311fdafd88506726bbe480f3fcc2d1a3.tar.gz
editing source code from within the app
integrated from pong.love via text.love:
  https://merveilles.town/@akkartik/108933336531898243
Diffstat (limited to 'main.lua')
-rw-r--r--main.lua367
1 files changed, 205 insertions, 162 deletions
diff --git a/main.lua b/main.lua
index a41d93b..e3f1d93 100644
--- a/main.lua
+++ b/main.lua
@@ -1,217 +1,260 @@
-utf8 = require 'utf8'
-
-require 'app'
-require 'test'
+-- Entrypoint for the app. You can edit this file from within the app if
+-- you're careful.
 
-require 'keychord'
-require 'button'
+-- files that come with LÖVE; we can't edit those from within the app
+utf8 = require 'utf8'
 
-require 'main_tests'
+function load_file_from_source_or_save_directory(filename)
+  local contents = love.filesystem.read(filename)
+  local code, err = loadstring(contents, filename)
+  if code == nil then
+    error(err)
+  end
+  return code()
+end
 
--- delegate most business logic to a layer that can be reused by other projects
-require 'edit'
-Editor_state = {}
+json = load_file_from_source_or_save_directory('json.lua')
 
--- called both in tests and real run
-function App.initialize_globals()
-  -- tests currently mostly clear their own state
+load_file_from_source_or_save_directory('app.lua')
+load_file_from_source_or_save_directory('test.lua')
 
-  -- a few text objects we can avoid recomputing unless the font changes
-  Text_cache = {}
+load_file_from_source_or_save_directory('keychord.lua')
+load_file_from_source_or_save_directory('button.lua')
 
-  -- blinking cursor
-  Cursor_time = 0
-
-  -- for hysteresis in a few places
-  Last_resize_time = App.getTime()
-  Last_focus_time = App.getTime()  -- https://love2d.org/forums/viewtopic.php?p=249700
-end
-
--- called only for real run
-function App.initialize(arg)
-  love.keyboard.setTextInput(true)  -- bring up keyboard on touch screen
-  love.keyboard.setKeyRepeat(true)
-
-  love.graphics.setBackgroundColor(1,1,1)
+-- both sides require (different parts of) the logging framework
+load_file_from_source_or_save_directory('log.lua')
 
+-- but some files we want to only load sometimes
+function App.load()
   if love.filesystem.getInfo('config') then
-    load_settings()
-  else
-    initialize_default_settings()
-  end
-
-  if #arg > 0 then
-    Editor_state.filename = arg[1]
-    load_from_disk(Editor_state)
-    Text.redraw_all(Editor_state)
-    Editor_state.screen_top1 = {line=1, pos=1}
-    Editor_state.cursor1 = {line=1, pos=1}
-    edit.fixup_cursor(Editor_state)
-  else
-    load_from_disk(Editor_state)
-    Text.redraw_all(Editor_state)
-    if Editor_state.cursor1.line > #Editor_state.lines or Editor_state.lines[Editor_state.cursor1.line].mode ~= 'text' then
-      edit.fixup_cursor(Editor_state)
-    end
+    Settings = json.decode(love.filesystem.read('config'))
+    Current_app = Settings.current_app
   end
-  love.window.setTitle('lines.love - '..Editor_state.filename)
 
-  if #arg > 1 then
-    print('ignoring commandline args after '..arg[1])
+  if Current_app == nil then
+    Current_app = 'run'
   end
 
-  if rawget(_G, 'jit') then
-    jit.off()
-    jit.flush()
+  if Current_app == 'run' then
+    load_file_from_source_or_save_directory('file.lua')
+    load_file_from_source_or_save_directory('run.lua')
+      load_file_from_source_or_save_directory('edit.lua')
+      load_file_from_source_or_save_directory('text.lua')
+        load_file_from_source_or_save_directory('search.lua')
+        load_file_from_source_or_save_directory('select.lua')
+        load_file_from_source_or_save_directory('undo.lua')
+      load_file_from_source_or_save_directory('icons.lua')
+      load_file_from_source_or_save_directory('text_tests.lua')
+    load_file_from_source_or_save_directory('run_tests.lua')
+    load_file_from_source_or_save_directory('drawing.lua')
+      load_file_from_source_or_save_directory('geom.lua')
+      load_file_from_source_or_save_directory('help.lua')
+    load_file_from_source_or_save_directory('drawing_tests.lua')
+  else
+    load_file_from_source_or_save_directory('source_file.lua')
+    load_file_from_source_or_save_directory('source.lua')
+      load_file_from_source_or_save_directory('commands.lua')
+      load_file_from_source_or_save_directory('source_edit.lua')
+      load_file_from_source_or_save_directory('log_browser.lua')
+      load_file_from_source_or_save_directory('source_text.lua')
+        load_file_from_source_or_save_directory('search.lua')
+        load_file_from_source_or_save_directory('select.lua')
+        load_file_from_source_or_save_directory('source_undo.lua')
+        load_file_from_source_or_save_directory('colorize.lua')
+      load_file_from_source_or_save_directory('source_text_tests.lua')
+    load_file_from_source_or_save_directory('source_tests.lua')
   end
 end
 
-function load_settings()
-  local settings = json.decode(love.filesystem.read('config'))
-  love.graphics.setFont(love.graphics.newFont(settings.font_height))
-  -- maximize window to determine maximum allowable dimensions
-  App.screen.width, App.screen.height, App.screen.flags = love.window.getMode()
-  -- set up desired window dimensions
-  love.window.setPosition(settings.x, settings.y, settings.displayindex)
-  App.screen.flags.resizable = true
-  App.screen.flags.minwidth = math.min(App.screen.width, 200)
-  App.screen.flags.minheight = math.min(App.screen.width, 200)
-  App.screen.width, App.screen.height = settings.width, settings.height
-  love.window.setMode(App.screen.width, App.screen.height, App.screen.flags)
-  Editor_state = edit.initialize_state(Margin_top, Margin_left, App.screen.width-Margin_right, settings.font_height, math.floor(settings.font_height*1.3))
-  Editor_state.filename = settings.filename
-  Editor_state.screen_top1 = settings.screen_top
-  Editor_state.cursor1 = settings.cursor
-end
+function App.initialize_globals()
+  if Current_app == 'run' then
+    run.initialize_globals()
+  elseif Current_app == 'source' then
+    source.initialize_globals()
+  else
+    assert(false, 'unknown app "'..Current_app..'"')
+  end
 
-function initialize_default_settings()
-  local font_height = 20
-  love.graphics.setFont(love.graphics.newFont(font_height))
-  local em = App.newText(love.graphics.getFont(), 'm')
-  initialize_window_geometry(App.width(em))
-  Editor_state = edit.initialize_state(Margin_top, Margin_left, App.screen.width-Margin_right)
-  Editor_state.font_height = font_height
-  Editor_state.line_height = math.floor(font_height*1.3)
-  Editor_state.em = em
+  -- for hysteresis in a few places
+  Last_focus_time = App.getTime()  -- https://love2d.org/forums/viewtopic.php?p=249700
+  Last_resize_time = App.getTime()
 end
 
-function initialize_window_geometry(em_width)
-  -- maximize window
-  love.window.setMode(0, 0)  -- maximize
-  App.screen.width, App.screen.height, App.screen.flags = love.window.getMode()
-  -- shrink height slightly to account for window decoration
-  App.screen.height = App.screen.height-100
-  App.screen.width = 40*em_width
-  App.screen.flags.resizable = true
-  App.screen.flags.minwidth = math.min(App.screen.width, 200)
-  App.screen.flags.minheight = math.min(App.screen.width, 200)
-  love.window.setMode(App.screen.width, App.screen.height, App.screen.flags)
+function App.initialize(arg)
+  if Current_app == 'run' then
+    run.initialize(arg)
+  elseif Current_app == 'source' then
+    source.initialize(arg)
+  else
+    assert(false, 'unknown app "'..Current_app..'"')
+  end
+  love.window.setTitle('text.love - '..Current_app)
 end
 
-function App.resize(w, h)
---?   print(("Window resized to width: %d and height: %d."):format(w, h))
-  App.screen.width, App.screen.height = w, h
-  Text.redraw_all(Editor_state)
-  Editor_state.selection1 = {}  -- no support for shift drag while we're resizing
-  Editor_state.right = App.screen.width-Margin_right
-  Editor_state.width = Editor_state.right-Editor_state.left
-  Text.tweak_screen_top_and_cursor(Editor_state, Editor_state.left, Editor_state.right)
+function App.resize(w,h)
+  if Current_app == 'run' then
+    if run.resize then run.resize(w,h) end
+  elseif Current_app == 'source' then
+    if source.resize then source.resize(w,h) end
+  else
+    assert(false, 'unknown app "'..Current_app..'"')
+  end
   Last_resize_time = App.getTime()
 end
 
 function App.filedropped(file)
-  -- first make sure to save edits on any existing file
-  if Editor_state.next_save then
-    save_to_disk(Editor_state)
-  end
-  -- clear the slate for the new file
-  App.initialize_globals()
-  Editor_state.filename = file:getFilename()
-  file:open('r')
-  Editor_state.lines = load_from_file(file)
-  file:close()
-  Text.redraw_all(Editor_state)
-  edit.fixup_cursor(Editor_state)
-  love.window.setTitle('lines.love - '..Editor_state.filename)
+  if Current_app == 'run' then
+    if run.filedropped then run.filedropped(file) end
+  elseif Current_app == 'source' then
+    if source.filedropped then source.filedropped(file) end
+  else
+    assert(false, 'unknown app "'..Current_app..'"')
+  end
+  love.window.setTitle('text.love - '..Current_app)
+end
+
+function App.focus(in_focus)
+  if in_focus then
+    Last_focus_time = App.getTime()
+  end
+  if Current_app == 'run' then
+    if run.focus then run.focus(in_focus) end
+  elseif Current_app == 'source' then
+    if source.focus then source.focus(in_focus) end
+  else
+    assert(false, 'unknown app "'..Current_app..'"')
+  end
 end
 
 function App.draw()
-  edit.draw(Editor_state)
+  if Current_app == 'run' then
+    run.draw()
+  elseif Current_app == 'source' then
+    source.draw()
+  else
+    assert(false, 'unknown app "'..Current_app..'"')
+  end
 end
 
 function App.update(dt)
-  Cursor_time = Cursor_time + dt
   -- some hysteresis while resizing
   if App.getTime() < Last_resize_time + 0.1 then
     return
   end
-  edit.update(Editor_state, dt)
-end
-
-function love.quit()
-  edit.quit(Editor_state)
-  -- save some important settings
-  local x,y,displayindex = love.window.getPosition()
-  local filename = Editor_state.filename
-  if filename:sub(1,1) ~= '/' then
-    filename = love.filesystem.getWorkingDirectory()..'/'..filename  -- '/' should work even on Windows
-  end
-  local settings = {
-    x=x, y=y, displayindex=displayindex,
-    width=App.screen.width, height=App.screen.height,
-    font_height=Editor_state.font_height,
-    filename=filename,
-    screen_top=Editor_state.screen_top1, cursor=Editor_state.cursor1}
-  love.filesystem.write('config', json.encode(settings))
-end
-
-function App.mousepressed(x,y, mouse_button)
-  Cursor_time = 0  -- ensure cursor is visible immediately after it moves
-  return edit.mouse_pressed(Editor_state, x,y, mouse_button)
-end
-
-function App.mousereleased(x,y, mouse_button)
-  Cursor_time = 0  -- ensure cursor is visible immediately after it moves
-  return edit.mouse_released(Editor_state, x,y, mouse_button)
+  --
+  if Current_app == 'run' then
+    run.update(dt)
+  elseif Current_app == 'source' then
+    source.update(dt)
+  else
+    assert(false, 'unknown app "'..Current_app..'"')
+  end
 end
 
-function App.focus(in_focus)
-  if in_focus then
-    Last_focus_time = App.getTime()
+function App.keychord_pressed(chord, key)
+  -- ignore events for some time after window in focus (mostly alt-tab)
+  if App.getTime() < Last_focus_time + 0.01 then
+    return
+  end
+  --
+  if chord == 'C-e' then
+    -- carefully save settings
+    if Current_app == 'run' then
+      local source_settings = Settings.source
+      Settings = run.settings()
+      Settings.source = source_settings
+      if run.quit then run.quit() end
+      Current_app = 'source'
+    elseif Current_app == 'source' then
+      Settings.source = source.settings()
+      if source.quit then source.quit() end
+      Current_app = 'run'
+    else
+      assert(false, 'unknown app "'..Current_app..'"')
+    end
+    Settings.current_app = Current_app
+    love.filesystem.write('config', json.encode(Settings))
+    -- reboot
+    load_file_from_source_or_save_directory('main.lua')
+    App.undo_initialize()
+    App.run_tests_and_initialize()
+    return
+  end
+  if Current_app == 'run' then
+    if run.keychord_pressed then run.keychord_pressed(chord, key) end
+  elseif Current_app == 'source' then
+    if source.keychord_pressed then source.keychord_pressed(chord, key) end
+  else
+    assert(false, 'unknown app "'..Current_app..'"')
   end
 end
 
 function App.textinput(t)
-  -- ignore events for some time after window in focus
+  -- ignore events for some time after window in focus (mostly alt-tab)
   if App.getTime() < Last_focus_time + 0.01 then
     return
   end
-  Cursor_time = 0  -- ensure cursor is visible immediately after it moves
-  return edit.textinput(Editor_state, t)
+  --
+  if Current_app == 'run' then
+    if run.textinput then run.textinput(t) end
+  elseif Current_app == 'source' then
+    if source.textinput then source.textinput(t) end
+  else
+    assert(false, 'unknown app "'..Current_app..'"')
+  end
 end
 
-function App.keychord_pressed(chord, key)
-  -- ignore events for some time after window in focus
+function App.keyreleased(chord, key)
+  -- ignore events for some time after window in focus (mostly alt-tab)
   if App.getTime() < Last_focus_time + 0.01 then
     return
   end
-  Cursor_time = 0  -- ensure cursor is visible immediately after it moves
-  return edit.keychord_pressed(Editor_state, chord, key)
+  --
+  if Current_app == 'run' then
+    if run.key_released then run.key_released(chord, key) end
+  elseif Current_app == 'source' then
+    if source.key_released then source.key_released(chord, key) end
+  else
+    assert(false, 'unknown app "'..Current_app..'"')
+  end
 end
 
-function App.keyreleased(key, scancode)
-  -- ignore events for some time after window in focus
-  if App.getTime() < Last_focus_time + 0.01 then
-    return
+function App.mousepressed(x,y, mouse_button)
+--?   print('mouse press', x,y)
+  if Current_app == 'run' then
+    if run.mouse_pressed then run.mouse_pressed(x,y, mouse_button) end
+  elseif Current_app == 'source' then
+    if source.mouse_pressed then source.mouse_pressed(x,y, mouse_button) end
+  else
+    assert(false, 'unknown app "'..Current_app..'"')
+  end
+end
+
+function App.mousereleased(x,y, mouse_button)
+  if Current_app == 'run' then
+    if run.mouse_released then run.mouse_released(x,y, mouse_button) end
+  elseif Current_app == 'source' then
+    if source.mouse_released then source.mouse_released(x,y, mouse_button) end
+  else
+    assert(false, 'unknown app "'..Current_app..'"')
   end
-  Cursor_time = 0  -- ensure cursor is visible immediately after it moves
-  return edit.key_released(Editor_state, key, scancode)
 end
 
--- use this sparingly
-function to_text(s)
-  if Text_cache[s] == nil then
-    Text_cache[s] = App.newText(love.graphics.getFont(), s)
+function love.quit()
+  if Current_app == 'run' then
+    local source_settings = Settings.source
+    Settings = run.settings()
+    Settings.source = source_settings
+  else
+    Settings.source = source.settings()
+  end
+  Settings.current_app = Current_app
+  love.filesystem.write('config', json.encode(Settings))
+  if Current_app == 'run' then
+    if run.quit then run.quit() end
+  elseif Current_app == 'source' then
+    if source.quit then source.quit() end
+  else
+    assert(false, 'unknown app "'..Current_app..'"')
   end
-  return Text_cache[s]
 end