📄 shell.c
字号:
static const char CVSID[] = "$Id: shell.c,v 1.25 2003/05/02 19:19:02 edg Exp $";/******************************************************************************** ** shell.c -- Nirvana Editor shell command execution ** ** Copyright (C) 1999 Mark Edel ** ** This is free software; you can redistribute it and/or modify it under the ** terms of the GNU General Public License as published by the Free Software ** Foundation; either version 2 of the License, or (at your option) any later ** version. ** ** This software is distributed in the hope that it will be useful, but WITHOUT ** ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ** FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License ** for more details. ** ** You should have received a copy of the GNU General Public License along with ** software; if not, write to the Free Software Foundation, Inc., 59 Temple ** Place, Suite 330, Boston, MA 02111-1307 USA ** ** Nirvana Text Editor ** December, 1993 ** ** Written by Mark Edel ** ********************************************************************************/#ifdef HAVE_CONFIG_H#include "../config.h"#endif#include "shell.h"#include "textBuf.h"#include "text.h"#include "nedit.h"#include "window.h"#include "preferences.h"#include "file.h"#include "macro.h"#include "interpret.h"#include "../util/DialogF.h"#include "../util/misc.h"#include <stdio.h>#include <stdlib.h>#include <string.h>#include <signal.h>#include <sys/types.h>#ifndef __MVS__#include <sys/param.h>#endif#include <sys/wait.h>#include <unistd.h>#include <fcntl.h>#include <ctype.h>#include <errno.h>#ifdef notdef#ifdef IBM#define NBBY 8#include <sys/select.h>#endif#include <time.h>#endif#ifdef __EMX__#include <process.h>#endif#include <Xm/Xm.h>#include <Xm/MessageB.h>#include <Xm/Text.h>#include <Xm/Form.h>#include <Xm/PushBG.h>#ifdef HAVE_DEBUG_H#include "../debug.h"#endif/* Tuning parameters */#define IO_BUF_SIZE 4096 /* size of buffers for collecting cmd output */#define MAX_OUT_DIALOG_ROWS 30 /* max height of dialog for command output */#define MAX_OUT_DIALOG_COLS 80 /* max width of dialog for command output */#define OUTPUT_FLUSH_FREQ 1000 /* how often (msec) to flush output buffers when process is taking too long */#define BANNER_WAIT_TIME 6000 /* how long to wait (msec) before putting up Shell Command Executing... banner *//* flags for issueCommand */#define ACCUMULATE 1#define ERROR_DIALOGS 2#define REPLACE_SELECTION 4#define RELOAD_FILE_AFTER 8#define OUTPUT_TO_DIALOG 16#define OUTPUT_TO_STRING 32/* element of a buffer list for collecting output from shell processes */typedef struct bufElem { struct bufElem *next; int length; char contents[IO_BUF_SIZE];} buffer;/* data attached to window during shell command execution with information for controling and communicating with the process */typedef struct { int flags; int stdinFD, stdoutFD, stderrFD; pid_t childPid; XtInputId stdinInputID, stdoutInputID, stderrInputID; buffer *outBufs, *errBufs; char *input; char *inPtr; Widget textW; int leftPos, rightPos; int inLength; XtIntervalId bannerTimeoutID, flushTimeoutID; char bannerIsUp; char fromMacro;} shellCmdInfo;static void issueCommand(WindowInfo *window, const char *command, char *input, int inputLen, int flags, Widget textW, int replaceLeft, int replaceRight, int fromMacro);static void stdoutReadProc(XtPointer clientData, int *source, XtInputId *id);static void stderrReadProc(XtPointer clientData, int *source, XtInputId *id);static void stdinWriteProc(XtPointer clientData, int *source, XtInputId *id);static void finishCmdExecution(WindowInfo *window, int terminatedOnError);static pid_t forkCommand(Widget parent, const char *command, const char *cmdDir, int *stdinFD, int *stdoutFD, int *stderrFD);static void addOutput(buffer **bufList, buffer *buf);static char *coalesceOutput(buffer **bufList, int *length);static void freeBufList(buffer **bufList);static void removeTrailingNewlines(char *string);static void createOutputDialog(Widget parent, char *text);static void destroyOutDialogCB(Widget w, XtPointer callback, XtPointer closure);static void measureText(char *text, int wrapWidth, int *rows, int *cols, int *wrapped);static void truncateString(char *string, int length);static void bannerTimeoutProc(XtPointer clientData, XtIntervalId *id);static void flushTimeoutProc(XtPointer clientData, XtIntervalId *id);static void safeBufReplace(textBuffer *buf, int *start, int *end, const char *text);static char *shellCommandSubstitutes(const char *inStr, const char *fileStr, const char *lineStr);static int shellSubstituter(char *outStr, const char *inStr, const char *fileStr, const char *lineStr, int outLen, int predictOnly);/*** Filter the current selection through shell command "command". The selection** is removed, and replaced by the output from the command execution. Failed** command status and output to stderr are presented in dialog form.*/void FilterSelection(WindowInfo *window, const char *command, int fromMacro){ int left, right, textLen; char *text; /* Can't do two shell commands at once in the same window */ if (window->shellCmdData != NULL) { XBell(TheDisplay, 0); return; } /* Get the selection and the range in character positions that it occupies. Beep and return if no selection */ text = BufGetSelectionText(window->buffer); if (*text == '\0') { XtFree(text); XBell(TheDisplay, 0); return; } textLen = strlen(text); BufUnsubstituteNullChars(text, window->buffer); left = window->buffer->primary.start; right = window->buffer->primary.end; /* Issue the command and collect its output */ issueCommand(window, command, text, textLen, ACCUMULATE | ERROR_DIALOGS | REPLACE_SELECTION, window->lastFocus, left, right, fromMacro);}/*** Execute shell command "command", depositing the result at the current** insert position or in the current selection if the window has a** selection.*/void ExecShellCommand(WindowInfo *window, const char *command, int fromMacro){ int left, right, flags = 0; char *subsCommand, fullName[MAXPATHLEN]; int pos, line, column; char lineNumber[11]; /* Can't do two shell commands at once in the same window */ if (window->shellCmdData != NULL) { XBell(TheDisplay, 0); return; } /* get the selection or the insert position */ pos = TextGetCursorPos(window->lastFocus); if (GetSimpleSelection(window->buffer, &left, &right)) flags = ACCUMULATE | REPLACE_SELECTION; else left = right = pos; /* Substitute the current file name for % and the current line number for # in the shell command */ strcpy(fullName, window->path); strcat(fullName, window->filename); TextPosToLineAndCol(window->lastFocus, pos, &line, &column); sprintf(lineNumber, "%d", line); subsCommand = shellCommandSubstitutes(command, fullName, lineNumber); if (subsCommand == NULL) { DialogF(DF_ERR, window->shell, 1, "Shell Command", "Shell command is too long due to\n" "filename substitutions with '%%' or\n" "line number substitutions with '#'", "OK"); return; } /* issue the command */ issueCommand(window, subsCommand, NULL, 0, flags, window->lastFocus, left, right, fromMacro); free(subsCommand);}/*** Execute shell command "command", on input string "input", depositing the** in a macro string (via a call back to ReturnShellCommandOutput).*/void ShellCmdToMacroString(WindowInfo *window, const char *command, const char *input){ char *inputCopy; /* Make a copy of the input string for issueCommand to hold and free upon completion */ inputCopy = *input == '\0' ? NULL : XtNewString(input); /* fork the command and begin processing input/output */ issueCommand(window, command, inputCopy, strlen(input), ACCUMULATE | OUTPUT_TO_STRING, NULL, 0, 0, True);}/*** Execute the line of text where the the insertion cursor is positioned** as a shell command.*/void ExecCursorLine(WindowInfo *window, int fromMacro){ char *cmdText; int left, right, insertPos; char *subsCommand, fullName[MAXPATHLEN]; int pos, line, column; char lineNumber[11]; /* Can't do two shell commands at once in the same window */ if (window->shellCmdData != NULL) { XBell(TheDisplay, 0); return; } /* get all of the text on the line with the insert position */ pos = TextGetCursorPos(window->lastFocus); if (!GetSimpleSelection(window->buffer, &left, &right)) { left = right = pos; left = BufStartOfLine(window->buffer, left); right = BufEndOfLine(window->buffer, right); insertPos = right; } else insertPos = BufEndOfLine(window->buffer, right); cmdText = BufGetRange(window->buffer, left, right); BufUnsubstituteNullChars(cmdText, window->buffer); /* insert a newline after the entire line */ BufInsert(window->buffer, insertPos, "\n"); /* Substitute the current file name for % and the current line number for # in the shell command */ strcpy(fullName, window->path); strcat(fullName, window->filename); TextPosToLineAndCol(window->lastFocus, pos, &line, &column); sprintf(lineNumber, "%d", line); subsCommand = shellCommandSubstitutes(cmdText, fullName, lineNumber); if (subsCommand == NULL) { DialogF(DF_ERR, window->shell, 1, "Shell Command", "Shell command is too long due to\n" "filename substitutions with '%%' or\n" "line number substitutions with '#'", "OK"); return; } /* issue the command */ issueCommand(window, subsCommand, NULL, 0, 0, window->lastFocus, insertPos+1, insertPos+1, fromMacro); free(subsCommand); XtFree(cmdText);}/*** Do a shell command, with the options allowed to users (input source,** output destination, save first and load after) in the shell commands** menu.*/void DoShellMenuCmd(WindowInfo *window, const char *command, int input, int output, int outputReplacesInput, int saveFirst, int loadAfter, int fromMacro) { int flags = 0; char *text; char *subsCommand, fullName[MAXPATHLEN]; int left, right, textLen; int pos, line, column; char lineNumber[11]; WindowInfo *inWindow = window; Widget outWidget; /* Can't do two shell commands at once in the same window */ if (window->shellCmdData != NULL) { XBell(TheDisplay, 0); return; } /* Substitute the current file name for % and the current line number for # in the shell command */ strcpy(fullName, window->path); strcat(fullName, window->filename); pos = TextGetCursorPos(window->lastFocus); TextPosToLineAndCol(window->lastFocus, pos, &line, &column); sprintf(lineNumber, "%d", line); subsCommand = shellCommandSubstitutes(command, fullName, lineNumber); if (subsCommand == NULL) { DialogF(DF_ERR, window->shell, 1, "Shell Command", "Shell command is too long due to\n" "filename substitutions with '%%' or\n" "line number substitutions with '#'", "OK"); return; } /* Get the command input as a text string. If there is input, errors shouldn't be mixed in with output, so set flags to ERROR_DIALOGS */ if (input == FROM_SELECTION) { text = BufGetSelectionText(window->buffer); if (*text == '\0') { XtFree(text); free(subsCommand); XBell(TheDisplay, 0); return; } flags |= ACCUMULATE | ERROR_DIALOGS; } else if (input == FROM_WINDOW) { text = BufGetAll(window->buffer); flags |= ACCUMULATE | ERROR_DIALOGS; } else if (input == FROM_EITHER) { text = BufGetSelectionText(window->buffer); if (*text == '\0') { XtFree(text); text = BufGetAll(window->buffer); } flags |= ACCUMULATE | ERROR_DIALOGS; } else /* FROM_NONE */ text = NULL; /* If the buffer was substituting another character for ascii-nuls, put the nuls back in before exporting the text */ if (text != NULL) { textLen = strlen(text); BufUnsubstituteNullChars(text, window->buffer); } else textLen = 0; /* Assign the output destination. If output is to a new window, create it, and run the command from it instead of the current one, to free the current one from waiting for lengthy execution */ if (output == TO_DIALOG) { outWidget = NULL; flags |= OUTPUT_TO_DIALOG; left = right = 0; } else if (output == TO_NEW_WINDOW) { EditNewFile(NULL, False, NULL, window->path); outWidget = WindowList->textArea; inWindow = WindowList; left = right = 0; } else { /* TO_SAME_WINDOW */ outWidget = window->lastFocus; if (outputReplacesInput && input != FROM_NONE) { if (input == FROM_WINDOW) { left = 0; right = window->buffer->length; } else if (input == FROM_SELECTION) { GetSimpleSelection(window->buffer, &left, &right); flags |= ACCUMULATE | REPLACE_SELECTION; } else if (input == FROM_EITHER) { if (GetSimpleSelection(window->buffer, &left, &right)) flags |= ACCUMULATE | REPLACE_SELECTION; else { left = 0; right = window->buffer->length; } } } else { if (GetSimpleSelection(window->buffer, &left, &right)) flags |= ACCUMULATE | REPLACE_SELECTION; else left = right = TextGetCursorPos(window->lastFocus); } } /* If the command requires the file be saved first, save it */ if (saveFirst) { if (!SaveWindow(window)) { if (input != FROM_NONE) XtFree(text); free(subsCommand); return; } } /* If the command requires the file to be reloaded after execution, set a flag for issueCommand to deal with it when execution is complete */ if (loadAfter) flags |= RELOAD_FILE_AFTER; /* issue the command */ issueCommand(inWindow, subsCommand, text, textLen, flags, outWidget, left, right, fromMacro); free(subsCommand);}/*** Cancel the shell command in progress*/void AbortShellCommand(WindowInfo *window){ shellCmdInfo *cmdData = window->shellCmdData; if (cmdData == NULL) return;
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -