///|
pub(all) struct CodeCursor {
position : Int
selection : @text.TextRange?
}
///|
pub fn CodeCursor::new(
position~ : Int,
selection? : @text.TextRange? = None,
) -> CodeCursor {
{ position, selection }
}
///|
pub(all) struct CodeFindState {
query : String
replacement : String
matches : Array[@text.TextRange]
active_index : Int
}
///|
pub fn CodeFindState::new() -> CodeFindState {
{ query: "", replacement: "", matches: [], active_index: -1 }
}
///|
fn CodeFindState::recompute(
self : CodeFindState,
source : String,
) -> CodeFindState {
{
..self,
matches: code_find_matches(source, self.query),
active_index: if self.query == "" {
-1
} else {
self.active_index
},
}
}
///|
pub fn code_find_matches(
source : String,
query : String,
) -> Array[@text.TextRange] {
let matches : Array[@text.TextRange] = []
if query == "" {
return matches
}
let source_chars = source.to_array()
let query_chars = query.to_array()
if query_chars.is_empty() || query_chars.length() > source_chars.length() {
return matches
}
let mut i = 0
while i <= source_chars.length() - query_chars.length() {
let mut ok = true
for j in 0..<query_chars.length() {
if source_chars[i + j] != query_chars[j] {
ok = false
}
}
if ok {
matches.push(@text.TextRange::new(start=i, end=i + query_chars.length()))
i = i + query_chars.length()
} else {
i = i + 1
}
}
matches
}
///|
pub fn code_replace_all(
source : String,
query : String,
replacement : String,
) -> String {
if query == "" {
return source
}
let chars = source.to_array()
let q = query.to_array()
let out : Array[Char] = []
let replacement_chars = replacement.to_array()
let mut i = 0
while i < chars.length() {
if q.length() > 0 &&
i + q.length() <= chars.length() &&
chars_match(chars, i, q) {
out.append(replacement_chars)
i = i + q.length()
} else {
out.push(chars[i])
i = i + 1
}
}
String::from_array(out)
}
///|
fn chars_match(chars : Array[Char], start : Int, query : Array[Char]) -> Bool {
for i in 0..<query.length() {
if chars[start + i] != query[i] {
return false
}
}
true
}
///|
pub fn code_insert_text(
source : String,
cursors : Array[CodeCursor],
inserted : String,
) -> (String, Array[CodeCursor]) {
let chars = source.to_array()
let sorted = sort_cursor_edits(cursors, chars.length())
let out : Array[Char] = []
let next_cursors : Array[CodeCursor] = []
let mut source_index = 0
for edit in sorted {
let start = if edit.start < source_index {
source_index
} else {
edit.start
}
let end = if edit.end < start { start } else { edit.end }
while source_index < start {
out.push(chars[source_index])
source_index = source_index + 1
}
let insertion = if inserted == "\n" {
"\n" + auto_indent_for_position(source, start)
} else {
inserted
}
let insert_chars = insertion.to_array()
out.append(insert_chars)
next_cursors.push(CodeCursor::new(position=out.length()))
source_index = end
}
while source_index < chars.length() {
out.push(chars[source_index])
source_index = source_index + 1
}
(String::from_array(out), next_cursors)
}
///|
priv struct CursorEdit {
start : Int
end : Int
}
///|
fn sort_cursor_edits(
cursors : Array[CodeCursor],
source_length : Int,
) -> Array[CursorEdit] {
let sorted = cursors.map(cursor => cursor_edit(cursor, source_length))
for i in 0..<sorted.length() {
for j in (i + 1)..<sorted.length() {
if sorted[j].start < sorted[i].start ||
(sorted[j].start == sorted[i].start && sorted[j].end < sorted[i].end) {
let tmp = sorted[i]
sorted[i] = sorted[j]
sorted[j] = tmp
}
}
}
sorted
}
///|
fn cursor_edit(cursor : CodeCursor, source_length : Int) -> CursorEdit {
match cursor.selection {
Some(range) => {
let raw_start = if range.start < range.end {
range.start
} else {
range.end
}
let raw_end = if range.start < range.end {
range.end
} else {
range.start
}
{
start: clamp_int(raw_start, 0, source_length),
end: clamp_int(raw_end, 0, source_length),
}
}
None => {
let position = clamp_int(cursor.position, 0, source_length)
{ start: position, end: position }
}
}
}
///|
pub fn auto_indent_for_position(source : String, position : Int) -> String {
let chars = source.to_array()
let pos = clamp_int(position, 0, chars.length())
let mut line_start = pos
while line_start > 0 && chars[line_start - 1] != '\n' {
line_start = line_start - 1
}
let indent : Array[Char] = []
let mut i = line_start
while i < pos && (chars[i] == ' ' || chars[i] == '\t') {
indent.push(chars[i])
i = i + 1
}
if pos > 0 && chars[pos - 1] == '{' {
indent.push(' ')
indent.push(' ')
}
String::from_array(indent)
}
///|
pub(all) struct BracketMatch {
open : Int
close : Int
}
///|
pub fn code_bracket_match(source : String, offset : Int) -> BracketMatch? {
let chars = source.to_array()
if chars.is_empty() {
return None
}
if offset >= 0 && offset < chars.length() {
match code_bracket_match_at(chars, offset) {
Some(value) => return Some(value)
None => ()
}
}
if offset > 0 {
let previous = clamp_int(offset - 1, 0, chars.length() - 1)
return code_bracket_match_at(chars, previous)
}
None
}
///|
fn code_bracket_match_at(chars : Array[Char], index : Int) -> BracketMatch? {
let ch = chars[index]
if ch == '(' || ch == '[' || ch == '{' {
return find_forward_bracket(chars, index, ch, matching_close(ch))
}
if ch == ')' || ch == ']' || ch == '}' {
return find_backward_bracket(chars, index, matching_open(ch), ch)
}
None
}
///|
fn find_forward_bracket(
chars : Array[Char],
start : Int,
open : Char,
close : Char,
) -> BracketMatch? {
let mut depth = 0
for i in start..<chars.length() {
if chars[i] == open {
depth = depth + 1
} else if chars[i] == close {
depth = depth - 1
if depth == 0 {
return Some({ open: start, close: i })
}
}
}
None
}
///|
fn find_backward_bracket(
chars : Array[Char],
end : Int,
open : Char,
close : Char,
) -> BracketMatch? {
let mut depth = 0
let mut i = end
while i >= 0 {
if chars[i] == close {
depth = depth + 1
} else if chars[i] == open {
depth = depth - 1
if depth == 0 {
return Some({ open: i, close: end })
}
}
i = i - 1
}
None
}
///|
fn matching_close(ch : Char) -> Char {
match ch {
'(' => ')'
'[' => ']'
'{' => '}'
_ => ch
}
}
///|
fn matching_open(ch : Char) -> Char {
match ch {
')' => '('
']' => '['
'}' => '{'
_ => ch
}
}
///|
fn clamp_int(value : Int, min : Int, max : Int) -> Int {
if value < min {
min
} else if value > max {
max
} else {
value
}
}