Ticket #4196: InstructionStation.patch
File InstructionStation.patch, 42.1 KB (added by psprint, 4 years ago) |
---|
-
lib/global.h
From 221429752ef6bc253101aa52b4f6e66e8a864e48 Mon Sep 17 00:00:00 2001 From: Sebastian Gniazdowski <sgniazdowski@gmail.com> Date: Wed, 10 Feb 2021 13:41:35 -0600 Subject: =?UTF-8?q?Instructions=20Station=20=E2=80=93=20a=20CLI=20window?= =?UTF-8?q?=20for=20MCEdit?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/global.h | 3 + lib/keybind.c | 1 + lib/keybind.h | 1 + lib/tty/key.c | 10 +- lib/widget/Makefile.am | 1 + lib/widget/instr_station.c | 663 +++++++++++++++++++++++++++++++++++++ lib/widget/instr_station.h | 81 +++++ misc/mc.default.keymap | 1 + misc/mc.emacs.keymap | 1 + src/editor/edit-impl.h | 12 +- src/editor/edit.c | 27 +- src/editor/editcmd.c | 9 + src/editor/editdraw.c | 1 - src/editor/editmenu.c | 3 + src/editor/editwidget.c | 139 ++++---- src/editor/editwidget.h | 3 + src/history.h | 1 + src/keybind-defaults.c | 1 + 18 files changed, 888 insertions(+), 70 deletions(-) create mode 100644 lib/widget/instr_station.c create mode 100644 lib/widget/instr_station.h diff --git a/lib/global.h b/lib/global.h index f1a3e702c..1f76061c6 100644
a b 103 103 #include "lib/logging.h" 104 104 #endif 105 105 106 #define has_flag(x,y) (((x) & (y)) != 0) 107 #define set_flag_in(x,y) ((x) |= (y)) 108 106 109 /* Just for keeping Your's brains from invention a proper size of the buffer :-) */ 107 110 #define BUF_10K 10240L 108 111 #define BUF_8K 8192L -
lib/keybind.c
diff --git a/lib/keybind.c b/lib/keybind.c index 65ac5f15c..2f9d3cf29 100644
a b static name_keymap_t command_names_start[] = { 300 300 ADD_KEYMAP_NAME (EditMail), 301 301 ADD_KEYMAP_NAME (ParagraphFormat), 302 302 ADD_KEYMAP_NAME (MatchBracket), 303 ADD_KEYMAP_NAME (InstructionStation), 303 304 ADD_KEYMAP_NAME (ExternalCommand), 304 305 ADD_KEYMAP_NAME (MacroStartRecord), 305 306 ADD_KEYMAP_NAME (MacroStopRecord), -
lib/keybind.h
diff --git a/lib/keybind.h b/lib/keybind.h index 4e954ade2..18c7d95e4 100644
a b enum 324 324 CK_SyntaxOnOff, 325 325 CK_SyntaxChoose, 326 326 CK_InsertLiteral, 327 CK_InstructionStation, 327 328 CK_ExternalCommand, 328 329 CK_Date, 329 330 CK_EditMail, -
lib/tty/key.c
diff --git a/lib/tty/key.c b/lib/tty/key.c index 58a2f018b..cec17801a 100644
a b tty_get_event (struct Gpm_Event *event, gboolean redo_event, gboolean block) 2031 2031 time_out.tv_sec = 0; 2032 2032 time_out.tv_usec = 0; 2033 2033 } 2034 2034 else if (time_addr == NULL) 2035 { 2036 /* Standard timeout to call GLib main loop */ 2037 time_addr = &time_out; 2038 time_addr->tv_sec = 0; 2039 time_addr->tv_usec = 200000; 2040 } 2035 2041 tty_enable_interrupt_key (); 2036 2042 flag = select (nfd, &select_set, NULL, NULL, time_addr); 2037 2043 tty_disable_interrupt_key (); … … tty_get_event (struct Gpm_Event *event, gboolean redo_event, gboolean block) 2043 2049 */ 2044 2050 if (flag == 0) 2045 2051 { 2052 /* Provide CPU time to GLib main loop's default context */ 2053 g_main_context_iteration (NULL, 0); 2046 2054 if (redo_event) 2047 2055 return EV_MOUSE; 2048 2056 if (!block || tty_got_winch ()) -
lib/widget/Makefile.am
diff --git a/lib/widget/Makefile.am b/lib/widget/Makefile.am index 2d8434391..2500af671 100644
a b libmcwidget_la_SOURCES = \ 8 8 check.c check.h \ 9 9 dialog.c dialog.h \ 10 10 dialog-switch.c dialog-switch.h \ 11 instr_station.c instr_station.h \ 11 12 frame.c frame.h \ 12 13 gauge.c gauge.h \ 13 14 group.c group.h \ -
new file lib/widget/instr_station.c
diff --git a/lib/widget/instr_station.c b/lib/widget/instr_station.c new file mode 100644 index 000000000..1dfb453dc
- + 1 /* 2 Implementation of a CLI window for MCEdit. 3 4 Copyright (C) 2021 5 Free Software Foundation, Inc. 6 7 Written by: 8 Sebastian Gniazdowski <sgniazdowski@gmail.com>, 2021. 9 10 This file is part of the Midnight Commander. 11 12 The Midnight Commander is free software: you can redistribute it 13 and/or modify it under the terms of the GNU General Public License as 14 published by the Free Software Foundation, either version 3 of the License, 15 or (at your option) any later version. 16 17 The Midnight Commander is distributed in the hope that it will be useful, 18 but WITHOUT ANY WARRANTY; without even the implied warranty of 19 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 20 GNU General Public License for more details. 21 22 You should have received a copy of the GNU General Public License 23 along with this program. If not, see <http://www.gnu.org/licenses/>. 24 */ 25 26 /** \file instr_station.c 27 * \brief Implementation of a CLI special window for MCEdit. 28 * \author Sebastian Gniazdowski 29 * \date 2021 30 * 31 * Such window runs a set up program (/bin/bash by default) allowing to 32 * provide an input to it and read its output. 33 */ 34 35 #include <config.h> 36 37 #include "lib/global.h" 38 39 #include <glib/gstdio.h> /* g_close() */ 40 #include <fcntl.h> 41 #include <sys/types.h> 42 #include <sys/stat.h> 43 44 #include "lib/tty/key.h" 45 #include "lib/widget.h" 46 #include "lib/widget/instr_station.h" 47 #include "src/history.h" 48 49 /*** global variables ****************************************************************************/ 50 51 /*** file scope macro definitions ****************************************************************/ 52 53 /*** file scope type declarations ****************************************************************/ 54 55 /*** file scope variables ************************************************************************/ 56 57 static gboolean alt_prog_first_run = TRUE; 58 59 /*** file scope functions ************************************************************************/ 60 /* --------------------------------------------------------------------------------------------- */ 61 62 static char * 63 instr_stat_get_instruction_text (WInstructionStation * ip) 64 { 65 char *ret_str; 66 GString *instr; 67 off_t bol, eol, begin_instr; 68 long size; 69 int idx, byte; 70 71 /* Calculate offset of text after prompt */ 72 bol = edit_buffer_get_current_bol (&EDIT (ip)->buffer); 73 eol = edit_buffer_get_current_eol (&EDIT (ip)->buffer); 74 begin_instr = bol + ip->prompt_span; 75 76 /* Is there anything entered? */ 77 size = ((long) eol) - ((long) begin_instr); 78 if (size <= 0) 79 return NULL; 80 81 /* Allocate expected size string and fill it */ 82 instr = g_string_sized_new (size + 2); 83 for (idx = 0; idx < size; idx++) 84 { 85 byte = edit_buffer_get_byte (&EDIT (ip)->buffer, begin_instr + idx); 86 g_string_append_c (instr, byte); 87 } 88 89 /* Append new line if needed */ 90 if (instr->str[instr->len - 1] != '\n') 91 g_string_append_c (instr, '\n'); 92 93 /* Return char buffer */ 94 ret_str = instr->str; 95 g_string_free (instr, FALSE); 96 return ret_str; 97 } 98 99 /* --------------------------------------------------------------------------------------------- */ 100 101 static cb_ret_t 102 instr_stat_run_activity (WInstructionStation * ip, long activity) 103 { 104 cb_ret_t ret = MSG_NOT_HANDLED; 105 switch (activity) 106 { 107 case CK_BackSpace: 108 if (ip->cur_col <= ip->prompt_span) 109 { 110 ret = MSG_HANDLED; 111 } 112 else 113 { 114 EDIT (ip)->force |= REDRAW_PAGE; 115 edit_update_screen (EDIT (ip)); 116 } 117 break; 118 case CK_Enter: 119 case CK_Return: 120 if (EDIT (ip)->buffer.curs_line == ip->cur_line) 121 { 122 ip->instruction = instr_stat_get_instruction_text (ip); 123 /* Should dispatch to write callback which will send instruction */ 124 g_main_context_iteration (NULL, 0); 125 EDIT (ip)->force |= REDRAW_PAGE; 126 edit_update_screen (EDIT (ip)); 127 } 128 ret = MSG_HANDLED; 129 break; 130 default: 131 break; 132 } 133 return ret; 134 } 135 136 /* --------------------------------------------------------------------------------------------- */ 137 138 static gboolean 139 instr_release (WInstructionStation * ip, gboolean free_all) 140 { 141 if (ip->finalizing) 142 return FALSE; 143 ip->finalizing = TRUE; 144 145 /* Close process watcher first, suppressing *program_ended_cb() callback */ 146 if (ip->proc_src_id != 0) 147 { 148 GSource *src; 149 src = g_main_context_find_source_by_id (NULL, ip->proc_src_id); 150 if (src != NULL) 151 g_source_destroy (src); 152 ip->proc_src_id = 0; 153 } 154 155 /* Release the pipes, channels, etc. */ 156 for (int i = 0; i <= STDERR; i++) 157 { 158 if (ip->io[i].src_id != 0) 159 { 160 GSource *src; 161 src = g_main_context_find_source_by_id (NULL, ip->io[i].src_id); 162 if (src != NULL) 163 g_source_destroy (src); 164 ip->io[i].src_id = 0; 165 } 166 if (ip->io[i].ch != NULL) 167 { 168 g_io_channel_unref (ip->io[i].ch); 169 ip->io[i].ch = NULL; 170 } 171 if (ip->io[i].fd >= 0) 172 { 173 close (ip->io[i].fd); 174 ip->io[i].fd = -1; 175 } 176 } 177 178 if (free_all) 179 { 180 if (ip->program != NULL) 181 { 182 g_free (ip->program); 183 ip->program = NULL; 184 } 185 186 /* Release prompt */ 187 g_free (ip->prompt); 188 /* Release error prompt */ 189 g_free (ip->eprompt); 190 191 /* Free main object */ 192 g_free (ip); 193 } 194 return TRUE; 195 } 196 197 /* --------------------------------------------------------------------------------------------- */ 198 199 static void 200 helper_program_ended_cb (GPid pid, gint status, gpointer user_data) 201 { 202 WInstructionStation *ip = INSTR_STATION (user_data); 203 char *msg; 204 mc_log ("Program %d ended with status: %d (%s)", pid, status, 205 g_spawn_check_exit_status (status, NULL) ? "normally" : "abnormally"); 206 207 instr_release (ip, FALSE); 208 g_spawn_close_pid (pid); 209 msg = _("\nProgram closed."); 210 edit_insert_string (EDIT (ip), msg, strlen (msg)); 211 } 212 213 /* --------------------------------------------------------------------------------------------- */ 214 215 static gboolean 216 instr_stat_update_cursor (WInstructionStation * ip, int y, int x) 217 { 218 WEdit *edit = EDIT (ip); 219 220 x += option_line_state_width; 221 222 edit->prev_col = x - edit->start_col - option_line_state_width; 223 224 if (y > edit->curs_row) 225 edit_move_down (edit, y - edit->curs_row, FALSE); 226 else if (y < edit->curs_row) 227 edit_move_up (edit, edit->curs_row - y, FALSE); 228 else 229 edit_move_to_prev_col (edit, edit_buffer_get_current_bol (&edit->buffer)); 230 231 return TRUE; 232 } 233 234 /* --------------------------------------------------------------------------------------------- */ 235 236 static void 237 instr_stat_show_prompt (WInstructionStation * ip, gboolean is_ok) 238 { 239 char *sel_prompt = is_ok ? ip->prompt : ip->eprompt; 240 int sel_prompt_span = is_ok ? ip->prompt_span : ip->eprompt_span; 241 242 /* Print prompt */ 243 if (!edit_newline_end_check_only (EDIT (ip))) 244 { 245 edit_insert (EDIT (ip), '\n'); 246 ip->cur_line += 1; 247 } 248 edit_insert_string (EDIT (ip), sel_prompt, sel_prompt_span); 249 250 /* Raise an indicator flag */ 251 ip->prompt_shown = TRUE; 252 253 /* Set cursor position to reflect state */ 254 ip->cur_col = sel_prompt_span; 255 } 256 257 /* --------------------------------------------------------------------------------------------- */ 258 259 static gboolean 260 helper_timeout_cb (gpointer data) 261 { 262 WInstructionStation *ip = INSTR_STATION (data); 263 guint64 cur_time; 264 cur_time = g_get_real_time (); 265 if (cur_time - ip->read_time > 500000) 266 { 267 instr_stat_show_prompt (ip, TRUE); 268 repaint_screen (); 269 return FALSE; 270 } 271 return TRUE; 272 } 273 274 /* --------------------------------------------------------------------------------------------- */ 275 276 static gboolean 277 helper_configure_src_and_ch (GIOChannel ** ch, gint fd, guint * src_id, gboolean write, 278 GIOFunc cb_fun, gpointer data, const char *name) 279 { 280 /* Initialize the output variables */ 281 *ch = NULL; 282 *src_id = 0; 283 284 *ch = g_io_channel_unix_new (fd); 285 286 /* Channel created? */ 287 if (*ch == NULL) 288 goto cleanup_and_err; 289 290 /* Automatic shutdown of channel */ 291 g_io_channel_set_close_on_unref (*ch, TRUE); 292 293 /* Trim down buffering on this channel */ 294 g_io_channel_set_buffer_size (*ch, 5); 295 296 /* Attempt to set non-blocking flag on channel */ 297 if (g_io_channel_set_flags (*ch, G_IO_FLAG_NONBLOCK, NULL) != G_IO_STATUS_NORMAL) 298 mc_log ("Problem setting a channel to non-blocking state (channel flags: 0x%x)", 299 g_io_channel_get_flags (*ch)); 300 301 /* Create GSource */ 302 *src_id = g_io_add_watch (*ch, (write ? G_IO_OUT : G_IO_IN) | G_IO_HUP, cb_fun, data); 303 304 /* Source created OK? */ 305 if (*src_id == 0) 306 goto cleanup_and_err; 307 308 /* Configure the sources */ 309 g_source_set_name_by_id (*src_id, name); 310 311 /* Return true */ 312 return TRUE; 313 314 cleanup_and_err: 315 if (*src_id != 0) 316 { 317 GSource *src; 318 src = g_main_context_find_source_by_id (NULL, *src_id); 319 if (src != NULL) 320 /* Triggers also unref of wrapped channel */ 321 g_source_unref (src); 322 } 323 if (*ch != NULL) 324 g_io_channel_unref (*ch); 325 *ch = NULL; 326 *src_id = 0; 327 328 return FALSE; 329 } 330 331 /* --------------------------------------------------------------------------------------------- */ 332 333 static gboolean 334 helper_read_all (GIOChannel * channel, char *buf, gsize * out_bytes_read_in_len) 335 { 336 gsize len = *out_bytes_read_in_len; 337 gsize count, retries = 10; 338 GIOStatus stat; 339 340 *out_bytes_read_in_len = 0; 341 342 while (*out_bytes_read_in_len < len) 343 { 344 count = 0; 345 stat = 346 g_io_channel_read_chars (channel, buf + *out_bytes_read_in_len, 347 len - *out_bytes_read_in_len, &count, NULL); 348 *out_bytes_read_in_len += count; 349 350 /* Should end the reading ? */ 351 if (stat == G_IO_STATUS_ERROR || (stat == G_IO_STATUS_NORMAL && count == 0) || 352 stat == G_IO_STATUS_EOF) 353 return (stat != G_IO_STATUS_ERROR); 354 else if (stat == G_IO_STATUS_AGAIN && retries-- == 0) 355 /* Exhausted retries - there must be no data to read - return OK */ 356 return TRUE; 357 } 358 359 return TRUE; 360 } 361 362 /* --------------------------------------------------------------------------------------------- */ 363 364 static gboolean 365 helper_write_all (GIOChannel * channel, char *buf, gsize * out_bytes_written_in_len) 366 { 367 gsize len = *out_bytes_written_in_len; 368 gsize count; 369 GIOError err; 370 371 *out_bytes_written_in_len = 0; 372 373 while (*out_bytes_written_in_len < len) 374 { 375 count = 0; 376 err = 377 g_io_channel_write (channel, buf + *out_bytes_written_in_len, 378 len - *out_bytes_written_in_len, &count); 379 *out_bytes_written_in_len += count; 380 381 if (err && err != G_IO_ERROR_AGAIN) 382 return FALSE; 383 } 384 385 return TRUE; 386 } 387 388 /* --------------------------------------------------------------------------------------------- */ 389 390 static gboolean 391 stdout_read_callback (GIOChannel * source, GIOCondition condition, gpointer data) 392 { 393 WInstructionStation *ip = INSTR_STATION (data); 394 char buf[2048]; 395 gsize count = 2047; 396 gboolean r_ret, first_response = ip->prompt_shown; 397 398 if ((condition & G_IO_IN) == 0) 399 return TRUE; 400 401 r_ret = helper_read_all (source, buf, &count); 402 buf[count] = '\0'; 403 if (count == 0) 404 { 405 mc_log ("Error while reading program's output: %s", g_strerror (errno)); 406 } 407 else 408 { 409 ip->read_time = g_get_real_time (); 410 buf[count] = '\0'; 411 if (count > 0) 412 { 413 gsize idx = 0, nl_count = 0; 414 char *p = buf; 415 while (idx++ < count) 416 if (*p++ == '\n') 417 nl_count++; 418 419 /* Move to next line after prompt */ 420 if (first_response) 421 { 422 edit_insert (EDIT (ip), '\n'); 423 nl_count++; 424 } 425 426 /* Print whole buffer into file's window */ 427 edit_insert_string (EDIT (ip), buf, count); 428 429 /* Increase the row pointer */ 430 ip->cur_line += nl_count; 431 432 /* Mark a lack of prompt display */ 433 ip->prompt_shown = FALSE; 434 } 435 else if (!ip->prompt_shown) 436 instr_stat_show_prompt (ip, r_ret); 437 438 repaint_screen (); 439 } 440 441 return TRUE; 442 } 443 444 /* --------------------------------------------------------------------------------------------- */ 445 446 static gboolean 447 stdin_write_callback (GIOChannel * source, GIOCondition condition, gpointer data) 448 { 449 WInstructionStation *ip = INSTR_STATION (data); 450 gsize count; 451 gboolean ret = TRUE; 452 453 /* Any instruction to send? */ 454 if (ip->instruction == NULL) 455 return ret; 456 457 if ((condition & G_IO_OUT) == 0) 458 return TRUE; 459 460 errno = 0; 461 count = strlen (ip->instruction); 462 ret = helper_write_all (ip->io[STDIN].ch, ip->instruction, &count); 463 if (!ret) 464 { 465 mc_log ("Error during sending instruction to program: %s", g_strerror (errno)); 466 } 467 else 468 { 469 g_free (ip->instruction); 470 ip->instruction = NULL; 471 g_timeout_add (200, helper_timeout_cb, ip); 472 } 473 474 g_io_channel_flush (ip->io[STDIN].ch, NULL); 475 476 return ret; 477 } 478 479 /* --------------------------------------------------------------------------------------------- */ 480 481 /* --------------------------------------------------------------------------------------------- */ 482 /*** public functions ****************************************************************************/ 483 /* --------------------------------------------------------------------------------------------- */ 484 485 WInstructionStation * 486 instr_stat_create (int y, int x, int lines, int cols, const char *program) 487 { 488 WInstructionStation *ip; 489 WEdit *iret; 490 gboolean proc_ret = FALSE; 491 char *program_cline[2]; 492 int rounds = 0; 493 494 ip = g_new0 (WInstructionStation, 1); 495 if (ip == NULL) 496 return NULL; 497 498 iret = edit_init (EDIT (ip), y, x, lines, cols, NULL, 0, EDIT_DO_INIT_BASE_CLASS); 499 if (iret == NULL) 500 goto cleanup_and_err; 501 502 WIDGET (ip)->callback = instr_stat_callback; 503 WIDGET (ip)->mouse_callback = edit_mouse_callback; 504 EDIT (ip)->fullscreen = 0; 505 506 ip->io[0].name = g_strdup ("Std-In (WInstructionStation)"); 507 ip->io[1].name = g_strdup ("Std-Out (WInstructionStation)"); 508 ip->io[2].name = g_strdup ("Std-Error (WInstructionStation)"); 509 ip->io[0].fd = ip->io[1].fd = ip->io[2].fd = -1; 510 511 ip->prompt = g_strdup ("[guest@localhost]# "); 512 ip->prompt_span = strlen (ip->prompt); 513 ip->eprompt = g_strdup ("[guest@localhost]! "); 514 ip->eprompt_span = strlen (ip->eprompt); 515 516 /* Set initial program (most probably a shell, like Bash) */ 517 if (program != NULL) 518 ip->program = g_strdup (program); 519 else 520 /* Fallback instruction if no program given */ 521 ip->program = g_strdup ("bash++__"); 522 523 while (++rounds <= 2 && !proc_ret) 524 { 525 if (ip->error != NULL) 526 { 527 g_error_free (ip->error); 528 ip->error = NULL; 529 } 530 program_cline[0] = ip->program; 531 program_cline[1] = NULL; 532 proc_ret = g_spawn_async_with_pipes (NULL, program_cline, 533 NULL, G_SPAWN_SEARCH_PATH | 534 G_SPAWN_DO_NOT_REAP_CHILD | 535 G_SPAWN_LEAVE_DESCRIPTORS_OPEN, 536 NULL, NULL, &ip->proc_id, &ip->io[STDIN].fd, 537 &ip->io[STDOUT].fd, &ip->io[STDERR].fd, &ip->error); 538 539 if (!proc_ret) 540 { 541 char *new_program, *msg_text; 542 mc_log ("Creating the background process [program:%s] failed: %s", 543 program, ip->error ? ip->error->message : "<no error message>"); 544 545 /* Create a transatable, parametrized message */ 546 msg_text = g_strdup_printf ("%s%s:", _("The specified program has failed to run.\n" 547 "Enter a new full path or a program name"), 548 (rounds >= 2 ? _(" (last try)") : "")); 549 550 /* Display message asking for a new program */ 551 new_program = input_expand_dialog (_("Provide an alternate program to run"), 552 msg_text, MC_HISTORY_EDIT_INSTR_STATION_PATH, 553 alt_prog_first_run ? ip->program : INPUT_LAST_TEXT, 554 INPUT_COMPLETE_COMMANDS | INPUT_COMPLETE_FILENAMES | 555 INPUT_COMPLETE_VARIABLES); 556 557 alt_prog_first_run = FALSE; 558 559 /* Obtained a new program? */ 560 if (new_program != NULL) 561 { 562 g_free (ip->program); 563 ip->program = new_program; 564 } 565 } 566 } 567 568 /* If no program to run has been found, then exit. */ 569 if (!proc_ret) 570 { 571 message (D_ERROR, _("InstructionStation startup failed"), 572 _("Failed to initialize the CLI window.")); 573 goto cleanup_and_err; 574 } 575 576 /* Display initial prompt */ 577 instr_stat_show_prompt (ip, TRUE); 578 579 for (int i = 0; i <= STDERR; i++) 580 { 581 if (helper_configure_src_and_ch (&ip->io[i].ch, ip->io[i].fd, &ip->io[i].src_id, 582 (i == STDIN ? TRUE : FALSE), 583 (i == STDIN ? stdin_write_callback : stdout_read_callback), 584 ip, ip->io[i].name) == FALSE) 585 goto cleanup_and_err; 586 } 587 /* Set a process watcher and restarter for the program */ 588 ip->proc_src_id = g_child_watch_add (ip->proc_id, helper_program_ended_cb, ip); 589 590 return ip; 591 592 cleanup_and_err: 593 594 instr_release (ip, TRUE); 595 return NULL; 596 } 597 598 /* --------------------------------------------------------------------------------------------- */ 599 600 cb_ret_t 601 instr_stat_callback (Widget * w, Widget * sender, widget_msg_t msg, int parm, void *data) 602 { 603 cb_ret_t ret = MSG_NOT_HANDLED; 604 WInstructionStation *ip = INSTR_STATION (w); 605 WEdit *e = EDIT (w); 606 607 switch (msg) 608 { 609 610 case MSG_KEY: 611 g_main_context_iteration (NULL, 0); 612 { 613 gboolean ext_mode; 614 long activity; 615 616 /* keep and then extmod flag */ 617 ext_mode = w->ext_mode; 618 activity = widget_lookup_key (w, parm); 619 w->ext_mode = ext_mode; 620 621 if (activity == CK_IgnoreKey) 622 w->ext_mode = FALSE; 623 else 624 { 625 ret = instr_stat_run_activity (ip, activity); 626 /* if activity was not handled, keep the extended mode 627 for the further key processing */ 628 if (ret == MSG_HANDLED) 629 w->ext_mode = FALSE; 630 } 631 632 /* 633 * Due to the "end of bracket" escape the editor sees input with is_idle() == false 634 * (expects more characters) and hence doesn't yet refresh the screen, but then 635 * no further characters arrive (there's only an "end of bracket" which is swallowed 636 * by tty_get_event()), so you end up with a screen that's not refreshed after pasting. 637 * So let's trigger an IDLE signal. 638 */ 639 if (!is_idle ()) 640 widget_idle (w, TRUE); 641 } 642 break; 643 case MSG_ACTION: 644 ret = instr_stat_run_activity (ip, parm); 645 break; 646 case MSG_IDLE: 647 edit_update_screen (e); 648 break; 649 650 651 default: 652 break; 653 } 654 655 if (ret == MSG_NOT_HANDLED) 656 { 657 ret = edit_callback (w, sender, msg, parm, data); 658 ip->cur_col = EDIT (w)->curs_col; 659 } 660 return ret; 661 } 662 663 /* --------------------------------------------------------------------------------------------- */ -
new file lib/widget/instr_station.h
diff --git a/lib/widget/instr_station.h b/lib/widget/instr_station.h new file mode 100644 index 000000000..0daf8281f
- + 1 #ifndef MC__INSTR_STATION_H 2 #define MC__INSTR_STATION_H 3 4 #include <gio/gio.h> 5 6 #include "src/editor/editwidget.h" 7 8 /*** typedefs(not structures) and defined constants **********************************************/ 9 10 #define INSTR_STATION(x) ((WInstructionStation *)x) 11 #define CONST_INSTR_STATION(x) ((const WInstructionStation *)x) 12 13 /*** enums ***************************************************************************************/ 14 15 typedef enum instr_stat_io_type 16 { 17 STDIN = 0, 18 STDOUT, 19 STDERR 20 } instr_stat_io_type_t; 21 22 /*** structures declarations (and typedefs of structures)*****************************************/ 23 24 typedef struct 25 { 26 WEdit base; 27 28 int cur_line; 29 int cur_col; 30 int prompt_span; 31 int eprompt_span; 32 33 /* Shell (or other program) that is being run in station */ 34 char *program; 35 /* The prompt (a simulation) */ 36 char *prompt; 37 char *eprompt; 38 /* The current instruction to send */ 39 char *instruction; 40 41 /* An indicator of the prompt being printed after running an instruction */ 42 gboolean prompt_shown; 43 44 /* Process ID of the program */ 45 GPid proc_id; 46 47 /* A mark whether process ID is ready to be released */ 48 gboolean program_working; 49 50 /* Pipes to and from the program */ 51 struct 52 { 53 gint fd; 54 GIOChannel *ch; 55 guint src_id; 56 char *name; 57 } io[3]; 58 59 /* Time of last read of data from program */ 60 guint64 read_time; 61 62 /* GSource id (tag) of program process watcher */ 63 guint proc_src_id; 64 65 /* A GError placeholder */ 66 GError *error; 67 68 gboolean io_complete; 69 gboolean finalizing; 70 } WInstructionStation; 71 72 /*** global variables defined in .c file *********************************************************/ 73 74 /*** declarations of public functions ************************************************************/ 75 76 WInstructionStation *instr_stat_create (int y, int x, int lines, int cols, const char *program); 77 cb_ret_t instr_stat_callback (Widget * w, Widget * sender, widget_msg_t msg, int parm, void *data); 78 79 /*** inline functions ****************************************************************************/ 80 81 #endif /* MC__INSTR_STATION_H */ -
misc/mc.default.keymap
diff --git a/misc/mc.default.keymap b/misc/mc.default.keymap index 73b4c0eab..a7269d07f 100644
a b Sort = alt-t 352 352 Mail = alt-m 353 353 ParagraphFormat = alt-p 354 354 MatchBracket = alt-b 355 InstructionStation = alt-i 355 356 ExternalCommand = alt-u 356 357 UserMenu = f11 357 358 Menu = f9 -
misc/mc.emacs.keymap
diff --git a/misc/mc.emacs.keymap b/misc/mc.emacs.keymap index 955929444..98412b75f 100644
a b Sort = alt-t 351 351 # Mail = 352 352 ParagraphFormat = alt-p 353 353 # MatchBracket = 354 InstructionStation = alt-i 354 355 ExternalCommand = alt-u 355 356 UserMenu = f11 356 357 Menu = f9 -
src/editor/edit-impl.h
diff --git a/src/editor/edit-impl.h b/src/editor/edit-impl.h index 881ef549b..5bc88ec12 100644
a b typedef enum 88 88 LB_MAC 89 89 } LineBreaks; 90 90 91 typedef enum 92 { 93 EDIT_DO_INIT_BASE_CLASS = 1 << 0, 94 95 } edit_init_flags_t; 96 91 97 typedef enum 92 98 { 93 99 EDIT_QUICK_SAVE = 0, … … extern char *edit_window_state_char; 146 152 extern char *edit_window_close_char; 147 153 148 154 /*** declarations of public functions ************************************************************/ 155 cb_ret_t edit_callback (Widget * w, Widget * sender, widget_msg_t msg, int parm, void *data); 156 void edit_mouse_callback (Widget * w, mouse_msg_t msg, mouse_event_t * event); 149 157 150 158 gboolean edit_add_window (WDialog * h, int y, int x, int lines, int cols, 151 159 const vfs_path_t * f, long fline); … … void edit_move_to_prev_col (WEdit * edit, off_t p); 169 177 long edit_get_col (const WEdit * edit); 170 178 void edit_update_curs_row (WEdit * edit); 171 179 void edit_update_curs_col (WEdit * edit); 180 gboolean edit_newline_end_check_only (const WEdit * edit); 172 181 void edit_find_bracket (WEdit * edit); 173 182 gboolean edit_reload_line (WEdit * edit, const vfs_path_t * filename_vpath, long line); 174 183 void edit_set_codeset (WEdit * edit); … … void edit_delete_line (WEdit * edit); 180 189 181 190 int edit_delete (WEdit * edit, gboolean byte_delete); 182 191 int edit_backspace (WEdit * edit, gboolean byte_delete); 192 void edit_insert_string (WEdit * edit, char *str_text, gsize len); 183 193 void edit_insert (WEdit * edit, int c); 184 194 void edit_insert_over (WEdit * edit); 185 195 void edit_cursor_move (WEdit * edit, off_t increment); … … char *edit_get_write_filter (const vfs_path_t * write_name_vpath, 193 203 gboolean edit_save_confirm_cmd (WEdit * edit); 194 204 gboolean edit_save_as_cmd (WEdit * edit); 195 205 WEdit *edit_init (WEdit * edit, int y, int x, int lines, int cols, 196 const vfs_path_t * filename_vpath, long line );206 const vfs_path_t * filename_vpath, long line, edit_init_flags_t flags); 197 207 gboolean edit_clean (WEdit * edit); 198 208 gboolean edit_ok_to_exit (WEdit * edit); 199 209 file_suitable_rank_t edit_check_file_suitable (const vfs_path_t * fs_path); -
src/editor/edit.c
diff --git a/src/editor/edit.c b/src/editor/edit.c index 8477939aa..ea193a8de 100644
a b edit_modification (WEdit * edit) 652 652 { 653 653 edit->caches_valid = FALSE; 654 654 655 /* raise lock when file modified */656 if ( !edit->modified && !edit->delete_file)655 /* raise lock when file is about to be modified */ 656 if (edit->filename_vpath && !edit->modified && !edit->delete_file) 657 657 edit->locked = lock_file (edit->filename_vpath); 658 658 edit->modified = 1; 659 659 } … … edit_insert_file (WEdit * edit, const vfs_path_t * filename_vpath) 2106 2106 2107 2107 WEdit * 2108 2108 edit_init (WEdit * edit, int y, int x, int lines, int cols, const vfs_path_t * filename_vpath, 2109 long line )2109 long line, edit_init_flags_t flags) 2110 2110 { 2111 2111 gboolean to_free = FALSE; 2112 2112 … … edit_init (WEdit * edit, int y, int x, int lines, int cols, const vfs_path_t * f 2128 2128 edit->fullscreen = fullscreen; 2129 2129 edit->loc_prev = loc_prev; 2130 2130 } 2131 else2131 if (has_flag (flags, EDIT_DO_INIT_BASE_CLASS) || edit == NULL) 2132 2132 { 2133 2133 Widget *w; 2134 edit = g_malloc0 (sizeof (WEdit)); 2135 to_free = TRUE; 2134 if (edit == NULL) 2135 { 2136 edit = g_malloc0 (sizeof (WEdit)); 2137 to_free = TRUE; 2138 } 2136 2139 2137 2140 w = WIDGET (edit); 2138 2141 widget_init (w, y, x, lines, cols, NULL, NULL); … … edit_reload_line (WEdit * edit, const vfs_path_t * filename_vpath, long line) 2271 2274 e->fullscreen = edit->fullscreen; 2272 2275 e->loc_prev = edit->loc_prev; 2273 2276 2274 if (edit_init (e, w->y, w->x, w->lines, w->cols, filename_vpath, line ) == NULL)2277 if (edit_init (e, w->y, w->x, w->lines, w->cols, filename_vpath, line, 0) == NULL) 2275 2278 { 2276 2279 g_free (e); 2277 2280 return FALSE; … … edit_push_redo_action (WEdit * edit, long c) 2528 2531 edit->redo_stack_bottom = edit->redo_stack_pointer = 0; 2529 2532 } 2530 2533 2534 /* --------------------------------------------------------------------------------------------- */ 2535 2536 void 2537 edit_insert_string (WEdit * edit, char *str_text, gsize len) 2538 { 2539 size_t i; 2540 for (i = 0; i < len; i++) 2541 edit_insert (edit, str_text[i]); 2542 } 2543 2531 2544 /* --------------------------------------------------------------------------------------------- */ 2532 2545 /** 2533 2546 Basic low level single character buffer alterations and movements at the cursor. -
src/editor/editcmd.c
diff --git a/src/editor/editcmd.c b/src/editor/editcmd.c index be58f3db7..7318aebd5 100644
a b edit_complete_word_insert_recoded_completion (WEdit * edit, char *completion, gs 1573 1573 /*** public functions ****************************************************************************/ 1574 1574 /* --------------------------------------------------------------------------------------------- */ 1575 1575 1576 gboolean 1577 edit_newline_end_check_only (const WEdit * e) 1578 { 1579 const edit_buffer_t *buf = &e->buffer; 1580 return (buf->size > 0 && edit_buffer_get_byte (buf, buf->size - 1) == '\n'); 1581 } 1582 1583 /* --------------------------------------------------------------------------------------------- */ 1584 1576 1585 void 1577 1586 edit_refresh_cmd (void) 1578 1587 { -
src/editor/editdraw.c
diff --git a/src/editor/editdraw.c b/src/editor/editdraw.c index 61bbdcbee..80495dcd0 100644
a b edit_draw_this_line (WEdit * edit, off_t b, long row, long start_col, long end_c 820 820 } 821 821 822 822 p->ch = 0; 823 824 823 print_to_widget (edit, row, start_col, start_col_real, end_col, line, line_stat, book_mark); 825 824 } 826 825 -
src/editor/editmenu.c
diff --git a/src/editor/editmenu.c b/src/editor/editmenu.c index c20e9fb7e..0c8e60801 100644
a b create_window_menu (void) 238 238 entries = g_list_prepend (entries, menu_entry_create (_("&Next"), CK_WindowNext)); 239 239 entries = g_list_prepend (entries, menu_entry_create (_("&Previous"), CK_WindowPrev)); 240 240 entries = g_list_prepend (entries, menu_entry_create (_("&List..."), CK_WindowList)); 241 entries = 242 g_list_prepend (entries, 243 menu_entry_create (_("Instr&uction Station"), CK_InstructionStation)); 241 244 242 245 return g_list_reverse (entries); 243 246 } -
src/editor/editwidget.c
diff --git a/src/editor/editwidget.c b/src/editor/editwidget.c index e32541cd9..bafc65332 100644
a b 68 68 #ifdef HAVE_ASPELL 69 69 #include "spell.h" 70 70 #endif 71 #include "lib/widget/instr_station.h" 71 72 72 73 /*** global variables ****************************************************************************/ 73 74 … … static unsigned int edit_dlg_init_refcounter = 0; 89 90 static cb_ret_t edit_dialog_callback (Widget * w, Widget * sender, widget_msg_t msg, int parm, 90 91 void *data); 91 92 93 /* --------------------------------------------------------------------------------------------- */ 94 /* Creates a CLI special window, windowed (not fullscreen) */ 95 96 static gboolean 97 edit_add_instr_stat_window (WDialog * h) 98 { 99 WInstructionStation *ip; 100 ip = instr_stat_create (3, 10, 17, 80, "/bin/bash"); 101 if (ip == NULL) 102 return FALSE; 103 group_add_widget_autopos (GROUP (h), ip, WPOS_KEEP_ALL, NULL); 104 widget_draw (WIDGET (h)); 105 return TRUE; 106 } 107 92 108 /* --------------------------------------------------------------------------------------------- */ 93 109 94 110 static char * … … edit_dialog_command_execute (WDialog * h, long command) 718 734 case CK_Refresh: 719 735 edit_refresh_cmd (); 720 736 break; 737 case CK_InstructionStation: 738 edit_add_instr_stat_window (h); 739 break; 721 740 case CK_Shell: 722 741 toggle_subshell (); 723 742 break; … … edit_dialog_bg_callback (Widget * w, Widget * sender, widget_msg_t msg, int parm 1208 1227 1209 1228 /* --------------------------------------------------------------------------------------------- */ 1210 1229 1211 static cb_ret_t 1230 /** 1231 * Handle move/resize mouse events. 1232 */ 1233 static void 1234 edit_mouse_handle_move_resize (Widget * w, mouse_msg_t msg, mouse_event_t * event) 1235 { 1236 WEdit *edit = (WEdit *) (w); 1237 Widget *h = WIDGET (w->owner); 1238 int global_x, global_y; 1239 1240 if (msg == MSG_MOUSE_UP) 1241 { 1242 /* Exit move/resize mode. */ 1243 edit_execute_cmd (edit, CK_Enter, -1); 1244 edit_update_screen (edit); /* Paint the buttonbar over our possibly overlapping frame. */ 1245 return; 1246 } 1247 1248 if (msg != MSG_MOUSE_DRAG) 1249 /** 1250 * We ignore any other events. Specifically, MSG_MOUSE_DOWN. 1251 * 1252 * When the move/resize is initiated by the menu, we let the user 1253 * stop it by clicking with the mouse. Which is why we don't want 1254 * a mouse down to affect the window. 1255 */ 1256 return; 1257 1258 /* Convert point to global coordinates for easier calculations. */ 1259 global_x = event->x + w->x; 1260 global_y = event->y + w->y; 1261 1262 /* Clamp the point to the dialog's client area. */ 1263 global_y = CLAMP (global_y, h->y + 1, h->y + h->lines - 2); /* Status line, buttonbar */ 1264 global_x = CLAMP (global_x, h->x, h->x + h->cols - 1); /* Currently a no-op, as the dialog has no left/right margins. */ 1265 1266 if (edit->drag_state == MCEDIT_DRAG_MOVE) 1267 { 1268 w->y = global_y; 1269 w->x = global_x - edit->drag_state_start; 1270 } 1271 else if (edit->drag_state == MCEDIT_DRAG_RESIZE) 1272 { 1273 w->lines = MAX (WINDOW_MIN_LINES, global_y - w->y + 1); 1274 w->cols = MAX (WINDOW_MIN_COLS, global_x - w->x + 1); 1275 } 1276 1277 edit->force |= REDRAW_COMPLETELY; /* Not really needed as WEdit's MSG_DRAW already does this. */ 1278 1279 /* We draw the whole dialog because dragging/resizing exposes area beneath. */ 1280 widget_draw (WIDGET (w->owner)); 1281 } 1282 1283 /* --------------------------------------------------------------------------------------------- */ 1284 /*** public functions ****************************************************************************/ 1285 /* --------------------------------------------------------------------------------------------- */ 1286 1287 cb_ret_t 1212 1288 edit_callback (Widget * w, Widget * sender, widget_msg_t msg, int parm, void *data) 1213 1289 { 1214 1290 WEdit *e = (WEdit *) w; … … edit_callback (Widget * w, Widget * sender, widget_msg_t msg, int parm, void *da 1278 1354 1279 1355 /* --------------------------------------------------------------------------------------------- */ 1280 1356 1281 /**1282 * Handle move/resize mouse events.1283 */1284 static void1285 edit_mouse_handle_move_resize (Widget * w, mouse_msg_t msg, mouse_event_t * event)1286 {1287 WEdit *edit = (WEdit *) (w);1288 Widget *h = WIDGET (w->owner);1289 int global_x, global_y;1290 1291 if (msg == MSG_MOUSE_UP)1292 {1293 /* Exit move/resize mode. */1294 edit_execute_cmd (edit, CK_Enter, -1);1295 edit_update_screen (edit); /* Paint the buttonbar over our possibly overlapping frame. */1296 return;1297 }1298 1299 if (msg != MSG_MOUSE_DRAG)1300 /**1301 * We ignore any other events. Specifically, MSG_MOUSE_DOWN.1302 *1303 * When the move/resize is initiated by the menu, we let the user1304 * stop it by clicking with the mouse. Which is why we don't want1305 * a mouse down to affect the window.1306 */1307 return;1308 1309 /* Convert point to global coordinates for easier calculations. */1310 global_x = event->x + w->x;1311 global_y = event->y + w->y;1312 1313 /* Clamp the point to the dialog's client area. */1314 global_y = CLAMP (global_y, h->y + 1, h->y + h->lines - 2); /* Status line, buttonbar */1315 global_x = CLAMP (global_x, h->x, h->x + h->cols - 1); /* Currently a no-op, as the dialog has no left/right margins. */1316 1317 if (edit->drag_state == MCEDIT_DRAG_MOVE)1318 {1319 w->y = global_y;1320 w->x = global_x - edit->drag_state_start;1321 }1322 else if (edit->drag_state == MCEDIT_DRAG_RESIZE)1323 {1324 w->lines = MAX (WINDOW_MIN_LINES, global_y - w->y + 1);1325 w->cols = MAX (WINDOW_MIN_COLS, global_x - w->x + 1);1326 }1327 1328 edit->force |= REDRAW_COMPLETELY; /* Not really needed as WEdit's MSG_DRAW already does this. */1329 1330 /* We draw the whole dialog because dragging/resizing exposes area beneath. */1331 widget_draw (WIDGET (w->owner));1332 }1333 1334 /* --------------------------------------------------------------------------------------------- */1335 1336 1357 /** 1337 1358 * Handle mouse events of editor window 1338 1359 * … … edit_mouse_handle_move_resize (Widget * w, mouse_msg_t msg, mouse_event_t * even 1340 1361 * @param msg mouse event message 1341 1362 * @param event mouse event data 1342 1363 */ 1343 staticvoid1364 void 1344 1365 edit_mouse_callback (Widget * w, mouse_msg_t msg, mouse_event_t * event) 1345 1366 { 1346 1367 WEdit *edit = (WEdit *) w; … … edit_mouse_callback (Widget * w, mouse_msg_t msg, mouse_event_t * event) 1454 1475 } 1455 1476 } 1456 1477 1457 /* --------------------------------------------------------------------------------------------- */1458 /*** public functions ****************************************************************************/1459 1478 /* --------------------------------------------------------------------------------------------- */ 1460 1479 1461 1480 gboolean … … edit_add_window (WDialog * h, int y, int x, int lines, int cols, const vfs_path_ 1686 1705 WEdit *edit; 1687 1706 Widget *w; 1688 1707 1689 edit = edit_init (NULL, y, x, lines, cols, f, fline );1708 edit = edit_init (NULL, y, x, lines, cols, f, fline, 0); 1690 1709 if (edit == NULL) 1691 1710 return FALSE; 1692 1711 -
src/editor/editwidget.h
diff --git a/src/editor/editwidget.h b/src/editor/editwidget.h index b4b10692e..a80057393 100644
a b 13 13 14 14 /*** typedefs(not structures) and defined constants **********************************************/ 15 15 16 #define EDIT(x) ((WEdit *)(x)) 17 #define CONST_EDIT(x) ((const WEdit *)(x)) 18 16 19 #define N_LINE_CACHES 32 17 20 18 21 /*** enums ***************************************************************************************/ -
src/history.h
diff --git a/src/history.h b/src/history.h index 7a8d73fa2..738d75c66 100644
a b 17 17 #define MC_HISTORY_EDIT_SORT "mc.edit.sort" 18 18 #define MC_HISTORY_EDIT_PASTE_EXTCMD "mc.edit.paste-extcmd" 19 19 #define MC_HISTORY_EDIT_REPEAT "mc.edit.repeat-action" 20 #define MC_HISTORY_EDIT_INSTR_STATION_PATH "mc.edit.instr-panel-path" 20 21 21 22 #define MC_HISTORY_FM_VIEW_FILE "mc.fm.view-file" 22 23 #define MC_HISTORY_FM_MKDIR "mc.fm.mkdir" -
src/keybind-defaults.c
diff --git a/src/keybind-defaults.c b/src/keybind-defaults.c index db483382a..c0cede558 100644
a b static const global_keymap_ini_t default_editor_keymap[] = { 478 478 {"FileNext", "alt-plus"}, 479 479 {"Sort", "alt-t"}, 480 480 {"Mail", "alt-m"}, 481 {"InstructionStation", "alt-i"}, 481 482 {"ExternalCommand", "alt-u"}, 482 483 {"WindowMove", "ctrl-alt-e"}, 483 484 {"WindowResize", "ctrl-alt-s"},