| 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() | 
| 17 | 9 local to_number = String.to_number or error() | 
| 0 | 10 local Io = require "luan:Io.luan" | 
|  | 11 local print = Io.print or error() | 
| 3 | 12 local new_file = Io.schemes.file or error() | 
| 7 | 13 local Math = require "luan:Math.luan" | 
|  | 14 local min = Math.min or error() | 
| 0 | 15 local Swing = require "luan:swing/Swing.luan" | 
|  | 16 local new_frame = require("luan:swing/Frame.luan").new or error() | 
|  | 17 local new_label = require("luan:swing/Label.luan").new or error() | 
|  | 18 local new_text_area = require("luan:swing/Text_area.luan").new or error() | 
|  | 19 local new_scroll_pane = require("luan:swing/Scroll_pane.luan").new or error() | 
| 18 | 20 local new_text_area_line_numbers = require("luan:swing/Text_area_line_numbers.luan").new or error() | 
| 0 | 21 local new_menu_bar = require("luan:swing/Menu_bar.luan").new or error() | 
|  | 22 local new_menu = require("luan:swing/Menu.luan").new or error() | 
|  | 23 local new_menu_item = require("luan:swing/Menu_item.luan").new or error() | 
| 2 | 24 local new_check_box_menu_item = require("luan:swing/Check_box_menu_item.luan").new or error() | 
| 12 | 25 local int_to_color = require("luan:swing/Color.luan").int_to_color or error() | 
| 18 | 26 local Border = require "luan:swing/Border.luan" | 
|  | 27 local create_empty_border = Border.create_empty_border or error() | 
|  | 28 local create_line_border = Border.create_line_border or error() | 
|  | 29 local Layout = require "luan:swing/Layout.luan" | 
|  | 30 local new_flow_layout = Layout.new_flow_layout or error() | 
|  | 31 local new_box_layout = Layout.new_box_layout or error() | 
| 16 | 32 local Option_pane = require "luan:swing/Option_pane.luan" | 
|  | 33 local show_message_dialog = Option_pane.show_message_dialog or error() | 
| 17 | 34 local show_input_dialog = Option_pane.show_input_dialog or error() | 
| 18 | 35 local new_dialog = require("luan:swing/Dialog.luan").new or error() | 
|  | 36 local new_panel = require("luan:swing/Component.luan").new_panel or error() | 
|  | 37 local new_button = require("luan:swing/Button.luan").new or error() | 
| 0 | 38 local Logging = require "luan:logging/Logging.luan" | 
|  | 39 local logger = Logging.logger "editor" | 
|  | 40 | 
|  | 41 | 
|  | 42 local new_window | 
|  | 43 | 
| 1 | 44 local function make_menu_bar(window) | 
| 0 | 45 	local menu_bar = new_menu_bar() | 
| 4 | 46 	do | 
| 1 | 47 		local file_menu = new_menu() | 
|  | 48 		file_menu.text = "File" | 
| 4 | 49 		do | 
| 1 | 50 			local new_file = new_menu_item() | 
|  | 51 			new_file.text = "New File" | 
|  | 52 			new_file.accelerator = "meta N" | 
| 5 | 53 			new_file.add_action_listener(new_window) | 
| 1 | 54 			file_menu.add(new_file) | 
| 4 | 55 		end | 
|  | 56 		do | 
| 1 | 57 			local open = new_menu_item() | 
|  | 58 			open.text = "Open..." | 
|  | 59 			open.accelerator = "meta O" | 
| 5 | 60 			open.add_action_listener(window.open) | 
| 1 | 61 			file_menu.add(open) | 
| 4 | 62 		end | 
| 10 | 63 		local revert | 
| 4 | 64 		do | 
|  | 65 			local save = new_menu_item() | 
|  | 66 			save.text = "Save" | 
|  | 67 			save.accelerator = "meta S" | 
| 7 | 68 			save.add_action_listener(function() | 
|  | 69 				if window.save() then | 
|  | 70 					revert.set_enabled(true) | 
|  | 71 				end | 
|  | 72 			end) | 
| 4 | 73 			file_menu.add(save) | 
|  | 74 		end | 
| 7 | 75 		do | 
|  | 76 			revert = new_menu_item() | 
|  | 77 			revert.text = "Revert" | 
|  | 78 			revert.set_enabled(window.has_file) | 
|  | 79 			revert.add_action_listener(window.revert) | 
|  | 80 			file_menu.add(revert) | 
|  | 81 		end | 
| 1 | 82 		menu_bar.add(file_menu) | 
| 4 | 83 	end | 
|  | 84 	do | 
| 6 | 85 		local edit_menu = new_menu() | 
|  | 86 		edit_menu.text = "Edit" | 
| 9 | 87 		local document = window.text_area.document | 
|  | 88 		local undo, redo | 
|  | 89 		do | 
|  | 90 			undo = new_menu_item() | 
|  | 91 			undo.text = "Undo" | 
|  | 92 			undo.accelerator = "meta Z" | 
| 10 | 93 			undo.add_action_listener(document.undo) | 
| 9 | 94 			edit_menu.add(undo) | 
|  | 95 		end | 
|  | 96 		do | 
|  | 97 			redo = new_menu_item() | 
|  | 98 			redo.text = "Redo" | 
|  | 99 			redo.accelerator = "meta shift Z" | 
| 10 | 100 			redo.add_action_listener(document.redo) | 
| 9 | 101 			edit_menu.add(redo) | 
|  | 102 		end | 
| 10 | 103 		local function update_undo_redo() | 
|  | 104 			undo.set_enabled(document.can_undo()) | 
|  | 105 			redo.set_enabled(document.can_redo()) | 
|  | 106 		end | 
| 11 | 107 		edit_menu.dont_gc(update_undo_redo) | 
| 9 | 108 		update_undo_redo() | 
| 10 | 109 		document.add_undo_listener(update_undo_redo) | 
| 9 | 110 		edit_menu.add_separator() | 
| 6 | 111 		do | 
|  | 112 			local cut = new_menu_item() | 
|  | 113 			cut.text = "Cut" | 
|  | 114 			cut.accelerator = "meta X" | 
|  | 115 			cut.add_action_listener(window.text_area.cut) | 
|  | 116 			edit_menu.add(cut) | 
|  | 117 		end | 
|  | 118 		do | 
|  | 119 			local copy = new_menu_item() | 
|  | 120 			copy.text = "Copy" | 
|  | 121 			copy.accelerator = "meta C" | 
|  | 122 			copy.add_action_listener(window.text_area.copy) | 
|  | 123 			edit_menu.add(copy) | 
|  | 124 		end | 
|  | 125 		do | 
|  | 126 			local paste = new_menu_item() | 
|  | 127 			paste.text = "Paste" | 
|  | 128 			paste.accelerator = "meta V" | 
|  | 129 			paste.add_action_listener(window.text_area.paste) | 
|  | 130 			edit_menu.add(paste) | 
|  | 131 		end | 
|  | 132 		edit_menu.add_separator() | 
|  | 133 		do | 
| 15 | 134 			local indent = new_menu_item() | 
|  | 135 			indent.text = "Indent" | 
|  | 136 			indent.accelerator = "meta CLOSE_BRACKET" | 
|  | 137 			indent.add_action_listener(window.indent) | 
|  | 138 			edit_menu.add(indent) | 
|  | 139 		end | 
|  | 140 		do | 
|  | 141 			local unindent = new_menu_item() | 
|  | 142 			unindent.text = "Unindent" | 
|  | 143 			unindent.accelerator = "meta OPEN_BRACKET" | 
|  | 144 			unindent.add_action_listener(window.unindent) | 
|  | 145 			edit_menu.add(unindent) | 
|  | 146 		end | 
|  | 147 		edit_menu.add_separator() | 
|  | 148 		do | 
| 6 | 149 			local select_all = new_menu_item() | 
|  | 150 			select_all.text = "Select All" | 
|  | 151 			select_all.accelerator = "meta A" | 
|  | 152 			select_all.add_action_listener(window.text_area.select_all) | 
|  | 153 			edit_menu.add(select_all) | 
|  | 154 		end | 
| 18 | 155 		edit_menu.add_separator() | 
|  | 156 		do | 
|  | 157 			local find = new_menu_item() | 
|  | 158 			find.text = "Find and Replace" | 
|  | 159 			find.accelerator = "meta F" | 
|  | 160 			find.add_action_listener(window.show_find_dialog) | 
|  | 161 			edit_menu.add(find) | 
|  | 162 		end | 
| 6 | 163 		menu_bar.add(edit_menu) | 
|  | 164 	end | 
|  | 165 	do | 
| 2 | 166 		local view_menu = new_menu() | 
|  | 167 		view_menu.text = "View" | 
| 4 | 168 		do | 
| 2 | 169 			local word_wrap = new_check_box_menu_item() | 
|  | 170 			word_wrap.text = "Word Wrap" | 
|  | 171 			word_wrap.state = window.text_area.line_wrap | 
|  | 172 			word_wrap.add_action_listener(function() | 
|  | 173 				window.text_area.line_wrap = word_wrap.state | 
|  | 174 			end) | 
|  | 175 			view_menu.add(word_wrap) | 
| 4 | 176 		end | 
| 14 | 177 		do | 
|  | 178 			local show_whitespace = new_check_box_menu_item() | 
|  | 179 			show_whitespace.text = "Show Whitespace" | 
|  | 180 			show_whitespace.add_action_listener(function() | 
|  | 181 				window.text_area.show_whitespace(show_whitespace.state) | 
|  | 182 			end) | 
|  | 183 			view_menu.add(show_whitespace) | 
|  | 184 		end | 
| 16 | 185 		do | 
|  | 186 			local show_column = new_menu_item() | 
|  | 187 			show_column.text = "Show Cursor Column" | 
|  | 188 			show_column.add_action_listener(function() | 
|  | 189 				show_message_dialog( window.frame, "Cursor Column: "..window.cursor_column() ) | 
|  | 190 			end) | 
|  | 191 			view_menu.add(show_column) | 
|  | 192 		end | 
| 17 | 193 		do | 
|  | 194 			local goto = new_menu_item() | 
|  | 195 			goto.text = "Goto Line" | 
|  | 196 			goto.accelerator = "meta G" | 
|  | 197 			goto.add_action_listener(function() | 
| 18 | 198 				local input = show_input_dialog( window.frame, "Goto line" ) | 
| 17 | 199 				--logger.info("input "..input) | 
|  | 200 				local line = input and to_number(input) | 
|  | 201 				if line ~= nil then | 
|  | 202 					window.goto(line) | 
|  | 203 				end | 
|  | 204 			end) | 
|  | 205 			view_menu.add(goto) | 
|  | 206 		end | 
| 2 | 207 		menu_bar.add(view_menu) | 
| 4 | 208 	end | 
| 0 | 209 	return menu_bar | 
|  | 210 end | 
|  | 211 | 
| 18 | 212 local function make_find_dialog(window) | 
|  | 213 	local dialog = new_dialog(window.frame) | 
|  | 214 	local root = dialog.component | 
|  | 215 	root.set_layout(new_box_layout(root,"y_axis")) | 
|  | 216 	do | 
|  | 217 		local buttons = new_panel() | 
|  | 218 		buttons.set_layout(new_flow_layout("left")) | 
|  | 219 		--buttons.border = create_empty_border(8,8,8,8) | 
|  | 220 		buttons.border = create_line_border(int_to_color(0)) | 
|  | 221 | 
|  | 222 		local find_next = new_button() | 
|  | 223 		find_next.text = "Find Next" | 
|  | 224 		buttons.add(find_next) | 
|  | 225 | 
|  | 226 		local find_prev = new_button() | 
|  | 227 		find_prev.text = "Find Previous" | 
|  | 228 		buttons.add(find_prev) | 
|  | 229 | 
|  | 230 		root.add(buttons) | 
|  | 231 	end | 
|  | 232 	dialog.pack() | 
|  | 233 	local was_shown = false | 
|  | 234 	function window.show_find_dialog() | 
|  | 235 		if not was_shown then | 
|  | 236 			was_shown = true | 
|  | 237 			dialog.move_into_owner() | 
|  | 238 		end | 
|  | 239 		dialog.visible = true | 
|  | 240 	end | 
|  | 241 end | 
|  | 242 | 
| 0 | 243 local n_windows = 0 | 
| 5 | 244 local documents = {} | 
| 0 | 245 | 
| 1 | 246 function new_window(file) | 
| 5 | 247 	local window = {} | 
| 7 | 248 	window.has_file = file~=nil and file.is_file() | 
| 0 | 249 	local frame = new_frame() | 
| 16 | 250 	window.frame = frame | 
| 5 | 251 	local title = file and file.canonical().to_string() or "new" | 
| 0 | 252 	frame.add_close_listener(function() | 
|  | 253 		n_windows = n_windows - 1 | 
|  | 254 		if n_windows == 0 then | 
|  | 255 			Luan.exit() | 
|  | 256 		end | 
|  | 257 	end) | 
|  | 258 	local text_area = new_text_area() | 
| 5 | 259 	window.text_area = text_area | 
|  | 260 	if file ~= nil then | 
|  | 261 		local document = documents[title] | 
|  | 262 		if document == nil then | 
|  | 263 			documents[title] = text_area.document | 
|  | 264 		else | 
|  | 265 			text_area.document = document | 
|  | 266 		end | 
|  | 267 		if file.is_file() then | 
|  | 268 			text_area.text = file.read_text() | 
|  | 269 		end | 
| 1 | 270 	end | 
| 10 | 271 	local function set_title() | 
|  | 272 		local s = title | 
|  | 273 		if not text_area.document.is_unedited() then | 
|  | 274 			s = s.." *" | 
|  | 275 		end | 
|  | 276 		frame.title = s | 
|  | 277 	end | 
|  | 278 	set_title() | 
| 11 | 279 	text_area.dont_gc(set_title) | 
| 10 | 280 	text_area.document.add_undo_listener(set_title) | 
| 0 | 281 	text_area.rows = 10 | 
|  | 282 	text_area.columns = 20 | 
|  | 283 	text_area.wrap_style_word = true | 
|  | 284 	text_area.line_wrap = true | 
|  | 285 	text_area.tab_size = 4 | 
|  | 286 	text_area.set_font{ family="Monospaced", size=13 } | 
| 15 | 287 	text_area.set_selection(0) | 
| 0 | 288 	--print(text_area.line_count) | 
|  | 289 	local scroll_pane = new_scroll_pane(text_area) | 
| 13 | 290 	local line_numbers = new_text_area_line_numbers(text_area) | 
|  | 291 	line_numbers.foreground_color = int_to_color(0x888888) | 
|  | 292 	line_numbers.border = create_empty_border(0,8,0,8) | 
|  | 293 	scroll_pane.set_row_header_view(line_numbers) | 
| 0 | 294 	frame.add(scroll_pane) | 
| 5 | 295 	function window.open() | 
|  | 296 		local file_chooser = frame.file_chooser_load() | 
|  | 297 		if file ~= nil then | 
|  | 298 			file_chooser.directory = file.parent() | 
|  | 299 		end | 
|  | 300 		file_chooser.visible = true | 
|  | 301 		local new_file = file_chooser.file | 
|  | 302 		if new_file ~= nil then | 
|  | 303 			new_window(new_file) | 
|  | 304 		end | 
|  | 305 	end | 
|  | 306 	function window.save() | 
|  | 307 		if file == nil then | 
|  | 308 			local file_chooser = frame.file_chooser_save() | 
|  | 309 			file_chooser.visible = true | 
|  | 310 			file = file_chooser.file | 
|  | 311 			if file == nil then | 
| 7 | 312 				return false | 
| 5 | 313 			end | 
|  | 314 			title = file.canonical().to_string() | 
|  | 315 			frame.title = title | 
|  | 316 			documents[title] = text_area.document | 
|  | 317 		end | 
|  | 318 		file.write_text(text_area.text) | 
| 10 | 319 		text_area.document.set_unedited() | 
| 7 | 320 		return true | 
|  | 321 	end | 
|  | 322 	function window.revert() | 
| 15 | 323 		local selection = text_area.get_selection() | 
| 7 | 324 		local text = file.read_text() | 
|  | 325 		text_area.text = text | 
| 15 | 326 		text_area.set_selection(min(selection,#text+1)) | 
| 10 | 327 		text_area.document.set_unedited() | 
| 5 | 328 	end | 
| 15 | 329 	local function selection_lines() | 
|  | 330 		local start_seletion, end_selection = text_area.get_selection() | 
|  | 331 		local end_ = end_selection == start_seletion and end_selection or end_selection - 1 | 
|  | 332 		local start_line = text_area.get_line_from_position(start_seletion) | 
|  | 333 		local end_line = text_area.get_line_from_position(end_) | 
|  | 334 		local start_pos = text_area.get_line_start_position(start_line) | 
|  | 335 		local end_pos = text_area.get_line_end_position(end_line) | 
|  | 336 		local text = text_area.text | 
|  | 337 		text = sub_string(text,start_pos,end_pos-2) | 
|  | 338 		return { | 
|  | 339 			text = text | 
|  | 340 			start_pos = start_pos | 
|  | 341 			length = #text | 
|  | 342 			lines = end_line - start_line + 1 | 
|  | 343 			start_seletion = start_seletion | 
|  | 344 			end_selection = end_selection | 
|  | 345 		} | 
|  | 346 	end | 
|  | 347 	function window.indent() | 
|  | 348 		local r = selection_lines() | 
|  | 349 		local text = r.text | 
|  | 350 		local start_pos = r.start_pos | 
|  | 351 		text = "\t"..replace(text,"\n","\n\t") | 
|  | 352 		text_area.replace(start_pos,r.length,text) | 
|  | 353 		--logger.info(stringify{text_area.get_selection()}) | 
|  | 354 		text_area.set_selection( r.start_seletion+1, r.end_selection+r.lines ) | 
|  | 355 	end | 
|  | 356 	function window.unindent() | 
|  | 357 		local r = selection_lines() | 
|  | 358 		local text = r.text | 
|  | 359 		text = "\n"..text | 
|  | 360 		local start_seletion = r.start_seletion | 
|  | 361 		if starts_with(text,"\n\t") then | 
|  | 362 			start_seletion = start_seletion - 1 | 
|  | 363 		end | 
|  | 364 		local len1 = #text | 
|  | 365 		text = replace(text,"\n\t","\n") | 
|  | 366 		local len2 = #text | 
|  | 367 		local end_selection = r.end_selection - (len1 - len2) | 
|  | 368 		text = sub_string(text,2) | 
|  | 369 		text_area.replace(r.start_pos,r.length,text) | 
|  | 370 		text_area.set_selection(start_seletion,end_selection) | 
|  | 371 	end | 
| 16 | 372 	function window.cursor_column() | 
|  | 373 		local cursor_pos = text_area.get_selection() | 
|  | 374 		local line = text_area.get_line_from_position(cursor_pos) | 
|  | 375 		local start_line_pos = text_area.get_line_start_position(line) | 
|  | 376 		return cursor_pos - start_line_pos + 1 | 
|  | 377 	end | 
| 17 | 378 	function window.goto(line) | 
|  | 379 		local pos = text_area.get_line_start_position(line) | 
|  | 380 		text_area.set_selection(pos) | 
|  | 381 	end | 
| 18 | 382 	make_find_dialog(window) | 
| 1 | 383 	local menu_bar = make_menu_bar(window) | 
| 0 | 384 	frame.set_menu_bar(menu_bar) | 
|  | 385 	frame.pack() | 
|  | 386 	frame.visible = true | 
|  | 387 	text_area.request_focus_in_window() | 
|  | 388 	n_windows = n_windows + 1 | 
|  | 389 end | 
|  | 390 | 
| 1 | 391 Swing.run(function() | 
| 3 | 392 	local args = Luan.arg | 
|  | 393 	if #args == 0 then | 
|  | 394 		new_window() | 
|  | 395 	else | 
|  | 396 		for _, arg in ipairs(args) do | 
|  | 397 			local file = new_file(arg) | 
|  | 398 			new_window(file) | 
|  | 399 		end | 
|  | 400 	end | 
| 1 | 401 end) |