/* Chrysalide - Outil d'analyse de fichiers binaires * gcodebuffer.c - affichage d'un fragment de code d'assemblage * * Copyright (C) 2010-2014 Cyrille Bagard * * This file is part of Chrysalide. * * OpenIDA 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 3 of the License, or * (at your option) any later version. * * OpenIDA 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 Foobar. If not, see . */ #include "gcodebuffer.h" #include #include #include #include #include "chrysamarshal.h" #include "delayed-int.h" #include "../common/extstr.h" /* -------------------------- PARCOURS DU CODE D'UN TAMPON -------------------------- */ #define G_TYPE_BUFFER_SCAN g_buffer_scan_get_type() #define G_BUFFER_SCAN(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), g_buffer_scan_get_type(), GDelayedExport)) #define G_IS_BUFFER_SCAN(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), g_buffer_scan_get_type())) #define G_BUFFER_SCAN_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), G_TYPE_BUFFER_SCAN, GDelayedExportClass)) #define G_IS_BUFFER_SCAN_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), G_TYPE_BUFFER_SCAN)) #define G_BUFFER_SCAN_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), G_TYPE_BUFFER_SCAN, GDelayedExportClass)) /* Ensembles binaires à désassembler (instance) */ typedef struct _GBufferScan { GDelayedWork parent; /* A laisser en premier */ GCodeBuffer *buffer; /* Tampon à manipuler */ vmpa2t start; /* Début du parcours */ bool has_start; /* Validité des données #1 */ vmpa2t end; /* Fin du parcours */ bool has_end; /* Validité des données #2 */ char *message; /* Message de progression */ process_line_fc process; /* Fonction de traitement réel */ void *user_data; /* Données à faire suivre */ } GBufferScan; /* Ensembles binaires à désassembler (classe) */ typedef struct _GBufferScanClass { GDelayedWorkClass parent; /* A laisser en premier */ } GBufferScanClass; /* Indique le type défini pour les tâches d'exportation différée. */ static GType g_buffer_scan_get_type(void); /* Initialise la classe des tâches d'exportation différée. */ static void g_buffer_scan_class_init(GBufferScanClass *); /* Initialise une tâche d'exportation différée. */ static void g_buffer_scan_init(GBufferScan *); /* Supprime toutes les références externes. */ static void g_buffer_scan_dispose(GBufferScan *); /* Procède à la libération totale de la mémoire. */ static void g_buffer_scan_finalize(GBufferScan *); /* Crée une tâche d'exportation différée. */ static GBufferScan *g_buffer_scan_new(GCodeBuffer *, const vmpa2t *, const vmpa2t *, const char *, process_line_fc, void *); /* Assure l'exportation en différé. */ static void g_buffer_scan_process(GBufferScan *, GtkStatusStack *); /* -------------------------- TAMPON POUR CODE DESASSEMBLE -------------------------- */ /* Suivi distant des évolutions */ typedef struct _view_callback { buffer_size_changed_cb size_changed; /* Evolution de taille */ GObject *data; /* Données à associer */ } view_callback; /* Tampon pour code désassemblé (instance) */ struct _GCodeBuffer { GObject parent; /* A laisser en premier */ BufferLineColumn main_column; /* Colonne principale */ GBufferLine **lines; /* Liste des lignes intégrées */ size_t count; /* Quantité en cache */ size_t used; /* Quantité utilisée */ GWidthTracker *tracker; /* Suivi des largeurs */ size_t indent; /* Indentation des lignes */ view_callback *vcallbacks; /* Vues à mettre à jour */ size_t vcount; /* Quantité de ces vues */ }; /* Tampon pour code désassemblé (classe) */ struct _GCodeBufferClass { GObjectClass parent; /* A laisser en premier */ /* Signaux */ void (* line_changed) (GCodeBuffer *, GBufferLine *, line_segment *); }; /* Taille des allocations de masse */ #define LINE_ALLOC_BULK 20 /* Procède à l'initialisation d'une classe de tampon de code. */ static void g_code_buffer_class_init(GCodeBufferClass *); /* Procède à l'initialisation d'un tampon pour code désassemblé. */ static void g_code_buffer_init(GCodeBuffer *); /* Réagit à un changement de contenu d'une ligne donnée. */ static void on_line_content_change(GBufferLine *, line_segment *, GCodeBuffer *); /* Réagit à un changement de propriété rattachée à une ligne. */ static void on_line_flag_flip(GBufferLine *, BufferLineFlags, BufferLineFlags, GCodeBuffer *); /* Ajoute de nouvelles lignes à une position donnée. */ static void g_code_buffer_insert_lines_at(GCodeBuffer *, GBufferLine **, size_t, size_t); /* ------------------------- CONFORTS POUR LES COMMENTAIRES ------------------------- */ /* Affiche un commentaire sur une ligne de tampon donnée. */ static bool _g_code_buffer_write_inlined_comment(GCodeBuffer *, GBufferLine *, const char *, GObject *); /* Affiche un commentaire sur une ligne de tampon dédiée. */ static bool _g_code_buffer_write_comment_area(GCodeBuffer *, GBufferLine *, const char *, bool, GObject *); /* Retrouve la première ligne d'une zone de commentaire. */ static size_t g_code_buffer_find_first_line_comment(const GCodeBuffer *, size_t); /* Retrouve la dernière ligne d'une zone de commentaire. */ static size_t g_code_buffer_find_last_line_comment(const GCodeBuffer *, size_t); /* Supprime un commentaire existant. */ static bool _g_code_buffer_delete_lines_comment(GCodeBuffer *, GBufferLine *); /* ------------------------- SIGNAUX IMMEDIATS POUR UNE VUE ------------------------- */ /* Fait suivre une variation de la quantité de lignes du tampon. */ static void g_code_buffer_notify_size_changed(const GCodeBuffer *, bool, size_t, size_t); /* ---------------------------------------------------------------------------------- */ /* PARCOURS DU CODE D'UN TAMPON */ /* ---------------------------------------------------------------------------------- */ /* Indique le type défini pour les tâches d'exportation différée. */ G_DEFINE_TYPE(GBufferScan, g_buffer_scan, G_TYPE_DELAYED_WORK); /****************************************************************************** * * * Paramètres : klass = classe à initialiser. * * * * Description : Initialise la classe des tâches d'exportation différée. * * * * Retour : - * * * * Remarques : - * * * ******************************************************************************/ static void g_buffer_scan_class_init(GBufferScanClass *klass) { GObjectClass *object; /* Autre version de la classe */ GDelayedWorkClass *work; /* Version en classe parente */ object = G_OBJECT_CLASS(klass); object->dispose = (GObjectFinalizeFunc/* ! */)g_buffer_scan_dispose; object->finalize = (GObjectFinalizeFunc)g_buffer_scan_finalize; work = G_DELAYED_WORK_CLASS(klass); work->run = (run_task_fc)g_buffer_scan_process; } /****************************************************************************** * * * Paramètres : scan = instance à initialiser. * * * * Description : Initialise une tâche d'exportation différée. * * * * Retour : - * * * * Remarques : - * * * ******************************************************************************/ static void g_buffer_scan_init(GBufferScan *scan) { } /****************************************************************************** * * * Paramètres : scan = instance d'objet GLib à traiter. * * * * Description : Supprime toutes les références externes. * * * * Retour : - * * * * Remarques : - * * * ******************************************************************************/ static void g_buffer_scan_dispose(GBufferScan *scan) { g_object_unref(G_OBJECT(scan->buffer)); G_OBJECT_CLASS(g_buffer_scan_parent_class)->dispose(G_OBJECT(scan)); } /****************************************************************************** * * * Paramètres : scan = instance d'objet GLib à traiter. * * * * Description : Procède à la libération totale de la mémoire. * * * * Retour : - * * * * Remarques : - * * * ******************************************************************************/ static void g_buffer_scan_finalize(GBufferScan *scan) { free(scan->message); G_OBJECT_CLASS(g_buffer_scan_parent_class)->finalize(G_OBJECT(scan)); } /****************************************************************************** * * * Paramètres : buffer = tampon à manipuler. * * start = première adresse visée ou NULL. * * end = dernière adresse visée ou NULL. * * message = message à afficher lors de la progression. * * process = fonction assurant le traitement effectif. * * data = données utilisateur à faire suivre. * * * * Description : Crée une tâche d'exportation différée. * * * * Retour : Tâche créée. * * * * Remarques : - * * * ******************************************************************************/ static GBufferScan *g_buffer_scan_new(GCodeBuffer *buffer, const vmpa2t *start, const vmpa2t *end, const char *message, process_line_fc process, void *data) { GBufferScan *result; /* Tâche à retourner */ result = g_object_new(G_TYPE_BUFFER_SCAN, NULL); result->buffer = buffer; g_object_ref(G_OBJECT(buffer)); result->has_start = (start != NULL); if (result->has_start) copy_vmpa(&result->start, start); result->has_end = (end != NULL); if (result->has_end) copy_vmpa(&result->end, end); result->message = strdup(message); result->process = process; result->user_data = data; return result; } /****************************************************************************** * * * Paramètres : scan = parcours à mener. * * status = barre de statut à tenir informée. * * * * Description : Assure l'exportation en différé. * * * * Retour : - * * * * Remarques : - * * * ******************************************************************************/ static void g_buffer_scan_process(GBufferScan *scan, GtkStatusStack *status) { size_t first; /* Première ligne visée */ size_t last; /* Dernière ligne visée + 1 */ GBufferLine **lines; /* Liste des lignes à traiter */ //bstatus_id_t id; /* Identifiant de statut */ size_t i; /* Boucle de parcours */ /* TODO : lock scan->buffer->lines */ if (scan->has_start) first = g_code_buffer_get_index_from_address(scan->buffer, &scan->start, true); else first = 0; if (scan->has_end) last = g_code_buffer_get_index_from_address(scan->buffer, &scan->end, false); else last = scan->buffer->used - 1; lines = scan->buffer->lines; //id = gtk_extended_status_bar_push(statusbar, scan->message, true); if (scan->buffer->used > 0) for (i = first; i <= last; i++) { if (!scan->process(scan->buffer, lines[i], scan->user_data)) break; /* gtk_extended_status_bar_update_activity(statusbar, id, (i - first) * 1.0 / (last - first)); */ } /* TODO : unlock scan->buffer->lines */ //gtk_extended_status_bar_remove(statusbar, id); } /* ---------------------------------------------------------------------------------- */ /* TAMPON POUR CODE DESASSEMBLE */ /* ---------------------------------------------------------------------------------- */ /* Détermine le type du composant de tampon pour code désassemblé. */ G_DEFINE_TYPE(GCodeBuffer, g_code_buffer, G_TYPE_OBJECT); /****************************************************************************** * * * Paramètres : class = classe de composant GTK à initialiser. * * * * Description : Procède à l'initialisation d'une classe de tampon de code. * * * * Retour : - * * * * Remarques : - * * * ******************************************************************************/ static void g_code_buffer_class_init(GCodeBufferClass *class) { g_signal_new("line-changed", G_TYPE_CODE_BUFFER, G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET(GCodeBufferClass, line_changed), NULL, NULL, g_cclosure_user_marshal_VOID__OBJECT_OBJECT, G_TYPE_NONE, 2, G_TYPE_OBJECT, G_TYPE_OBJECT); } /****************************************************************************** * * * Paramètres : buffer = composant GTK à initialiser. * * * * Description : Procède à l'initialisation d'un tampon pour code désassemblé.* * * * Retour : - * * * * Remarques : - * * * ******************************************************************************/ static void g_code_buffer_init(GCodeBuffer *buffer) { buffer->tracker = g_width_tracker_new(buffer); } /****************************************************************************** * * * Paramètres : main = colonne à référencer comme étant la principale. * * * * Description : Crée un nouveau composant de tampon pour code désassemblé. * * * * Retour : Composant GTK créé. * * * * Remarques : - * * * ******************************************************************************/ GCodeBuffer *g_code_buffer_new(BufferLineColumn main) { GCodeBuffer *result; /* Composant à retourner */ result = g_object_new(G_TYPE_CODE_BUFFER, NULL); result->main_column = main; return result; } /****************************************************************************** * * * Paramètres : buffer = composant GLib à consulter. * * * * Description : Compte le nombre de lignes rassemblées dans un tampon. * * * * Retour : Nombre de lignes constituant le tampon. * * * * Remarques : - * * * ******************************************************************************/ size_t g_code_buffer_count_lines(const GCodeBuffer *buffer) { return buffer->used; } /****************************************************************************** * * * Paramètres : buffer = composant GLib à consulter. * * * * Description : Fournit un lien vers la structure de suivi de largeurs. * * * * Retour : Gestionnaire de largeurs de lignes. * * * * Remarques : - * * * ******************************************************************************/ const GWidthTracker *g_code_buffer_get_width_tracker(const GCodeBuffer *buffer) { return buffer->tracker; } /****************************************************************************** * * * Paramètres : buffer = composant GLib à consulter. * * range = emplacement où va se situer la ligne. * * * * Description : Initie une nouvelle ligne devant être insérée dans le tampon.* * * * Retour : Nouvelle ligne vierge à écrire. * * * * Remarques : - * * * ******************************************************************************/ GBufferLine *g_code_buffer_prepare_new_line(GCodeBuffer *buffer, const mrange_t *range) { GBufferLine *result; /* Instance à retourner */ size_t i; /* Boucle de parcours */ result = g_buffer_line_new(range, buffer->main_column); for (i = 0; i < buffer->indent; i++) g_buffer_line_append_text(result, BLC_ASSEMBLY_HEAD, " ", 4, RTT_RAW, NULL); return result; } /****************************************************************************** * * * Paramètres : line = ligne dont la définition vient d'évoluer. * * segment = éventuel segment qui vient d'évoluer ou NULL. * * buffer = tampon de lignes cohérentes à manipuler. * * * * Description : Réagit à un changement de contenu d'une ligne donnée. * * * * Retour : - * * * * Remarques : - * * * ******************************************************************************/ static void on_line_content_change(GBufferLine *line, line_segment *segment, GCodeBuffer *buffer) { g_signal_emit_by_name(buffer, "line-changed", line, segment); } /****************************************************************************** * * * Paramètres : line = ligne dont la définition vient d'évoluer. * * old = ancien groupe de propriétés associées. * * old = nouveau groupe de propriétés associées. * * buffer = tampon de lignes cohérentes à manipuler. * * * * Description : Réagit à un changement de propriété rattachée à une ligne. * * * * Retour : - * * * * Remarques : - * * * ******************************************************************************/ static void on_line_flag_flip(GBufferLine *line, BufferLineFlags old, BufferLineFlags new, GCodeBuffer *buffer) { g_signal_emit_by_name(buffer, "line-changed", line, NULL); } /****************************************************************************** * * * Paramètres : buffer = composant GLib à mettre à jour. * * lines = liste de lignes à insérer. * * count = taille de cette liste. * * index = point d'insertion de la première ligne. * * * * Description : Ajoute de nouvelles lignes à une position donnée. * * * * Retour : - * * * * Remarques : - * * * ******************************************************************************/ static void g_code_buffer_insert_lines_at(GCodeBuffer *buffer, GBufferLine **lines, size_t count, size_t index) { size_t i; /* Boucle de parcours */ /* Elaboration d'un espace suffisant */ if ((buffer->used + count) > buffer->count) { if (count > LINE_ALLOC_BULK) buffer->count += count; else buffer->count += LINE_ALLOC_BULK; buffer->lines = (GBufferLine **)realloc(buffer->lines, buffer->count * sizeof(GBufferLine *)); } /* Insertion des lignes */ if (index < buffer->used) { memmove(&buffer->lines[index + count], &buffer->lines[index], (buffer->used - index) * sizeof(GBufferLine *)); } buffer->used += count; for (i = 0; i < count; i++) { assert((index + i) < buffer->used); buffer->lines[index + i] = lines[i]; g_signal_connect(lines[i], "content-changed", G_CALLBACK(on_line_content_change), buffer); g_signal_connect(lines[i], "flip-flag", G_CALLBACK(on_line_flag_flip), buffer); } /* Recueil initial des largeurs */ g_width_tracker_update_added(buffer->tracker, index, count); g_code_buffer_notify_size_changed(buffer, true, index, count); } /****************************************************************************** * * * Paramètres : buffer = composant GLib à mettre à jour. * * line = lign à insérer à la fin du tampon. * * * * Description : Ajoute une nouvelle ligne en fin de tampon. * * * * Retour : - * * * * Remarques : - * * * ******************************************************************************/ void g_code_buffer_append_new_line(GCodeBuffer *buffer, GBufferLine *line) { g_code_buffer_insert_lines_at(buffer, (GBufferLine *[]) { line }, 1, buffer->used); } /****************************************************************************** * * * Paramètres : buffer = composant GLib à mettre à jour. * * lines = liste de lignes à insérer. * * count = taille de cette liste. * * point = point d'insertion du bloc de ligne. * * before = emplacement de l'insertion par rapport au point. * * * * Description : Ajoute de nouvelles lignes par rapport à une ligne donnée. * * * * Retour : Bilan : insertion réussie ou non ? * * * * Remarques : - * * * ******************************************************************************/ bool g_code_buffer_insert_lines(GCodeBuffer *buffer, GBufferLine **lines, size_t count, const GBufferLine *point, bool before) { bool result; /* Bilan à retourner */ size_t index; /* Indice d'insertion final */ result = false; index = g_code_buffer_find_index_by_line(buffer, point); if (index == buffer->used) goto gcbil_exit; if (!before) index++; g_code_buffer_insert_lines_at(buffer, lines, count, index); result = true; gcbil_exit: return result; } /****************************************************************************** * * * Paramètres : buffer = composant GLib à mettre à jour. * * start = première ligne devant être supprimée. * * end = dernière ligne devant être supprimée. * * * * Description : Supprime une ou plusieurs lignes du tampon indiqué. * * * * Retour : - * * * * Remarques : - * * * ******************************************************************************/ void g_code_buffer_delete_lines(GCodeBuffer *buffer, size_t start, size_t end) { size_t i; /* Boucle de parcours */ GBufferLine *line; /* Ligne en cours de traitement*/ assert(start < buffer->used); assert(end < buffer->used); for (i = start; i <= end; i++) { line = buffer->lines[i]; g_signal_handlers_disconnect_by_func(line, G_CALLBACK(on_line_content_change), buffer); g_signal_handlers_disconnect_by_func(line, G_CALLBACK(on_line_flag_flip), buffer); g_object_unref(G_OBJECT(line)); } if ((end + 1) < buffer->used) memmove(&buffer->lines[start], &buffer->lines[end + 1], (buffer->used - end - 1) * sizeof(GBufferLine *)); buffer->used -= (end - start + 1); g_width_tracker_update_deleted(buffer->tracker, start, end); g_code_buffer_notify_size_changed(buffer, false, start, end - start + 1); } /****************************************************************************** * * * Paramètres : buffer = composant GTK à mettre à jour. * * addr = adresse où retrouver la ligne recherchée. * * flags = propriétés à vérifier en tout ou partie. * * idx = indice de la ligne trouvée ou NULL. [OUT] * * * * Description : Retrouve une ligne au sein d'un tampon avec une adresse. * * * * Retour : Line retrouvée ou NULL en cas d'échec. * * * * Remarques : - * * * ******************************************************************************/ GBufferLine *g_code_buffer_find_line_by_addr(const GCodeBuffer *buffer, const vmpa2t *addr, BufferLineFlags flags, size_t *idx) { GBufferLine *result; /* Instance à retourner */ size_t index; /* Indice de la ligne visée */ index = g_code_buffer_get_index_from_address(buffer, addr, true); if (index == buffer->used) result = NULL; else { if (idx != NULL) *idx = index; result = buffer->lines[index]; if (flags != BLF_NONE) while ((g_buffer_line_get_flags(result) & flags) != flags) { if ((index + 1) == buffer->used) break; /* FIXME : vérifier que l'adresse est toujours celle recherchée ! */ if (idx != NULL) (*idx)++; result = buffer->lines[++index]; } g_object_ref(G_OBJECT(result)); } return result; } /****************************************************************************** * * * Paramètres : buffer = tampon de lignes à consulter. * * index = indice de la ligne recherchée. * * * * Description : Retrouve une ligne au sein d'un tampon avec un indice. * * * * Retour : Line retrouvée ou NULL en cas d'échec. * * * * Remarques : - * * * ******************************************************************************/ GBufferLine *g_code_buffer_find_line_by_index(const GCodeBuffer *buffer, size_t index) { GBufferLine *result; /* Ligne trouvée à retourner */ /* TODO : ref */ if (index < buffer->used) result = buffer->lines[index]; else result = NULL; return result; } /****************************************************************************** * * * Paramètres : buffer = composant GTK à mettre à jour. * * addr = adresse où va se situer la ligne. * * first = indique si on l'arrête à la première ou la dernière.* * * * Description : Convertit une adresse en indice de ligne. * * * * Retour : Indice de l'adresse trouvée, ou le nombre de lignes sinon. * * * * Remarques : - * * * ******************************************************************************/ size_t g_code_buffer_get_index_from_address(const GCodeBuffer *buffer, const vmpa2t *addr, bool first) { size_t result; /* Indice à retourner */ GBufferLine **found; /* Renvoi vers une trouvaille */ const mrange_t *range; /* Couverture d'une ligne */ /** * Si aucune adresse (ie. aucune limite ?) n'est précisée, on se base sur * la direction pour trouver le bon indice. */ if (addr == NULL) result = (first ? 0 : buffer->used - 1); /** * Sinon on parcourt méthodiquement toutes les lignes ! */ else { /* Recherche dichotomique grossière */ int cmp_addr_and_line(const vmpa2t *addr, const GBufferLine **line) { int status; /* Bilan d'une comparaison */ const mrange_t *lrange; /* Couverture d'une ligne */ lrange = g_buffer_line_get_range(*line); status = cmp_mrange_with_vmpa(lrange, addr); return status; } found = bsearch(addr, buffer->lines, buffer->used, sizeof(GBufferLine *), (__compar_fn_t)cmp_addr_and_line); /* Dernier raffinage pour approcher la cible réelle */ if (found == NULL) result = buffer->used; else { result = found - buffer->lines; if (first) for (; result > 0; result--) { range = g_buffer_line_get_range(buffer->lines[result - 1]); if (!mrange_contains_addr(range, addr)) break; } else for (; (result + 1) < buffer->used; result++) { range = g_buffer_line_get_range(buffer->lines[result + 1]); if (!mrange_contains_addr(range, addr)) break; } } } return result; } /****************************************************************************** * * * Paramètres : buffer = tampon de lignes à consulter. * * line = ligne dont l'indice est à retrouver. * * * * Description : Retrouve l'indice associé à une ligne au sein d'un tampon. * * * * Retour : Indice de l'adresse trouvée, ou le nombre de lignes sinon. * * * * Remarques : - * * * ******************************************************************************/ size_t g_code_buffer_find_index_by_line(const GCodeBuffer *buffer, const GBufferLine *line) { size_t result; /* Indice trouvé à retourner */ const mrange_t *range; /* Emplacement de la ligne */ const mrange_t *next; /* Emplacement suivant */ range = g_buffer_line_get_range(line); result = g_code_buffer_get_index_from_address(buffer, get_mrange_addr(range), true); /** * Comme plusieurs lignes consécutives peuvent avoir la même adresse, * on parcourt les lignes suivantes pour retrouver la ligne recherchée. */ if (result < buffer->used) { while (buffer->lines[result] != line) { if (++result == buffer->used) break; next = g_buffer_line_get_range(buffer->lines[result]); if (cmp_vmpa(get_mrange_addr(range), get_mrange_addr(next)) != 0) { result = buffer->used; break; } } } return result; } /****************************************************************************** * * * Paramètres : buffer = composant GTK à mettre à jour. * * * * Description : Augmente l'indentation des prochaines lignes. * * * * Retour : - * * * * Remarques : - * * * ******************************************************************************/ void g_code_buffer_inc_indentation(GCodeBuffer *buffer) { buffer->indent++; } /****************************************************************************** * * * Paramètres : buffer = composant GTK à mettre à jour. * * * * Description : Diminue l'indentation des prochaines lignes. * * * * Retour : - * * * * Remarques : - * * * ******************************************************************************/ void g_code_buffer_dec_indentation(GCodeBuffer *buffer) { /* BUG_ON(buffer->indent == 0) */ buffer->indent--; } /****************************************************************************** * * * Paramètres : buffer = tampon de données à utiliser. * * start = première adresse visée ou 0. * * end = dernière adresse visée ou VMPA_MAX. * * message = message à afficher lors de la progression. * * process = fonction assurant le traitement effectif. * * data = données utilisateur à faire suivre. * * * * Description : Lance un parcours des différentes lignes du tampon de code. * * * * Retour : - * * * * Remarques : - * * * ******************************************************************************/ GDelayedWork *g_buffer_code_scan(GCodeBuffer *buffer, const vmpa2t *start, const vmpa2t *end, const char *message, process_line_fc process, void *data) { GBufferScan *result; /* Procédure à créer / renvoyer*/ GWorkQueue *queue; /* Gestionnaire de différés */ result = g_buffer_scan_new(buffer, start, end, message, process, data); g_object_ref(G_OBJECT(result)); queue = get_work_queue(); g_work_queue_schedule_work(queue, G_DELAYED_WORK(result), DEFAULT_WORK_GROUP); return G_DELAYED_WORK(result); } /* ---------------------------------------------------------------------------------- */ /* CONFORTS POUR LES COMMENTAIRES */ /* ---------------------------------------------------------------------------------- */ /****************************************************************************** * * * Paramètres : buffer = tampon de lignes à consulter. * * line = ligne à l'intérieur d'un commentaire. * * comment = nouveau commentaire à inscrire à la ligne donnée. * * creator = créateur à l'origine de la construction. * * * * Description : Affiche un commentaire sur une ligne de tampon donnée. * * * * Retour : Bilan de l'opération : ajout ou non ? * * * * Remarques : - * * * ******************************************************************************/ static bool _g_code_buffer_write_inlined_comment(GCodeBuffer *buffer, GBufferLine *line, const char *comment, GObject *creator) { bool result; /* Bilan à retourner */ const mrange_t *range; /* Emplace de ligne à utiliser */ char *wcopy; /* Copie de travail */ GBufferLine **extra; /* Lignes supplémentaires */ size_t extra_count; /* Quantité de ces lignes */ char *saveptr; /* Sauvegarde pour la sécurité */ char *token; /* Fragment à insérer */ size_t len; /* Taille dudit fragment */ GBufferLine *new; /* Nouvelle ligne créée */ size_t i; /* Boucle de parcours */ assert(!g_buffer_line_has_comment(line)); result = false; range = g_buffer_line_get_range(line); wcopy = strdup(comment); extra = NULL; extra_count = 0; for (token = strtok_r(wcopy, COMMENT_LINE_SEP, &saveptr); token != NULL; token = strtok_r(NULL, COMMENT_LINE_SEP, &saveptr)) { len = strlen(token); if (!result) g_buffer_line_append_text(line, BLC_COMMENTS, token, len, RTT_COMMENT, creator); else { new = g_code_buffer_prepare_new_line(buffer, range); g_buffer_line_append_text(new, BLC_COMMENTS, token, len, RTT_COMMENT, creator); extra = (GBufferLine **)realloc(extra, ++extra_count * sizeof(GBufferLine *)); extra[extra_count - 1] = new; } result = true; } free(wcopy); if (extra_count > 0) { result &= g_code_buffer_insert_lines(buffer, extra, extra_count, line, false); if (!result) for (i = 0; i < extra_count; i++) g_object_unref(G_OBJECT(extra[i])); free(extra); } return result; } /****************************************************************************** * * * Paramètres : buffer = tampon de lignes à consulter. * * line = ligne à l'intérieur d'un commentaire. * * comment = nouveau commentaire à inscrire à la ligne donnée. * * creator = créateur à l'origine de la construction. * * * * Description : Affiche un commentaire sur une ligne de tampon donnée. * * * * Retour : Bilan de l'opération : ajout ou non ? * * * * Remarques : - * * * ******************************************************************************/ bool g_code_buffer_update_inlined_comment(GCodeBuffer *buffer, GBufferLine *line, const char *comment, GObject *creator) { bool result; /* Bilan à retourner */ if (g_buffer_line_has_comment(line)) result = _g_code_buffer_delete_lines_comment(buffer, line); else result = true; if (result) result = _g_code_buffer_write_inlined_comment(buffer, line, comment, creator); /* TODO : emit() */ return result; } /****************************************************************************** * * * Paramètres : buffer = tampon de lignes à consulter. * * line = ligne à l'intérieur d'un commentaire. * * comment = nouveau commentaire à inscrire à la ligne donnée. * * before = précise la position du commentaire. * * creator = créateur à l'origine de la construction. * * * * Description : Affiche un commentaire sur une ligne de tampon dédiée. * * * * Retour : Bilan de l'opération : ajout ou non ? * * * * Remarques : - * * * ******************************************************************************/ static bool _g_code_buffer_write_comment_area(GCodeBuffer *buffer, GBufferLine *line, const char *comment, bool before, GObject *creator) { bool result; /* Bilan à retourner */ const mrange_t *range; /* Emplace de ligne à utiliser */ char *wcopy; /* Copie de travail */ GBufferLine **extra; /* Lignes supplémentaires */ size_t extra_count; /* Quantité de ces lignes */ char *saveptr; /* Sauvegarde pour la sécurité */ char *token; /* Fragment à insérer */ size_t len; /* Taille dudit fragment */ GBufferLine *new; /* Nouvelle ligne créée */ size_t i; /* Boucle de parcours */ assert(!g_buffer_line_has_comment(line)); result = false; range = g_buffer_line_get_range(line); wcopy = strdup(comment); extra = NULL; extra_count = 0; for (token = strtok_r(wcopy, COMMENT_LINE_SEP, &saveptr); token != NULL; token = strtok_r(NULL, COMMENT_LINE_SEP, &saveptr)) { len = strlen(token); new = g_code_buffer_prepare_new_line(buffer, range); g_buffer_line_start_merge_at(new, BLC_DISPLAY); g_buffer_line_append_text(new, BLC_DISPLAY, token, len, RTT_COMMENT, creator); extra = (GBufferLine **)realloc(extra, ++extra_count * sizeof(GBufferLine *)); extra[extra_count - 1] = new; result = true; } free(wcopy); if (extra_count > 0) { result &= g_code_buffer_insert_lines(buffer, extra, extra_count, line, before); if (!result) for (i = 0; i < extra_count; i++) g_object_unref(G_OBJECT(extra[i])); free(extra); } return result; } /****************************************************************************** * * * Paramètres : buffer = tampon de lignes à consulter. * * line = ligne à l'intérieur d'un commentaire. * * comment = nouveau commentaire à inscrire à la ligne donnée. * * before = précise la position du commentaire. * * creator = créateur à l'origine de la construction. * * * * Description : Affiche un commentaire sur une ligne de tampon dédiée. * * * * Retour : Bilan de l'opération : ajout ou non ? * * * * Remarques : - * * * ******************************************************************************/ bool g_code_buffer_update_comment_area(GCodeBuffer *buffer, GBufferLine *line, const char *comment, bool before, GObject *creator) { bool result; /* Bilan à retourner */ if (g_buffer_line_has_comment(line)) result = _g_code_buffer_delete_lines_comment(buffer, line); else result = true; if (result) result = _g_code_buffer_write_comment_area(buffer, line, comment, before, creator); /* TODO : emit() */ return result; } /****************************************************************************** * * * Paramètres : buffer = tampon de lignes à consulter. * * index = indice de ligne à l'intérieur d'un commentaire. * * * * Description : Retrouve la première ligne d'une zone de commentaire. * * * * Retour : Indice de l'adresse trouvée, ou le nombre de lignes sinon. * * * * Remarques : - * * * ******************************************************************************/ static size_t g_code_buffer_find_first_line_comment(const GCodeBuffer *buffer, size_t index) { size_t result; /* Indice trouvé à retourner */ GBufferLine *prev; /* Ligne précédente */ assert(index < buffer->used); bool is_first_line_of_comment(const GBufferLine *ln, const GBufferLine *pv) { bool first; /* Statut à renvoyer */ BufferLineColumn merge_col; /* Colonne de fusion #1 */ BufferLineColumn prev_merge_col; /* Colonne de fusion #2 */ merge_col = g_buffer_line_get_merge_start(ln); /** * La ligne consultée contient toujours un commentaire. * * Deux cas de figures sont possibles ici : * * - soit du texte est présent dans la colonne "commentaires". * Si du texte est présent avant, alors il s'agit forcément de * la première (et unique ?) ligne de commentaire. * * - soit la ligne effectue une fusion des colonnes depuis BLC_DISPLAY. * Si la ligne qui précède fait de même, il s'agit alors d'une étiquette * ou de l'amont du commentaire. * */ if (g_buffer_line_has_text(ln, BLC_COMMENTS, BLC_COUNT)) { first = g_buffer_line_has_text(ln, BLC_DISPLAY, BLC_COMMENTS); if (!first) { /* La ligne d'avant doit avoir un commentaire ! */ first = !g_buffer_line_has_text(pv, BLC_COMMENTS, BLC_COUNT); } } else { /** * Le prologue "merge_col == BLC_FIRST" n'étant pas éditable, * la seule fusion possible ici est la suivante. */ assert(merge_col == BLC_DISPLAY); /** * La première ligne d'un tampon est toujours un prologue. */ assert(pv != NULL); prev_merge_col = g_buffer_line_get_merge_start(pv); first = (prev_merge_col != BLC_DISPLAY); if (!first) first = (g_buffer_line_get_flags(pv) & BLF_IS_LABEL); } return first; } for (result = index; result > 0; result--) { prev = (result > 0 ? buffer->lines[result - 1] : NULL); if (is_first_line_of_comment(buffer->lines[result], prev)) break; } if (result == 0) { if (!is_first_line_of_comment(buffer->lines[0], NULL)) result = buffer->used; } return result; } /****************************************************************************** * * * Paramètres : buffer = tampon de lignes à consulter. * * index = indice de ligne à l'intérieur d'un commentaire. * * * * Description : Retrouve la dernière ligne d'une zone de commentaire. * * * * Retour : Indice de l'adresse trouvée, ou le nombre de lignes sinon. * * * * Remarques : - * * * ******************************************************************************/ static size_t g_code_buffer_find_last_line_comment(const GCodeBuffer *buffer, size_t index) { size_t result; /* Indice trouvé à retourner */ GBufferLine *next; /* Ligne suivante */ assert(index < buffer->used); bool is_last_line_of_comment(const GBufferLine *ln, const GBufferLine *nx) { bool last; /* Statut à renvoyer */ BufferLineColumn merge_col; /* Colonne de fusion #1 */ BufferLineColumn next_merge_col; /* Colonne de fusion #2 */ merge_col = g_buffer_line_get_merge_start(ln); /** * La ligne consultée contient toujours un commentaire. * * Deux cas de figures sont possibles ici : * * - soit du texte est présent dans la colonne "commentaires". * Si la ligne suivante est similaire et si du texte est présent avant, * alors il s'agit forcément de d'un nouveau commentaire. S'il n'y a * aucun texte, il s'agit de la suite du commentaire. * * - soit la ligne effectue une fusion des colonnes depuis BLC_DISPLAY. * Si la ligne qui suit fait de même, il s'agit alors d'une étiquette * ou de l'aval du commentaire. * */ if (g_buffer_line_has_text(ln, BLC_COMMENTS, BLC_COUNT)) { last = !g_buffer_line_has_text(nx, BLC_COMMENTS, BLC_COUNT); if (!last) last = g_buffer_line_has_text(nx, BLC_DISPLAY, BLC_COMMENTS); } else { /** * Le prologue "merge_col == BLC_FIRST" n'étant pas éditable, * la seule fusion possible ici est la suivante. */ assert(merge_col == BLC_DISPLAY); if (nx == NULL) last = true; else { next_merge_col = g_buffer_line_get_merge_start(nx); last = (next_merge_col != BLC_DISPLAY); if (!last) last = (g_buffer_line_get_flags(nx) & BLF_IS_LABEL); } } return last; } for (result = index; result < buffer->used; result++) { next = ((result + 1) < buffer->used ? buffer->lines[result + 1] : NULL); if (is_last_line_of_comment(buffer->lines[result], next)) break; } return result; } /****************************************************************************** * * * Paramètres : buffer = tampon de lignes à consulter. * * line = ligne à l'intérieur d'un commentaire. * * * * Description : Retrouve le créateur d'un commentaire existant. * * * * Retour : Instance trouvée à déréférencer ensuite ou NULL si aucune. * * * * Remarques : - * * * ******************************************************************************/ GObject *g_code_buffer_get_comment_creator(const GCodeBuffer *buffer, const GBufferLine *line) { GObject *result; /* Instance à retourner */ BufferLineColumn merge_col; /* Colonne de fusion */ if (g_buffer_line_has_comment(line)) { merge_col = g_buffer_line_get_merge_start(line); if (merge_col == BLC_DISPLAY) result = g_buffer_line_find_first_segment_creator(line, BLC_DISPLAY); else result = g_buffer_line_find_first_segment_creator(line, BLC_COMMENTS); } else result = NULL; return result; } /****************************************************************************** * * * Paramètres : buffer = tampon de lignes à consulter. * * line = ligne à l'intérieur d'un commentaire. * * * * Description : Récupère le contenu d'un commentaire existant. * * * * Retour : Commentaire retrouver à libérer ou NULL en cas d'échec. * * * * Remarques : - * * * ******************************************************************************/ char *g_code_buffer_get_lines_comment(const GCodeBuffer *buffer, const GBufferLine *line) { char *result; /* Contenu à retourner */ size_t index; /* Indice de la ligne fournie */ size_t start; /* Ligne de départ */ size_t end; /* Ligne d'arrivée */ BufferLineColumn merge_col; /* Colonne de fusion */ size_t i; /* Boucle de parcours */ char *extra; /* Commentaire supplémentaire */ /* Pas de prologue ici ! */ assert(g_buffer_line_has_comment(line)); result = NULL; index = g_code_buffer_find_index_by_line(buffer, line); if (index == buffer->used) goto gcbglc_exit; start = g_code_buffer_find_first_line_comment(buffer, index); if (start == buffer->used) goto gcbglc_exit; end = g_code_buffer_find_last_line_comment(buffer, index); if (end == buffer->used) goto gcbglc_exit; merge_col = g_buffer_line_get_merge_start(line); for (i = start; i <= end; i++) { if (merge_col == BLC_DISPLAY) extra = g_buffer_line_get_text(buffer->lines[i], BLC_DISPLAY, BLC_COUNT, false); else extra = g_buffer_line_get_text(buffer->lines[i], BLC_COMMENTS, BLC_COUNT, false); assert(extra != NULL); if (result == NULL) result = extra; else { result = stradd(result, extra); free(extra); } } gcbglc_exit: return result; } /****************************************************************************** * * * Paramètres : buffer = tampon de lignes à modifier. * * line = ligne à l'intérieur d'un commentaire. * * * * Description : Supprime un commentaire existant. * * * * Retour : Bilan de l'opération. * * * * Remarques : - * * * ******************************************************************************/ static bool _g_code_buffer_delete_lines_comment(GCodeBuffer *buffer, GBufferLine *line) { bool result; /* Bilan à retourner */ size_t index; /* Indice de la ligne fournie */ size_t start; /* Ligne de départ */ size_t end; /* Ligne d'arrivée */ BufferLineColumn merge_col; /* Colonne de fusion */ /* Pas de prologue ici ! */ assert(g_buffer_line_has_comment(line)); result = false; index = g_code_buffer_find_index_by_line(buffer, line); if (index == buffer->used) goto gcbdlc_exit; start = g_code_buffer_find_first_line_comment(buffer, index); if (start == buffer->used) goto gcbdlc_exit; end = g_code_buffer_find_last_line_comment(buffer, index); if (end == buffer->used) goto gcbdlc_exit; result = true; merge_col = g_buffer_line_get_merge_start(line); if (merge_col == BLC_DISPLAY) g_code_buffer_delete_lines(buffer, start, end); else { g_buffer_line_delete_text(buffer->lines[start], BLC_COMMENTS, BLC_COUNT); if (end > start) g_code_buffer_delete_lines(buffer, start + 1, end); } gcbdlc_exit: return result; } /****************************************************************************** * * * Paramètres : buffer = tampon de lignes à modifier. * * line = ligne à l'intérieur d'un commentaire. * * * * Description : Supprime un commentaire existant. * * * * Retour : Bilan de l'opération. * * * * Remarques : - * * * ******************************************************************************/ bool g_code_buffer_delete_lines_comment(GCodeBuffer *buffer, GBufferLine *line) { bool result; /* Bilan à retourner */ result = _g_code_buffer_delete_lines_comment(buffer, line); /* TODO : emit() */ return result; } /* ---------------------------------------------------------------------------------- */ /* SIGNAUX IMMEDIATS POUR UNE VUE */ /* ---------------------------------------------------------------------------------- */ /****************************************************************************** * * * Paramètres : buffer = tampon de lignes à modifier. * * cb = fonction à appeler au moment opportun. * * data = object GLib à associer à l'appel. * * * * Description : Enregistre l'adresse d'une fonction de mise à jour de vue. * * * * Retour : - * * * * Remarques : - * * * ******************************************************************************/ void g_code_buffer_register_view_callback(GCodeBuffer *buffer, buffer_size_changed_cb cb, GObject *data) { view_callback *new; /* Informations sur l'appel */ buffer->vcount++; buffer->vcallbacks = (view_callback *)realloc(buffer->vcallbacks, buffer->vcount * sizeof(view_callback)); new = &buffer->vcallbacks[buffer->vcount - 1]; new->size_changed = cb; new->data = data; } /****************************************************************************** * * * Paramètres : buffer = tampon de lignes à modifier. * * data = object GLib à associer à l'appel. * * * * Description : Supprime un élément des vues à contacter pour mises à jour. * * * * Retour : - * * * * Remarques : - * * * ******************************************************************************/ void g_code_buffer_unregister_view_callback(GCodeBuffer *buffer, GObject *data) { size_t i; /* Boucle de parcours */ for (i = 0; i < buffer->vcount; i++) if (buffer->vcallbacks[i].data == data) { if ((i + 1) < buffer->vcount) memmove(&buffer->vcallbacks[i], &buffer->vcallbacks[i + 1], (buffer->vcount - i - 1) * sizeof(view_callback)); buffer->vcount--; buffer->vcallbacks = (view_callback *)realloc(buffer->vcallbacks, buffer->vcount * sizeof(view_callback)); } } /****************************************************************************** * * * Paramètres : buffer = tampon de lignes à diffuser. * * added = indication sur la variation de la taille du tampon. * * index = indice de la première ligne à traiter. * * count = nombre de lignes à traiter. * * * * Description : Fait suivre une variation de la quantité de lignes du tampon.* * * * Retour : - * * * * Remarques : - * * * ******************************************************************************/ static void g_code_buffer_notify_size_changed(const GCodeBuffer *buffer, bool added, size_t index, size_t count) { size_t i; /* Boucle de parcours */ view_callback *cb; /* Informations sur l'appel */ for (i = 0; i < buffer->vcount; i++) { cb = &buffer->vcallbacks[i]; cb->size_changed(buffer, added, index, count, cb->data); } }