// Copyright 2025 International Digital Economy Academy
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
///|
/// Internal syntax-highlighting editor layer inspired by
/// `cosmic-text/src/edit/syntect.rs`.
pub struct SyntaxTheme {
name : String
background : Color
foreground : Color
cursor_opt : Color?
selection_opt : Color?
keyword : Color
type_name : Color
string_lit : Color
comment : Color
number : Color
}
///|
pub fn SyntaxTheme::name(self : SyntaxTheme) -> String {
self.name
}
///|
pub fn SyntaxTheme::background(self : SyntaxTheme) -> Color {
self.background
}
///|
pub fn SyntaxTheme::foreground(self : SyntaxTheme) -> Color {
self.foreground
}
///|
pub fn SyntaxTheme::cursor(self : SyntaxTheme) -> Color {
match self.cursor_opt {
Some(color) => color
None => self.foreground
}
}
///|
pub fn SyntaxTheme::selection(self : SyntaxTheme) -> Color {
match self.selection_opt {
Some(color) => color
None =>
Color::rgba(
self.foreground.r(),
self.foreground.g(),
self.foreground.b(),
0x33,
)
}
}
///|
fn theme_base16_eighties_dark() -> SyntaxTheme {
SyntaxTheme::{
name: "base16-eighties.dark",
background: Color::rgb(0x2D, 0x2D, 0x2D),
foreground: Color::rgb(0xD3, 0xD0, 0xC8),
cursor_opt: Some(Color::rgb(0xD3, 0xD0, 0xC8)),
selection_opt: Some(Color::rgba(0x51, 0x51, 0x51, 0xCC)),
keyword: Color::rgb(0xCC, 0x99, 0xCC),
type_name: Color::rgb(0xF2, 0x77, 0x7A),
string_lit: Color::rgb(0x99, 0xCC, 0x99),
comment: Color::rgb(0x74, 0x74, 0x74),
number: Color::rgb(0xF9, 0x91, 0x57),
}
}
///|
fn theme_base16_ocean_light() -> SyntaxTheme {
SyntaxTheme::{
name: "base16-ocean.light",
background: Color::rgb(0xEF, 0xF1, 0xF5),
foreground: Color::rgb(0x4F, 0x5B, 0x66),
cursor_opt: Some(Color::rgb(0x4F, 0x5B, 0x66)),
selection_opt: Some(Color::rgba(0xBF, 0xC7, 0xD5, 0xD8)),
keyword: Color::rgb(0xA7, 0xB8, 0xC8),
type_name: Color::rgb(0xBF, 0x61, 0x6A),
string_lit: Color::rgb(0xA3, 0xBE, 0x8C),
comment: Color::rgb(0xA7, 0xAD, 0xBA),
number: Color::rgb(0xD0, 0x87, 0x70),
}
}
///|
pub struct SyntaxSystem {}
///|
pub fn SyntaxSystem::new() -> SyntaxSystem {
SyntaxSystem::{ }
}
///|
fn ascii_lower_char(ch : Char) -> Char {
let c = ch.to_int()
if c >= 65 && c <= 90 {
(c + 32).unsafe_to_char()
} else {
ch
}
}
///|
fn ascii_lower(s : String) -> String {
let sb = StringBuilder::new(size_hint=s.length())
for ch in s {
sb.write_char(ascii_lower_char(ch))
}
sb.to_string()
}
///|
pub fn SyntaxSystem::theme(
_self : SyntaxSystem,
theme_name : String,
) -> SyntaxTheme? {
let name = ascii_lower(theme_name)
if name == "base16-eighties.dark" ||
name == "base16-ocean.dark" ||
name == "base16-mocha.dark" ||
name == "solarized (dark)" {
Some(theme_base16_eighties_dark())
} else if name == "base16-ocean.light" ||
name == "inspiredgithub" ||
name == "solarized (light)" {
Some(theme_base16_ocean_light())
} else {
None
}
}
///|
fn normalize_extension(extension : String) -> String {
let lower = ascii_lower(extension)
if lower.length() > 0 && lower.code_unit_at(0) == 46 {
let sb = StringBuilder::new(size_hint=lower.length() - 1)
sb.write_view(lower[:].view(start_offset=1, end_offset=lower.length()))
sb.to_string()
} else {
lower
}
}
///|
pub fn SyntaxSystem::syntax_for_extension(
_self : SyntaxSystem,
extension : String,
) -> String? {
let ext = normalize_extension(extension)
if ext == "rs" || ext == "rlib" {
Some("rust")
} else if ext == "py" || ext == "pyi" || ext == "pyw" {
Some("python")
} else if ext == "json" {
Some("json")
} else if ext == "jsonc" {
Some("jsonc")
} else if ext == "json5" {
Some("json5")
} else if ext == "toml" {
Some("toml")
} else if ext == "yaml" || ext == "yml" {
Some("yaml")
} else if ext == "html" || ext == "htm" || ext == "xml" {
Some("markup")
} else if ext == "md" ||
ext == "markdown" ||
ext == "mdown" ||
ext == "mkd" ||
ext == "mkdn" ||
ext == "mdx" {
Some("markdown")
} else if ext == "sql" {
Some("sql")
} else if ext == "sh" || ext == "bash" || ext == "zsh" {
Some("shell")
} else if ext == "rb" {
Some("ruby")
} else if ext == "lua" {
Some("lua")
} else if ext == "c" ||
ext == "h" ||
ext == "cc" ||
ext == "hh" ||
ext == "cpp" ||
ext == "hpp" ||
ext == "cxx" ||
ext == "hxx" ||
ext == "js" ||
ext == "ts" ||
ext == "tsx" ||
ext == "jsx" ||
ext == "mjs" ||
ext == "cjs" ||
ext == "java" ||
ext == "go" ||
ext == "kt" ||
ext == "kts" ||
ext == "swift" ||
ext == "cs" ||
ext == "php" ||
ext == "css" ||
ext == "scss" ||
ext == "sass" ||
ext == "less" {
Some("clike")
} else {
None
}
}
///|
pub fn SyntaxSystem::plain_text_syntax(_self : SyntaxSystem) -> String {
"plain"
}
///|
pub(all) enum TokenClass {
Keyword
TypeName
StringLit
Comment
Number
}
///|
pub struct LineState {
block_comment_depth : Int
markup_comment_open : Bool
markup_tag_open : Bool
ruby_block_comment_open : Bool
shell_heredoc_delim_opt : String?
shell_heredoc_strip_tabs : Bool
lua_long_eq_count_opt : Int?
lua_long_is_comment : Bool
string_delim_opt : Char?
string_is_triple : Bool
raw_hash_count_opt : Int?
markdown_fence_char_opt : Char?
clike_preproc_continuation : Bool
}
///|
pub fn LineState::new() -> LineState {
LineState::{
block_comment_depth: 0,
markup_comment_open: false,
markup_tag_open: false,
ruby_block_comment_open: false,
shell_heredoc_delim_opt: None,
shell_heredoc_strip_tabs: false,
lua_long_eq_count_opt: None,
lua_long_is_comment: false,
string_delim_opt: None,
string_is_triple: false,
raw_hash_count_opt: None,
markdown_fence_char_opt: None,
clike_preproc_continuation: false,
}
}
///|
pub impl Eq for LineState with fn equal(self, other) {
self.block_comment_depth == other.block_comment_depth &&
self.markup_comment_open == other.markup_comment_open &&
self.markup_tag_open == other.markup_tag_open &&
self.ruby_block_comment_open == other.ruby_block_comment_open &&
self.shell_heredoc_delim_opt == other.shell_heredoc_delim_opt &&
self.shell_heredoc_strip_tabs == other.shell_heredoc_strip_tabs &&
self.lua_long_eq_count_opt == other.lua_long_eq_count_opt &&
self.lua_long_is_comment == other.lua_long_is_comment &&
self.string_delim_opt == other.string_delim_opt &&
self.string_is_triple == other.string_is_triple &&
self.raw_hash_count_opt == other.raw_hash_count_opt &&
self.markdown_fence_char_opt == other.markdown_fence_char_opt &&
self.clike_preproc_continuation == other.clike_preproc_continuation
}
///|
pub struct TokenSpan {
start : Int
end : Int
class : TokenClass
}
///|
pub struct CachedLine {
text : String
state_in : LineState
state_out : LineState
spans : Array[TokenSpan]
}
///|
pub struct SyntaxEditor {
editor : Editor
syntax_system : SyntaxSystem
syntax_name : String
theme : SyntaxTheme
cache : Array[CachedLine]
}
///|
fn syntax_slice_string(s : String, start : Int, end : Int) -> String {
let sb = StringBuilder::new(size_hint=(end - start) * 2)
sb.write_view(s[:].view(start_offset=start, end_offset=end))
sb.to_string()
}
///|
fn is_ascii_digit(ch : Char) -> Bool {
let c = ch.to_int()
c >= 48 && c <= 57
}
///|
fn is_ascii_alpha(ch : Char) -> Bool {
let c = ch.to_int()
(c >= 65 && c <= 90) || (c >= 97 && c <= 122)
}
///|
fn is_identifier_start(ch : Char) -> Bool {
is_ascii_alpha(ch) || ch == '_'
}
///|
fn is_identifier_part(ch : Char) -> Bool {
is_identifier_start(ch) || is_ascii_digit(ch)
}
///|
fn code_unit_offsets(_text : String, chars : Array[Char]) -> Array[Int] {
let offsets : Array[Int] = []
let mut pos = 0
for ch in chars {
offsets.push(pos)
pos = pos + ch.utf16_len()
}
offsets
}
///|
fn offset_at(text : String, offsets : Array[Int], char_i : Int) -> Int {
if char_i < 0 {
0
} else if char_i >= offsets.length() {
text.length()
} else {
offsets[char_i]
}
}
///|
fn push_token(
out : Array[TokenSpan],
start : Int,
end : Int,
class : TokenClass,
) -> Unit {
if end > start {
out.push(TokenSpan::{ start, end, class })
}
}
///|
fn is_rust_keyword(word : String) -> Bool {
word == "as" ||
word == "break" ||
word == "const" ||
word == "continue" ||
word == "crate" ||
word == "else" ||
word == "enum" ||
word == "extern" ||
word == "false" ||
word == "fn" ||
word == "for" ||
word == "if" ||
word == "impl" ||
word == "in" ||
word == "let" ||
word == "loop" ||
word == "match" ||
word == "mod" ||
word == "move" ||
word == "mut" ||
word == "pub" ||
word == "ref" ||
word == "return" ||
word == "self" ||
word == "Self" ||
word == "static" ||
word == "struct" ||
word == "super" ||
word == "trait" ||
word == "true" ||
word == "type" ||
word == "unsafe" ||
word == "use" ||
word == "where" ||
word == "while" ||
word == "async" ||
word == "await" ||
word == "dyn"
}
///|
fn is_rust_type(word : String) -> Bool {
word == "String" ||
word == "str" ||
word == "Vec" ||
word == "Option" ||
word == "Result" ||
word == "u8" ||
word == "u16" ||
word == "u32" ||
word == "u64" ||
word == "u128" ||
word == "usize" ||
word == "i8" ||
word == "i16" ||
word == "i32" ||
word == "i64" ||
word == "i128" ||
word == "isize" ||
word == "bool" ||
word == "char" ||
word == "f32" ||
word == "f64"
}
///|
fn is_clike_keyword(word : String) -> Bool {
word == "if" ||
word == "else" ||
word == "switch" ||
word == "case" ||
word == "default" ||
word == "for" ||
word == "while" ||
word == "do" ||
word == "break" ||
word == "continue" ||
word == "return" ||
word == "class" ||
word == "struct" ||
word == "enum" ||
word == "namespace" ||
word == "using" ||
word == "public" ||
word == "private" ||
word == "protected" ||
word == "static" ||
word == "const" ||
word == "final" ||
word == "new" ||
word == "delete" ||
word == "try" ||
word == "catch" ||
word == "throw" ||
word == "import" ||
word == "export" ||
word == "function" ||
word == "var" ||
word == "let"
}
///|
fn is_clike_type(word : String) -> Bool {
word == "void" ||
word == "char" ||
word == "short" ||
word == "int" ||
word == "long" ||
word == "float" ||
word == "double" ||
word == "bool" ||
word == "size_t" ||
word == "string" ||
word == "String" ||
word == "Object" ||
word == "Array" ||
word == "Map"
}
///|
fn is_python_keyword(word : String) -> Bool {
word == "and" ||
word == "as" ||
word == "assert" ||
word == "break" ||
word == "class" ||
word == "continue" ||
word == "def" ||
word == "del" ||
word == "elif" ||
word == "else" ||
word == "except" ||
word == "False" ||
word == "finally" ||
word == "for" ||
word == "from" ||
word == "global" ||
word == "if" ||
word == "import" ||
word == "in" ||
word == "is" ||
word == "lambda" ||
word == "None" ||
word == "nonlocal" ||
word == "not" ||
word == "or" ||
word == "pass" ||
word == "raise" ||
word == "return" ||
word == "True" ||
word == "try" ||
word == "while" ||
word == "with" ||
word == "yield"
}
///|
fn is_shell_keyword(word : String) -> Bool {
word == "if" ||
word == "then" ||
word == "else" ||
word == "elif" ||
word == "fi" ||
word == "for" ||
word == "while" ||
word == "until" ||
word == "do" ||
word == "done" ||
word == "case" ||
word == "esac" ||
word == "in" ||
word == "function" ||
word == "select"
}
///|
fn is_ruby_keyword(word : String) -> Bool {
word == "BEGIN" ||
word == "END" ||
word == "alias" ||
word == "and" ||
word == "begin" ||
word == "break" ||
word == "case" ||
word == "class" ||
word == "def" ||
word == "defined?" ||
word == "do" ||
word == "else" ||
word == "elsif" ||
word == "end" ||
word == "ensure" ||
word == "false" ||
word == "for" ||
word == "if" ||
word == "in" ||
word == "module" ||
word == "next" ||
word == "nil" ||
word == "not" ||
word == "or" ||
word == "redo" ||
word == "rescue" ||
word == "retry" ||
word == "return" ||
word == "self" ||
word == "super" ||
word == "then" ||
word == "true" ||
word == "undef" ||
word == "unless" ||
word == "until" ||
word == "when" ||
word == "while" ||
word == "yield"
}
///|
fn is_lua_keyword(word : String) -> Bool {
word == "and" ||
word == "break" ||
word == "do" ||
word == "else" ||
word == "elseif" ||
word == "end" ||
word == "false" ||
word == "for" ||
word == "function" ||
word == "goto" ||
word == "if" ||
word == "in" ||
word == "local" ||
word == "nil" ||
word == "not" ||
word == "or" ||
word == "repeat" ||
word == "return" ||
word == "then" ||
word == "true" ||
word == "until" ||
word == "while"
}
///|
fn is_sql_keyword(word : String) -> Bool {
let lower = ascii_lower(word)
lower == "select" ||
lower == "from" ||
lower == "where" ||
lower == "group" ||
lower == "by" ||
lower == "order" ||
lower == "having" ||
lower == "limit" ||
lower == "offset" ||
lower == "join" ||
lower == "inner" ||
lower == "left" ||
lower == "right" ||
lower == "full" ||
lower == "cross" ||
lower == "on" ||
lower == "as" ||
lower == "insert" ||
lower == "into" ||
lower == "values" ||
lower == "update" ||
lower == "set" ||
lower == "delete" ||
lower == "create" ||
lower == "table" ||
lower == "alter" ||
lower == "drop" ||
lower == "distinct" ||
lower == "union" ||
lower == "all" ||
lower == "and" ||
lower == "or" ||
lower == "not" ||
lower == "null" ||
lower == "is" ||
lower == "between" ||
lower == "like" ||
lower == "exists"
}
///|
fn is_sql_type(word : String) -> Bool {
let lower = ascii_lower(word)
lower == "int" ||
lower == "integer" ||
lower == "bigint" ||
lower == "smallint" ||
lower == "text" ||
lower == "varchar" ||
lower == "char" ||
lower == "boolean" ||
lower == "float" ||
lower == "double" ||
lower == "decimal" ||
lower == "numeric" ||
lower == "date" ||
lower == "time" ||
lower == "timestamp"
}
///|
fn is_yaml_toml_keyword(word : String) -> Bool {
let lower = ascii_lower(word)
lower == "true" ||
lower == "false" ||
lower == "null" ||
lower == "yes" ||
lower == "no" ||
lower == "on" ||
lower == "off"
}
///|
fn classify_identifier(word : String, syntax_name : String) -> TokenClass? {
if syntax_name == "rust" {
if is_rust_keyword(word) {
Some(Keyword)
} else if is_rust_type(word) {
Some(TypeName)
} else {
None
}
} else if syntax_name == "clike" {
if is_clike_keyword(word) {
Some(Keyword)
} else if is_clike_type(word) {
Some(TypeName)
} else {
None
}
} else if syntax_name == "python" {
if is_python_keyword(word) {
Some(Keyword)
} else {
None
}
} else if syntax_name == "shell" {
if is_shell_keyword(word) {
Some(Keyword)
} else {
None
}
} else if syntax_name == "ruby" {
if is_ruby_keyword(word) {
Some(Keyword)
} else {
None
}
} else if syntax_name == "lua" {
if is_lua_keyword(word) {
Some(Keyword)
} else {
None
}
} else if syntax_name == "sql" {
if is_sql_keyword(word) {
Some(Keyword)
} else if is_sql_type(word) {
Some(TypeName)
} else {
None
}
} else if syntax_name == "yaml" || syntax_name == "toml" {
if is_yaml_toml_keyword(word) {
Some(Keyword)
} else {
None
}
} else if syntax_name == "json" ||
syntax_name == "jsonc" ||
syntax_name == "json5" {
if word == "true" || word == "false" || word == "null" {
Some(Keyword)
} else {
None
}
} else {
None
}
}
///|
fn has_line_comment_prefix(
syntax_name : String,
chars : Array[Char],
i : Int,
) -> Bool {
if syntax_name == "python" ||
syntax_name == "shell" ||
syntax_name == "ruby" ||
syntax_name == "yaml" ||
syntax_name == "toml" {
i < chars.length() && chars[i] == '#'
} else if syntax_name == "lua" || syntax_name == "sql" {
i + 1 < chars.length() && chars[i] == '-' && chars[i + 1] == '-'
} else if syntax_name == "jsonc" || syntax_name == "json5" {
i + 1 < chars.length() && chars[i] == '/' && chars[i + 1] == '/'
} else if syntax_name == "rust" || syntax_name == "clike" {
i + 1 < chars.length() && chars[i] == '/' && chars[i + 1] == '/'
} else {
false
}
}
///|
fn supports_block_comment(syntax_name : String) -> Bool {
syntax_name == "rust" ||
syntax_name == "clike" ||
syntax_name == "sql" ||
syntax_name == "jsonc" ||
syntax_name == "json5"
}
///|
fn supports_nested_block_comment(syntax_name : String) -> Bool {
syntax_name == "rust"
}
///|
fn is_python_string_prefix_char(ch : Char) -> Bool {
ch == 'r' ||
ch == 'R' ||
ch == 'b' ||
ch == 'B' ||
ch == 'f' ||
ch == 'F' ||
ch == 'u' ||
ch == 'U'
}
///|
fn lua_long_bracket_open_count(chars : Array[Char], start : Int) -> Int? {
let n = chars.length()
if start >= n || chars[start] != '[' {
return None
}
let mut i = start + 1
while i < n && chars[i] == '=' {
i = i + 1
}
if i < n && chars[i] == '[' {
Some(i - (start + 1))
} else {
None
}
}
///|
fn lua_long_bracket_close(
chars : Array[Char],
start : Int,
eq_count : Int,
) -> Bool {
let n = chars.length()
if start >= n || chars[start] != ']' {
return false
}
let mut i = start + 1
let mut j = 0
while j < eq_count {
if i >= n || chars[i] != '=' {
return false
}
i = i + 1
j = j + 1
}
i < n && chars[i] == ']'
}
///|
fn rust_char_literal_end(chars : Array[Char], start : Int) -> Int? {
let n = chars.length()
if start + 2 >= n || chars[start] != '\'' {
return None
}
let mut j = start + 1
if chars[j] == '\\' {
j = j + 1
if j >= n {
return None
}
if chars[j] == 'u' && j + 1 < n && chars[j + 1] == '{' {
j = j + 2
while j < n && chars[j] != '}' {
j = j + 1
}
if j >= n {
return None
}
j = j + 1
} else if chars[j] == 'x' && j + 2 < n {
j = j + 3
} else {
j = j + 1
}
} else {
j = j + 1
}
if j < n && chars[j] == '\'' {
Some(j + 1)
} else {
None
}
}
///|
fn markdown_first_non_whitespace(chars : Array[Char]) -> Int {
let n = chars.length()
let mut i = 0
while i < n && chars[i].is_whitespace() {
i = i + 1
}
i
}
///|
fn line_starts_with(chars : Array[Char], start : Int, token : String) -> Bool {
let token_chars = token.to_array()
if start < 0 || start + token_chars.length() > chars.length() {
return false
}
let mut i = 0
while i < token_chars.length() {
if chars[start + i] != token_chars[i] {
return false
}
i = i + 1
}
true
}
///|
fn trim_leading_tabs(text : String) -> String {
let chars = text.to_array()
let offsets = code_unit_offsets(text, chars)
let mut i = 0
while i < chars.length() && chars[i] == '\t' {
i = i + 1
}
slice_string(text, offset_at(text, offsets, i), text.length())
}
///|
fn shell_heredoc_start(
text : String,
chars : Array[Char],
offsets : Array[Int],
start : Int,
) -> (String, Bool, Int)? {
let n = chars.length()
if start + 1 >= n || chars[start] != '<' || chars[start + 1] != '<' {
return None
}
let mut i = start + 2
let mut strip_tabs = false
if i < n && chars[i] == '-' {
strip_tabs = true
i = i + 1
}
while i < n && chars[i].is_whitespace() {
i = i + 1
}
if i >= n {
return None
}
let delim_start = i
let delim_end = if chars[i] == '"' || chars[i] == '\'' {
let quote = chars[i]
i = i + 1
let start_quoted = i
while i < n && chars[i] != quote {
i = i + 1
}
if i >= n {
return None
}
let end_quoted = i
i = i + 1
if end_quoted == start_quoted {
return None
}
return Some(
(
slice_string(
text,
offset_at(text, offsets, start_quoted),
offset_at(text, offsets, end_quoted),
),
strip_tabs,
i,
),
)
} else {
while i < n &&
!chars[i].is_whitespace() &&
chars[i] != ';' &&
chars[i] != ')' &&
chars[i] != '<' &&
chars[i] != '>' {
i = i + 1
}
i
}
if delim_end == delim_start {
None
} else {
Some(
(
slice_string(
text,
offset_at(text, offsets, delim_start),
offset_at(text, offsets, delim_end),
),
strip_tabs,
delim_end,
),
)
}
}
///|
fn markdown_fence_char(chars : Array[Char], start : Int) -> Char? {
if start + 2 >= chars.length() {
return None
}
let c = chars[start]
if (c == '`' || c == '~') && chars[start + 1] == c && chars[start + 2] == c {
Some(c)
} else {
None
}
}
///|
fn line_ends_with_backslash(chars : Array[Char]) -> Bool {
let mut i = chars.length()
while i > 0 {
i = i - 1
if chars[i].is_whitespace() {
continue
}
return chars[i] == '\\'
}
false
}
///|
fn tokenize_line(
text : String,
syntax_name : String,
state_in : LineState,
) -> (Array[TokenSpan], LineState) {
let chars = text.to_array()
let offsets = code_unit_offsets(text, chars)
let spans : Array[TokenSpan] = []
let mut state = state_in
let mut i = 0
let n = chars.length()
let line_first = markdown_first_non_whitespace(chars)
if syntax_name == "clike" {
let preproc_line = state.clike_preproc_continuation ||
(line_first < n && chars[line_first] == '#')
if preproc_line {
let continuation = line_ends_with_backslash(chars)
return (
if text.length() == 0 {
[]
} else {
[
TokenSpan::{
start: offset_at(text, offsets, line_first),
end: text.length(),
class: Keyword,
},
]
},
LineState::{ ..state, clike_preproc_continuation: continuation },
)
}
if state.clike_preproc_continuation {
state = LineState::{ ..state, clike_preproc_continuation: false }
}
} else if state.clike_preproc_continuation {
state = LineState::{ ..state, clike_preproc_continuation: false }
}
if syntax_name == "ruby" {
if state.ruby_block_comment_open {
let closes = line_starts_with(chars, line_first, "=end")
return (
if text.length() == 0 {
[]
} else {
[
TokenSpan::{
start: offset_at(text, offsets, line_first),
end: text.length(),
class: Comment,
},
]
},
LineState::{ ..state, ruby_block_comment_open: !closes },
)
}
if line_starts_with(chars, line_first, "=begin") {
return (
if text.length() == 0 {
[]
} else {
[
TokenSpan::{
start: offset_at(text, offsets, line_first),
end: text.length(),
class: Comment,
},
]
},
LineState::{ ..state, ruby_block_comment_open: true },
)
}
}
if syntax_name == "shell" {
match state.shell_heredoc_delim_opt {
None => ()
Some(delim) => {
let candidate = if state.shell_heredoc_strip_tabs {
trim_leading_tabs(text)
} else {
text
}
let closes = candidate == delim
return (
if text.length() == 0 {
[]
} else {
[TokenSpan::{ start: 0, end: text.length(), class: StringLit }]
},
LineState::{
..state,
shell_heredoc_delim_opt: if closes {
None
} else {
Some(delim)
},
shell_heredoc_strip_tabs: if closes {
false
} else {
state.shell_heredoc_strip_tabs
},
},
)
}
}
}
if syntax_name == "markdown" {
let first = markdown_first_non_whitespace(chars)
match state.markdown_fence_char_opt {
Some(fence) =>
match markdown_fence_char(chars, first) {
Some(c) if c == fence =>
return (
[
TokenSpan::{
start: offset_at(text, offsets, first),
end: text.length(),
class: Keyword,
},
],
LineState::{ ..state, markdown_fence_char_opt: None },
)
_ =>
return (
if text.length() == 0 {
[]
} else {
[TokenSpan::{ start: 0, end: text.length(), class: StringLit }]
},
state,
)
}
None => {
let spans_md : Array[TokenSpan] = []
let mut next_state = state
match markdown_fence_char(chars, first) {
Some(c) => {
spans_md.push(TokenSpan::{
start: offset_at(text, offsets, first),
end: text.length(),
class: Keyword,
})
next_state = LineState::{
..next_state,
markdown_fence_char_opt: Some(c),
}
return (spans_md, next_state)
}
None => ()
}
if first < n && chars[first] == '#' {
spans_md.push(TokenSpan::{
start: offset_at(text, offsets, first),
end: text.length(),
class: Keyword,
})
}
let mut p = 0
while p < n {
if chars[p] == '`' {
let mut run = 1
while p + run < n && chars[p + run] == '`' {
run = run + 1
}
let start_i = p
p = p + run
let mut closed = false
while p < n {
if chars[p] == '`' {
let mut run2 = 1
while p + run2 < n && chars[p + run2] == '`' {
run2 = run2 + 1
}
if run2 == run {
p = p + run2
closed = true
break
}
p = p + run2
} else {
p = p + 1
}
}
let end_i = if closed { p } else { n }
spans_md.push(TokenSpan::{
start: offset_at(text, offsets, start_i),
end: offset_at(text, offsets, end_i),
class: StringLit,
})
continue
}
p = p + 1
}
return (spans_md, next_state)
}
}
}
while i < n {
match state.raw_hash_count_opt {
None => ()
Some(hashes) => {
let start_i = i
let mut closed = false
while i < n {
if chars[i] == '"' {
let mut ok = true
let mut j = 0
while j < hashes {
if i + 1 + j >= n || chars[i + 1 + j] != '#' {
ok = false
break
}
j = j + 1
}
if ok {
i = i + 1 + hashes
closed = true
break
}
}
i = i + 1
}
push_token(
spans,
offset_at(text, offsets, start_i),
offset_at(text, offsets, i),
StringLit,
)
if closed {
state = LineState::{ ..state, raw_hash_count_opt: None }
}
continue
}
}
match state.lua_long_eq_count_opt {
None => ()
Some(eq_count) => {
let start_i = i
let mut closed = false
while i < n {
if lua_long_bracket_close(chars, i, eq_count) {
i = i + eq_count + 2
closed = true
break
}
i = i + 1
}
push_token(
spans,
offset_at(text, offsets, start_i),
offset_at(text, offsets, i),
if state.lua_long_is_comment {
Comment
} else {
StringLit
},
)
if closed {
state = LineState::{
..state,
lua_long_eq_count_opt: None,
lua_long_is_comment: false,
}
}
continue
}
}
if syntax_name == "markup" && state.markup_comment_open {
let start_i = i
let mut closed = false
while i + 2 < n {
if chars[i] == '-' && chars[i + 1] == '-' && chars[i + 2] == '>' {
i = i + 3
closed = true
break
}
i = i + 1
}
if !closed {
i = n
}
push_token(
spans,
offset_at(text, offsets, start_i),
offset_at(text, offsets, i),
Comment,
)
state = LineState::{ ..state, markup_comment_open: !closed }
continue
}
if syntax_name == "markup" && state.markup_tag_open {
let start_i = i
let mut closed = false
while i < n {
if chars[i] == '>' {
i = i + 1
closed = true
break
}
i = i + 1
}
push_token(
spans,
offset_at(text, offsets, start_i),
offset_at(text, offsets, i),
Keyword,
)
state = LineState::{ ..state, markup_tag_open: !closed }
continue
}
if state.block_comment_depth > 0 {
let start_i = i
let nested = supports_nested_block_comment(syntax_name)
let mut depth = state.block_comment_depth
let mut end_i = n
let mut close_found = false
while i + 1 < n {
if nested && chars[i] == '/' && chars[i + 1] == '*' {
depth = depth + 1
i = i + 2
continue
}
if chars[i] == '*' && chars[i + 1] == '/' {
depth = depth - 1
i = i + 2
if depth == 0 {
end_i = i
close_found = true
break
}
continue
}
i = i + 1
}
if !close_found {
i = n
}
push_token(
spans,
offset_at(text, offsets, start_i),
offset_at(text, offsets, end_i),
Comment,
)
if close_found {
state = LineState::{ ..state, block_comment_depth: 0 }
} else {
state = LineState::{ ..state, block_comment_depth: depth }
}
continue
}
match state.string_delim_opt {
Some(delim) => {
let triple = state.string_is_triple
let start_i = i
let mut escaped = false
let mut closed = false
while i < n {
let ch = chars[i]
if triple {
if i + 2 < n &&
chars[i] == delim &&
chars[i + 1] == delim &&
chars[i + 2] == delim {
i = i + 3
closed = true
break
}
i = i + 1
continue
}
if escaped {
escaped = false
i = i + 1
continue
}
if ch == '\\' {
escaped = true
i = i + 1
continue
}
if ch == delim {
i = i + 1
closed = true
break
}
i = i + 1
}
push_token(
spans,
offset_at(text, offsets, start_i),
offset_at(text, offsets, i),
StringLit,
)
if closed {
state = LineState::{
..state,
string_delim_opt: None,
string_is_triple: false,
}
} else if !triple {
state = LineState::{
..state,
string_delim_opt: None,
string_is_triple: false,
}
}
continue
}
None => ()
}
if syntax_name == "markup" &&
i + 3 < n &&
chars[i] == '<' &&
chars[i + 1] == '!' &&
chars[i + 2] == '-' &&
chars[i + 3] == '-' {
let start_i = i
i = i + 4
let mut closed = false
while i + 2 < n {
if chars[i] == '-' && chars[i + 1] == '-' && chars[i + 2] == '>' {
i = i + 3
closed = true
break
}
i = i + 1
}
if !closed {
i = n
}
push_token(
spans,
offset_at(text, offsets, start_i),
offset_at(text, offsets, i),
Comment,
)
if !closed {
state = LineState::{ ..state, markup_comment_open: true }
}
continue
}
if syntax_name == "markup" &&
chars[i] == '<' &&
i + 1 < n &&
(
chars[i + 1] == '/' ||
chars[i + 1] == '!' ||
chars[i + 1] == '?' ||
is_identifier_start(chars[i + 1])
) {
let start_i = i
i = i + 1
let mut closed = false
while i < n {
if chars[i] == '>' {
i = i + 1
closed = true
break
}
i = i + 1
}
push_token(
spans,
offset_at(text, offsets, start_i),
offset_at(text, offsets, i),
Keyword,
)
if !closed {
state = LineState::{ ..state, markup_tag_open: true }
}
continue
}
if syntax_name == "lua" &&
i + 2 < n &&
chars[i] == '-' &&
chars[i + 1] == '-' &&
chars[i + 2] == '[' {
match lua_long_bracket_open_count(chars, i + 2) {
None => ()
Some(eq_count) => {
let start_i = i
i = i + 4 + eq_count
let mut closed = false
while i < n {
if lua_long_bracket_close(chars, i, eq_count) {
i = i + eq_count + 2
closed = true
break
}
i = i + 1
}
push_token(
spans,
offset_at(text, offsets, start_i),
offset_at(text, offsets, i),
Comment,
)
if !closed {
state = LineState::{
..state,
lua_long_eq_count_opt: Some(eq_count),
lua_long_is_comment: true,
}
}
continue
}
}
}
if syntax_name == "lua" && chars[i] == '[' {
match lua_long_bracket_open_count(chars, i) {
None => ()
Some(eq_count) => {
let start_i = i
i = i + 2 + eq_count
let mut closed = false
while i < n {
if lua_long_bracket_close(chars, i, eq_count) {
i = i + eq_count + 2
closed = true
break
}
i = i + 1
}
push_token(
spans,
offset_at(text, offsets, start_i),
offset_at(text, offsets, i),
StringLit,
)
if !closed {
state = LineState::{
..state,
lua_long_eq_count_opt: Some(eq_count),
lua_long_is_comment: false,
}
}
continue
}
}
}
if syntax_name == "shell" {
match shell_heredoc_start(text, chars, offsets, i) {
None => ()
Some(info) => {
let delimiter = info.0
let strip_tabs = info.1
let end_i = info.2
push_token(
spans,
offset_at(text, offsets, i),
offset_at(text, offsets, end_i),
Keyword,
)
state = LineState::{
..state,
shell_heredoc_delim_opt: Some(delimiter),
shell_heredoc_strip_tabs: strip_tabs,
}
i = end_i
continue
}
}
}
if has_line_comment_prefix(syntax_name, chars, i) {
push_token(spans, offset_at(text, offsets, i), text.length(), Comment)
break
}
if i == line_first &&
syntax_name == "rust" &&
chars[i] == '#' &&
i + 1 < n &&
(
chars[i + 1] == '[' ||
(chars[i + 1] == '!' && i + 2 < n && chars[i + 2] == '[')
) {
push_token(spans, offset_at(text, offsets, i), text.length(), Keyword)
break
}
if supports_block_comment(syntax_name) &&
i + 1 < n &&
chars[i] == '/' &&
chars[i + 1] == '*' {
let start_i = i
i = i + 2
let nested = supports_nested_block_comment(syntax_name)
let mut depth = 1
let mut end_i = n
let mut close_found = false
while i + 1 < n {
if nested && chars[i] == '/' && chars[i + 1] == '*' {
depth = depth + 1
i = i + 2
continue
}
if chars[i] == '*' && chars[i + 1] == '/' {
depth = depth - 1
i = i + 2
if depth == 0 {
end_i = i
close_found = true
break
}
continue
}
i = i + 1
}
if !close_found {
i = n
state = LineState::{ ..state, block_comment_depth: depth }
}
push_token(
spans,
offset_at(text, offsets, start_i),
offset_at(text, offsets, end_i),
Comment,
)
continue
}
if syntax_name == "python" && is_python_string_prefix_char(chars[i]) {
let start_i = i
let mut j = i
while j < n && j - start_i < 2 && is_python_string_prefix_char(chars[j]) {
j = j + 1
}
if j < n && (chars[j] == '"' || chars[j] == '\'') {
let delim = chars[j]
let triple = j + 2 < n && chars[j + 1] == delim && chars[j + 2] == delim
if triple {
i = j + 3
} else {
i = j + 1
}
let mut escaped = false
let mut closed = false
while i < n {
let ch = chars[i]
if triple {
if i + 2 < n &&
chars[i] == delim &&
chars[i + 1] == delim &&
chars[i + 2] == delim {
i = i + 3
closed = true
break
}
i = i + 1
continue
}
if escaped {
escaped = false
i = i + 1
continue
}
if ch == '\\' {
escaped = true
i = i + 1
continue
}
if ch == delim {
i = i + 1
closed = true
break
}
i = i + 1
}
push_token(
spans,
offset_at(text, offsets, start_i),
offset_at(text, offsets, i),
StringLit,
)
if !closed {
let continue_string = triple || escaped
if continue_string {
state = LineState::{
..state,
string_delim_opt: Some(delim),
string_is_triple: triple,
}
}
}
continue
}
}
if syntax_name == "rust" && chars[i] == 'b' && i + 1 < n {
if chars[i + 1] == '"' || chars[i + 1] == '\'' {
let start_i = i
let delim = chars[i + 1]
i = i + 2
let mut escaped = false
let mut closed = false
while i < n {
let ch = chars[i]
if escaped {
escaped = false
i = i + 1
continue
}
if ch == '\\' {
escaped = true
i = i + 1
continue
}
if ch == delim {
i = i + 1
closed = true
break
}
i = i + 1
}
push_token(
spans,
offset_at(text, offsets, start_i),
offset_at(text, offsets, i),
StringLit,
)
if !closed && escaped {
state = LineState::{ ..state, string_delim_opt: Some(delim) }
}
continue
}
if chars[i + 1] == 'r' {
let start_i = i
let mut j = i + 2
while j < n && chars[j] == '#' {
j = j + 1
}
if j < n && chars[j] == '"' {
let hashes = j - (i + 2)
i = j + 1
let mut closed = false
while i < n {
if chars[i] == '"' {
let mut ok = true
let mut k = 0
while k < hashes {
if i + 1 + k >= n || chars[i + 1 + k] != '#' {
ok = false
break
}
k = k + 1
}
if ok {
i = i + 1 + hashes
closed = true
break
}
}
i = i + 1
}
push_token(
spans,
offset_at(text, offsets, start_i),
offset_at(text, offsets, i),
StringLit,
)
if !closed {
state = LineState::{ ..state, raw_hash_count_opt: Some(hashes) }
}
continue
}
}
}
if syntax_name == "rust" && chars[i] == 'r' {
let start_i = i
let mut j = i + 1
while j < n && chars[j] == '#' {
j = j + 1
}
if j < n && chars[j] == '"' {
let hashes = j - (i + 1)
i = j + 1
let mut closed = false
while i < n {
if chars[i] == '"' {
let mut ok = true
let mut k = 0
while k < hashes {
if i + 1 + k >= n || chars[i + 1 + k] != '#' {
ok = false
break
}
k = k + 1
}
if ok {
i = i + 1 + hashes
closed = true
break
}
}
i = i + 1
}
push_token(
spans,
offset_at(text, offsets, start_i),
offset_at(text, offsets, i),
StringLit,
)
if !closed {
state = LineState::{ ..state, raw_hash_count_opt: Some(hashes) }
}
continue
}
}
if syntax_name == "rust" && chars[i] == '\'' {
match rust_char_literal_end(chars, i) {
Some(end_i) => {
push_token(
spans,
offset_at(text, offsets, i),
offset_at(text, offsets, end_i),
StringLit,
)
i = end_i
continue
}
None =>
if i + 1 < n && is_identifier_start(chars[i + 1]) {
// Rust lifetime like `'a` should not be tokenized as a string.
i = i + 1
continue
}
}
}
if chars[i] == '"' || chars[i] == '\'' {
let delim = chars[i]
let start_i = i
let triple = (syntax_name == "python" || syntax_name == "toml") &&
i + 2 < n &&
chars[i + 1] == delim &&
chars[i + 2] == delim
if triple {
i = i + 3
} else {
i = i + 1
}
let mut escaped = false
let mut closed = false
while i < n {
let ch = chars[i]
if triple {
if i + 2 < n &&
chars[i] == delim &&
chars[i + 1] == delim &&
chars[i + 2] == delim {
i = i + 3
closed = true
break
}
i = i + 1
continue
}
if escaped {
escaped = false
i = i + 1
continue
}
if ch == '\\' {
escaped = true
i = i + 1
continue
}
if ch == delim {
i = i + 1
closed = true
break
}
i = i + 1
}
push_token(
spans,
offset_at(text, offsets, start_i),
offset_at(text, offsets, i),
StringLit,
)
if !closed {
let continue_string = triple || (syntax_name == "python" && escaped)
if continue_string {
state = LineState::{
..state,
string_delim_opt: Some(delim),
string_is_triple: triple,
}
}
}
continue
}
if is_ascii_digit(chars[i]) {
let start_i = i
i = i + 1
let allow_alpha = syntax_name == "rust" ||
syntax_name == "clike" ||
syntax_name == "python"
while i < n {
let ch = chars[i]
if is_ascii_digit(ch) ||
ch == '.' ||
ch == '_' ||
(allow_alpha && is_ascii_alpha(ch)) {
i = i + 1
} else if allow_alpha &&
(ch == '+' || ch == '-') &&
i > start_i &&
(
chars[i - 1] == 'e' ||
chars[i - 1] == 'E' ||
chars[i - 1] == 'p' ||
chars[i - 1] == 'P'
) {
i = i + 1
} else {
break
}
}
push_token(
spans,
offset_at(text, offsets, start_i),
offset_at(text, offsets, i),
Number,
)
continue
}
if is_identifier_start(chars[i]) {
let start_i = i
i = i + 1
while i < n && is_identifier_part(chars[i]) {
i = i + 1
}
let start = offset_at(text, offsets, start_i)
let end = offset_at(text, offsets, i)
let word = syntax_slice_string(text, start, end)
match classify_identifier(word, syntax_name) {
None => ()
Some(class) => push_token(spans, start, end, class)
}
continue
}
i = i + 1
}
(spans, state)
}
///|
fn attrs_for_token(
defaults : Attrs,
theme : SyntaxTheme,
class : TokenClass,
) -> Attrs {
match class {
Keyword => defaults.color(theme.keyword).weight(Weight::bold())
TypeName => defaults.color(theme.type_name)
StringLit => defaults.color(theme.string_lit)
Comment => defaults.color(theme.comment).style(@moon_swash.Style::Italic)
Number => defaults.color(theme.number)
}
}
///|
fn themed_defaults(defaults : Attrs, theme : SyntaxTheme) -> Attrs {
defaults.color(theme.foreground)
}
///|
fn copy_token_spans(spans : Array[TokenSpan]) -> Array[TokenSpan] {
let copied : Array[TokenSpan] = []
for span in spans {
copied.push(span)
}
copied
}
///|
fn highlighted_attrs_list_for_line(
defaults : Attrs,
theme : SyntaxTheme,
spans : Array[TokenSpan],
) -> AttrsList {
let mut attrs_list = AttrsList::new(defaults)
for span in spans {
let attrs = attrs_for_token(defaults, theme, span.class)
if attrs != defaults {
attrs_list = attrs_list.add_span(span.start, span.end, attrs)
}
}
attrs_list
}
///|
fn syntax_highlight_buffer(
buffer : Buffer,
theme : SyntaxTheme,
syntax_name : String,
old_cache : Array[CachedLine],
allow_cache : Bool,
) -> (Buffer, Array[CachedLine], Int) {
let lines = buffer.lines()
let new_lines : Array[BufferLine] = []
let new_cache : Array[CachedLine] = []
let mut state = LineState::new()
let mut highlighted = 0
let mut changed = false
for p in lines.iter2() {
let line_i = p.0
let line = p.1
let text = line.text()
let defaults = line.attrs_list().defaults()
let cache_hit = if allow_cache &&
line_i < old_cache.length() &&
line.metadata() is Some(_) {
let cached = old_cache[line_i]
cached.text == text && cached.state_in == state
} else {
false
}
let spans_and_state = if cache_hit {
(old_cache[line_i].spans, old_cache[line_i].state_out)
} else {
highlighted = highlighted + 1
tokenize_line(text, syntax_name, state)
}
let spans = spans_and_state.0
let state_out = spans_and_state.1
let attrs_list = highlighted_attrs_list_for_line(defaults, theme, spans)
let set_attrs = line.set_attrs_list(attrs_list)
let mut updated_line = set_attrs.0
if set_attrs.1 {
changed = true
}
let metadata_set = match updated_line.metadata() {
Some(v) => v != line_i
None => true
}
if metadata_set {
updated_line = updated_line.set_metadata(line_i)
}
new_lines.push(updated_line)
new_cache.push(CachedLine::{
text,
state_in: state,
state_out,
spans: copy_token_spans(spans),
})
state = state_out
}
let redraw = buffer.redraw() || changed || highlighted > 0
(Buffer::{ ..buffer, lines: new_lines, redraw }, new_cache, highlighted)
}
///|
fn syntax_apply_theme_defaults(buffer : Buffer, theme : SyntaxTheme) -> Buffer {
let lines = buffer.lines()
let new_lines : Array[BufferLine] = []
let mut changed = false
for line in lines {
let defaults = themed_defaults(line.attrs_list().defaults(), theme)
let set_line = line.set_attrs_list(AttrsList::new(defaults))
if set_line.1 {
changed = true
}
new_lines.push(set_line.0)
}
Buffer::{ ..buffer, lines: new_lines, redraw: buffer.redraw() || changed }
}
///|
fn syntax_rehighlight_editor(
editor : Editor,
theme : SyntaxTheme,
syntax_name : String,
old_cache : Array[CachedLine],
allow_cache : Bool,
) -> (Editor, Array[CachedLine], Int) {
let highlighted = syntax_highlight_buffer(
editor.buffer(),
theme,
syntax_name,
old_cache,
allow_cache,
)
(Editor::{ ..editor, buffer: highlighted.0 }, highlighted.1, highlighted.2)
}
///|
pub fn SyntaxEditor::new(
editor : Editor,
syntax_system : SyntaxSystem,
theme_name : String,
) -> SyntaxEditor? {
match syntax_system.theme(theme_name) {
None => None
Some(theme) => {
let syntax_name = syntax_system.plain_text_syntax()
Some(SyntaxEditor::{
editor,
syntax_system,
syntax_name,
theme,
cache: [],
})
}
}
}
///|
pub fn SyntaxEditor::editor(self : SyntaxEditor) -> Editor {
self.editor
}
///|
pub fn SyntaxEditor::theme(self : SyntaxEditor) -> SyntaxTheme {
self.theme
}
///|
pub fn SyntaxEditor::syntax_name(self : SyntaxEditor) -> String {
self.syntax_name
}
///|
pub fn SyntaxEditor::background_color(self : SyntaxEditor) -> Color {
self.theme.background()
}
///|
pub fn SyntaxEditor::foreground_color(self : SyntaxEditor) -> Color {
self.theme.foreground()
}
///|
pub fn SyntaxEditor::cursor_color(self : SyntaxEditor) -> Color {
self.theme.cursor()
}
///|
pub fn SyntaxEditor::selection_color(self : SyntaxEditor) -> Color {
self.theme.selection()
}
///|
pub fn SyntaxEditor::set_editor(
self : SyntaxEditor,
editor : Editor,
) -> SyntaxEditor {
SyntaxEditor::{ ..self, editor, }
}
///|
pub fn SyntaxEditor::update_theme(
self : SyntaxEditor,
theme_name : String,
) -> (SyntaxEditor, Bool) {
match self.syntax_system.theme(theme_name) {
None => (self, false)
Some(theme) => {
let themed_buffer = syntax_apply_theme_defaults(
self.editor.buffer(),
theme,
)
let themed_editor = Editor::{ ..self.editor, buffer: themed_buffer }
(SyntaxEditor::{ ..self, editor: themed_editor, theme, cache: [] }, true)
}
}
}
///|
pub fn SyntaxEditor::syntax_by_extension(
self : SyntaxEditor,
extension : String,
) -> SyntaxEditor {
let syntax_name = match self.syntax_system.syntax_for_extension(extension) {
None => self.syntax_system.plain_text_syntax()
Some(name) => name
}
SyntaxEditor::{ ..self, syntax_name, cache: [] }
}
///|
pub fn SyntaxEditor::rehighlight(self : SyntaxEditor) -> SyntaxEditor {
let highlighted = syntax_rehighlight_editor(
self.editor,
self.theme,
self.syntax_name,
self.cache,
true,
)
SyntaxEditor::{ ..self, editor: highlighted.0, cache: highlighted.1 }
}
///|
pub fn SyntaxEditor::action(
self : SyntaxEditor,
action : Action,
) -> SyntaxEditor {
SyntaxEditor::{ ..self, editor: self.editor.action(action) }
}
///|
pub fn SyntaxEditor::cursor(self : SyntaxEditor) -> Cursor {
self.editor.cursor()
}
///|
pub fn SyntaxEditor::set_cursor(
self : SyntaxEditor,
cursor : Cursor,
) -> SyntaxEditor {
SyntaxEditor::{ ..self, editor: self.editor.set_cursor(cursor) }
}
///|
pub fn SyntaxEditor::selection(self : SyntaxEditor) -> Selection {
self.editor.selection()
}
///|
pub fn SyntaxEditor::set_selection(
self : SyntaxEditor,
selection : Selection,
) -> SyntaxEditor {
SyntaxEditor::{ ..self, editor: self.editor.set_selection(selection) }
}
///|
pub fn SyntaxEditor::auto_indent(self : SyntaxEditor) -> Bool {
self.editor.auto_indent()
}
///|
pub fn SyntaxEditor::set_auto_indent(
self : SyntaxEditor,
auto_indent : Bool,
) -> SyntaxEditor {
SyntaxEditor::{ ..self, editor: self.editor.set_auto_indent(auto_indent) }
}
///|
pub fn SyntaxEditor::tab_width(self : SyntaxEditor) -> Int {
self.editor.tab_width()
}
///|
pub fn SyntaxEditor::set_tab_width(
self : SyntaxEditor,
tab_width : Int,
) -> SyntaxEditor {
SyntaxEditor::{ ..self, editor: self.editor.set_tab_width(tab_width) }
}
///|
pub fn SyntaxEditor::start_change(self : SyntaxEditor) -> SyntaxEditor {
SyntaxEditor::{ ..self, editor: self.editor.start_change() }
}
///|
pub fn SyntaxEditor::finish_change(
self : SyntaxEditor,
) -> (SyntaxEditor, Change?) {
let finished = self.editor.finish_change()
(SyntaxEditor::{ ..self, editor: finished.0 }, finished.1)
}
///|
pub fn SyntaxEditor::copy_selection(self : SyntaxEditor) -> String? {
self.editor.copy_selection()
}
///|
pub fn SyntaxEditor::delete_selection(
self : SyntaxEditor,
) -> (SyntaxEditor, Bool) {
let deleted = self.editor.delete_selection()
(SyntaxEditor::{ ..self, editor: deleted.0 }, deleted.1)
}
///|
pub fn SyntaxEditor::delete_range(
self : SyntaxEditor,
start : Cursor,
end : Cursor,
) -> SyntaxEditor {
SyntaxEditor::{
..self,
editor: self.editor.start_change().delete_range(start, end),
}
}
///|
pub fn SyntaxEditor::insert_string(
self : SyntaxEditor,
data : String,
attrs_list_opt : AttrsList?,
) -> SyntaxEditor {
SyntaxEditor::{
..self,
editor: self.editor.start_change().insert_string(data, attrs_list_opt),
}
}
///|
pub fn SyntaxEditor::apply_change(
self : SyntaxEditor,
change : Change,
) -> (SyntaxEditor, Bool) {
let applied = self.editor.apply_change(change)
(SyntaxEditor::{ ..self, editor: applied.0 }, applied.1)
}
///|
pub fn SyntaxEditor::cursor_position(self : SyntaxEditor) -> (Int, Int)? {
self.editor.cursor_position()
}
///|
pub fn[R : Renderer] SyntaxEditor::render(
self : SyntaxEditor,
renderer : R,
) -> Unit {
let size = self.editor.buffer().size()
match size.0 {
None => ()
Some(width) =>
match size.1 {
None => ()
Some(height) =>
renderer.rectangle(
0,
0,
width.to_double().to_int().reinterpret_as_uint(),
height.to_double().to_int().reinterpret_as_uint(),
self.background_color(),
)
}
}
self.editor.render(
renderer,
self.foreground_color(),
self.cursor_color(),
self.selection_color(),
self.foreground_color(),
)
}