summaryrefslogtreecommitdiff
path: root/fluid/ExternalCodeEditor_UNIX.cxx
diff options
context:
space:
mode:
authorMatthias Melcher <github@matthiasm.com>2025-03-07 16:34:35 +0100
committerMatthias Melcher <github@matthiasm.com>2025-03-07 16:34:48 +0100
commit1985aefc0e502048f92b91beef87c0dfbe669fed (patch)
treeaf62874def4590e437a47784b4428d975ceb262f /fluid/ExternalCodeEditor_UNIX.cxx
parent42a04c064d4b31c3a85210311f3ada163c406a25 (diff)
Restructuring Fluid source files.
Diffstat (limited to 'fluid/ExternalCodeEditor_UNIX.cxx')
-rw-r--r--fluid/ExternalCodeEditor_UNIX.cxx576
1 files changed, 0 insertions, 576 deletions
diff --git a/fluid/ExternalCodeEditor_UNIX.cxx b/fluid/ExternalCodeEditor_UNIX.cxx
deleted file mode 100644
index 4f0bea3f9..000000000
--- a/fluid/ExternalCodeEditor_UNIX.cxx
+++ /dev/null
@@ -1,576 +0,0 @@
-//
-// External code editor management class for Unix
-//
-// Note: This entire file Unix only
-
-#include "ExternalCodeEditor_UNIX.h"
-
-#include "fluid.h"
-
-#include <FL/Fl.H> /* Fl_Timeout_Handler.. */
-#include <FL/fl_ask.H> /* fl_alert() */
-#include <FL/fl_string_functions.h> /* fl_strdup() */
-
-#include <errno.h> /* errno */
-#include <string.h> /* strerror() */
-#include <sys/types.h> /* stat().. */
-#include <sys/stat.h>
-#include <sys/wait.h> /* waitpid().. */
-#include <fcntl.h> /* open().. */
-#include <signal.h> /* kill().. */
-#include <unistd.h>
-#include <stdlib.h> /* free().. */
-#include <stdio.h> /* snprintf().. */
-
-// Static local data
-static int L_editors_open = 0; // keep track of #editors open
-static Fl_Timeout_Handler L_update_timer_cb = 0; // app's update timer callback
-
-// [Static/Local] See if file exists
-static int is_file(const char *filename) {
- struct stat buf;
- if ( stat(filename, &buf) < 0 ) return(0);
- return(S_ISREG(buf.st_mode) ? 1 : 0); // regular file?
-}
-
-// [Static/Local] See if dir exists
-static int is_dir(const char *dirname) {
- struct stat buf;
- if ( stat(dirname, &buf) < 0 ) return(0);
- return(S_ISDIR(buf.st_mode) ? 1 : 0); // a dir?
-}
-
-// ---- ExternalCodeEditor implementation
-
-/** \class ExternalCodeEditor
- Support for an external C++ code editor for Fluid Code block.
-
- This class can launch and quit a user defined program for editing
- code outside of Fluid.
- It observes changes in the external file and updates the Fluid
- widget to stay synchronized.
- */
-
-/**
- Create the manager for external code editors.
- */
-ExternalCodeEditor::ExternalCodeEditor() {
- pid_ = -1;
- filename_ = 0;
- file_mtime_ = 0;
- file_size_ = 0;
- alert_pipe_[0] = alert_pipe_[1] = -1;
- alert_pipe_open_ = false;
-}
-
-/**
- Destroy the manager.
- This also closes the external editor.
- */
-ExternalCodeEditor::~ExternalCodeEditor() {
- if ( G_debug )
- printf("ExternalCodeEditor() DTOR CALLED (this=%p, pid=%ld)\n",
- (void*)this, (long)pid_);
- close_editor(); // close editor, delete tmp file
- set_filename(0); // free()s filename
-
- if (alert_pipe_open_) {
- Fl::remove_fd(alert_pipe_[0]);
- if (alert_pipe_[0] != -1) ::close(alert_pipe_[0]);
- if (alert_pipe_[1] != -1) ::close(alert_pipe_[1]);
- }
-}
-
-/**
- Set the filename for the file we wish to edit.
- Handles memory allocation/free.
- If set to NULL, frees memory.
- \param[in] val new filename
- */
-void ExternalCodeEditor::set_filename(const char *val) {
- if ( filename_ ) free((void*)filename_);
- filename_ = val ? fl_strdup(val) : 0;
-}
-
-/**
- Is editor running?
- \return 1 if we are currently editing a file.
- */
-int ExternalCodeEditor::is_editing() {
- return( (pid_ != -1) ? 1 : 0 );
-}
-
-/**
- Wait for editor to close
- */
-void ExternalCodeEditor::close_editor() {
- if ( G_debug ) printf("close_editor() called: pid=%ld\n", long(pid_));
- // Wait until editor is closed + reaped
- while ( is_editing() ) {
- switch ( reap_editor() ) {
- case -2: // no editor running (unlikely to happen)
- return;
- case -1: // error
- fl_alert("Error reaping external editor\n"
- "pid=%ld file=%s", long(pid_), filename());
- break;
- case 0: // process still running
- switch ( fl_choice("Please close external editor\npid=%ld file=%s",
- "Force Close", // button 0
- "Closed", // button 1
- 0, // button 2
- long(pid_), filename() ) ) {
- case 0: // Force Close
- kill_editor();
- continue;
- case 1: // Closed? try to reap
- continue;
- }
- break;
- case 1: // process reaped
- return;
- }
- }
-}
-
-/**
- Kill the running editor (if any).
-
- Kills the editor, reaps the process, and removes the tmp file.
- The dtor calls this to ensure no editors remain running when fluid exits.
- */
-void ExternalCodeEditor::kill_editor() {
- if ( G_debug ) printf("kill_editor() called: pid=%ld\n", (long)pid_);
- if ( !is_editing() ) return; // editor not running? return..
- kill(pid_, SIGTERM); // kill editor
- int wcount = 0;
- while ( is_editing() ) { // and wait for editor to finish..
- usleep(100000); // 1/10th sec delay gives editor time to close itself
- pid_t pid_reaped;
- switch ( reap_editor(&pid_reaped) ) {
- case -2: // editor not running (unlikely to happen)
- return;
- case -1: // error
- fl_alert("Can't seem to close editor of file: %s\n"
- "waitpid() returned: %s\n"
- "Please close editor and hit OK",
- filename(), strerror(errno));
- continue;
- case 0: // process still running
- if ( ++wcount > 3 ) { // retry 3x with 1/10th delay before showing dialog
- fl_alert("Can't seem to close editor of file: %s\n"
- "Please close editor and hit OK", filename());
- }
- continue;
- case 1: // process reaped (reap_editor() sets pid_ to -1)
- if ( G_debug )
- printf("*** REAPED KILLED EXTERNAL EDITOR: PID %ld\n", (long)pid_reaped);
- break;
- }
- }
- return;
-}
-
-/**
- Handle if file changed since last check, and update records if so.
-
- Load new data into 'code', which caller must free().
- If 'force' set, forces reload even if file size/time didn't change.
-
- \param[in] code
- \param[in] force
- \return 0 if file unchanged or not editing
- \return 1 if file changed, internal records updated, 'code' has new content
- \return -1 error getting file info (strerror() has reason)
-*/
-int ExternalCodeEditor::handle_changes(const char **code, int force) {
- code[0] = 0;
- if ( !is_editing() ) return 0;
- // Get current time/size info, see if file changed
- int changed = 0;
- {
- struct stat sbuf;
- if ( stat(filename(), &sbuf) < 0 ) return(-1); // TODO: show fl_alert(), do this in win32 too, adjust func call docs above
- time_t now_mtime = sbuf.st_mtime;
- size_t now_size = sbuf.st_size;
- // OK, now see if file changed; update records if so
- if ( now_mtime != file_mtime_ ) { changed = 1; file_mtime_ = now_mtime; }
- if ( now_size != file_size_ ) { changed = 1; file_size_ = now_size; }
- }
- // No changes? done
- if ( !changed && !force ) return 0;
- // Changes? Load file, and fallthru to close()
- int fd = open(filename(), O_RDONLY);
- if ( fd < 0 ) {
- fl_alert("ERROR: can't open '%s': %s", filename(), strerror(errno));
- return -1;
- }
- int ret = 0;
- char *buf = (char*)malloc(file_size_ + 1);
- ssize_t count = read(fd, buf, file_size_);
- if ( count == -1 ) {
- fl_alert("ERROR: read() %s: %s", filename(), strerror(errno));
- free((void*)buf);
- ret = -1;
- } else if ( (long)count != (long)file_size_ ) {
- fl_alert("ERROR: read() failed for %s:\n"
- "expected %ld bytes, only got %ld",
- filename(), long(file_size_), long(count));
- ret = -1;
- } else {
- // Success -- file loaded OK
- buf[count] = '\0';
- code[0] = buf; // return pointer to allocated buffer
- ret = 1;
- }
- close(fd);
- return ret;
-}
-
-/**
- Remove the tmp file (if it exists), and zero out filename/mtime/size.
- \return -1 on error (dialog is posted as to why)
- \return 0 no file to remove
- \return 1 -- file was removed
- */
-int ExternalCodeEditor::remove_tmpfile() {
- const char *tmpfile = filename();
- if ( !tmpfile ) return 0;
- // Filename set? remove (if exists) and zero filename/mtime/size
- if ( is_file(tmpfile) ) {
- if ( G_debug ) printf("Removing tmpfile '%s'\n", tmpfile);
- if ( remove(tmpfile) < 0 ) {
- fl_alert("WARNING: Can't remove() '%s': %s", tmpfile, strerror(errno));
- return -1;
- }
- }
- set_filename(0);
- file_mtime_ = 0;
- file_size_ = 0;
- return 1;
-}
-
-/**
- Return tmpdir name for this fluid instance.
- \return pointer to static memory.
- */
-const char* ExternalCodeEditor::tmpdir_name() {
- static char dirname[100];
- snprintf(dirname, sizeof(dirname), "/tmp/.fluid-%ld", (long)getpid());
- return dirname;
-}
-
-/**
- Clear the external editor's tempdir.
- Static so that the main program can call it on exit to clean up.
- */
-void ExternalCodeEditor::tmpdir_clear() {
- const char *tmpdir = tmpdir_name();
- if ( is_dir(tmpdir) ) {
- if ( G_debug ) printf("Removing tmpdir '%s'\n", tmpdir);
- if ( rmdir(tmpdir) < 0 ) {
- fl_alert("WARNING: Can't rmdir() '%s': %s", tmpdir, strerror(errno));
- }
- }
-}
-
-/**
- Creates temp dir (if doesn't exist) and returns the dirname
- as a static string.
- \return NULL on error, dialog shows reason.
- */
-const char* ExternalCodeEditor::create_tmpdir() {
- const char *dirname = tmpdir_name();
- if ( ! is_dir(dirname) ) {
- if ( mkdir(dirname, 0777) < 0 ) {
- fl_alert("can't create directory '%s': %s",
- dirname, strerror(errno));
- return NULL;
- }
- }
- return dirname;
-}
-
-/**
- Returns temp filename in static buffer.
- \return NULL if can't, posts dialog explaining why.
- */
-const char* ExternalCodeEditor::tmp_filename() {
- static char path[FL_PATH_MAX+1];
- const char *tmpdir = create_tmpdir();
- if ( !tmpdir ) return 0;
- const char *ext = g_project.code_file_name.c_str(); // e.g. ".cxx"
- snprintf(path, FL_PATH_MAX, "%s/%p%s", tmpdir, (void*)this, ext);
- path[FL_PATH_MAX] = 0;
- return path;
-}
-
-/**
- Save string 'code' to 'filename', returning file's mtime/size.
- 'code' can be NULL -- writes an empty file if so.
- \return 0 on success
- \return -1 on error (posts dialog with reason)
- */
-static int save_file(const char *filename, const char *code) {
- if ( code == 0 ) code = ""; // NULL? write an empty file
- int fd = open(filename, O_WRONLY|O_CREAT, 0666);
- if ( fd == -1 ) {
- fl_alert("ERROR: open() '%s': %s", filename, strerror(errno));
- return -1;
- }
- ssize_t clen = strlen(code);
- ssize_t count = write(fd, code, clen);
- int ret = 0;
- if ( count == -1 ) {
- fl_alert("ERROR: write() '%s': %s", filename, strerror(errno));
- ret = -1; // fallthru to close()
- } else if ( count != clen ) {
- fl_alert("ERROR: write() '%s': wrote only %lu bytes, expected %lu",
- filename, (unsigned long)count, (unsigned long)clen);
- ret = -1; // fallthru to close()
- }
- close(fd);
- return(ret);
-}
-
-/**
- Convert string 's' to array of argv[], useful for execve().
- - 's' will be modified (words will be NULL separated)
- - argv[] will end up pointing to the words of 's'
- - Caller must free argv with: free(argv);
- \return -1 in case of memory allocation error
- \return number of arguments in argv (same value as in argc)
- */
-static int make_args(char *s, // string containing words (gets trashed!)
- int *aargc, // pointer to argc
- char ***aargv) { // pointer to argv
- char *ss, **argv;
- if ((argv=(char**)malloc(sizeof(char*) * (strlen(s)/2)))==NULL) {
- return -1;
- }
- int t;
- for(t=0; (t==0)?(ss=strtok(s," \t")):(ss=strtok(0," \t")); t++) {
- argv[t] = ss;
- }
- argv[t] = 0;
- aargv[0] = argv;
- aargc[0] = t;
- return(t);
-}
-
-/**
- If no alert pipe is open yet, try to create the pipe and hook it up the the fd callback.
-
- The alert pipe is used to communicate from the forked process to the main
- FLTK app in case launching the editor failed.
- */
-void ExternalCodeEditor::open_alert_pipe() {
- if (!alert_pipe_open_) {
- if (::pipe(alert_pipe_) == 0) {
- Fl::add_fd(alert_pipe_[0], FL_READ, alert_pipe_cb, this);
- alert_pipe_open_ = true;
- } else {
- alert_pipe_[0] = alert_pipe_[1] = -1;
- }
- }
-}
-
-/**
- Start editor in background (fork/exec)
- \return 0 on success, leaves editor child process running as 'pid_'
- \return -1 on error, posts dialog with reason (child exits)
- */
-int ExternalCodeEditor::start_editor(const char *editor_cmd,
- const char *filename) {
- if ( G_debug ) printf("start_editor() cmd='%s', filename='%s'\n",
- editor_cmd, filename);
- char cmd[1024];
- snprintf(cmd, sizeof(cmd), "%s %s", editor_cmd, filename);
- command_line_ = editor_cmd;
- open_alert_pipe();
- // Fork editor to background..
- switch ( pid_ = fork() ) {
- case -1: // error
- fl_alert("couldn't fork(): %s", strerror(errno));
- return -1;
- case 0: { // child
- // NOTE: OSX wants minimal code between fork/exec, see Apple TN2083
- // NOTE: no FLTK calls after a fork. Use a pipe to tell the app if the
- // command can't launch
- int nargs;
- char **args = 0;
- if (make_args(cmd, &nargs, &args) > 0) {
- execvp(args[0], args); // run command - doesn't return if succeeds
- if (alert_pipe_open_) {
- int err = errno;
- if (::write(alert_pipe_[1], &err, sizeof(int)) != sizeof(int)) {
- // should not happen, but if it does, at least we tried
- }
- }
- exit(1);
- }
- exit(1);
- // break;
- }
- default: // parent
- if ( L_editors_open++ == 0 ) // first editor? start timers
- { start_update_timer(); }
- if ( G_debug )
- printf("--- EDITOR STARTED: pid_=%ld #open=%d\n", (long)pid_, L_editors_open);
- break;
- }
- return 0;
-}
-
-/**
- Try to reap external editor process.
-
- If 'pid_reaped' not NULL, returns PID of reaped editor.
-
- \return -2: editor not open
- \return -1: waitpid() failed (errno has reason)
- \return 0: process still running
- \return 1: process finished + reaped ('pid_reaped' has pid), pid_ set to -1.
- Handles removing tmpfile/zeroing file_mtime/file_size/filename
- \return If return value <=0, 'pid_reaped' is set to zero.
- */
-int ExternalCodeEditor::reap_editor(pid_t *pid_reaped) {
- if ( pid_reaped ) *pid_reaped = 0;
- if ( !is_editing() ) return -2;
- int status = 0;
- pid_t wpid;
- switch (wpid = waitpid(pid_, &status, WNOHANG)) {
- case -1: // waitpid() failed
- return -1;
- case 0: // process didn't reap, still running
- return 0;
- default: // process reaped
- if ( pid_reaped ) *pid_reaped = wpid; // return pid to caller
- remove_tmpfile(); // also zeroes mtime/size
- pid_ = -1;
- if ( --L_editors_open <= 0 )
- { stop_update_timer(); }
- break;
- }
- if ( G_debug )
- printf("*** EDITOR REAPED: pid=%ld #open=%d\n", long(wpid), L_editors_open);
- return 1;
-}
-
-/**
- Open external editor using 'editor_cmd' to edit 'code'.
-
- 'code' contains multiline code to be edited as a temp file.
- 'code' can be NULL -- edits an empty file if so.
-
- \return 0 if succeeds
- \return -1 if can't open editor (already open, etc),
- errors were shown to user in a dialog
- */
-int ExternalCodeEditor::open_editor(const char *editor_cmd,
- const char *code) {
- // Make sure a temp filename exists
- if ( !filename() ) {
- set_filename(tmp_filename());
- if ( !filename() ) return -1;
- }
- // See if tmpfile already exists or editor already open
- if ( is_file(filename()) ) {
- if ( is_editing() ) {
- // See if editor recently closed but not reaped; try to reap
- pid_t wpid;
- switch ( reap_editor(&wpid) ) {
- case -2: // no editor running? (unlikely if is_editing() true)
- break;
- case -1: // waitpid() failed
- fl_alert("ERROR: waitpid() failed: %s\nfile='%s', pid=%ld",
- strerror(errno), filename(), (long)pid_);
- return -1;
- case 0: // process still running
- fl_alert("Editor Already Open\n file='%s'\n pid=%ld",
- filename(), (long)pid_);
- return 0;
- case 1: // process reaped, wpid is pid reaped
- if ( G_debug )
- printf("*** REAPED EXTERNAL EDITOR: PID %ld\n", (long)wpid);
- break; // fall thru to open new editor instance
- }
- // Reinstate tmp filename (reap_editor() clears it)
- set_filename(tmp_filename());
- }
- }
- if ( save_file(filename(), code) < 0 ) {
- return -1; // errors were shown in dialog
- }
- // Update mtime/size from closed file
- struct stat sbuf;
- if ( stat(filename(), &sbuf) < 0 ) {
- fl_alert("ERROR: can't stat('%s'): %s", filename(), strerror(errno));
- return -1;
- }
- file_mtime_ = sbuf.st_mtime;
- file_size_ = sbuf.st_size;
- if ( start_editor(editor_cmd, filename()) < 0 ) { // open file in external editor
- if ( G_debug ) printf("Editor failed to start\n");
- return -1; // errors were shown in dialog
- }
- return 0;
-}
-
-/**
- Start update timer.
- */
-void ExternalCodeEditor::start_update_timer() {
- if ( !L_update_timer_cb ) return;
- if ( G_debug ) printf("--- TIMER: STARTING UPDATES\n");
- Fl::add_timeout(2.0, L_update_timer_cb);
-}
-
-/**
- Stop update timer.
- */
-void ExternalCodeEditor::stop_update_timer() {
- if ( !L_update_timer_cb ) return;
- if ( G_debug ) printf("--- TIMER: STOPPING UPDATES\n");
- Fl::remove_timeout(L_update_timer_cb);
-}
-
-/**
- Set app's external editor update timer callback.
-
- This is the app's callback callback we start while editors are open,
- and stop when all editors are closed.
- */
-void ExternalCodeEditor::set_update_timer_callback(Fl_Timeout_Handler cb) {
- L_update_timer_cb = cb;
-}
-
-/**
- See if any external editors are open.
- App's timer cb can see if any editors need checking..
- */
-int ExternalCodeEditor::editors_open() {
- return L_editors_open;
-}
-
-/**
- It the forked process can't run the editor, it will send the errno through a pipe.
- */
-void ExternalCodeEditor::alert_pipe_cb(FL_SOCKET s, void* d) {
- ExternalCodeEditor* self = (ExternalCodeEditor*)d;
- self->last_error_ = 0;
- if (::read(s, &self->last_error_, sizeof(int)) != sizeof(int))
- return;
- const char* cmd = self->command_line_.c_str();
- if (cmd && *cmd) {
- if (cmd[0] == '/') { // is this an absolute filename?
- fl_alert("Can't launch external editor '%s':\n%s\n\ncmd: \"%s\"",
- fl_filename_name(cmd), strerror(self->last_error_), cmd);
- } else {
- char pwd[FL_PATH_MAX+1];
- fl_getcwd(pwd, FL_PATH_MAX);
- fl_alert("Can't launch external editor '%s':\n%s\n\ncmd: \"%s\"\npwd: \"%s\"",
- fl_filename_name(cmd), strerror(self->last_error_), cmd, pwd);
- }
- }
-}