···11local json = require 'json'
2233+require('test')
44+35OS = love.system.getOS()
4657-- some global names accessible to Carousel
···2325 if not edit.is_this_love_version_supported() then
2426 unsupported_version_screen_until_keypress()
2527 end
2828+2929+ run_tests()
26302731 love.keyboard.setKeyRepeat(true)
2832
+39-30
rects.lua
···9494 local indent = 0
9595 if editor.indent_wrapped_lines then
9696 indent = editor.font:getWidth(line:match('^%s*'))
9797+ if indent > editor.width*0.5 then indent = 0 end
9798 end
9899 -- keep in sync with defs/*compute_rects_for_line
99100 for pos,char in my_utf8.chars(line, loc.pos) do
100101 local w = editor.font:getWidth(char)
101101- local word_wrap = I.should_word_wrap(editor, line, pos, char, x)
102102- if word_wrap
103103- or x+w > editor.width -- truncate within a word
104104- then
102102+ local wrap = I.should_wrap(editor, line, pos, char, x, w)
103103+ if wrap then
105104 assert(pos > 1)
106105 local filler_rect = {x=x, y=y, dx=editor.width-x, dy=editor.line_height, pos=pos, show_cursor=true}
107107- if word_wrap then
106106+ if wrap.word_wrap then
108107 -- draw word wrap indicator
109108 filler_rect.draw = {
110109 {type='circle', mode='line', fg={0.7,0.7,0.7}, x=x+5+2, y=y+editor.font_height-5, r=2},
···151150 return editor.line_height
152151end
153152154154--- Check whether to word-wrap line at pos which will be positioned at x.
153153+-- Check whether to wrap line before char at pos of width w, which will be
154154+-- positioned at x.
155155--
156156--- We wrap at the start of a word (non-space just after space) if the word
157157--- (non-spaces followed by spaces) wouldn't fit in the rest of the line.
158158---
159159--- x lies between 0 and editor.width.
160160---
156156+-- Precondition:
157157+-- 0 <= x <= editor.width
161158-- Postcondition:
162159-- Current line is not wider than editor.width
163160--
164161-- Desired properties in priority order:
165162-- Next line doesn't start with whitespace
166166--- Current line ends with whitespace (a.k.a. word wrap)
167163-- Current line is close to full
164164+-- Current line ends with whitespace (a.k.a. word wrap)
168165-- None of these is guaranteed. But we should never satisfy a lower priority
169166-- before a higher one.
170170-function I.should_word_wrap(editor, line, pos, char, x)
171171- if char:match('%s') then return false end
172172- if pos == 1 then return false end
173173- if my_utf8.match_at(line, pos-1, '%S') then return false end
174174- local offset = my_utf8.offset(line, pos)
175175- -- most of the time a word is printable chars + whitespace
176176- local s = line:match('%S+%s*', offset)
177177- assert(s)
178178- local w = editor.font:getWidth(s)
179179- if x+w < editor.width then return false end
180180- if w > editor.width then return false end -- we're going to need to truncate the next word anyway
181181- if x < 0.8*editor.width then
182182- local s2 = line:match('%S+', offset)
183183- local w2 = editor.font:getWidth(s2)
184184- if x+w2 > editor.width then
185185- -- there'll be some non-whitespace left over for the next line
186186- return false
167167+function I.should_wrap(editor, line, pos, char, x, w)
168168+ assert(x <= editor.width)
169169+ if pos == 1 then return nil end
170170+ if x+w > editor.width then
171171+ -- return non-nil to ensure postcondition
172172+ return {word_wrap=my_utf8.match_at(line, pos-1, '%s') and char:match('%S')}
173173+ end
174174+ -- don't try to wrap short lines
175175+ if x < 0.8*editor.width then return nil end
176176+ if my_utf8.match_at(line, pos-1, '%s') and char:match('%S') then
177177+ -- at start of word
178178+ local offset = my_utf8.offset(line, pos)
179179+ local word_and_spaces = line:match('%S+%s*', offset)
180180+ local width_with_spaces = editor.font:getWidth(word_and_spaces)
181181+ if x+width_with_spaces > editor.width then
182182+ return {word_wrap=true}
183183+ end
184184+ elseif char:match('%S') then
185185+ -- in middle of word
186186+ local offset = my_utf8.offset(line, pos)
187187+ local word_and_spaces = line:match('%S+%s*', offset)
188188+ local width_with_spaces = editor.font:getWidth(word_and_spaces)
189189+ local word_no_space = line:match('%S+', offset)
190190+ local width_without_spaces = editor.font:getWidth(word_no_space)
191191+ -- ensure we don't start next line with spaces
192192+ if x+width_without_spaces < editor.width
193193+ and x+width_with_spaces > editor.width
194194+ then
195195+ return {word_wrap=nil}
187196 end
188197 end
189189- return true
198198+ return nil
190199end
191200192201return rects
+66
test.lua
···11+-- Some primitives for tests.
22+33+function run_tests()
44+ local sorted_names = {}
55+ for name,binding in pairs(_G) do
66+ if name:find('test_') == 1 then
77+ table.insert(sorted_names, name)
88+ end
99+ end
1010+ table.sort(sorted_names)
1111+ Test_errors = {}
1212+ for _,name in ipairs(sorted_names) do
1313+ xpcall(_G[name], function(err) prepend_debug_info_to_test_failure(name, err) end)
1414+ end
1515+ if #Test_errors > 0 then
1616+ error(('There were %d test failures:\n%s'):format(#Test_errors, table.concat(Test_errors)))
1717+ end
1818+end
1919+2020+function prepend_debug_info_to_test_failure(test_name, err)
2121+ local err_without_line_number = err:gsub('^[^:]*:[^:]*: ', '')
2222+ local stack_trace = debug.traceback('', --[[stack frame]]5) -- most likely to be useful, but set to 0 for a complete stack trace
2323+ local file_and_line_number = stack_trace:gsub('stack traceback:\n', ''):gsub(': .*', '')
2424+ local full_error = file_and_line_number..':'..test_name..' -- '..err_without_line_number
2525+ -- uncomment this line for a complete stack trace
2626+--? local full_error = file_and_line_number..':'..test_name..' -- '..err_without_line_number..'\t\t'..stack_trace:gsub('\n', '\n\t\t')
2727+ table.insert(Test_errors, full_error)
2828+end
2929+3030+3131+function check(x, msg)
3232+ if not x then
3333+ error(msg)
3434+ end
3535+end
3636+3737+function check_nil(x, msg)
3838+ if x ~= nil then
3939+ error(msg..'; should be nil but got "'..x..'"')
4040+ end
4141+end
4242+4343+function check_eq(x, expected, msg)
4444+ if not eq(x, expected) then
4545+ error(msg..'; should be "'..expected..'" but got "'..x..'"')
4646+ end
4747+end
4848+4949+function eq(a, b)
5050+ if type(a) ~= type(b) then return false end
5151+ if type(a) == 'table' then
5252+ if #a ~= #b then return false end
5353+ for k, v in pairs(a) do
5454+ if not eq(b[k], v) then
5555+ return false
5656+ end
5757+ end
5858+ for k, v in pairs(b) do
5959+ if not eq(a[k], v) then
6060+ return false
6161+ end
6262+ end
6363+ return true
6464+ end
6565+ return a == b
6666+end