Template repo for tiny cross-platform apps that can be modified on phone, tablet or computer.
2
fork

Configure Feed

Select the types of activity you want to include in your feed.

Merge carousel2.love

+116 -37
+7 -7
edit.lua
··· 1257 1257 return endpos-#pat+1 1258 1258 end 1259 1259 1260 - --? function test_rfind() 1261 - --? check_eq(I.rfind('abc', ''), 3, 'empty pattern') 1262 - --? check_eq(I.rfind('abc', 'c'), 3, 'final char') 1263 - --? check_eq(I.rfind('acbc', 'c', 3), 2, 'previous char') 1264 - --? check_nil(I.rfind('abc', 'd'), 'missing char') 1265 - --? check_nil(I.rfind('abc', 'c', 2), 'no more char') 1266 - --? end 1260 + function test_rfind() 1261 + check_eq(I.rfind('abc', ''), 3, 'empty pattern') 1262 + check_eq(I.rfind('abc', 'c'), 3, 'final char') 1263 + check_eq(I.rfind('acbc', 'c', 3), 2, 'previous char') 1264 + check_nil(I.rfind('abc', 'd'), 'missing char') 1265 + check_nil(I.rfind('abc', 'c', 2), 'no more char') 1266 + end 1267 1267 1268 1268 -- helpers for selecting portions of text 1269 1269
+4
main.lua
··· 1 1 local json = require 'json' 2 2 3 + require('test') 4 + 3 5 OS = love.system.getOS() 4 6 5 7 -- some global names accessible to Carousel ··· 23 25 if not edit.is_this_love_version_supported() then 24 26 unsupported_version_screen_until_keypress() 25 27 end 28 + 29 + run_tests() 26 30 27 31 love.keyboard.setKeyRepeat(true) 28 32
+39 -30
rects.lua
··· 94 94 local indent = 0 95 95 if editor.indent_wrapped_lines then 96 96 indent = editor.font:getWidth(line:match('^%s*')) 97 + if indent > editor.width*0.5 then indent = 0 end 97 98 end 98 99 -- keep in sync with defs/*compute_rects_for_line 99 100 for pos,char in my_utf8.chars(line, loc.pos) do 100 101 local w = editor.font:getWidth(char) 101 - local word_wrap = I.should_word_wrap(editor, line, pos, char, x) 102 - if word_wrap 103 - or x+w > editor.width -- truncate within a word 104 - then 102 + local wrap = I.should_wrap(editor, line, pos, char, x, w) 103 + if wrap then 105 104 assert(pos > 1) 106 105 local filler_rect = {x=x, y=y, dx=editor.width-x, dy=editor.line_height, pos=pos, show_cursor=true} 107 - if word_wrap then 106 + if wrap.word_wrap then 108 107 -- draw word wrap indicator 109 108 filler_rect.draw = { 110 109 {type='circle', mode='line', fg={0.7,0.7,0.7}, x=x+5+2, y=y+editor.font_height-5, r=2}, ··· 151 150 return editor.line_height 152 151 end 153 152 154 - -- Check whether to word-wrap line at pos which will be positioned at x. 153 + -- Check whether to wrap line before char at pos of width w, which will be 154 + -- positioned at x. 155 155 -- 156 - -- We wrap at the start of a word (non-space just after space) if the word 157 - -- (non-spaces followed by spaces) wouldn't fit in the rest of the line. 158 - -- 159 - -- x lies between 0 and editor.width. 160 - -- 156 + -- Precondition: 157 + -- 0 <= x <= editor.width 161 158 -- Postcondition: 162 159 -- Current line is not wider than editor.width 163 160 -- 164 161 -- Desired properties in priority order: 165 162 -- Next line doesn't start with whitespace 166 - -- Current line ends with whitespace (a.k.a. word wrap) 167 163 -- Current line is close to full 164 + -- Current line ends with whitespace (a.k.a. word wrap) 168 165 -- None of these is guaranteed. But we should never satisfy a lower priority 169 166 -- before a higher one. 170 - function I.should_word_wrap(editor, line, pos, char, x) 171 - if char:match('%s') then return false end 172 - if pos == 1 then return false end 173 - if my_utf8.match_at(line, pos-1, '%S') then return false end 174 - local offset = my_utf8.offset(line, pos) 175 - -- most of the time a word is printable chars + whitespace 176 - local s = line:match('%S+%s*', offset) 177 - assert(s) 178 - local w = editor.font:getWidth(s) 179 - if x+w < editor.width then return false end 180 - if w > editor.width then return false end -- we're going to need to truncate the next word anyway 181 - if x < 0.8*editor.width then 182 - local s2 = line:match('%S+', offset) 183 - local w2 = editor.font:getWidth(s2) 184 - if x+w2 > editor.width then 185 - -- there'll be some non-whitespace left over for the next line 186 - return false 167 + function I.should_wrap(editor, line, pos, char, x, w) 168 + assert(x <= editor.width) 169 + if pos == 1 then return nil end 170 + if x+w > editor.width then 171 + -- return non-nil to ensure postcondition 172 + return {word_wrap=my_utf8.match_at(line, pos-1, '%s') and char:match('%S')} 173 + end 174 + -- don't try to wrap short lines 175 + if x < 0.8*editor.width then return nil end 176 + if my_utf8.match_at(line, pos-1, '%s') and char:match('%S') then 177 + -- at start of word 178 + local offset = my_utf8.offset(line, pos) 179 + local word_and_spaces = line:match('%S+%s*', offset) 180 + local width_with_spaces = editor.font:getWidth(word_and_spaces) 181 + if x+width_with_spaces > editor.width then 182 + return {word_wrap=true} 183 + end 184 + elseif char:match('%S') then 185 + -- in middle of word 186 + local offset = my_utf8.offset(line, pos) 187 + local word_and_spaces = line:match('%S+%s*', offset) 188 + local width_with_spaces = editor.font:getWidth(word_and_spaces) 189 + local word_no_space = line:match('%S+', offset) 190 + local width_without_spaces = editor.font:getWidth(word_no_space) 191 + -- ensure we don't start next line with spaces 192 + if x+width_without_spaces < editor.width 193 + and x+width_with_spaces > editor.width 194 + then 195 + return {word_wrap=nil} 187 196 end 188 197 end 189 - return true 198 + return nil 190 199 end 191 200 192 201 return rects
+66
test.lua
··· 1 + -- Some primitives for tests. 2 + 3 + function run_tests() 4 + local sorted_names = {} 5 + for name,binding in pairs(_G) do 6 + if name:find('test_') == 1 then 7 + table.insert(sorted_names, name) 8 + end 9 + end 10 + table.sort(sorted_names) 11 + Test_errors = {} 12 + for _,name in ipairs(sorted_names) do 13 + xpcall(_G[name], function(err) prepend_debug_info_to_test_failure(name, err) end) 14 + end 15 + if #Test_errors > 0 then 16 + error(('There were %d test failures:\n%s'):format(#Test_errors, table.concat(Test_errors))) 17 + end 18 + end 19 + 20 + function prepend_debug_info_to_test_failure(test_name, err) 21 + local err_without_line_number = err:gsub('^[^:]*:[^:]*: ', '') 22 + local stack_trace = debug.traceback('', --[[stack frame]]5) -- most likely to be useful, but set to 0 for a complete stack trace 23 + local file_and_line_number = stack_trace:gsub('stack traceback:\n', ''):gsub(': .*', '') 24 + local full_error = file_and_line_number..':'..test_name..' -- '..err_without_line_number 25 + -- uncomment this line for a complete stack trace 26 + --? local full_error = file_and_line_number..':'..test_name..' -- '..err_without_line_number..'\t\t'..stack_trace:gsub('\n', '\n\t\t') 27 + table.insert(Test_errors, full_error) 28 + end 29 + 30 + 31 + function check(x, msg) 32 + if not x then 33 + error(msg) 34 + end 35 + end 36 + 37 + function check_nil(x, msg) 38 + if x ~= nil then 39 + error(msg..'; should be nil but got "'..x..'"') 40 + end 41 + end 42 + 43 + function check_eq(x, expected, msg) 44 + if not eq(x, expected) then 45 + error(msg..'; should be "'..expected..'" but got "'..x..'"') 46 + end 47 + end 48 + 49 + function eq(a, b) 50 + if type(a) ~= type(b) then return false end 51 + if type(a) == 'table' then 52 + if #a ~= #b then return false end 53 + for k, v in pairs(a) do 54 + if not eq(b[k], v) then 55 + return false 56 + end 57 + end 58 + for k, v in pairs(b) do 59 + if not eq(a[k], v) then 60 + return false 61 + end 62 + end 63 + return true 64 + end 65 + return a == b 66 + end