]> git.lizzy.rs Git - micro.git/blobdiff - runtime/plugins/autoclose/autoclose.lua
Merge branch 'python-highlight-zero' of https://github.com/a11ce/micro into a11ce...
[micro.git] / runtime / plugins / autoclose / autoclose.lua
index e35dc1775feef31fb56c69c623f870efb561c418..531b7601962d0a0881d773ccccc05587be47c30a 100644 (file)
--- $Id: utf8.lua 179 2009-04-03 18:10:03Z pasta $
---
--- Provides UTF-8 aware string functions implemented in pure lua:
--- * utf8len(s)
--- * utf8sub(s, i, j)
--- * utf8reverse(s)
--- * utf8char(unicode)
--- * utf8unicode(s, i, j)
--- * utf8gensub(s, sub_len)
--- * utf8find(str, regex, init, plain)
--- * utf8match(str, regex, init)
--- * utf8gmatch(str, regex, all)
--- * utf8gsub(str, regex, repl, limit)
---
--- If utf8data.lua (containing the lower<->upper case mappings) is loaded, these
--- additional functions are available:
--- * utf8upper(s)
--- * utf8lower(s)
---
--- All functions behave as their non UTF-8 aware counterparts with the exception
--- that UTF-8 characters are used instead of bytes for all units.
-
---[[
-Copyright (c) 2006-2007, Kyle Smith
-All rights reserved.
-
-Contributors:
-    Alimov Stepan
-
-Redistribution and use in source and binary forms, with or without
-modification, are permitted provided that the following conditions are met:
-
-    * Redistributions of source code must retain the above copyright notice,
-      this list of conditions and the following disclaimer.
-    * Redistributions in binary form must reproduce the above copyright
-      notice, this list of conditions and the following disclaimer in the
-      documentation and/or other materials provided with the distribution.
-    * Neither the name of the author nor the names of its contributors may be
-      used to endorse or promote products derived from this software without
-      specific prior written permission.
-
-THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
-AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
-IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
-DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
-FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
-DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
-SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
-CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
-OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
-OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
---]]
-
--- ABNF from RFC 3629
---
--- UTF8-octets = *( UTF8-char )
--- UTF8-char   = UTF8-1 / UTF8-2 / UTF8-3 / UTF8-4
--- UTF8-1      = %x00-7F
--- UTF8-2      = %xC2-DF UTF8-tail
--- UTF8-3      = %xE0 %xA0-BF UTF8-tail / %xE1-EC 2( UTF8-tail ) /
---               %xED %x80-9F UTF8-tail / %xEE-EF 2( UTF8-tail )
--- UTF8-4      = %xF0 %x90-BF 2( UTF8-tail ) / %xF1-F3 3( UTF8-tail ) /
---               %xF4 %x80-8F 2( UTF8-tail )
--- UTF8-tail   = %x80-BF
---
-
-local byte    = string.byte
-local char    = string.char
-local dump    = string.dump
-local find    = string.find
-local format  = string.format
-local len     = string.len
-local lower   = string.lower
-local rep     = string.rep
-local sub     = string.sub
-local upper   = string.upper
-
--- returns the number of bytes used by the UTF-8 character at byte i in s
--- also doubles as a UTF-8 character validator
-local function utf8charbytes (s, i)
-    -- argument defaults
-    i = i or 1
-
-    -- argument checking
-    if type(s) ~= "string" then
-        error("bad argument #1 to 'utf8charbytes' (string expected, got ".. type(s).. ")")
-    end
-    if type(i) ~= "number" then
-        error("bad argument #2 to 'utf8charbytes' (number expected, got ".. type(i).. ")")
-    end
-
-    local c = byte(s, i)
-
-    -- determine bytes needed for character, based on RFC 3629
-    -- validate byte 1
-    if c > 0 and c <= 127 then
-        -- UTF8-1
-        return 1
-
-    elseif c >= 194 and c <= 223 then
-        -- UTF8-2
-        local c2 = byte(s, i + 1)
-
-        if not c2 then
-            error("UTF-8 string terminated early")
-        end
-
-        -- validate byte 2
-        if c2 < 128 or c2 > 191 then
-            error("Invalid UTF-8 character")
-        end
-
-        return 2
-
-    elseif c >= 224 and c <= 239 then
-        -- UTF8-3
-        local c2 = byte(s, i + 1)
-        local c3 = byte(s, i + 2)
-
-        if not c2 or not c3 then
-            error("UTF-8 string terminated early")
-        end
-
-        -- validate byte 2
-        if c == 224 and (c2 < 160 or c2 > 191) then
-            error("Invalid UTF-8 character")
-        elseif c == 237 and (c2 < 128 or c2 > 159) then
-            error("Invalid UTF-8 character")
-        elseif c2 < 128 or c2 > 191 then
-            error("Invalid UTF-8 character")
-        end
-
-        -- validate byte 3
-        if c3 < 128 or c3 > 191 then
-            error("Invalid UTF-8 character")
-        end
-
-        return 3
-
-    elseif c >= 240 and c <= 244 then
-        -- UTF8-4
-        local c2 = byte(s, i + 1)
-        local c3 = byte(s, i + 2)
-        local c4 = byte(s, i + 3)
-
-        if not c2 or not c3 or not c4 then
-            error("UTF-8 string terminated early")
-        end
-
-        -- validate byte 2
-        if c == 240 and (c2 < 144 or c2 > 191) then
-            error("Invalid UTF-8 character")
-        elseif c == 244 and (c2 < 128 or c2 > 143) then
-            error("Invalid UTF-8 character")
-        elseif c2 < 128 or c2 > 191 then
-            error("Invalid UTF-8 character")
-        end
-
-        -- validate byte 3
-        if c3 < 128 or c3 > 191 then
-            error("Invalid UTF-8 character")
-        end
-
-        -- validate byte 4
-        if c4 < 128 or c4 > 191 then
-            error("Invalid UTF-8 character")
-        end
-
-        return 4
-
-    else
-        error("Invalid UTF-8 character")
-    end
-end
-
--- returns the number of characters in a UTF-8 string
-local function utf8len (s)
-    -- argument checking
-    if type(s) ~= "string" then
-        for k,v in pairs(s) do print('"',tostring(k),'"',tostring(v),'"') end
-        error("bad argument #1 to 'utf8len' (string expected, got ".. type(s).. ")")
-    end
-
-    local pos = 1
-    local bytes = len(s)
-    local length = 0
-
-    while pos <= bytes do
-        length = length + 1
-        pos = pos + utf8charbytes(s, pos)
-    end
-
-    return length
-end
-
--- functions identically to string.sub except that i and j are UTF-8 characters
--- instead of bytes
-local function utf8sub (s, i, j)
-    -- argument defaults
-    j = j or -1
-
-    local pos = 1
-    local bytes = len(s)
-    local length = 0
-
-    -- only set l if i or j is negative
-    local l = (i >= 0 and j >= 0) or utf8len(s)
-    local startChar = (i >= 0) and i or l + i + 1
-    local endChar   = (j >= 0) and j or l + j + 1
-
-    -- can't have start before end!
-    if startChar > endChar then
-        return ""
-    end
-
-    -- byte offsets to pass to string.sub
-    local startByte,endByte = 1,bytes
-
-    while pos <= bytes do
-        length = length + 1
-
-        if length == startChar then
-            startByte = pos
-        end
-
-        pos = pos + utf8charbytes(s, pos)
-
-        if length == endChar then
-            endByte = pos - 1
-            break
-        end
-    end
-
-    if startChar > length then startByte = bytes+1   end
-    if endChar   < 1      then endByte   = 0         end
-
-    return sub(s, startByte, endByte)
-end
-
---[[
--- replace UTF-8 characters based on a mapping table
-local function utf8replace (s, mapping)
-    -- argument checking
-    if type(s) ~= "string" then
-        error("bad argument #1 to 'utf8replace' (string expected, got ".. type(s).. ")")
-    end
-    if type(mapping) ~= "table" then
-        error("bad argument #2 to 'utf8replace' (table expected, got ".. type(mapping).. ")")
-    end
-
-    local pos = 1
-    local bytes = len(s)
-    local charbytes
-    local newstr = ""
-
-    while pos <= bytes do
-        charbytes = utf8charbytes(s, pos)
-        local c = sub(s, pos, pos + charbytes - 1)
-
-        newstr = newstr .. (mapping[c] or c)
-
-        pos = pos + charbytes
-    end
-
-    return newstr
-end
-
-
--- identical to string.upper except it knows about unicode simple case conversions
-local function utf8upper (s)
-    return utf8replace(s, utf8_lc_uc)
-end
-
--- identical to string.lower except it knows about unicode simple case conversions
-local function utf8lower (s)
-    return utf8replace(s, utf8_uc_lc)
-end
-]]
-
--- identical to string.reverse except that it supports UTF-8
-local function utf8reverse (s)
-    -- argument checking
-    if type(s) ~= "string" then
-        error("bad argument #1 to 'utf8reverse' (string expected, got ".. type(s).. ")")
-    end
-
-    local bytes = len(s)
-    local pos = bytes
-    local charbytes
-    local newstr = ""
-
-    while pos > 0 do
-        local c = byte(s, pos)
-        while c >= 128 and c <= 191 do
-            pos = pos - 1
-            c = byte(s, pos)
-        end
-
-        charbytes = utf8charbytes(s, pos)
-
-        newstr = newstr .. sub(s, pos, pos + charbytes - 1)
-
-        pos = pos - 1
-    end
-
-    return newstr
-end
-
--- http://en.wikipedia.org/wiki/Utf8
--- http://developer.coronalabs.com/code/utf-8-conversion-utility
-local function utf8char(unicode)
-    if unicode <= 0x7F then return char(unicode) end
-
-    if (unicode <= 0x7FF) then
-        local Byte0 = 0xC0 + math.floor(unicode / 0x40);
-        local Byte1 = 0x80 + (unicode % 0x40);
-        return char(Byte0, Byte1);
-    end;
-
-    if (unicode <= 0xFFFF) then
-        local Byte0 = 0xE0 +  math.floor(unicode / 0x1000);
-        local Byte1 = 0x80 + (math.floor(unicode / 0x40) % 0x40);
-        local Byte2 = 0x80 + (unicode % 0x40);
-        return char(Byte0, Byte1, Byte2);
-    end;
-
-    if (unicode <= 0x10FFFF) then
-        local code = unicode
-        local Byte3= 0x80 + (code % 0x40);
-        code       = math.floor(code / 0x40)
-        local Byte2= 0x80 + (code % 0x40);
-        code       = math.floor(code / 0x40)
-        local Byte1= 0x80 + (code % 0x40);
-        code       = math.floor(code / 0x40)
-        local Byte0= 0xF0 + code;
-
-        return char(Byte0, Byte1, Byte2, Byte3);
-    end;
-
-    error 'Unicode cannot be greater than U+10FFFF!'
-end
-
-local shift_6  = 2^6
-local shift_12 = 2^12
-local shift_18 = 2^18
-
-local utf8unicode
-utf8unicode = function(str, i, j, byte_pos)
-    i = i or 1
-    j = j or i
-
-    if i > j then return end
-
-    local ch,bytes
-
-    if byte_pos then
-        bytes = utf8charbytes(str,byte_pos)
-        ch  = sub(str,byte_pos,byte_pos-1+bytes)
-    else
-        ch,byte_pos = utf8sub(str,i,i), 0
-        bytes       = #ch
-    end
-
-    local unicode
-
-    if bytes == 1 then unicode = byte(ch) end
-    if bytes == 2 then
-        local byte0,byte1 = byte(ch,1,2)
-        local code0,code1 = byte0-0xC0,byte1-0x80
-        unicode = code0*shift_6 + code1
-    end
-    if bytes == 3 then
-        local byte0,byte1,byte2 = byte(ch,1,3)
-        local code0,code1,code2 = byte0-0xE0,byte1-0x80,byte2-0x80
-        unicode = code0*shift_12 + code1*shift_6 + code2
-    end
-    if bytes == 4 then
-        local byte0,byte1,byte2,byte3 = byte(ch,1,4)
-        local code0,code1,code2,code3 = byte0-0xF0,byte1-0x80,byte2-0x80,byte3-0x80
-        unicode = code0*shift_18 + code1*shift_12 + code2*shift_6 + code3
-    end
-
-    return unicode,utf8unicode(str, i+1, j, byte_pos+bytes)
-end
-
--- Returns an iterator which returns the next substring and its byte interval
-local function utf8gensub(str, sub_len)
-    sub_len        = sub_len or 1
-    local byte_pos = 1
-    local length   = #str
-    return function(skip)
-        if skip then byte_pos = byte_pos + skip end
-        local char_count = 0
-        local start      = byte_pos
-        repeat
-            if byte_pos > length then return end
-            char_count  = char_count + 1
-            local bytes = utf8charbytes(str,byte_pos)
-            byte_pos    = byte_pos+bytes
-
-        until char_count == sub_len
-
-        local last  = byte_pos-1
-        local slice = sub(str,start,last)
-        return slice, start, last
-    end
-end
-
-local function binsearch(sortedTable, item, comp)
-    local head, tail = 1, #sortedTable
-    local mid = math.floor((head + tail)/2)
-    if not comp then
-        while (tail - head) > 1 do
-            if sortedTable[tonumber(mid)] > item then
-                tail = mid
-            else
-                head = mid
-            end
-            mid = math.floor((head + tail)/2)
-        end
-    end
-    if sortedTable[tonumber(head)] == item then
-        return true, tonumber(head)
-    elseif sortedTable[tonumber(tail)] == item then
-        return true, tonumber(tail)
-    else
-        return false
-    end
-end
-local function classMatchGenerator(class, plain)
-    local codes = {}
-    local ranges = {}
-    local ignore = false
-    local range = false
-    local firstletter = true
-    local unmatch = false
-
-    local it = utf8gensub(class)
-
-    local skip
-    for c, _, be in it do
-        skip = be
-        if not ignore and not plain then
-            if c == "%" then
-                ignore = true
-            elseif c == "-" then
-                table.insert(codes, utf8unicode(c))
-                range = true
-            elseif c == "^" then
-                if not firstletter then
-                    error('!!!')
-                else
-                    unmatch = true
-                end
-            elseif c == ']' then
-                break
-            else
-                if not range then
-                    table.insert(codes, utf8unicode(c))
-                else
-                    table.remove(codes) -- removing '-'
-                    table.insert(ranges, {table.remove(codes), utf8unicode(c)})
-                    range = false
-                end
-            end
-        elseif ignore and not plain then
-            if c == 'a' then -- %a: represents all letters. (ONLY ASCII)
-                table.insert(ranges, {65, 90}) -- A - Z
-                table.insert(ranges, {97, 122}) -- a - z
-            elseif c == 'c' then -- %c: represents all control characters.
-                table.insert(ranges, {0, 31})
-                table.insert(codes, 127)
-            elseif c == 'd' then -- %d: represents all digits.
-                table.insert(ranges, {48, 57}) -- 0 - 9
-            elseif c == 'g' then -- %g: represents all printable characters except space.
-                table.insert(ranges, {1, 8})
-                table.insert(ranges, {14, 31})
-                table.insert(ranges, {33, 132})
-                table.insert(ranges, {134, 159})
-                table.insert(ranges, {161, 5759})
-                table.insert(ranges, {5761, 8191})
-                table.insert(ranges, {8203, 8231})
-                table.insert(ranges, {8234, 8238})
-                table.insert(ranges, {8240, 8286})
-                table.insert(ranges, {8288, 12287})
-            elseif c == 'l' then -- %l: represents all lowercase letters. (ONLY ASCII)
-                table.insert(ranges, {97, 122}) -- a - z
-            elseif c == 'p' then -- %p: represents all punctuation characters. (ONLY ASCII)
-                table.insert(ranges, {33, 47})
-                table.insert(ranges, {58, 64})
-                table.insert(ranges, {91, 96})
-                table.insert(ranges, {123, 126})
-            elseif c == 's' then -- %s: represents all space characters.
-                table.insert(ranges, {9, 13})
-                table.insert(codes, 32)
-                table.insert(codes, 133)
-                table.insert(codes, 160)
-                table.insert(codes, 5760)
-                table.insert(ranges, {8192, 8202})
-                table.insert(codes, 8232)
-                table.insert(codes, 8233)
-                table.insert(codes, 8239)
-                table.insert(codes, 8287)
-                table.insert(codes, 12288)
-            elseif c == 'u' then -- %u: represents all uppercase letters. (ONLY ASCII)
-                table.insert(ranges, {65, 90}) -- A - Z
-            elseif c == 'w' then -- %w: represents all alphanumeric characters. (ONLY ASCII)
-                table.insert(ranges, {48, 57}) -- 0 - 9
-                table.insert(ranges, {65, 90}) -- A - Z
-                table.insert(ranges, {97, 122}) -- a - z
-            elseif c == 'x' then -- %x: represents all hexadecimal digits.
-                table.insert(ranges, {48, 57}) -- 0 - 9
-                table.insert(ranges, {65, 70}) -- A - F
-                table.insert(ranges, {97, 102}) -- a - f
-            else
-                if not range then
-                    table.insert(codes, utf8unicode(c))
-                else
-                    table.remove(codes) -- removing '-'
-                    table.insert(ranges, {table.remove(codes), utf8unicode(c)})
-                    range = false
-                end
-            end
-            ignore = false
-        else
-            if not range then
-                table.insert(codes, utf8unicode(c))
-            else
-                table.remove(codes) -- removing '-'
-                table.insert(ranges, {table.remove(codes), utf8unicode(c)})
-                range = false
-            end
-            ignore = false
-        end
-
-        firstletter = false
-    end
-
-    table.sort(codes)
-
-    local function inRanges(charCode)
-        for _,r in ipairs(ranges) do
-            if r[1] <= charCode and charCode <= r[2] then
-                return true
-            end
-        end
-        return false
-    end
-    if not unmatch then
-        return function(charCode)
-            return binsearch(codes, charCode) or inRanges(charCode)
-        end, skip
-    else
-        return function(charCode)
-            return charCode ~= -1 and not (binsearch(codes, charCode) or inRanges(charCode))
-        end, skip
-    end
-end
-
---[[
--- utf8sub with extra argument, and extra result value
-local function utf8subWithBytes (s, i, j, sb)
-    -- argument defaults
-    j = j or -1
-
-    local pos = sb or 1
-    local bytes = len(s)
-    local length = 0
-
-    -- only set l if i or j is negative
-    local l = (i >= 0 and j >= 0) or utf8len(s)
-    local startChar = (i >= 0) and i or l + i + 1
-    local endChar   = (j >= 0) and j or l + j + 1
-
-    -- can't have start before end!
-    if startChar > endChar then
-        return ""
-    end
-
-    -- byte offsets to pass to string.sub
-    local startByte,endByte = 1,bytes
-
-    while pos <= bytes do
-        length = length + 1
-
-        if length == startChar then
-            startByte = pos
-        end
-
-        pos = pos + utf8charbytes(s, pos)
-
-        if length == endChar then
-            endByte = pos - 1
-            break
-        end
-    end
-
-    if startChar > length then startByte = bytes+1   end
-    if endChar   < 1      then endByte   = 0         end
-
-    return sub(s, startByte, endByte), endByte + 1
-end
-]]
-
-local cache = setmetatable({},{
-    __mode = 'kv'
-})
-local cachePlain = setmetatable({},{
-    __mode = 'kv'
-})
-local function matcherGenerator(regex, plain)
-    local matcher = {
-        functions = {},
-        captures = {}
-    }
-    if not plain then
-        cache[regex] =  matcher
-    else
-        cachePlain[regex] = matcher
-    end
-    local function simple(func)
-        return function(cC)
-            if func(cC) then
-                matcher:nextFunc()
-                matcher:nextStr()
-            else
-                matcher:reset()
-            end
-        end
-    end
-    local function star(func)
-        return function(cC)
-            if func(cC) then
-                matcher:fullResetOnNextFunc()
-                matcher:nextStr()
-            else
-                matcher:nextFunc()
-            end
-        end
-    end
-    local function minus(func)
-        return function(cC)
-            if func(cC) then
-                matcher:fullResetOnNextStr()
-            end
-            matcher:nextFunc()
-        end
-    end
-    local function question(func)
-        return function(cC)
-            if func(cC) then
-                matcher:fullResetOnNextFunc()
-                matcher:nextStr()
-            end
-            matcher:nextFunc()
-        end
-    end
-
-    local function capture(id)
-        return function(_)
-            local l = matcher.captures[id][2] - matcher.captures[id][1]
-            local captured = utf8sub(matcher.string, matcher.captures[id][1], matcher.captures[id][2])
-            local check = utf8sub(matcher.string, matcher.str, matcher.str + l)
-            if captured == check then
-                for _ = 0, l do
-                    matcher:nextStr()
-                end
-                matcher:nextFunc()
-            else
-                matcher:reset()
-            end
-        end
-    end
-    local function captureStart(id)
-        return function(_)
-            matcher.captures[id][1] = matcher.str
-            matcher:nextFunc()
-        end
-    end
-    local function captureStop(id)
-        return function(_)
-            matcher.captures[id][2] = matcher.str - 1
-            matcher:nextFunc()
-        end
-    end
-
-    local function balancer(str)
-        local sum = 0
-        local bc, ec = utf8sub(str, 1, 1), utf8sub(str, 2, 2)
-        local skip = len(bc) + len(ec)
-        bc, ec = utf8unicode(bc), utf8unicode(ec)
-        return function(cC)
-            if cC == ec and sum > 0 then
-                sum = sum - 1
-                if sum == 0 then
-                    matcher:nextFunc()
-                end
-                matcher:nextStr()
-            elseif cC == bc then
-                sum = sum + 1
-                matcher:nextStr()
-            else
-                if sum == 0 or cC == -1 then
-                    sum = 0
-                    matcher:reset()
-                else
-                    matcher:nextStr()
-                end
-            end
-        end, skip
-    end
-
-    matcher.functions[1] = function(_)
-        matcher:fullResetOnNextStr()
-        matcher.seqStart = matcher.str
-        matcher:nextFunc()
-        if (matcher.str > matcher.startStr and matcher.fromStart) or matcher.str >= matcher.stringLen then
-            matcher.stop = true
-            matcher.seqStart = nil
-        end
-    end
-
-    local lastFunc
-    local ignore = false
-    local skip = nil
-    local it = (function()
-        local gen = utf8gensub(regex)
-        return function()
-            return gen(skip)
-        end
-    end)()
-    local cs = {}
-    for c, bs, be in it do
-        skip = nil
-        if plain then
-            table.insert(matcher.functions, simple(classMatchGenerator(c, plain)))
-        else
-            if ignore then
-                if find('123456789', c, 1, true) then
-                    if lastFunc then
-                        table.insert(matcher.functions, simple(lastFunc))
-                        lastFunc = nil
-                    end
-                    table.insert(matcher.functions, capture(tonumber(c)))
-                elseif c == 'b' then
-                    if lastFunc then
-                        table.insert(matcher.functions, simple(lastFunc))
-                        lastFunc = nil
-                    end
-                    local b
-                    b, skip = balancer(sub(regex, be + 1, be + 9))
-                    table.insert(matcher.functions, b)
-                else
-                    lastFunc = classMatchGenerator('%' .. c)
-                end
-                ignore = false
-            else
-                if c == '*' then
-                    if lastFunc then
-                        table.insert(matcher.functions, star(lastFunc))
-                        lastFunc = nil
-                    else
-                        error('invalid regex after ' .. sub(regex, 1, bs))
-                    end
-                elseif c == '+' then
-                    if lastFunc then
-                        table.insert(matcher.functions, simple(lastFunc))
-                        table.insert(matcher.functions, star(lastFunc))
-                        lastFunc = nil
-                    else
-                        error('invalid regex after ' .. sub(regex, 1, bs))
-                    end
-                elseif c == '-' then
-                    if lastFunc then
-                        table.insert(matcher.functions, minus(lastFunc))
-                        lastFunc = nil
-                    else
-                        error('invalid regex after ' .. sub(regex, 1, bs))
-                    end
-                elseif c == '?' then
-                    if lastFunc then
-                        table.insert(matcher.functions, question(lastFunc))
-                        lastFunc = nil
-                    else
-                        error('invalid regex after ' .. sub(regex, 1, bs))
-                    end
-                elseif c == '^' then
-                    if bs == 1 then
-                        matcher.fromStart = true
-                    else
-                        error('invalid regex after ' .. sub(regex, 1, bs))
-                    end
-                elseif c == '$' then
-                    if be == len(regex) then
-                        matcher.toEnd = true
-                    else
-                        error('invalid regex after ' .. sub(regex, 1, bs))
-                    end
-                elseif c == '[' then
-                    if lastFunc then
-                        table.insert(matcher.functions, simple(lastFunc))
-                    end
-                    lastFunc, skip = classMatchGenerator(sub(regex, be + 1))
-                elseif c == '(' then
-                    if lastFunc then
-                        table.insert(matcher.functions, simple(lastFunc))
-                        lastFunc = nil
-                    end
-                    table.insert(matcher.captures, {})
-                    table.insert(cs, #matcher.captures)
-                    table.insert(matcher.functions, captureStart(cs[#cs]))
-                    if sub(regex, be + 1, be + 1) == ')' then matcher.captures[#matcher.captures].empty = true end
-                elseif c == ')' then
-                    if lastFunc then
-                        table.insert(matcher.functions, simple(lastFunc))
-                        lastFunc = nil
-                    end
-                    local cap = table.remove(cs)
-                    if not cap then
-                        error('invalid capture: "(" missing')
-                    end
-                    table.insert(matcher.functions, captureStop(cap))
-                elseif c == '.' then
-                    if lastFunc then
-                        table.insert(matcher.functions, simple(lastFunc))
-                    end
-                    lastFunc = function(cC) return cC ~= -1 end
-                elseif c == '%' then
-                    ignore = true
-                else
-                    if lastFunc then
-                        table.insert(matcher.functions, simple(lastFunc))
-                    end
-                    lastFunc = classMatchGenerator(c)
-                end
-            end
-        end
-    end
-    if #cs > 0 then
-        error('invalid capture: ")" missing')
-    end
-    if lastFunc then
-        table.insert(matcher.functions, simple(lastFunc))
-    end
-
-    table.insert(matcher.functions, function()
-        if matcher.toEnd and matcher.str ~= matcher.stringLen then
-            matcher:reset()
-        else
-            matcher.stop = true
-        end
-    end)
-
-    matcher.nextFunc = function(self)
-        self.func = self.func + 1
-    end
-    matcher.nextStr = function(self)
-        self.str = self.str + 1
-    end
-    matcher.strReset = function(self)
-        local oldReset = self.reset
-        local str = self.str
-        self.reset = function(s)
-            s.str = str
-            s.reset = oldReset
-        end
-    end
-    matcher.fullResetOnNextFunc = function(self)
-        local oldReset = self.reset
-        local func = self.func +1
-        local str = self.str
-        self.reset = function(s)
-            s.func = func
-            s.str = str
-            s.reset = oldReset
-        end
-    end
-    matcher.fullResetOnNextStr = function(self)
-        local oldReset = self.reset
-        local str = self.str + 1
-        local func = self.func
-        self.reset = function(s)
-            s.func = func
-            s.str = str
-            s.reset = oldReset
-        end
-    end
-
-    matcher.process = function(self, str, start)
-
-        self.func = 1
-        start = start or 1
-        self.startStr = (start >= 0) and start or utf8len(str) + start + 1
-        self.seqStart = self.startStr
-        self.str = self.startStr
-        self.stringLen = utf8len(str) + 1
-        self.string = str
-        self.stop = false
-
-        self.reset = function(s)
-            s.func = 1
-        end
-
-        -- local lastPos = self.str
-        -- local lastByte
-        local ch
-        while not self.stop do
-            if self.str < self.stringLen then
-                --[[ if lastPos < self.str then
-                    print('last byte', lastByte)
-                    ch, lastByte = utf8subWithBytes(str, 1, self.str - lastPos - 1, lastByte)
-                    ch, lastByte = utf8subWithBytes(str, 1, 1, lastByte)
-                    lastByte = lastByte - 1
-                else
-                    ch, lastByte = utf8subWithBytes(str, self.str, self.str)
-                end
-                lastPos = self.str ]]
-                ch = utf8sub(str, self.str,self.str)
-                --print('char', ch, utf8unicode(ch))
-                self.functions[self.func](utf8unicode(ch))
-            else
-                self.functions[self.func](-1)
-            end
-        end
-
-        if self.seqStart then
-            local captures = {}
-            for _,pair in pairs(self.captures) do
-                if pair.empty then
-                    table.insert(captures, pair[1])
-                else
-                    table.insert(captures, utf8sub(str, pair[1], pair[2]))
-                end
-            end
-            return self.seqStart, self.str - 1, unpack(captures)
-        end
-    end
-
-    return matcher
-end
-
--- string.find
-local function utf8find(str, regex, init, plain)
-    local matcher = cache[regex] or matcherGenerator(regex, plain)
-    return matcher:process(str, init)
-end
-
--- string.match
-local function utf8match(str, regex, init)
-    init = init or 1
-    local found = {utf8find(str, regex, init)}
-    if found[1] then
-        if found[3] then
-            return unpack(found, 3)
-        end
-        return utf8sub(str, found[1], found[2])
-    end
-end
-
--- string.gmatch
-local function utf8gmatch(str, regex, all)
-    regex = (utf8sub(regex,1,1) ~= '^') and regex or '%' .. regex
-    local lastChar = 1
-    return function()
-        local found = {utf8find(str, regex, lastChar)}
-        if found[1] then
-            lastChar = found[2] + 1
-            if found[all and 1 or 3] then
-                return unpack(found, all and 1 or 3)
-            end
-            return utf8sub(str, found[1], found[2])
-        end
-    end
-end
-
-local function replace(repl, args)
-    local ret = ''
-    if type(repl) == 'string' then
-        local ignore = false
-        local num
-        for c in utf8gensub(repl) do
-            if not ignore then
-                if c == '%' then
-                    ignore = true
-                else
-                    ret = ret .. c
-                end
-            else
-                num = tonumber(c)
-                if num then
-                    ret = ret .. args[num]
-                else
-                    ret = ret .. c
-                end
-                ignore = false
-            end
-        end
-    elseif type(repl) == 'table' then
-        ret = repl[args[1] or args[0]] or ''
-    elseif type(repl) == 'function' then
-        if #args > 0 then
-            ret = repl(unpack(args, 1)) or ''
-        else
-            ret = repl(args[0]) or ''
-        end
-    end
-    return ret
-end
--- string.gsub
-local function utf8gsub(str, regex, repl, limit)
-    limit = limit or -1
-    local ret = ''
-    local prevEnd = 1
-    local it = utf8gmatch(str, regex, true)
-    local found = {it()}
-    local n = 0
-    while #found > 0 and limit ~= n do
-        local args = {[0] = utf8sub(str, found[1], found[2]), unpack(found, 3)}
-        ret = ret .. utf8sub(str, prevEnd, found[1] - 1)
-        .. replace(repl, args)
-        prevEnd = found[2] + 1
-        n = n + 1
-        found = {it()}
-    end
-    return ret .. utf8sub(str, prevEnd), n
-end
-
-local utf8 = {}
-utf8.len = utf8len
-utf8.sub = utf8sub
-utf8.reverse = utf8reverse
-utf8.char = utf8char
-utf8.unicode = utf8unicode
-utf8.gensub = utf8gensub
-utf8.byte = utf8unicode
-utf8.find    = utf8find
-utf8.match   = utf8match
-utf8.gmatch  = utf8gmatch
-utf8.gsub    = utf8gsub
-utf8.dump    = dump
-utf8.format = format
-utf8.lower = lower
-utf8.upper = upper
-utf8.rep     = rep
-
-function charAt(str, i)
-    if i <= utf8.len(str) then
-        return utf8.sub(str, i, i)
-    else
-        return ""
-    end
-end
-
-if GetOption("autoclose") == nil then
-    AddOption("autoclose", true)
-end
+VERSION = "1.0.0"
 
+local uutil = import("micro/util")
+local utf8 = import("utf8")
 local autoclosePairs = {"\"\"", "''", "``", "()", "{}", "[]"}
 local autoNewlinePairs = {"()", "{}", "[]"}
 
-function onRune(r, v)
-    if not GetOption("autoclose") then
-        return
-    end
+function charAt(str, i)
+    -- lua indexing is one off from go
+    return uutil.RuneAt(str, i-1)
+end
 
+function onRune(bp, r)
     for i = 1, #autoclosePairs do
         if r == charAt(autoclosePairs[i], 2) then
-            local curLine = v.Buf:Line(v.Cursor.Y)
+            local curLine = bp.Buf:Line(bp.Cursor.Y)
 
-            if charAt(curLine, v.Cursor.X+1) == charAt(autoclosePairs[i], 2) then
-                v:Backspace(false)
-                v:CursorRight(false)
+            if charAt(curLine, bp.Cursor.X+1) == charAt(autoclosePairs[i], 2) then
+                bp:Backspace()
+                bp:CursorRight()
                 break
             end
 
-            if v.Cursor.X > 1 and (IsWordChar(charAt(curLine, v.Cursor.X-1)) or charAt(curLine, v.Cursor.X-1) == charAt(autoclosePairs[i], 1)) then
+            if bp.Cursor.X > 1 and (uutil.IsWordChar(charAt(curLine, bp.Cursor.X-1)) or charAt(curLine, bp.Cursor.X-1) == charAt(autoclosePairs[i], 1)) then
                 break
             end
         end
         if r == charAt(autoclosePairs[i], 1) then
-            local curLine = v.Buf:Line(v.Cursor.Y)
+            local curLine = bp.Buf:Line(bp.Cursor.Y)
 
-            if v.Cursor.X == utf8.len(curLine) or not IsWordChar(charAt(curLine, v.Cursor.X+1)) then
-                -- the '-' here is to derefence the pointer to v.Cursor.Loc which is automatically made
+            if bp.Cursor.X == uutil.CharacterCountInString(curLine) or not uutil.IsWordChar(charAt(curLine, bp.Cursor.X+1)) then
+                -- the '-' here is to derefence the pointer to bp.Cursor.Loc which is automatically made
                 -- when converting go structs to lua
                 -- It needs to be dereferenced because the function expects a non pointer struct
-                v.Buf:Insert(-v.Cursor.Loc, charAt(autoclosePairs[i], 2))
-                v:CursorLeft(false)
+                bp.Buf:Insert(-bp.Cursor.Loc, charAt(autoclosePairs[i], 2))
+                bp:CursorLeft()
                 break
             end
         end
     end
+    return true
 end
 
-function preInsertNewline(v)
-    if not GetOption("autoclose") then
-        return
-    end
-
-    local curLine = v.Buf:Line(v.Cursor.Y)
-    local curRune = charAt(curLine, v.Cursor.X)
-    local nextRune = charAt(curLine, v.Cursor.X+1)
-    local ws = GetLeadingWhitespace(curLine)
+function preInsertNewline(bp)
+    local curLine = bp.Buf:Line(bp.Cursor.Y)
+    local curRune = charAt(curLine, bp.Cursor.X)
+    local nextRune = charAt(curLine, bp.Cursor.X+1)
+    local ws = uutil.GetLeadingWhitespace(curLine)
 
     for i = 1, #autoNewlinePairs do
         if curRune == charAt(autoNewlinePairs[i], 1) then
             if nextRune == charAt(autoNewlinePairs[i], 2) then
-                v:InsertNewline(false)
-                v:InsertTab(false)
-                v.Buf:Insert(-v.Cursor.Loc, "\n" .. ws)
-                v:StartOfLine(false)
-                v:CursorLeft(false)
+                bp:InsertNewline()
+                bp:InsertTab()
+                bp.Buf:Insert(-bp.Cursor.Loc, "\n" .. ws)
+                bp:StartOfLine()
+                bp:CursorLeft()
                 return false
             end
         end
@@ -1118,15 +63,11 @@ function preInsertNewline(v)
     return true
 end
 
-function preBackspace(v)
-    if not GetOption("autoclose") then
-        return
-    end
-
+function preBackspace(bp)
     for i = 1, #autoclosePairs do
-        local curLine = v.Buf:Line(v.Cursor.Y)
-        if charAt(curLine, v.Cursor.X+1) == charAt(autoclosePairs[i], 2) and charAt(curLine, v.Cursor.X) == charAt(autoclosePairs[i], 1) then
-            v:Delete(false)
+        local curLine = bp.Buf:Line(bp.Cursor.Y)
+        if charAt(curLine, bp.Cursor.X+1) == charAt(autoclosePairs[i], 2) and charAt(curLine, bp.Cursor.X) == charAt(autoclosePairs[i], 1) then
+            bp:Delete()
         end
     end