| 0 | 1 local Luan = require "luan:Luan.luan" | 
|  | 2 local error = Luan.error | 
| 3 | 3 local ipairs = Luan.ipairs or error() | 
| 15 | 4 local stringify = Luan.stringify or error() | 
|  | 5 local String = require "luan:String.luan" | 
|  | 6 local sub_string = String.sub or error() | 
|  | 7 local replace = String.replace or error() | 
|  | 8 local starts_with = String.starts_with or error() | 
| 0 | 9 local Io = require "luan:Io.luan" | 
|  | 10 local print = Io.print or error() | 
| 3 | 11 local new_file = Io.schemes.file or error() | 
| 7 | 12 local Math = require "luan:Math.luan" | 
|  | 13 local min = Math.min or error() | 
| 0 | 14 local Swing = require "luan:swing/Swing.luan" | 
|  | 15 local new_frame = require("luan:swing/Frame.luan").new or error() | 
|  | 16 local new_label = require("luan:swing/Label.luan").new or error() | 
|  | 17 local new_text_area = require("luan:swing/Text_area.luan").new or error() | 
|  | 18 local new_scroll_pane = require("luan:swing/Scroll_pane.luan").new or error() | 
| 13 | 19 local new_text_area_line_numbers = require("luan:swing/TextAreaLineNumbers.luan").new or error() | 
| 0 | 20 local new_menu_bar = require("luan:swing/Menu_bar.luan").new or error() | 
|  | 21 local new_menu = require("luan:swing/Menu.luan").new or error() | 
|  | 22 local new_menu_item = require("luan:swing/Menu_item.luan").new or error() | 
| 2 | 23 local new_check_box_menu_item = require("luan:swing/Check_box_menu_item.luan").new or error() | 
| 12 | 24 local int_to_color = require("luan:swing/Color.luan").int_to_color or error() | 
| 13 | 25 local create_empty_border = require("luan:swing/Border.luan").create_empty_border or error() | 
| 0 | 26 local Logging = require "luan:logging/Logging.luan" | 
|  | 27 local logger = Logging.logger "editor" | 
|  | 28 | 
|  | 29 | 
|  | 30 local new_window | 
|  | 31 | 
| 1 | 32 local function make_menu_bar(window) | 
| 0 | 33 	local menu_bar = new_menu_bar() | 
| 4 | 34 	do | 
| 1 | 35 		local file_menu = new_menu() | 
|  | 36 		file_menu.text = "File" | 
| 4 | 37 		do | 
| 1 | 38 			local new_file = new_menu_item() | 
|  | 39 			new_file.text = "New File" | 
|  | 40 			new_file.accelerator = "meta N" | 
| 5 | 41 			new_file.add_action_listener(new_window) | 
| 1 | 42 			file_menu.add(new_file) | 
| 4 | 43 		end | 
|  | 44 		do | 
| 1 | 45 			local open = new_menu_item() | 
|  | 46 			open.text = "Open..." | 
|  | 47 			open.accelerator = "meta O" | 
| 5 | 48 			open.add_action_listener(window.open) | 
| 1 | 49 			file_menu.add(open) | 
| 4 | 50 		end | 
| 10 | 51 		local revert | 
| 4 | 52 		do | 
|  | 53 			local save = new_menu_item() | 
|  | 54 			save.text = "Save" | 
|  | 55 			save.accelerator = "meta S" | 
| 7 | 56 			save.add_action_listener(function() | 
|  | 57 				if window.save() then | 
|  | 58 					revert.set_enabled(true) | 
|  | 59 				end | 
|  | 60 			end) | 
| 4 | 61 			file_menu.add(save) | 
|  | 62 		end | 
| 7 | 63 		do | 
|  | 64 			revert = new_menu_item() | 
|  | 65 			revert.text = "Revert" | 
|  | 66 			revert.set_enabled(window.has_file) | 
|  | 67 			revert.add_action_listener(window.revert) | 
|  | 68 			file_menu.add(revert) | 
|  | 69 		end | 
| 1 | 70 		menu_bar.add(file_menu) | 
| 4 | 71 	end | 
|  | 72 	do | 
| 6 | 73 		local edit_menu = new_menu() | 
|  | 74 		edit_menu.text = "Edit" | 
| 9 | 75 		local document = window.text_area.document | 
|  | 76 		local undo, redo | 
|  | 77 		do | 
|  | 78 			undo = new_menu_item() | 
|  | 79 			undo.text = "Undo" | 
|  | 80 			undo.accelerator = "meta Z" | 
| 10 | 81 			undo.add_action_listener(document.undo) | 
| 9 | 82 			edit_menu.add(undo) | 
|  | 83 		end | 
|  | 84 		do | 
|  | 85 			redo = new_menu_item() | 
|  | 86 			redo.text = "Redo" | 
|  | 87 			redo.accelerator = "meta shift Z" | 
| 10 | 88 			redo.add_action_listener(document.redo) | 
| 9 | 89 			edit_menu.add(redo) | 
|  | 90 		end | 
| 10 | 91 		local function update_undo_redo() | 
|  | 92 			undo.set_enabled(document.can_undo()) | 
|  | 93 			redo.set_enabled(document.can_redo()) | 
|  | 94 		end | 
| 11 | 95 		edit_menu.dont_gc(update_undo_redo) | 
| 9 | 96 		update_undo_redo() | 
| 10 | 97 		document.add_undo_listener(update_undo_redo) | 
| 9 | 98 		edit_menu.add_separator() | 
| 6 | 99 		do | 
|  | 100 			local cut = new_menu_item() | 
|  | 101 			cut.text = "Cut" | 
|  | 102 			cut.accelerator = "meta X" | 
|  | 103 			cut.add_action_listener(window.text_area.cut) | 
|  | 104 			edit_menu.add(cut) | 
|  | 105 		end | 
|  | 106 		do | 
|  | 107 			local copy = new_menu_item() | 
|  | 108 			copy.text = "Copy" | 
|  | 109 			copy.accelerator = "meta C" | 
|  | 110 			copy.add_action_listener(window.text_area.copy) | 
|  | 111 			edit_menu.add(copy) | 
|  | 112 		end | 
|  | 113 		do | 
|  | 114 			local paste = new_menu_item() | 
|  | 115 			paste.text = "Paste" | 
|  | 116 			paste.accelerator = "meta V" | 
|  | 117 			paste.add_action_listener(window.text_area.paste) | 
|  | 118 			edit_menu.add(paste) | 
|  | 119 		end | 
|  | 120 		edit_menu.add_separator() | 
|  | 121 		do | 
| 15 | 122 			local indent = new_menu_item() | 
|  | 123 			indent.text = "Indent" | 
|  | 124 			indent.accelerator = "meta CLOSE_BRACKET" | 
|  | 125 			indent.add_action_listener(window.indent) | 
|  | 126 			edit_menu.add(indent) | 
|  | 127 		end | 
|  | 128 		do | 
|  | 129 			local unindent = new_menu_item() | 
|  | 130 			unindent.text = "Unindent" | 
|  | 131 			unindent.accelerator = "meta OPEN_BRACKET" | 
|  | 132 			unindent.add_action_listener(window.unindent) | 
|  | 133 			edit_menu.add(unindent) | 
|  | 134 		end | 
|  | 135 		edit_menu.add_separator() | 
|  | 136 		do | 
| 6 | 137 			local select_all = new_menu_item() | 
|  | 138 			select_all.text = "Select All" | 
|  | 139 			select_all.accelerator = "meta A" | 
|  | 140 			select_all.add_action_listener(window.text_area.select_all) | 
|  | 141 			edit_menu.add(select_all) | 
|  | 142 		end | 
|  | 143 		menu_bar.add(edit_menu) | 
|  | 144 	end | 
|  | 145 	do | 
| 2 | 146 		local view_menu = new_menu() | 
|  | 147 		view_menu.text = "View" | 
| 4 | 148 		do | 
| 2 | 149 			local word_wrap = new_check_box_menu_item() | 
|  | 150 			word_wrap.text = "Word Wrap" | 
|  | 151 			word_wrap.state = window.text_area.line_wrap | 
|  | 152 			word_wrap.add_action_listener(function() | 
|  | 153 				window.text_area.line_wrap = word_wrap.state | 
|  | 154 			end) | 
|  | 155 			view_menu.add(word_wrap) | 
| 4 | 156 		end | 
| 14 | 157 		do | 
|  | 158 			local show_whitespace = new_check_box_menu_item() | 
|  | 159 			show_whitespace.text = "Show Whitespace" | 
|  | 160 			show_whitespace.add_action_listener(function() | 
|  | 161 				window.text_area.show_whitespace(show_whitespace.state) | 
|  | 162 			end) | 
|  | 163 			view_menu.add(show_whitespace) | 
|  | 164 		end | 
| 2 | 165 		menu_bar.add(view_menu) | 
| 4 | 166 	end | 
| 0 | 167 	return menu_bar | 
|  | 168 end | 
|  | 169 | 
|  | 170 local n_windows = 0 | 
| 5 | 171 local documents = {} | 
| 0 | 172 | 
| 1 | 173 function new_window(file) | 
| 5 | 174 	local window = {} | 
| 7 | 175 	window.has_file = file~=nil and file.is_file() | 
| 0 | 176 	local frame = new_frame() | 
| 5 | 177 	local title = file and file.canonical().to_string() or "new" | 
| 0 | 178 	frame.add_close_listener(function() | 
|  | 179 		n_windows = n_windows - 1 | 
|  | 180 		if n_windows == 0 then | 
|  | 181 			Luan.exit() | 
|  | 182 		end | 
|  | 183 	end) | 
|  | 184 	local text_area = new_text_area() | 
| 5 | 185 	window.text_area = text_area | 
|  | 186 	if file ~= nil then | 
|  | 187 		local document = documents[title] | 
|  | 188 		if document == nil then | 
|  | 189 			documents[title] = text_area.document | 
|  | 190 		else | 
|  | 191 			text_area.document = document | 
|  | 192 		end | 
|  | 193 		if file.is_file() then | 
|  | 194 			text_area.text = file.read_text() | 
|  | 195 		end | 
| 1 | 196 	end | 
| 10 | 197 	local function set_title() | 
|  | 198 		local s = title | 
|  | 199 		if not text_area.document.is_unedited() then | 
|  | 200 			s = s.." *" | 
|  | 201 		end | 
|  | 202 		frame.title = s | 
|  | 203 	end | 
|  | 204 	set_title() | 
| 11 | 205 	text_area.dont_gc(set_title) | 
| 10 | 206 	text_area.document.add_undo_listener(set_title) | 
| 0 | 207 	text_area.rows = 10 | 
|  | 208 	text_area.columns = 20 | 
|  | 209 	text_area.wrap_style_word = true | 
|  | 210 	text_area.line_wrap = true | 
|  | 211 	text_area.tab_size = 4 | 
|  | 212 	text_area.set_font{ family="Monospaced", size=13 } | 
| 15 | 213 	text_area.set_selection(0) | 
| 0 | 214 	--print(text_area.line_count) | 
|  | 215 	local scroll_pane = new_scroll_pane(text_area) | 
| 13 | 216 	local line_numbers = new_text_area_line_numbers(text_area) | 
|  | 217 	line_numbers.foreground_color = int_to_color(0x888888) | 
|  | 218 	line_numbers.border = create_empty_border(0,8,0,8) | 
|  | 219 	scroll_pane.set_row_header_view(line_numbers) | 
| 0 | 220 	frame.add(scroll_pane) | 
| 5 | 221 	function window.open() | 
|  | 222 		local file_chooser = frame.file_chooser_load() | 
|  | 223 		if file ~= nil then | 
|  | 224 			file_chooser.directory = file.parent() | 
|  | 225 		end | 
|  | 226 		file_chooser.visible = true | 
|  | 227 		local new_file = file_chooser.file | 
|  | 228 		if new_file ~= nil then | 
|  | 229 			new_window(new_file) | 
|  | 230 		end | 
|  | 231 	end | 
|  | 232 	function window.save() | 
|  | 233 		if file == nil then | 
|  | 234 			local file_chooser = frame.file_chooser_save() | 
|  | 235 			file_chooser.visible = true | 
|  | 236 			file = file_chooser.file | 
|  | 237 			if file == nil then | 
| 7 | 238 				return false | 
| 5 | 239 			end | 
|  | 240 			title = file.canonical().to_string() | 
|  | 241 			frame.title = title | 
|  | 242 			documents[title] = text_area.document | 
|  | 243 		end | 
|  | 244 		file.write_text(text_area.text) | 
| 10 | 245 		text_area.document.set_unedited() | 
| 7 | 246 		return true | 
|  | 247 	end | 
|  | 248 	function window.revert() | 
| 15 | 249 		local selection = text_area.get_selection() | 
| 7 | 250 		local text = file.read_text() | 
|  | 251 		text_area.text = text | 
| 15 | 252 		text_area.set_selection(min(selection,#text+1)) | 
| 10 | 253 		text_area.document.set_unedited() | 
| 5 | 254 	end | 
| 15 | 255 	local function selection_lines() | 
|  | 256 		local start_seletion, end_selection = text_area.get_selection() | 
|  | 257 		local end_ = end_selection == start_seletion and end_selection or end_selection - 1 | 
|  | 258 		local start_line = text_area.get_line_from_position(start_seletion) | 
|  | 259 		local end_line = text_area.get_line_from_position(end_) | 
|  | 260 		local start_pos = text_area.get_line_start_position(start_line) | 
|  | 261 		local end_pos = text_area.get_line_end_position(end_line) | 
|  | 262 		local text = text_area.text | 
|  | 263 		text = sub_string(text,start_pos,end_pos-2) | 
|  | 264 		return { | 
|  | 265 			text = text | 
|  | 266 			start_pos = start_pos | 
|  | 267 			length = #text | 
|  | 268 			lines = end_line - start_line + 1 | 
|  | 269 			start_seletion = start_seletion | 
|  | 270 			end_selection = end_selection | 
|  | 271 		} | 
|  | 272 	end | 
|  | 273 	function window.indent() | 
|  | 274 		local r = selection_lines() | 
|  | 275 		local text = r.text | 
|  | 276 		local start_pos = r.start_pos | 
|  | 277 		text = "\t"..replace(text,"\n","\n\t") | 
|  | 278 		text_area.replace(start_pos,r.length,text) | 
|  | 279 		--logger.info(stringify{text_area.get_selection()}) | 
|  | 280 		text_area.set_selection( r.start_seletion+1, r.end_selection+r.lines ) | 
|  | 281 	end | 
|  | 282 	function window.unindent() | 
|  | 283 		local r = selection_lines() | 
|  | 284 		local text = r.text | 
|  | 285 		text = "\n"..text | 
|  | 286 		local start_seletion = r.start_seletion | 
|  | 287 		if starts_with(text,"\n\t") then | 
|  | 288 			start_seletion = start_seletion - 1 | 
|  | 289 		end | 
|  | 290 		local len1 = #text | 
|  | 291 		text = replace(text,"\n\t","\n") | 
|  | 292 		local len2 = #text | 
|  | 293 		local end_selection = r.end_selection - (len1 - len2) | 
|  | 294 		text = sub_string(text,2) | 
|  | 295 		text_area.replace(r.start_pos,r.length,text) | 
|  | 296 		text_area.set_selection(start_seletion,end_selection) | 
|  | 297 	end | 
| 1 | 298 	local menu_bar = make_menu_bar(window) | 
| 0 | 299 	frame.set_menu_bar(menu_bar) | 
|  | 300 	frame.pack() | 
|  | 301 	frame.visible = true | 
|  | 302 	text_area.request_focus_in_window() | 
|  | 303 	n_windows = n_windows + 1 | 
|  | 304 end | 
|  | 305 | 
| 1 | 306 Swing.run(function() | 
| 3 | 307 	local args = Luan.arg | 
|  | 308 	if #args == 0 then | 
|  | 309 		new_window() | 
|  | 310 	else | 
|  | 311 		for _, arg in ipairs(args) do | 
|  | 312 			local file = new_file(arg) | 
|  | 313 			new_window(file) | 
|  | 314 		end | 
|  | 315 	end | 
| 1 | 316 end) |