mirror of https://github.com/teverse/teverse
Compare commits
2 Commits
a9b9d0dec6
...
a2fbd3032d
Author | SHA1 | Date |
---|---|---|
Jay | a2fbd3032d | |
Jay | 23c9c1cdb6 |
|
@ -0,0 +1,431 @@
|
||||||
|
-- MIT License
|
||||||
|
--
|
||||||
|
-- Copyright (c) 2018 LoganDark
|
||||||
|
--
|
||||||
|
-- Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
-- of this software and associated documentation files (the "Software"), to deal
|
||||||
|
-- in the Software without restriction, including without limitation the rights
|
||||||
|
-- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
-- copies of the Software, and to permit persons to whom the Software is
|
||||||
|
-- furnished to do so, subject to the following conditions:
|
||||||
|
--
|
||||||
|
-- The above copyright notice and this permission notice shall be included in all
|
||||||
|
-- copies or substantial portions of the Software.
|
||||||
|
--
|
||||||
|
-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
-- SOFTWARE.
|
||||||
|
|
||||||
|
function lookupify(src, list)
|
||||||
|
list = list or {}
|
||||||
|
|
||||||
|
if type(src) == 'string' then
|
||||||
|
for i = 1, src:len() do
|
||||||
|
list[src:sub(i, i)] = true
|
||||||
|
end
|
||||||
|
elseif type(src) == 'table' then
|
||||||
|
for i = 1, #src do
|
||||||
|
list[src[i]] = true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return list
|
||||||
|
end
|
||||||
|
|
||||||
|
local base_ident = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_'
|
||||||
|
local base_digits = '0123456789'
|
||||||
|
local base_operators = '+-*/^%#'
|
||||||
|
|
||||||
|
local chars = {
|
||||||
|
whitespace = lookupify(' \n\t\r'),
|
||||||
|
validEscapes = lookupify('abfnrtv"\'\\'),
|
||||||
|
ident = lookupify(
|
||||||
|
base_ident .. base_digits,
|
||||||
|
{
|
||||||
|
start = lookupify(base_ident),
|
||||||
|
}
|
||||||
|
),
|
||||||
|
|
||||||
|
digits = lookupify(
|
||||||
|
base_digits,
|
||||||
|
{
|
||||||
|
hex = lookupify(base_digits .. 'abcdefABCDEF')
|
||||||
|
}
|
||||||
|
),
|
||||||
|
|
||||||
|
symbols = lookupify(
|
||||||
|
base_operators .. ',{}[]();.:', {
|
||||||
|
equality = lookupify('~=><'),
|
||||||
|
operators = lookupify(base_operators)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
local keywords = {
|
||||||
|
structure = lookupify({
|
||||||
|
'and', 'break', 'do', 'else', 'elseif', 'end', 'for', 'function',
|
||||||
|
'goto', 'if', 'in', 'local', 'not', 'or', 'repeat', 'return', 'then',
|
||||||
|
'until', 'while'
|
||||||
|
}),
|
||||||
|
|
||||||
|
values = lookupify({
|
||||||
|
'true', 'false', 'nil'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return function(text)
|
||||||
|
local pos = 1
|
||||||
|
local start = 1
|
||||||
|
local buffer = {}
|
||||||
|
local lines = {}
|
||||||
|
|
||||||
|
local function look(delta)
|
||||||
|
delta = pos + (delta or 0)
|
||||||
|
|
||||||
|
return text:sub(delta, delta)
|
||||||
|
end
|
||||||
|
|
||||||
|
local function get()
|
||||||
|
pos = pos + 1
|
||||||
|
|
||||||
|
return look(-1)
|
||||||
|
end
|
||||||
|
|
||||||
|
local function getDataLevel()
|
||||||
|
local num = 0
|
||||||
|
|
||||||
|
while look(num) == '=' do
|
||||||
|
num = num + 1
|
||||||
|
end
|
||||||
|
|
||||||
|
if look(num) == '[' then
|
||||||
|
pos = pos + num + 1
|
||||||
|
|
||||||
|
return num
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function getCurrentTokenText()
|
||||||
|
return text:sub(start, pos - 1)
|
||||||
|
end
|
||||||
|
|
||||||
|
local currentLineLength = 0
|
||||||
|
local lineoffset = 0
|
||||||
|
|
||||||
|
local function pushToken(type, text)
|
||||||
|
text = text or getCurrentTokenText()
|
||||||
|
|
||||||
|
local tk = buffer[#buffer]
|
||||||
|
|
||||||
|
if not tk or tk.type ~= type then
|
||||||
|
tk = {
|
||||||
|
type = type,
|
||||||
|
data = text,
|
||||||
|
posFirst = start - lineoffset,
|
||||||
|
posLast = pos - 1 - lineoffset
|
||||||
|
}
|
||||||
|
|
||||||
|
if tk.data ~= '' then
|
||||||
|
buffer[#buffer + 1] = tk
|
||||||
|
end
|
||||||
|
else
|
||||||
|
tk.data = tk.data .. text
|
||||||
|
tk.posLast = tk.posLast + text:len()
|
||||||
|
end
|
||||||
|
|
||||||
|
currentLineLength = currentLineLength + text:len()
|
||||||
|
start = pos
|
||||||
|
|
||||||
|
return tk
|
||||||
|
end
|
||||||
|
|
||||||
|
local function newline()
|
||||||
|
lines[#lines + 1] = buffer
|
||||||
|
buffer = {}
|
||||||
|
|
||||||
|
get()
|
||||||
|
pushToken('newline')
|
||||||
|
buffer[1] = nil
|
||||||
|
|
||||||
|
lineoffset = lineoffset + currentLineLength
|
||||||
|
currentLineLength = 0
|
||||||
|
end
|
||||||
|
|
||||||
|
local function getData(level, type)
|
||||||
|
while true do
|
||||||
|
local char = get()
|
||||||
|
|
||||||
|
if char == '' then
|
||||||
|
return
|
||||||
|
elseif char == '\n' then
|
||||||
|
pos = pos - 1
|
||||||
|
pushToken(type)
|
||||||
|
newline()
|
||||||
|
elseif char == ']' then
|
||||||
|
local valid = true
|
||||||
|
|
||||||
|
for i = 1, level do
|
||||||
|
if look() == '=' then
|
||||||
|
pos = pos + 1
|
||||||
|
else
|
||||||
|
valid = false
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if valid and look() == ']' then
|
||||||
|
pos = pos - level - 1
|
||||||
|
|
||||||
|
return
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function chompWhitespace()
|
||||||
|
while true do
|
||||||
|
local char = look()
|
||||||
|
|
||||||
|
if char == '\n' then
|
||||||
|
pushToken('whitespace')
|
||||||
|
newline()
|
||||||
|
elseif chars.whitespace[char] then
|
||||||
|
pos = pos + 1
|
||||||
|
else
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
pushToken('whitespace')
|
||||||
|
end
|
||||||
|
|
||||||
|
while true do
|
||||||
|
chompWhitespace()
|
||||||
|
|
||||||
|
local char = get()
|
||||||
|
|
||||||
|
if char == '' then
|
||||||
|
break
|
||||||
|
elseif char == '-' and look() == '-' then
|
||||||
|
pos = pos + 1
|
||||||
|
|
||||||
|
if look() == '[' then
|
||||||
|
pos = pos + 1
|
||||||
|
|
||||||
|
local level = getDataLevel()
|
||||||
|
|
||||||
|
if level then
|
||||||
|
getData(level, 'comment')
|
||||||
|
|
||||||
|
pos = pos + level + 2
|
||||||
|
pushToken('comment')
|
||||||
|
else
|
||||||
|
while true do
|
||||||
|
local char2 = get()
|
||||||
|
|
||||||
|
if char2 == '' or char2 == '\n' then
|
||||||
|
pos = pos - 1
|
||||||
|
pushToken('comment')
|
||||||
|
|
||||||
|
if char2 == '\n' then
|
||||||
|
newline()
|
||||||
|
end
|
||||||
|
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
else
|
||||||
|
while true do
|
||||||
|
local char2 = get()
|
||||||
|
|
||||||
|
if char2 == '' or char2 == '\n' then
|
||||||
|
pos = pos - 1
|
||||||
|
pushToken('comment')
|
||||||
|
|
||||||
|
if char2 == '\n' then
|
||||||
|
newline()
|
||||||
|
end
|
||||||
|
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
pushToken('comment')
|
||||||
|
elseif char == '\'' or char == '"' then
|
||||||
|
pushToken('string_start')
|
||||||
|
|
||||||
|
while true do
|
||||||
|
local char2 = get()
|
||||||
|
|
||||||
|
if char2 == '\\' then
|
||||||
|
pos = pos - 1
|
||||||
|
pushToken('string')
|
||||||
|
get()
|
||||||
|
|
||||||
|
local char3 = get()
|
||||||
|
|
||||||
|
if chars.digits[char3] then
|
||||||
|
for i = 1, 2 do
|
||||||
|
if chars.digits[look()] then
|
||||||
|
pos = pos + 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
elseif char3 == 'x' then
|
||||||
|
if chars.digits.hex[look()] and chars.digits.hex[look(1)] then
|
||||||
|
pos = pos + 2
|
||||||
|
else
|
||||||
|
pushToken('unidentified')
|
||||||
|
end
|
||||||
|
elseif char3 == '\n' then
|
||||||
|
pos = pos - 1
|
||||||
|
pushToken('escape')
|
||||||
|
newline()
|
||||||
|
elseif not chars.validEscapes[char3] then
|
||||||
|
pushToken('unidentified')
|
||||||
|
end
|
||||||
|
|
||||||
|
pushToken('escape')
|
||||||
|
elseif char2 == '\n' then
|
||||||
|
pos = pos - 1
|
||||||
|
pushToken('string')
|
||||||
|
newline()
|
||||||
|
|
||||||
|
break
|
||||||
|
elseif char2 == char or char2 == '' then
|
||||||
|
pos = pos - 1
|
||||||
|
pushToken('string')
|
||||||
|
get()
|
||||||
|
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
pushToken('string_end')
|
||||||
|
elseif chars.ident.start[char] then
|
||||||
|
while chars.ident[look()] do
|
||||||
|
pos = pos + 1
|
||||||
|
end
|
||||||
|
|
||||||
|
local word = getCurrentTokenText()
|
||||||
|
|
||||||
|
if keywords.structure[word] then
|
||||||
|
pushToken('keyword')
|
||||||
|
elseif keywords.values[word] then
|
||||||
|
pushToken('value')
|
||||||
|
else
|
||||||
|
pushToken('ident')
|
||||||
|
end
|
||||||
|
elseif chars.digits[char] or (char == '.' and chars.digits[look()]) then
|
||||||
|
if char == '0' and look() == 'x' then
|
||||||
|
pos = pos + 1
|
||||||
|
|
||||||
|
while chars.digits.hex[look()] do
|
||||||
|
pos = pos + 1
|
||||||
|
end
|
||||||
|
else
|
||||||
|
while chars.digits[look()] do
|
||||||
|
pos = pos + 1
|
||||||
|
end
|
||||||
|
|
||||||
|
if look() == '.' then
|
||||||
|
pos = pos + 1
|
||||||
|
|
||||||
|
while chars.digits[look()] do
|
||||||
|
pos = pos + 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if look():lower() == 'e' then
|
||||||
|
pos = pos + 1
|
||||||
|
|
||||||
|
if look() == '-' then
|
||||||
|
pos = pos + 1
|
||||||
|
end
|
||||||
|
|
||||||
|
while chars.digits[look()] do
|
||||||
|
pos = pos + 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
pushToken('number')
|
||||||
|
elseif char == '[' then
|
||||||
|
local level = getDataLevel()
|
||||||
|
|
||||||
|
if level then
|
||||||
|
pushToken('string_start')
|
||||||
|
|
||||||
|
getData(level, 'string')
|
||||||
|
pushToken('string')
|
||||||
|
|
||||||
|
pos = pos + level + 2
|
||||||
|
pushToken('string_end')
|
||||||
|
else
|
||||||
|
pushToken('symbol')
|
||||||
|
end
|
||||||
|
elseif char == '.' then
|
||||||
|
if look() == '.' then
|
||||||
|
pos = pos + 1
|
||||||
|
|
||||||
|
if look() == '.' then
|
||||||
|
pos = pos + 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if getCurrentTokenText():len() == 3 then
|
||||||
|
pushToken('vararg')
|
||||||
|
else
|
||||||
|
pushToken('symbol')
|
||||||
|
end
|
||||||
|
elseif char == ':' and look() == ':' then
|
||||||
|
get()
|
||||||
|
|
||||||
|
pushToken('label_start')
|
||||||
|
|
||||||
|
chompWhitespace()
|
||||||
|
|
||||||
|
if chars.ident.start[look()] then
|
||||||
|
get()
|
||||||
|
|
||||||
|
while chars.ident[look()] do
|
||||||
|
get()
|
||||||
|
end
|
||||||
|
|
||||||
|
pushToken('label')
|
||||||
|
|
||||||
|
chompWhitespace()
|
||||||
|
|
||||||
|
if look() == ':' and look(1) == ':' then
|
||||||
|
get()
|
||||||
|
get()
|
||||||
|
|
||||||
|
pushToken('label_end')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
elseif chars.symbols.equality[char] then
|
||||||
|
if look() == '=' then
|
||||||
|
pos = pos + 1
|
||||||
|
end
|
||||||
|
|
||||||
|
pushToken('operator')
|
||||||
|
elseif chars.symbols[char] then
|
||||||
|
if chars.symbols.operators[char] then
|
||||||
|
pushToken('operator')
|
||||||
|
else
|
||||||
|
pushToken('symbol')
|
||||||
|
end
|
||||||
|
else
|
||||||
|
pushToken('unidentified')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
lines[#lines + 1] = buffer
|
||||||
|
|
||||||
|
return lines
|
||||||
|
end
|
|
@ -0,0 +1,51 @@
|
||||||
|
local lex = require("tevgit:core/editor/lexer.lua")
|
||||||
|
local theme = require("tevgit:core/editor/theme/default.lua")
|
||||||
|
|
||||||
|
local editor = teverse.construct("guiRichTextBox", {
|
||||||
|
parent = teverse.interface,
|
||||||
|
size = guiCoord(1, 0, 1, -100),
|
||||||
|
position = guiCoord(0, 0, 0, 50),
|
||||||
|
text = "local test = 10 + 10\n\n",
|
||||||
|
textWrap = true,
|
||||||
|
textFont = "tevurl:fonts/firaCodeRegular.otf",
|
||||||
|
textEditable = true
|
||||||
|
})
|
||||||
|
|
||||||
|
local function doLex()
|
||||||
|
editor:clearColours()
|
||||||
|
editor.backgroundColour = theme.background
|
||||||
|
editor.textColour = theme.foreground
|
||||||
|
|
||||||
|
local lines = lex(editor.text)
|
||||||
|
local index = 0
|
||||||
|
local lastColour = nil
|
||||||
|
for lineNumber, line in pairs(lines) do
|
||||||
|
local lineCount = 0
|
||||||
|
for _, token in pairs(line) do
|
||||||
|
local c = theme[token.type]
|
||||||
|
if c and lastColour ~= c then
|
||||||
|
editor:setColour(index + token.posFirst, c)
|
||||||
|
lastColour = c
|
||||||
|
end
|
||||||
|
|
||||||
|
lineCount = token.posLast
|
||||||
|
end
|
||||||
|
index = index + lineCount
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Highlight any pre-entered text
|
||||||
|
doLex()
|
||||||
|
|
||||||
|
local lastStroke = nil
|
||||||
|
editor:on("keyUp", function()
|
||||||
|
if lastStroke then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Limit lexxer to once very 0.2 seconds (every keystroke is inefficient)
|
||||||
|
lastStroke = true
|
||||||
|
sleep(0.2)
|
||||||
|
doLex()
|
||||||
|
lastStroke = nil
|
||||||
|
end)
|
|
@ -0,0 +1,44 @@
|
||||||
|
-- This file uses colours from https://ethanschoonover.com/solarized/
|
||||||
|
|
||||||
|
local colours = {
|
||||||
|
Base0 = colour.rgb(131, 148, 150),
|
||||||
|
Base1 = colour.rgb(147, 161, 161),
|
||||||
|
Base2 = colour.rgb(238, 232, 213),
|
||||||
|
Base3 = colour.rgb(253, 246, 227),
|
||||||
|
|
||||||
|
Base00 = colour.rgb(101, 123, 131),
|
||||||
|
Base01 = colour.rgb(88, 110, 117),
|
||||||
|
Base02 = colour.rgb(7, 54, 66),
|
||||||
|
Base03 = colour.rgb(0, 43, 54),
|
||||||
|
|
||||||
|
Yellow = colour.rgb(181, 137, 0),
|
||||||
|
Orange = colour.rgb(203, 75, 22),
|
||||||
|
Red = colour.rgb(220, 50, 47),
|
||||||
|
Magenta = colour.rgb(211, 54, 130),
|
||||||
|
Violet = colour.rgb(108, 113, 196),
|
||||||
|
Blue = colour.rgb(38, 139, 210),
|
||||||
|
Cyan = colour.rgb(42, 161, 152),
|
||||||
|
Green = colour.rgb(133, 153, 0),
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
background = colours.Base03,
|
||||||
|
foreground = colours.Base0,
|
||||||
|
|
||||||
|
comment = colours.Base01,
|
||||||
|
string_start = colours.Cyan,
|
||||||
|
string_end = colours.Cyan,
|
||||||
|
string = colours.Cyan,
|
||||||
|
escape = colours.Blue,
|
||||||
|
keyword = colours.Green,
|
||||||
|
value = colours.Green,
|
||||||
|
ident = colours.Blue,
|
||||||
|
number = colours.Magenta,
|
||||||
|
symbol = colours.Base1,
|
||||||
|
vararg = colours.Red,
|
||||||
|
operator = colours.Green,
|
||||||
|
label_start = colours.Red,
|
||||||
|
label_end = colours.Red,
|
||||||
|
label = colours.Red,
|
||||||
|
unidentified = colours.Base01
|
||||||
|
}
|
Loading…
Reference in New Issue