view src/luan_editor/find.luan @ 37:b7ff52d45b9a default tip

copy from luan
author Franklin Schmidt <fschmidt@gmail.com>
date Mon, 21 Apr 2025 13:07:29 -0600
parents
children
line wrap: on
line source

local Luan = require "luan:Luan.luan"
local error = Luan.error
local ipairs = Luan.ipairs or error()
local range = Luan.range or error()
local String = require "luan:String.luan"
local regex = String.regex or error()
local regex_quote = String.regex_quote or error()
local regex_quote_replacement = String.regex_quote_replacement or error()
local repeated = String.repeated or error()
local new_text_field = require("luan:swing/Text_field.luan").new or error()
local new_check_box = require("luan:swing/Check_box.luan").new or error()
local new_label = require("luan:swing/Label.luan").new or error()
local new_panel = require("luan:swing/Component.luan").new_panel or error()
local Layout = require "luan:swing/Layout.luan"
local new_mig_layout = Layout.new_mig_layout or error()
local new_button = require("luan:swing/Button.luan").new or error()
local Swing = require "luan:swing/Swing.luan"
local browse = Swing.browse or error()


local function get_matches(text,s)
	local r = regex(s)
	local matches = {}
	local i = 1
	local n = #text
	while i <= n do
		local j1, j2 = r.find(text,i)
		if j1 == nil then
			break
		end
		j2 = j2 + 1
		if j1 == j2 then
			i = j2 + 1
		else
			matches[#matches+1] = { start=j1, end_=j2 }
			i = j2
		end
	end
	return matches
end

local function make_find_panel(window)
	local text_area = window.text_area
	local status_bar = window.status_bar
	local find_field, replace_field, regex_check_box
	local function find_match(event)
		--logger.info("action "..event.action)
		local s = find_field.text
		if #s == 0 then
			status_bar.text = " "
			return
		end
		if not regex_check_box.is_selected then
			s = regex_quote(s)
		end
		local matches
		try
			matches = get_matches( text_area.text, s )
		catch e
			status_bar.text = "Regex error: "..e.get_message()
			return
		end
		text_area.set_hightlights(matches)
		local n_matches = #matches
		if n_matches == 0 then
			status_bar.text = "0 matches"
			return
		end
		local action = event.action
		if action == "next" then
			local _, pos = text_area.get_selection()
			for i, match in ipairs(matches) do
				if match.start >= pos then
					text_area.set_selection( match.start, match.end_ )
					status_bar.text = i.." of "..n_matches.." matches"
					return
				end
			end
			local match = matches[1]
			text_area.set_selection( match.start, match.end_ )
			status_bar.text = "1 of "..n_matches.." matches; wrapped past end"
		elseif action == "previous" then
			local pos = text_area.get_selection()
			for i in range(n_matches,1,-1) do
				local match = matches[i]
				if match.end_ <= pos then
					text_area.set_selection( match.start, match.end_ )
					status_bar.text = i.." of "..n_matches.." matches"
					return
				end
			end
			local match = matches[n_matches]
			text_area.set_selection( match.start, match.end_ )
			status_bar.text = n_matches.." of "..n_matches.." matches; wrapped past end"
		else
			error(action)
		end
	end
	local function replace_match(event)
		local find = find_field.text
		if #find == 0 then
			status_bar.text = " "
			return
		end
		local replace = replace_field.text
		if not regex_check_box.is_selected then
			find = regex_quote(find)
			replace = regex_quote_replacement(replace)
		end
		local r
		try
			r = regex(find)
		catch e
			status_bar.text = "Regex error: "..e.get_message()
			return
		end
		local new, n
		local action = event.action
		if action == "replace" then
			local selection = text_area.selected_text
			new, n = r.gsub(selection,replace)
			if n > 0 then
				text_area.selected_text = new
			end
		elseif action == "replace_all" then
			local text = text_area.text
			new, n = r.gsub(text,replace)
			if n > 0 then
				text_area.text = new
			end
		else
			error(action)
		end
		status_bar.text = n.." replacements"
	end
	find_field = new_text_field{
		constraints = "growx"
		whitespace_visible = true
		action = "next"
		action_listener = find_match
	}
	replace_field = new_text_field{
		constraints = "growx"
		whitespace_visible = true
		action = "replace"
		action_listener = replace_match
	}
	regex_check_box = new_check_box{
		text = "Use Regex"
	}
	local find_panel = new_panel{
		constraints = "growy 0,growx"
		layout = new_mig_layout("insets 8 16 0 16","[][grow][grow 0]")
		visible = false
		children = {
			new_label{
				constraints = "right"
				text = "Find:"
			}
			find_field
			new_button{
				constraints = "grow"
				text = "Find Next"
				action = "next"
				action_listener = find_match
			}
			new_button{
				constraints = "grow,wrap"
				text = "Find Previous"
				action = "previous"
				action_listener = find_match
			}
			new_label{
				constraints = "right"
				text = "Replace:"
			}
			replace_field
			new_button{
				constraints = "grow"
				text = "Replace"
				tool_tip_text = "Replace matches in selected text"
				action = "replace"
				action_listener = replace_match
			}
			new_button{
				constraints = "grow,wrap"
				text = "Replace All"
				action = "replace_all"
				action_listener = replace_match
			}
			new_panel{
				constraints = "span,wrap"
				layout = new_mig_layout("insets 0,gap 16px")
				children = {
					regex_check_box
					new_button{
						text = "Learn About Regular Expressions"
						action_listener = function(_)
							browse("https://www.reactionary.software/learn.html#regex")
						end
					}
				}
			}
		}
	}
	function window.show_find_panel(visible)
		find_panel.visible = visible
		if visible then
			find_field.request_focus_in_window()
		else
			text_area.clear_hightlights()
		end
	end
	function window.find_case_insensitive(_)
		find_panel.visible = true
		window.find_menu_item.is_selected = true
		regex_check_box.is_selected = true
		find_field.text = "(?i)\Q\E"
		find_field.set_selection(7)
		find_field.request_focus_in_window()
		status_bar.text = [[Put search text between "\Q" and "\E"]]
	end
	function window.tabs_to_spaces(_)
		find_panel.visible = true
		window.find_menu_item.is_selected = true
		regex_check_box.is_selected = true
		find_field.text = [[(?m)^(\t*)\t]]
		local spaces = repeated( " ", text_area.tab_size )
		replace_field.text = "$1"..spaces
		status_bar.text = [[Do "Replace All" until 0 replacements]]
	end
	function window.spaces_to_tabs(_)
		find_panel.visible = true
		window.find_menu_item.is_selected = true
		regex_check_box.is_selected = true
		local tab_size = text_area.tab_size
		find_field.text = `%>(?m)^(( {<%=tab_size%>})*) {<%=tab_size%>}<%`
		replace_field.text = "$1\t"
		status_bar.text = [[Do "Replace All" until 0 replacements]]
	end
	return find_panel
end

return make_find_panel