FLUID: Refactors MergeBack
* moved functionality into its own files * refactored all methods to be less than a page * documented all calls * tested all situations I could think of
This commit is contained in:
parent
8663b86749
commit
0b408792c0
@ -38,6 +38,7 @@ set (CPPFILES
|
||||
file.cxx
|
||||
fluid_filename.cxx
|
||||
function_panel.cxx
|
||||
mergeback.cxx
|
||||
pixmaps.cxx
|
||||
shell_command.cxx
|
||||
sourceview_panel.cxx
|
||||
@ -71,6 +72,7 @@ set (HEADERFILES
|
||||
factory.h
|
||||
file.h
|
||||
function_panel.h
|
||||
mergeback.h
|
||||
print_panel.h
|
||||
pixmaps.h
|
||||
shell_command.h
|
||||
|
@ -24,6 +24,7 @@
|
||||
#include "code.h"
|
||||
#include "function_panel.h"
|
||||
#include "comments.h"
|
||||
#include "mergeback.h"
|
||||
|
||||
#include <FL/fl_string_functions.h>
|
||||
#include <FL/Fl_File_Chooser.H>
|
||||
@ -353,6 +354,7 @@ static bool fd_isspace(int c) {
|
||||
return (c>0 && c<128 && isspace(c));
|
||||
}
|
||||
|
||||
// code duplication: see int is_id(char c) in code.cxx
|
||||
static bool fd_iskeyword(int c) {
|
||||
return (c>0 && c<128 && (isalnum(c) || c=='_'));
|
||||
}
|
||||
|
@ -29,6 +29,7 @@
|
||||
#include "code.h"
|
||||
#include "Fluid_Image.h"
|
||||
#include "custom_widgets.h"
|
||||
#include "mergeback.h"
|
||||
|
||||
#include <FL/Fl.H>
|
||||
#include <FL/fl_message.H>
|
||||
|
@ -27,6 +27,7 @@
|
||||
#include "alignment_panel.h"
|
||||
#include "widget_panel.h"
|
||||
#include "undo.h"
|
||||
#include "mergeback.h"
|
||||
|
||||
#include <FL/Fl.H>
|
||||
#include <FL/Fl_Group.H>
|
||||
@ -2631,7 +2632,7 @@ void live_mode_cb(Fl_Button*o,void *) {
|
||||
}
|
||||
|
||||
// update the panel according to current widget set:
|
||||
static void load_panel() {
|
||||
void load_panel() {
|
||||
if (!the_panel) return;
|
||||
|
||||
// find all the Fl_Widget subclasses currently selected:
|
||||
|
@ -39,6 +39,7 @@ CPPFILES = \
|
||||
fluid.cxx \
|
||||
fluid_filename.cxx \
|
||||
function_panel.cxx \
|
||||
mergeback.cxx \
|
||||
pixmaps.cxx \
|
||||
shell_command.cxx \
|
||||
sourceview_panel.cxx \
|
||||
|
379
fluid/code.cxx
379
fluid/code.cxx
@ -33,7 +33,7 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "zlib.h"
|
||||
#include <zlib.h>
|
||||
|
||||
/// \defgroup cfile C Code File Operations
|
||||
/// \{
|
||||
@ -42,13 +42,19 @@
|
||||
/**
|
||||
Return true if c can be in a C identifier.
|
||||
I needed this so it is not messed up by locale settings.
|
||||
\param[in] c a character, or the start of a utf-8 sequence
|
||||
\return 1 if c is alphanumeric or '_'
|
||||
*/
|
||||
int is_id(char c) {
|
||||
return (c>='a' && c<='z') || (c>='A' && c<='Z') || (c>='0' && c<='9') || c=='_';
|
||||
}
|
||||
|
||||
/**
|
||||
Write a file that contains all label and tooltip strings for internationalisation.
|
||||
Write a file that contains all label and tooltip strings for internationalization.
|
||||
The user is responsible to set the right file name extension. The file format
|
||||
is determined by `g_project.i18n_type`.
|
||||
\param[in] filename file path and name to a file that will hold the strings
|
||||
\return 1 if the file could not be opened for writing, or the result of `fclose`.
|
||||
*/
|
||||
int write_strings(const Fl_String &filename) {
|
||||
Fl_Type *p;
|
||||
@ -175,6 +181,7 @@ int write_strings(const Fl_String &filename) {
|
||||
////////////////////////////////////////////////////////////////
|
||||
// Generate unique but human-readable identifiers:
|
||||
|
||||
/** A binary searchable tree storing identifiers for quick retrieval. */
|
||||
struct Fd_Identifier_Tree {
|
||||
char* text;
|
||||
void* object;
|
||||
@ -286,6 +293,7 @@ const char *Fd_Code_Writer::indent_plus(int offset) {
|
||||
// the header file. This is done by keeping a binary tree of all
|
||||
// the calls so far and not printing it if it is in the tree.
|
||||
|
||||
/** A binary searchable tree storing text for quick retrieval. */
|
||||
struct Fd_Text_Tree {
|
||||
char *text;
|
||||
Fd_Text_Tree *left, *right;
|
||||
@ -302,6 +310,7 @@ Fd_Text_Tree::~Fd_Text_Tree() {
|
||||
delete right;
|
||||
}
|
||||
|
||||
/** A binary searchable tree storing pointers for quick retrieval. */
|
||||
struct Fd_Pointer_Tree {
|
||||
void *ptr;
|
||||
Fd_Pointer_Tree *left, *right;
|
||||
@ -319,7 +328,9 @@ Fd_Pointer_Tree::~Fd_Pointer_Tree() {
|
||||
|
||||
/**
|
||||
Print a formatted line to the header file, unless the same line was produced before in this header file.
|
||||
\note Resulting line is cropped at 1023 bytes.
|
||||
\param[in] format printf-style formatting text, followed by a vararg list
|
||||
\return 1 if the text was written to the file, 0 if it was previously written.
|
||||
*/
|
||||
int Fd_Code_Writer::write_h_once(const char *format, ...) {
|
||||
va_list args;
|
||||
@ -341,7 +352,9 @@ int Fd_Code_Writer::write_h_once(const char *format, ...) {
|
||||
|
||||
/**
|
||||
Print a formatted line to the source file, unless the same line was produced before in this code file.
|
||||
\note Resulting line is cropped at 1023 bytes.
|
||||
\param[in] format printf-style formatting text, followed by a vararg list
|
||||
\return 1 if the text was written to the file, 0 if it was previously written.
|
||||
*/
|
||||
int Fd_Code_Writer::write_c_once(const char *format, ...) {
|
||||
va_list args;
|
||||
@ -371,6 +384,8 @@ int Fd_Code_Writer::write_c_once(const char *format, ...) {
|
||||
/**
|
||||
Return true if this pointer was already included in the code file.
|
||||
If it was not, add it to the list and return false.
|
||||
\param[in] pp ay pointer
|
||||
\return true if found in the tree, false if added to the tree
|
||||
*/
|
||||
bool Fd_Code_Writer::c_contains(void *pp) {
|
||||
Fd_Pointer_Tree **p = &ptr_in_code;
|
||||
@ -506,6 +521,9 @@ void Fd_Code_Writer::write_cstring(const char *s) {
|
||||
Write an array of C binary data (does not add a null).
|
||||
The output is bracketed in { and }. The content is written
|
||||
as decimal bytes, i.e. `{ 1, 2, 200 }`
|
||||
|
||||
\param[in] s a block of binary data, interpreted as unsigned bytes
|
||||
\param[in] length size of the block in bytes
|
||||
*/
|
||||
void Fd_Code_Writer::write_cdata(const char *s, int length) {
|
||||
if (varused_test) {
|
||||
@ -615,8 +633,11 @@ void Fd_Code_Writer::write_hc(const char *indent, int n, const char* c, const ch
|
||||
/**
|
||||
Write one or more lines of code, indenting each one of them.
|
||||
\param[in] textlines one or more lines of text, separated by \\n
|
||||
\param[in] inIndent increment indentation by this amount
|
||||
\param[in] inTrailWith append this character if the last line did not end with
|
||||
a newline, usually 0 or newline.
|
||||
*/
|
||||
void Fd_Code_Writer::write_c_indented(const char *textlines, int inIndent, char inTrailwWith) {
|
||||
void Fd_Code_Writer::write_c_indented(const char *textlines, int inIndent, char inTrailWith) {
|
||||
if (textlines) {
|
||||
indentation += inIndent;
|
||||
for (;;) {
|
||||
@ -638,8 +659,8 @@ void Fd_Code_Writer::write_c_indented(const char *textlines, int inIndent, char
|
||||
if (newline) {
|
||||
write_c("\n");
|
||||
} else {
|
||||
if (inTrailwWith)
|
||||
write_c("%c", inTrailwWith);
|
||||
if (inTrailWith)
|
||||
write_c("%c", inTrailWith);
|
||||
break;
|
||||
}
|
||||
textlines = newline+1;
|
||||
@ -651,9 +672,11 @@ void Fd_Code_Writer::write_c_indented(const char *textlines, int inIndent, char
|
||||
|
||||
/**
|
||||
Recursively dump code, putting children between the two parts of the parent code.
|
||||
\param[in] p write this type and all its children
|
||||
\return pointer to the next sibling
|
||||
*/
|
||||
Fl_Type* Fd_Code_Writer::write_code(Fl_Type* p) {
|
||||
// write all code that come before the children code
|
||||
// write all code that comes before the children code
|
||||
// (but don't write the last comment until the very end)
|
||||
if (!(p==Fl_Type::last && p->is_a(ID_Comment))) {
|
||||
if (write_sourceview) p->code1_start = (int)ftell(code_file);
|
||||
@ -712,6 +735,8 @@ Fl_Type* Fd_Code_Writer::write_code(Fl_Type* p) {
|
||||
|
||||
If the files already exist, they will be overwritten.
|
||||
|
||||
\note There is no true error checking here.
|
||||
|
||||
\param[in] s filename of source code file
|
||||
\param[in] t filename of the header file
|
||||
\return 0 if the operation failed, 1 if it was successful
|
||||
@ -881,6 +906,7 @@ int Fd_Code_Writer::write_code(const char *s, const char *t, bool to_sourceview)
|
||||
/**
|
||||
Write the public/private/protected keywords inside the class.
|
||||
This avoids repeating these words if the mode is already set.
|
||||
\param[in] state 0 for private, 1 for public, 2 for protected
|
||||
*/
|
||||
void Fd_Code_Writer::write_public(int state) {
|
||||
if (!current_class && !current_widget_class) return;
|
||||
@ -895,7 +921,9 @@ void Fd_Code_Writer::write_public(int state) {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
Create and initialize a new C++ source code writer.
|
||||
*/
|
||||
Fd_Code_Writer::Fd_Code_Writer()
|
||||
: code_file(NULL),
|
||||
header_file(NULL),
|
||||
@ -904,9 +932,9 @@ Fd_Code_Writer::Fd_Code_Writer()
|
||||
text_in_code(NULL),
|
||||
ptr_in_code(NULL),
|
||||
block_crc_(0),
|
||||
block_line_start_(true),
|
||||
block_buffer_(NULL),
|
||||
block_buffer_size_(0),
|
||||
block_line_start_(true),
|
||||
indentation(0),
|
||||
write_sourceview(false),
|
||||
varused_test(0),
|
||||
@ -915,6 +943,9 @@ Fd_Code_Writer::Fd_Code_Writer()
|
||||
block_crc_ = crc32(0, NULL, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
Release all resources.
|
||||
*/
|
||||
Fd_Code_Writer::~Fd_Code_Writer()
|
||||
{
|
||||
delete id_root;
|
||||
@ -924,12 +955,31 @@ Fd_Code_Writer::~Fd_Code_Writer()
|
||||
if (block_buffer_) ::free(block_buffer_);
|
||||
}
|
||||
|
||||
/**
|
||||
Write a MergeBack tag as a separate line of C++ comment.
|
||||
The tag contains information about the type of tag that we are writing, a
|
||||
link back to the type using its unique id, and the CRC of all code written
|
||||
after the previous tag up to this point.
|
||||
\param[in] type FD_TAG_GENERIC, FD_TAG_CODE, FD_TAG_MENU_CALLBACK, or FD_TAG_WIDGET_CALLBACK
|
||||
\param[in] uid the unique id of the current type
|
||||
*/
|
||||
void Fd_Code_Writer::tag(int type, unsigned short uid) {
|
||||
if (g_project.write_mergeback_data)
|
||||
fprintf(code_file, "//~fl~%d~%04x~%08x~~\n", type, (int)uid, (unsigned int)block_crc_);
|
||||
block_crc_ = crc32(0, NULL, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
Static function to calculate the CRC32 of a block of C source code.
|
||||
Calculation of the CRC ignores leading whitespace in a line and all linefeed
|
||||
characters ('\\r').
|
||||
\param[in] data a pointer to the data block
|
||||
\param[in] n the size of the data in bytes, or -1 to use strlen()
|
||||
\param[in] in_crc add to this CRC, 0 by default to start a new block
|
||||
\param[inout] inout_line_start optional pointer to flag that determines
|
||||
if we are the start of a line, used to find leading whitespace
|
||||
\return the new CRC
|
||||
*/
|
||||
unsigned long Fd_Code_Writer::block_crc(const void *data, int n, unsigned long in_crc, bool *inout_line_start) {
|
||||
if (!data) return 0;
|
||||
if (n==-1) n = (int)strlen((const char*)data);
|
||||
@ -953,10 +1003,19 @@ unsigned long Fd_Code_Writer::block_crc(const void *data, int n, unsigned long i
|
||||
return in_crc;
|
||||
}
|
||||
|
||||
/** Add the following block of text to the CRC of this class.
|
||||
\param[in] data a pointer to the data block
|
||||
\param[in] n the size of the data in bytes, or -1 to use strlen()
|
||||
*/
|
||||
void Fd_Code_Writer::crc_add(const void *data, int n) {
|
||||
block_crc_ = block_crc(data, n, block_crc_, &block_line_start_);
|
||||
}
|
||||
|
||||
/** Write formatted text to the code file.
|
||||
If MergeBack is enabled, the CRC calculation is continued.
|
||||
\param[in] format printf style formatting string
|
||||
\return see fprintf(FILE *, *const char*, ...)
|
||||
*/
|
||||
int Fd_Code_Writer::crc_printf(const char *format, ...) {
|
||||
va_list args;
|
||||
va_start(args, format);
|
||||
@ -965,6 +1024,12 @@ int Fd_Code_Writer::crc_printf(const char *format, ...) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
/** Write formatted text to the code file.
|
||||
If MergeBack is enabled, the CRC calculation is continued.
|
||||
\param[in] format printf style formatting string
|
||||
\param[in] args list of arguments
|
||||
\return see fprintf(FILE *, *const char*, ...)
|
||||
*/
|
||||
int Fd_Code_Writer::crc_vprintf(const char *format, va_list args) {
|
||||
if (g_project.write_mergeback_data) {
|
||||
int n = vsnprintf(block_buffer_, block_buffer_size_, format, args);
|
||||
@ -981,6 +1046,11 @@ int Fd_Code_Writer::crc_vprintf(const char *format, va_list args) {
|
||||
}
|
||||
}
|
||||
|
||||
/** Write some text to the code file.
|
||||
If MergeBack is enabled, the CRC calculation is continued.
|
||||
\param[in] text any text, no requirements to end in a newline or such
|
||||
\return see fputs(const char*, FILE*)
|
||||
*/
|
||||
int Fd_Code_Writer::crc_puts(const char *text) {
|
||||
if (g_project.write_mergeback_data) {
|
||||
crc_add(text);
|
||||
@ -988,6 +1058,12 @@ int Fd_Code_Writer::crc_puts(const char *text) {
|
||||
return fputs(text, code_file);
|
||||
}
|
||||
|
||||
/** Write a single ASCII character to the code file.
|
||||
If MergeBack is enabled, the CRC calculation is continued.
|
||||
\note to wrote UTF8 characters, use Fd_Code_Writer::crc_puts(const char *text)
|
||||
\param[in] c any character between 0 and 127 inclusive
|
||||
\return see fputc(int, FILE*)
|
||||
*/
|
||||
int Fd_Code_Writer::crc_putc(int c) {
|
||||
if (g_project.write_mergeback_data) {
|
||||
uchar uc = (uchar)c;
|
||||
@ -996,292 +1072,5 @@ int Fd_Code_Writer::crc_putc(int c) {
|
||||
return fputc(c, code_file);
|
||||
}
|
||||
|
||||
extern Fl_Window *the_panel;
|
||||
|
||||
/** Remove the first two spaces at every line start.
|
||||
\param[inout] s block of C code
|
||||
*/
|
||||
static void unindent(char *s) {
|
||||
char *d = s;
|
||||
bool line_start = true;
|
||||
while (*s) {
|
||||
if (line_start) {
|
||||
if (*s>0 && isspace(*s)) s++;
|
||||
if (*s>0 && isspace(*s)) s++;
|
||||
line_start = false;
|
||||
}
|
||||
if (*s=='\r') s++;
|
||||
if (*s=='\n') line_start = true;
|
||||
*d++ = *s++;
|
||||
}
|
||||
*d = 0;
|
||||
}
|
||||
|
||||
static Fl_String unindent_block(FILE *f, long start, long end) {
|
||||
long bsize = end-start;
|
||||
long here = ::ftell(f);
|
||||
::fseek(f, start, SEEK_SET);
|
||||
char *block = (char*)::malloc(bsize+1);
|
||||
size_t n = ::fread(block, bsize, 1, f);
|
||||
if (n!=1)
|
||||
block[0] = 0; // read error
|
||||
else
|
||||
block[bsize] = 0;
|
||||
unindent(block);
|
||||
Fl_String str = block;
|
||||
::free(block);
|
||||
::fseek(f, here, SEEK_SET);
|
||||
return str;
|
||||
}
|
||||
|
||||
// TODO: add level of mergeback support to user settings
|
||||
// TODO: command line option for mergeback
|
||||
// TODO: automatic mergeback when a new project is loaded
|
||||
// TODO: automatic mergeback when FLUID becomes app in focus
|
||||
// NOTE: automatic mergeback on timer when file changes if app focus doesn't work
|
||||
// NOTE: we could also let the user edit comment blocks
|
||||
|
||||
/**
|
||||
Merge external changes in a source code file back into the current project.
|
||||
|
||||
This experimental function reads a source code file line by line. When it
|
||||
encounters a special tag in a line, the crc32 stored in the tag is compared
|
||||
to the crc32 that was calculated from the code lines since the previous tag.
|
||||
|
||||
If the crc's differ, the user has modified the source file externally, and the
|
||||
given block differs from the block as it was generated by FLUID. Depending on
|
||||
the block type, the user has modified the widget code (FD_TAG_GENERIC), which
|
||||
can not be transferred back into the project.
|
||||
|
||||
Modifications to code blocks and callbacks (CODE, CALLBACK) can be merged back
|
||||
into the project. Their corresponding Fl_Type is found using the unique
|
||||
node id that is part of the tag. The block is only merged back if the crc's
|
||||
from the project and from the edited block differ.
|
||||
|
||||
The caller must make sure that this code file was generated by the currently
|
||||
loaded project.
|
||||
|
||||
The user is informed in detailed dialogs what the function discovered and
|
||||
offered to merge or cancel if appropriate. Just in case this function is
|
||||
destructive, "undo" restores the state before a MergeBack.
|
||||
|
||||
Callers can set different task. FD_MERGEBACK_CHECK checks if there are any
|
||||
modifications in the code file and returns -1 if there was an error, or a
|
||||
bit field where bit 0 is set if internal structures were modified, bit 1 if
|
||||
code was changed, and bit 2 if modified blocks were found, but no Type node.
|
||||
Bit 3 is set, if code was changed in the code file *and* the project.
|
||||
|
||||
FD_MERGEBACK_INTERACTIVE checks for changes and presents a status dialog box
|
||||
to the user if there were conflicting changes or if a mergeback is possible,
|
||||
presenting the user the option to merge or cancel. Returns 0 if the project
|
||||
remains unchanged, and 1 if the user merged changes back. -1 is returned if an
|
||||
invalid tag was found.
|
||||
|
||||
FD_MERGEBACK_GO merges all changes back into the project without any
|
||||
interaction. Returns 0 if nothing changed, and 1 if it merged any changes back.
|
||||
|
||||
FD_MERGEBACK_GO_SAFE merges changes back only if there are no conflicts.
|
||||
Returns 0 if nothing changed, and 1 if it merged any changes back, and -1 if
|
||||
there were conflicts.
|
||||
|
||||
\note this function is currently part of Fd_Code_Writer to get easy access
|
||||
to our crc32 code that also wrote the code file originally.
|
||||
|
||||
\param[in] s path and filename of the source code file
|
||||
\param[in] task see above
|
||||
\return see above
|
||||
*/
|
||||
int Fd_Code_Writer::merge_back(const char *s, int task) {
|
||||
// nothing to be done if the mergeback option is disabled in the project
|
||||
if (!g_project.write_mergeback_data) return 0;
|
||||
|
||||
int ret = 0;
|
||||
bool changed = false;
|
||||
FILE *code = fl_fopen(s, "r");
|
||||
if (!code) return 0;
|
||||
int iter = 0;
|
||||
|
||||
for (iter = 0; ; ++iter) {
|
||||
int line_no = 0;
|
||||
long block_start = 0;
|
||||
long block_end = 0;
|
||||
int num_changed_code = 0;
|
||||
int num_changed_structure = 0;
|
||||
int num_uid_not_found = 0;
|
||||
int num_possible_override = 0;
|
||||
int tag_error = 0;
|
||||
if (task==FD_MERGEBACK_GO)
|
||||
undo_checkpoint();
|
||||
// NOTE: if we can get the CRC from the current callback, and it's the same
|
||||
// as the code file CRC, merging back is very safe.
|
||||
block_crc_ = crc32(0, NULL, 0);
|
||||
block_line_start_ = true;
|
||||
::fseek(code, 0, SEEK_SET);
|
||||
changed = false;
|
||||
for (;;) {
|
||||
char line[1024];
|
||||
if (fgets(line, 1023, code)==0) break;
|
||||
line_no++;
|
||||
const char *tag = strstr(line, "//~fl~");
|
||||
if (!tag) {
|
||||
crc_add(line);
|
||||
block_end = ::ftell(code);
|
||||
} else {
|
||||
int type = -1;
|
||||
int uid = 0;
|
||||
unsigned long crc = 0;
|
||||
int n = sscanf(tag, "//~fl~%d~%04x~%08lx~~", &type, &uid, &crc);
|
||||
if (n!=3 || type<0 || type>FD_TAG_LAST ) { tag_error = 1; break; }
|
||||
if (block_crc_ != crc) {
|
||||
if (task==FD_MERGEBACK_GO) {
|
||||
if (type==FD_TAG_MENU_CALLBACK || type==FD_TAG_WIDGET_CALLBACK) {
|
||||
Fl_Type *tp = Fl_Type::find_by_uid(uid);
|
||||
if (tp && tp->is_true_widget()) {
|
||||
Fl_String cb = tp->callback(); cb += "\n";
|
||||
unsigned long proj_crc = block_crc(cb.c_str());
|
||||
if (proj_crc!=block_crc_)
|
||||
tp->callback(unindent_block(code, block_start, block_end).c_str());
|
||||
changed = true;
|
||||
}
|
||||
} else if (type==FD_TAG_CODE) {
|
||||
Fl_Type *tp = Fl_Type::find_by_uid(uid);
|
||||
if (tp && tp->is_a(ID_Code)) {
|
||||
Fl_String cb = tp->name(); cb += "\n";
|
||||
unsigned long proj_crc = block_crc(cb.c_str());
|
||||
if (proj_crc!=block_crc_)
|
||||
tp->name(unindent_block(code, block_start, block_end).c_str());
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (type==FD_TAG_MENU_CALLBACK || type==FD_TAG_WIDGET_CALLBACK) {
|
||||
Fl_Type *tp = Fl_Type::find_by_uid(uid);
|
||||
if (tp && tp->is_true_widget()) {
|
||||
Fl_String cb = tp->callback(); cb += "\n";
|
||||
unsigned long proj_crc = block_crc(cb.c_str());
|
||||
// check if the code and project crc are the same, so this modification was already applied
|
||||
if (proj_crc!=block_crc_) {
|
||||
num_changed_code++;
|
||||
// check if the block change on the project side as well, so we may override changes
|
||||
if (proj_crc!=crc) {
|
||||
num_possible_override++;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
num_uid_not_found++;
|
||||
num_changed_code++;
|
||||
}
|
||||
} else if (type==FD_TAG_CODE) {
|
||||
Fl_Type *tp = Fl_Type::find_by_uid(uid);
|
||||
if (tp && tp->is_a(ID_Code)) {
|
||||
Fl_String code = tp->name(); code += "\n";
|
||||
unsigned long proj_crc = block_crc(code.c_str());
|
||||
// check if the code and project crc are the same, so this modification was already applied
|
||||
if (proj_crc!=block_crc_) {
|
||||
num_changed_code++;
|
||||
// check if the block change on the project side as well, so we may override changes
|
||||
if (proj_crc!=crc) {
|
||||
num_possible_override++;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
num_changed_code++;
|
||||
num_uid_not_found++;
|
||||
}
|
||||
} else if (type==FD_TAG_GENERIC) {
|
||||
num_changed_structure++;
|
||||
}
|
||||
}
|
||||
}
|
||||
// reset everything for the next block
|
||||
block_crc_ = crc32(0, NULL, 0);
|
||||
block_line_start_ = true;
|
||||
block_start = ::ftell(code);
|
||||
}
|
||||
}
|
||||
if (task==FD_MERGEBACK_CHECK) {
|
||||
if (tag_error) { ret = -1; break; }
|
||||
if (num_changed_structure) ret |= 1;
|
||||
if (num_changed_code) ret |= 2;
|
||||
if (num_uid_not_found) ret |= 4;
|
||||
if (num_possible_override) ret |= 8;
|
||||
break;
|
||||
} else if (task==FD_MERGEBACK_INTERACTIVE) {
|
||||
if (tag_error) {
|
||||
fl_message("MergeBack found an error in line %d while reading tags\n"
|
||||
"from the source code. Merging code back is not possible.", line_no);
|
||||
ret = -1;
|
||||
break;
|
||||
}
|
||||
if (!num_changed_code && !num_changed_structure) {
|
||||
ret = 0;
|
||||
break;
|
||||
}
|
||||
if (num_changed_structure && !num_changed_code) {
|
||||
fl_message("MergeBack found %d modifications in the project structure\n"
|
||||
"of the source code. These kind of changes can no be\n"
|
||||
"merged back and will be lost when the source code is\n"
|
||||
"generated again from the open project.", num_changed_structure);
|
||||
ret = -1;
|
||||
break;
|
||||
}
|
||||
Fl_String msg = "MergeBack found %1$d modifications in the source code.";
|
||||
if (num_possible_override)
|
||||
msg += "\n\nWARNING: %4$d of these modified blocks appear to also have\n"
|
||||
"changed in the project. Merging will override changes in\n"
|
||||
"the project with changes from the source code file.";
|
||||
if (num_uid_not_found)
|
||||
msg += "\n\nWARNING: for %2$d of these modifications no Type node\n"
|
||||
"can be found and these modification can't be merged back.";
|
||||
if (!num_possible_override && !num_uid_not_found)
|
||||
msg += "\nMerging these changes back appears to be safe.";
|
||||
|
||||
if (num_changed_structure)
|
||||
msg += "\n\nWARNING: %3$d modifications were found in the project\n"
|
||||
"structure. These kind of changes can no be merged back\n"
|
||||
"and will be lost when the source code is generated again\n"
|
||||
"from the open project.";
|
||||
|
||||
if (num_changed_code==num_uid_not_found) {
|
||||
fl_message(msg.c_str(),
|
||||
num_changed_code, num_uid_not_found,
|
||||
num_changed_structure, num_possible_override);
|
||||
ret = -1;
|
||||
break;
|
||||
} else {
|
||||
msg += "\n\nClick Cancel to abort the MergeBack operation.\n"
|
||||
"Click Merge to merge all code changes back into\n"
|
||||
"the open project.";
|
||||
int c = fl_choice(msg.c_str(), "Cancel", "Merge", NULL,
|
||||
num_changed_code, num_uid_not_found,
|
||||
num_changed_structure, num_possible_override);
|
||||
if (c==0) { ret = 1; break; }
|
||||
task = FD_MERGEBACK_GO;
|
||||
continue;
|
||||
}
|
||||
} else if (task==FD_MERGEBACK_GO) {
|
||||
if (changed) ret = 1;
|
||||
break;
|
||||
} else if (task==FD_MERGEBACK_GO_SAFE) {
|
||||
if (tag_error || num_changed_structure || num_possible_override) {
|
||||
ret = -1;
|
||||
break;
|
||||
}
|
||||
if (num_changed_code==0) {
|
||||
ret = 0;
|
||||
break;
|
||||
}
|
||||
task = FD_MERGEBACK_GO;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
fclose(code);
|
||||
if (changed) {
|
||||
set_modflag(1);
|
||||
if (the_panel) propagate_load(the_panel, LOAD);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
/// \}
|
||||
|
||||
|
40
fluid/code.h
40
fluid/code.h
@ -31,31 +31,32 @@ struct Fd_Pointer_Tree;
|
||||
int is_id(char c);
|
||||
int write_strings(const Fl_String &filename);
|
||||
|
||||
const int FD_TAG_GENERIC = 0;
|
||||
const int FD_TAG_CODE = 1;
|
||||
const int FD_TAG_MENU_CALLBACK = 2;
|
||||
const int FD_TAG_WIDGET_CALLBACK = 3;
|
||||
const int FD_TAG_LAST = 3;
|
||||
|
||||
const int FD_MERGEBACK_CHECK = 0;
|
||||
const int FD_MERGEBACK_INTERACTIVE = 1;
|
||||
const int FD_MERGEBACK_GO = 2;
|
||||
const int FD_MERGEBACK_GO_SAFE = 3;
|
||||
|
||||
class Fd_Code_Writer
|
||||
{
|
||||
protected:
|
||||
/// file pointer for the C++ code file
|
||||
FILE *code_file;
|
||||
/// file pointer for the C++ header file
|
||||
FILE *header_file;
|
||||
|
||||
/// tree of unique but human-readable identifiers
|
||||
Fd_Identifier_Tree* id_root;
|
||||
/// searchable text tree for text that is only written once to the header file
|
||||
Fd_Text_Tree *text_in_header;
|
||||
/// searchable text tree for text that is only written once to the code file
|
||||
Fd_Text_Tree *text_in_code;
|
||||
/// searchable tree for pointers that are only written once to the code file
|
||||
Fd_Pointer_Tree *ptr_in_code;
|
||||
|
||||
/// crc32 for blocks of text written to the code file
|
||||
unsigned long block_crc_;
|
||||
char *block_buffer_;
|
||||
int block_buffer_size_;
|
||||
/// if set, we are at the start of a line and can ignore leading spaces in crc
|
||||
bool block_line_start_;
|
||||
/// expanding buffer for vsnprintf
|
||||
char *block_buffer_;
|
||||
/// size of expanding buffer for vsnprintf
|
||||
int block_buffer_size_;
|
||||
|
||||
void crc_add(const void *data, int n=-1);
|
||||
int crc_printf(const char *format, ...);
|
||||
int crc_vprintf(const char *format, va_list args);
|
||||
@ -63,19 +64,25 @@ protected:
|
||||
int crc_putc(int c);
|
||||
|
||||
public:
|
||||
/// current level of source code indentation
|
||||
int indentation;
|
||||
/// set if we write abbreviated file for the source code previewer
|
||||
/// (disables binary data blocks, for example)
|
||||
bool write_sourceview;
|
||||
// silly thing to prevent declaring unused variables:
|
||||
// When this symbol is on, all attempts to write code don't write
|
||||
// anything, but set a variable if it looks like the variable "o" is used:
|
||||
/// silly thing to prevent declaring unused variables:
|
||||
/// When this symbol is on, all attempts to write code don't write
|
||||
/// anything, but set a variable if it looks like the variable "o" is used:
|
||||
int varused_test;
|
||||
/// set to 1 if varused_test found that a variable is actually used
|
||||
int varused;
|
||||
|
||||
public:
|
||||
Fd_Code_Writer();
|
||||
~Fd_Code_Writer();
|
||||
const char* unique_id(void* o, const char*, const char*, const char*);
|
||||
/// Increment source code indentation level.
|
||||
void indent_more() { indentation++; }
|
||||
/// Decrement source code indentation level.
|
||||
void indent_less() { indentation--; }
|
||||
const char *indent();
|
||||
const char *indent(int set);
|
||||
@ -97,7 +104,6 @@ public:
|
||||
void write_public(int state); // writes pubic:/private: as needed
|
||||
|
||||
void tag(int type, unsigned short uid);
|
||||
int merge_back(const char *s, int task);
|
||||
|
||||
static unsigned long block_crc(const void *data, int n=-1, unsigned long in_crc=0, bool *inout_line_start=NULL);
|
||||
};
|
||||
|
@ -27,6 +27,7 @@
|
||||
#include "undo.h"
|
||||
#include "file.h"
|
||||
#include "code.h"
|
||||
#include "mergeback.h"
|
||||
|
||||
#include "alignment_panel.h"
|
||||
#include "function_panel.h"
|
||||
@ -1284,13 +1285,9 @@ int mergeback_code_files()
|
||||
return 0;
|
||||
}
|
||||
|
||||
// -- generate the file names with absolute paths
|
||||
Fd_Code_Writer f;
|
||||
Fl_String code_filename = g_project.codefile_path() + g_project.codefile_name();
|
||||
|
||||
// -- write the code and header files
|
||||
if (!batch_mode) enter_project_dir();
|
||||
int c = f.merge_back(code_filename.c_str(), FD_MERGEBACK_INTERACTIVE);
|
||||
int c = merge_back(code_filename, FD_MERGEBACK_INTERACTIVE);
|
||||
if (!batch_mode) leave_project_dir();
|
||||
if (c==0) fl_message("MergeBack found no external modifications\n"
|
||||
"in the source code.");
|
||||
|
476
fluid/mergeback.cxx
Normal file
476
fluid/mergeback.cxx
Normal file
@ -0,0 +1,476 @@
|
||||
//
|
||||
// Code output routines for the Fast Light Tool Kit (FLTK).
|
||||
//
|
||||
// Copyright 1998-2023 by Bill Spitzak and others.
|
||||
//
|
||||
// This library is free software. Distribution and use rights are outlined in
|
||||
// the file "COPYING" which should have been included with this file. If this
|
||||
// file is missing or damaged, see the license at:
|
||||
//
|
||||
// https://www.fltk.org/COPYING.php
|
||||
//
|
||||
// Please see the following page on how to report bugs and issues:
|
||||
//
|
||||
// https://www.fltk.org/bugs.php
|
||||
//
|
||||
|
||||
#include "mergeback.h"
|
||||
|
||||
#include "fluid.h"
|
||||
#include "code.h"
|
||||
#include "undo.h"
|
||||
#include "Fl_Function_Type.h"
|
||||
#include "Fl_Widget_Type.h"
|
||||
|
||||
#include <FL/Fl_Window.H>
|
||||
#include <FL/fl_ask.H>
|
||||
|
||||
#include <stdarg.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <ctype.h>
|
||||
#include <zlib.h>
|
||||
|
||||
extern void propagate_load(Fl_Group*, void*);
|
||||
extern void load_panel();
|
||||
extern void redraw_browser();
|
||||
|
||||
// TODO: add application user setting to control mergeback
|
||||
// [] new projects default to mergeback
|
||||
// [] check mergeback when loading project
|
||||
// [] check mergeback when app gets focus
|
||||
// [] always apply if safe
|
||||
// TODO: command line option for mergeback
|
||||
// -mb or --merge-back
|
||||
// -mbs or --merge-back-if-safe
|
||||
// NOTE: automatic mergeback on timer when file changes if app focus doesn't work
|
||||
// NOTE: allow the user to edit comment blocks
|
||||
|
||||
/**
|
||||
Merge external changes in a source code file back into the current project.
|
||||
|
||||
This experimental function reads a source code file line by line. When it
|
||||
encounters a special tag in a line, the crc32 stored in the tag is compared
|
||||
to the crc32 that was calculated from the code lines since the previous tag.
|
||||
|
||||
If the crc's differ, the user has modified the source file externally, and the
|
||||
given block differs from the block as it was generated by FLUID. Depending on
|
||||
the block type, the user has modified the widget code (FD_TAG_GENERIC), which
|
||||
can not be transferred back into the project.
|
||||
|
||||
Modifications to code blocks and callbacks (CODE, CALLBACK) can be merged back
|
||||
into the project. Their corresponding Fl_Type is found using the unique
|
||||
node id that is part of the tag. The block is only merged back if the crc's
|
||||
from the project and from the edited block differ.
|
||||
|
||||
The caller must make sure that this code file was generated by the currently
|
||||
loaded project.
|
||||
|
||||
The user is informed in detailed dialogs what the function discovered and
|
||||
offered to merge or cancel if appropriate. Just in case this function is
|
||||
destructive, "undo" restores the state before a MergeBack.
|
||||
|
||||
Callers can set different task. FD_MERGEBACK_ANALYSE checks if there are any
|
||||
modifications in the code file and returns -1 if there was an error, or a
|
||||
bit field where bit 0 is set if internal structures were modified, bit 1 if
|
||||
code was changed, and bit 2 if modified blocks were found, but no Type node.
|
||||
Bit 3 is set, if code was changed in the code file *and* the project.
|
||||
|
||||
FD_MERGEBACK_INTERACTIVE checks for changes and presents a status dialog box
|
||||
to the user if there were conflicting changes or if a mergeback is possible,
|
||||
presenting the user the option to merge or cancel. Returns 0 if the project
|
||||
remains unchanged, and 1 if the user merged changes back. -1 is returned if an
|
||||
invalid tag was found.
|
||||
|
||||
FD_MERGEBACK_APPLY merges all changes back into the project without any
|
||||
interaction. Returns 0 if nothing changed, and 1 if it merged any changes back.
|
||||
|
||||
FD_MERGEBACK_APPLY_IF_SAFE merges changes back only if there are no conflicts.
|
||||
Returns 0 if nothing changed, and 1 if it merged any changes back, and -1 if
|
||||
there were conflicts.
|
||||
|
||||
\note this function is currently part of Fd_Code_Writer to get easy access
|
||||
to our crc32 code that also wrote the code file originally.
|
||||
|
||||
\param[in] s path and filename of the source code file
|
||||
\param[in] task see above
|
||||
\return see above
|
||||
*/
|
||||
int merge_back(const Fl_String &s, int task) {
|
||||
if (g_project.write_mergeback_data) {
|
||||
Fd_Mergeback mergeback;
|
||||
return mergeback.merge_back(s, task);
|
||||
} else {
|
||||
// nothing to be done if the mergeback option is disabled in the project
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/** Allocate and initialize MergeBack class. */
|
||||
Fd_Mergeback::Fd_Mergeback() :
|
||||
code(NULL),
|
||||
line_no(0),
|
||||
tag_error(0),
|
||||
num_changed_code(0),
|
||||
num_changed_structure(0),
|
||||
num_uid_not_found(0),
|
||||
num_possible_override(0)
|
||||
{
|
||||
}
|
||||
|
||||
/** Release allocated resources. */
|
||||
Fd_Mergeback::~Fd_Mergeback()
|
||||
{
|
||||
if (code) ::fclose(code);
|
||||
}
|
||||
|
||||
/** Remove the first two spaces at every line start.
|
||||
\param[inout] s block of C code
|
||||
*/
|
||||
void Fd_Mergeback::unindent(char *s) {
|
||||
char *d = s;
|
||||
bool line_start = true;
|
||||
while (*s) {
|
||||
if (line_start) {
|
||||
if (*s>0 && isspace(*s)) s++;
|
||||
if (*s>0 && isspace(*s)) s++;
|
||||
line_start = false;
|
||||
}
|
||||
if (*s=='\r') s++;
|
||||
if (*s=='\n') line_start = true;
|
||||
*d++ = *s++;
|
||||
}
|
||||
*d = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
Read a block of text from the source file and remove the leading two spaces in every line.
|
||||
\param[in] start start of the block within the file
|
||||
\param[in] end end of text within the file
|
||||
\return a string holding the text that was found in the file
|
||||
*/
|
||||
Fl_String Fd_Mergeback::read_and_unindent_block(long start, long end) {
|
||||
long bsize = end-start;
|
||||
long here = ::ftell(code);
|
||||
::fseek(code, start, SEEK_SET);
|
||||
char *block = (char*)::malloc(bsize+1);
|
||||
size_t n = ::fread(block, bsize, 1, code);
|
||||
if (n!=1)
|
||||
block[0] = 0; // read error
|
||||
else
|
||||
block[bsize] = 0;
|
||||
unindent(block);
|
||||
Fl_String str = block;
|
||||
::free(block);
|
||||
::fseek(code, here, SEEK_SET);
|
||||
return str;
|
||||
}
|
||||
|
||||
/** Tell user the results of our MergeBack analysis and pop up a dialog to give
|
||||
the user a choice to merge or cancel.
|
||||
\return 1 if the user wants to merge (choice dialog was shown)
|
||||
\return 0 if there is nothing to merge (no dialog was shown)
|
||||
\return -1 if the user wants to cancel or an error occurred or an issue was presented
|
||||
(message or choice dialog was shown)
|
||||
*/
|
||||
int Fd_Mergeback::ask_user_to_merge() {
|
||||
if (tag_error) {
|
||||
fl_message("MergeBack found an error in line %d while reading tags\n"
|
||||
"from the source code. Merging code back is not possible.", line_no);
|
||||
return -1;
|
||||
}
|
||||
if (!num_changed_code && !num_changed_structure) {
|
||||
return 0;
|
||||
}
|
||||
if (num_changed_structure && !num_changed_code) {
|
||||
fl_message("MergeBack found %d modifications in the project structure\n"
|
||||
"of the source code. These kind of changes can no be\n"
|
||||
"merged back and will be lost when the source code is\n"
|
||||
"generated again from the open project.", num_changed_structure);
|
||||
return -1;
|
||||
}
|
||||
Fl_String msg = "MergeBack found %1$d modifications in the source code.";
|
||||
if (num_possible_override)
|
||||
msg += "\n\nWARNING: %4$d of these modified blocks appear to also have\n"
|
||||
"changed in the project. Merging will override changes in\n"
|
||||
"the project with changes from the source code file.";
|
||||
if (num_uid_not_found)
|
||||
msg += "\n\nWARNING: for %2$d of these modifications no Type node\n"
|
||||
"can be found and these modification can't be merged back.";
|
||||
if (!num_possible_override && !num_uid_not_found)
|
||||
msg += "\nMerging these changes back appears to be safe.";
|
||||
|
||||
if (num_changed_structure)
|
||||
msg += "\n\nWARNING: %3$d modifications were found in the project\n"
|
||||
"structure. These kind of changes can no be merged back\n"
|
||||
"and will be lost when the source code is generated again\n"
|
||||
"from the open project.";
|
||||
|
||||
if (num_changed_code==num_uid_not_found) {
|
||||
fl_message(msg.c_str(),
|
||||
num_changed_code, num_uid_not_found,
|
||||
num_changed_structure, num_possible_override);
|
||||
return -1;
|
||||
} else {
|
||||
msg += "\n\nClick Cancel to abort the MergeBack operation.\n"
|
||||
"Click Merge to merge all code changes back into\n"
|
||||
"the open project.";
|
||||
int c = fl_choice(msg.c_str(), "Cancel", "Merge", NULL,
|
||||
num_changed_code, num_uid_not_found,
|
||||
num_changed_structure, num_possible_override);
|
||||
if (c==0) return -1;
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
/** Analyse the block and its corresponding widget callback.
|
||||
Return findings in num_changed_code, num_changed_code, and num_uid_not_found.
|
||||
*/
|
||||
void Fd_Mergeback::analyse_callback(unsigned long code_crc, unsigned long tag_crc, int uid) {
|
||||
Fl_Type *tp = Fl_Type::find_by_uid(uid);
|
||||
if (tp && tp->is_true_widget()) {
|
||||
Fl_String cb = tp->callback(); cb += "\n";
|
||||
unsigned long proj_crc = Fd_Code_Writer::block_crc(cb.c_str());
|
||||
// check if the code and project crc are the same, so this modification was already applied
|
||||
if (proj_crc!=code_crc) {
|
||||
num_changed_code++;
|
||||
// check if the block change on the project side as well, so we may override changes
|
||||
if (proj_crc!=tag_crc) {
|
||||
num_possible_override++;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
num_uid_not_found++;
|
||||
num_changed_code++;
|
||||
}
|
||||
}
|
||||
|
||||
/** Analyse the block and its corresponding Code Type.
|
||||
Return findings in num_changed_code, num_changed_code, and num_uid_not_found.
|
||||
*/
|
||||
void Fd_Mergeback::analyse_code(unsigned long code_crc, unsigned long tag_crc, int uid) {
|
||||
Fl_Type *tp = Fl_Type::find_by_uid(uid);
|
||||
if (tp && tp->is_a(ID_Code)) {
|
||||
Fl_String code = tp->name(); code += "\n";
|
||||
unsigned long proj_crc = Fd_Code_Writer::block_crc(code.c_str());
|
||||
// check if the code and project crc are the same, so this modification was already applied
|
||||
if (proj_crc!=code_crc) {
|
||||
num_changed_code++;
|
||||
// check if the block change on the project side as well, so we may override changes
|
||||
if (proj_crc!=tag_crc) {
|
||||
num_possible_override++;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
num_changed_code++;
|
||||
num_uid_not_found++;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/** Analyse the code file and return findings in class member variables.
|
||||
|
||||
The code file must be open for reading already.
|
||||
|
||||
* tag_error is set if a tag was found, but could not be read
|
||||
* line_no returns the line where an error occured
|
||||
* num_changed_code is set to the number of changed code blocks in the file.
|
||||
Code changes can be merged back to the project.
|
||||
* num_changed_structure is set to the number of structural changes.
|
||||
Structural changes outside of code blocks can not be read back.
|
||||
* num_uid_not_found number of blocks that were modified, but the corresponding
|
||||
type or widget can not be found in the project
|
||||
* num_possible_override number of blocks that were changed in the code file,
|
||||
but also were changed in the project.
|
||||
|
||||
\return -1 if reading a tag failed, otherwise 0
|
||||
*/
|
||||
int Fd_Mergeback::analyse() {
|
||||
// initialize local variables
|
||||
unsigned long code_crc = 0;
|
||||
bool line_start = true;
|
||||
char line[1024];
|
||||
// bail if the caller has not opened a file yet
|
||||
if (!code) return 0;
|
||||
// initialize member variables to return our findings
|
||||
line_no = 0;
|
||||
tag_error = 0;
|
||||
num_changed_code = 0;
|
||||
num_changed_structure = 0;
|
||||
num_uid_not_found = 0;
|
||||
num_possible_override = 0;
|
||||
code_crc = 0;
|
||||
// loop through all lines in the code file
|
||||
::fseek(code, 0, SEEK_SET);
|
||||
for (;;) {
|
||||
// get the next line until end of file
|
||||
if (fgets(line, 1023, code)==0) break;
|
||||
line_no++;
|
||||
const char *tag = strstr(line, "//~fl~");
|
||||
if (!tag) {
|
||||
// if this line has no tag, add the contents to the CRC and continue
|
||||
code_crc = Fd_Code_Writer::block_crc(line, -1, code_crc, &line_start);
|
||||
} else {
|
||||
// if this line has a tag, read all tag data
|
||||
int tag_type = -1, uid = 0;
|
||||
unsigned long tag_crc = 0;
|
||||
int n = sscanf(tag, "//~fl~%d~%04x~%08lx~~", &tag_type, &uid, &tag_crc);
|
||||
if (n!=3 || tag_type<0 || tag_type>FD_TAG_LAST ) { tag_error = 1; return -1; }
|
||||
if (code_crc != tag_crc) {
|
||||
switch (tag_type) {
|
||||
case FD_TAG_GENERIC:
|
||||
num_changed_structure++;
|
||||
break;
|
||||
case FD_TAG_MENU_CALLBACK:
|
||||
case FD_TAG_WIDGET_CALLBACK:
|
||||
analyse_callback(code_crc, tag_crc, uid);
|
||||
break;
|
||||
case FD_TAG_CODE:
|
||||
analyse_code(code_crc, tag_crc, uid);
|
||||
break;
|
||||
}
|
||||
}
|
||||
// reset everything for the next block
|
||||
code_crc = 0;
|
||||
line_start = true;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/** Apply callback mergebacks from the code file to the project.
|
||||
\return 1 if the project changed
|
||||
*/
|
||||
int Fd_Mergeback::apply_callback(long block_end, long block_start, unsigned long code_crc, int uid) {
|
||||
Fl_Type *tp = Fl_Type::find_by_uid(uid);
|
||||
if (tp && tp->is_true_widget()) {
|
||||
Fl_String cb = tp->callback(); cb += "\n";
|
||||
unsigned long project_crc = Fd_Code_Writer::block_crc(cb.c_str());
|
||||
if (project_crc!=code_crc) {
|
||||
tp->callback(read_and_unindent_block(block_start, block_end).c_str());
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/** Apply callback mergebacks from the code file to the project.
|
||||
\return 1 if the project changed
|
||||
*/
|
||||
int Fd_Mergeback::apply_code(long block_end, long block_start, unsigned long code_crc, int uid) {
|
||||
Fl_Type *tp = Fl_Type::find_by_uid(uid);
|
||||
if (tp && tp->is_a(ID_Code)) {
|
||||
Fl_String cb = tp->name(); cb += "\n";
|
||||
unsigned long project_crc = Fd_Code_Writer::block_crc(cb.c_str());
|
||||
if (project_crc!=code_crc) {
|
||||
tp->name(read_and_unindent_block(block_start, block_end).c_str());
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/** Apply all possible mergebacks from the code file to the project.
|
||||
The code file must be open for reading already.
|
||||
\return -1 if reading a tag failed, 0 if nothing changed, 1 if the project changed
|
||||
*/
|
||||
int Fd_Mergeback::apply() {
|
||||
// initialize local variables
|
||||
unsigned long code_crc = 0;
|
||||
bool line_start = true;
|
||||
char line[1024];
|
||||
int changed = 0;
|
||||
long block_start = 0;
|
||||
long block_end = 0;
|
||||
// bail if the caller has not opened a file yet
|
||||
if (!code) return 0;
|
||||
// initialize member variables to return our findings
|
||||
line_no = 0;
|
||||
tag_error = 0;
|
||||
code_crc = 0;
|
||||
// loop through all lines in the code file
|
||||
::fseek(code, 0, SEEK_SET);
|
||||
for (;;) {
|
||||
// get the next line until end of file
|
||||
if (fgets(line, 1023, code)==0) break;
|
||||
line_no++;
|
||||
const char *tag = strstr(line, "//~fl~");
|
||||
if (!tag) {
|
||||
// if this line has no tag, add the contents to the CRC and continue
|
||||
code_crc = Fd_Code_Writer::block_crc(line, -1, code_crc, &line_start);
|
||||
block_end = ::ftell(code);
|
||||
} else {
|
||||
// if this line has a tag, read all tag data
|
||||
int tag_type = -1, uid = 0;
|
||||
unsigned long tag_crc = 0;
|
||||
int n = sscanf(tag, "//~fl~%d~%04x~%08lx~~", &tag_type, &uid, &tag_crc);
|
||||
if (n!=3 || tag_type<0 || tag_type>FD_TAG_LAST ) { tag_error = 1; return -1; }
|
||||
if (code_crc != tag_crc) {
|
||||
if (tag_type==FD_TAG_MENU_CALLBACK || tag_type==FD_TAG_WIDGET_CALLBACK) {
|
||||
changed |= apply_callback(block_end, block_start, code_crc, uid);
|
||||
} else if (tag_type==FD_TAG_CODE) {
|
||||
changed |= apply_code(block_end, block_start, code_crc, uid);
|
||||
}
|
||||
}
|
||||
// reset everything for the next block
|
||||
code_crc = 0;
|
||||
line_start = true;
|
||||
block_start = ::ftell(code);
|
||||
}
|
||||
}
|
||||
return changed;
|
||||
}
|
||||
|
||||
/** Dispatch the MergeBack into analysis, interactive, or apply directly.
|
||||
\param[in] s source code filename and path
|
||||
\param[in] task one of FD_MERGEBACK_ANALYSE, FD_MERGEBACK_INTERACTIVE,
|
||||
FD_MERGEBACK_APPLY_IF_SAFE, or FD_MERGEBACK_APPLY
|
||||
\return see ::merge_back(const Fl_String &s, int task)
|
||||
*/
|
||||
int Fd_Mergeback::merge_back(const Fl_String &s, int task) {
|
||||
int ret = 0;
|
||||
code = fl_fopen(s.c_str(), "r");
|
||||
if (!code) return -1;
|
||||
do { // no actual loop, just make sure we close the code file
|
||||
if (task == FD_MERGEBACK_ANALYSE) {
|
||||
analyse();
|
||||
if (tag_error) {ret = -1; break; }
|
||||
if (num_changed_structure) ret |= 1;
|
||||
if (num_changed_code) ret |= 2;
|
||||
if (num_uid_not_found) ret |= 4;
|
||||
if (num_possible_override) ret |= 8;
|
||||
break;
|
||||
}
|
||||
if (task == FD_MERGEBACK_INTERACTIVE) {
|
||||
analyse();
|
||||
ret = ask_user_to_merge();
|
||||
if (ret != 1)
|
||||
return ret;
|
||||
task = FD_MERGEBACK_APPLY; // fall through
|
||||
}
|
||||
if (task == FD_MERGEBACK_APPLY_IF_SAFE) {
|
||||
analyse();
|
||||
if (tag_error || num_changed_structure || num_possible_override) {
|
||||
ret = -1;
|
||||
break;
|
||||
}
|
||||
if (num_changed_code==0) {
|
||||
ret = 0;
|
||||
break;
|
||||
}
|
||||
task = FD_MERGEBACK_APPLY; // fall through
|
||||
}
|
||||
if (task == FD_MERGEBACK_APPLY) {
|
||||
ret = apply();
|
||||
if (ret == 1) {
|
||||
set_modflag(1);
|
||||
redraw_browser();
|
||||
load_panel();
|
||||
}
|
||||
ret = 1; // avoid message box in caller
|
||||
}
|
||||
} while (0);
|
||||
fclose(code);
|
||||
code = NULL;
|
||||
return ret;
|
||||
}
|
77
fluid/mergeback.h
Normal file
77
fluid/mergeback.h
Normal file
@ -0,0 +1,77 @@
|
||||
//
|
||||
// MergeBack routines for the Fast Light Tool Kit (FLTK).
|
||||
//
|
||||
// Copyright 2023 by Bill Spitzak and others.
|
||||
//
|
||||
// This library is free software. Distribution and use rights are outlined in
|
||||
// the file "COPYING" which should have been included with this file. If this
|
||||
// file is missing or damaged, see the license at:
|
||||
//
|
||||
// https://www.fltk.org/COPYING.php
|
||||
//
|
||||
// Please see the following page on how to report bugs and issues:
|
||||
//
|
||||
// https://www.fltk.org/bugs.php
|
||||
//
|
||||
|
||||
#ifndef _FLUID_MERGEBACK_H
|
||||
#define _FLUID_MERGEBACK_H
|
||||
|
||||
#include <FL/fl_attr.h>
|
||||
|
||||
#include "../src/Fl_String.H"
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
const int FD_TAG_GENERIC = 0;
|
||||
const int FD_TAG_CODE = 1;
|
||||
const int FD_TAG_MENU_CALLBACK = 2;
|
||||
const int FD_TAG_WIDGET_CALLBACK = 3;
|
||||
const int FD_TAG_LAST = 3;
|
||||
|
||||
const int FD_MERGEBACK_ANALYSE = 0;
|
||||
const int FD_MERGEBACK_INTERACTIVE = 1;
|
||||
const int FD_MERGEBACK_APPLY = 2;
|
||||
const int FD_MERGEBACK_APPLY_IF_SAFE = 3;
|
||||
|
||||
/** Class that implements the MergeBack functionality.
|
||||
\see merge_back(const Fl_String &s, int task)
|
||||
*/
|
||||
class Fd_Mergeback
|
||||
{
|
||||
protected:
|
||||
/// Pointer to the C++ code file.
|
||||
FILE *code;
|
||||
/// Current line number in the C++ code file.
|
||||
int line_no;
|
||||
/// Set if there was an error reading a tag.
|
||||
int tag_error;
|
||||
/// Number of code blocks that were different than the CRC in their tag.
|
||||
int num_changed_code;
|
||||
/// Number of generic structure blocks that were different than the CRC in their tag.
|
||||
int num_changed_structure;
|
||||
/// Number of code block that were modified, but a type node by that uid was not found.
|
||||
int num_uid_not_found;
|
||||
/// Number of modified code block where the corresponding project block also changed.
|
||||
int num_possible_override;
|
||||
|
||||
void unindent(char *s);
|
||||
Fl_String read_and_unindent_block(long start, long end);
|
||||
void analyse_callback(unsigned long code_crc, unsigned long tag_crc, int uid);
|
||||
void analyse_code(unsigned long code_crc, unsigned long tag_crc, int uid);
|
||||
int apply_callback(long block_end, long block_start, unsigned long code_crc, int uid);
|
||||
int apply_code(long block_end, long block_start, unsigned long code_crc, int uid);
|
||||
|
||||
public:
|
||||
Fd_Mergeback();
|
||||
~Fd_Mergeback();
|
||||
int merge_back(const Fl_String &s, int task);
|
||||
int ask_user_to_merge();
|
||||
int analyse();
|
||||
int apply();
|
||||
};
|
||||
|
||||
extern int merge_back(const Fl_String &s, int task);
|
||||
|
||||
|
||||
#endif // _FLUID_MERGEBACK_H
|
Loading…
x
Reference in New Issue
Block a user