[gnome-builder] plugin: clang-format: format code with ClangFormat
- From: Christian Hergert <chergert src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gnome-builder] plugin: clang-format: format code with ClangFormat
- Date: Mon, 8 Nov 2021 21:18:27 +0000 (UTC)
commit 9cf4dd4edad79338a205541d5413e89a6b51a354
Author: Tomi Lähteenmäki <lihis lihis net>
Date: Sun Oct 17 09:03:35 2021 +0300
plugin: clang-format: format code with ClangFormat
Closes #399.
meson_options.txt | 1 +
src/plugins/clang-format/clang-format-plugin.c | 34 ++
.../clang-format/clang-format.gresource.xml | 6 +
src/plugins/clang-format/clang-format.plugin | 10 +
.../clang-format/gb-clang-format-buffer-addin.c | 355 +++++++++++++++++++++
.../clang-format/gb-clang-format-buffer-addin.h | 31 ++
src/plugins/clang-format/meson.build | 16 +
src/plugins/meson.build | 2 +
8 files changed, 455 insertions(+)
---
diff --git a/meson_options.txt b/meson_options.txt
index 657e9beaa..8599060a0 100644
--- a/meson_options.txt
+++ b/meson_options.txt
@@ -24,6 +24,7 @@ option('plugin_beautifier', type: 'boolean')
option('plugin_c_pack', type: 'boolean')
option('plugin_cargo', type: 'boolean')
option('plugin_clang', type: 'boolean')
+option('plugin_clang_format', type: 'boolean')
option('plugin_cmake', type: 'boolean')
option('plugin_codespell', type: 'boolean')
option('plugin_code_index', type: 'boolean')
diff --git a/src/plugins/clang-format/clang-format-plugin.c b/src/plugins/clang-format/clang-format-plugin.c
new file mode 100644
index 000000000..c11df12a9
--- /dev/null
+++ b/src/plugins/clang-format/clang-format-plugin.c
@@ -0,0 +1,34 @@
+/* clang-format-plugin.c
+ *
+ * Copyright 2021 Tomi Lähteenmäki <lihis lihis net>
+ *
+ * This program 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.
+ *
+ * This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#include "config.h"
+
+#include <libide-editor.h>
+#include <libpeas/peas.h>
+
+#include "gb-clang-format-buffer-addin.h"
+
+_IDE_EXTERN void
+_gb_clang_format_register_types (PeasObjectModule *module)
+{
+ peas_object_module_register_extension_type (module,
+ IDE_TYPE_BUFFER_ADDIN,
+ GB_TYPE_CLANG_FORMAT_BUFFER_ADDIN);
+}
diff --git a/src/plugins/clang-format/clang-format.gresource.xml
b/src/plugins/clang-format/clang-format.gresource.xml
new file mode 100644
index 000000000..37c2320ed
--- /dev/null
+++ b/src/plugins/clang-format/clang-format.gresource.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<gresources>
+ <gresource prefix="/plugins/clang-format">
+ <file>clang-format.plugin</file>
+ </gresource>
+</gresources>
diff --git a/src/plugins/clang-format/clang-format.plugin b/src/plugins/clang-format/clang-format.plugin
new file mode 100644
index 000000000..308b009f5
--- /dev/null
+++ b/src/plugins/clang-format/clang-format.plugin
@@ -0,0 +1,10 @@
+[Plugin]
+Authors=Tomi Lähteenmäki <lihis lihis net>
+Builtin=true
+Copyright=Copyright © 2021 Tomi Lähteenmäki
+Depends=editor;
+Description=Format code based on project .clang-format config
+Hidden=false
+Embedded=_gb_clang_format_register_types
+Module=clang-format
+Name=ClangFormat
diff --git a/src/plugins/clang-format/gb-clang-format-buffer-addin.c
b/src/plugins/clang-format/gb-clang-format-buffer-addin.c
new file mode 100644
index 000000000..b49f4b0e3
--- /dev/null
+++ b/src/plugins/clang-format/gb-clang-format-buffer-addin.c
@@ -0,0 +1,355 @@
+/* gb-clang-format-buffer-addin.c
+ *
+ * Copyright 2021 Tomi Lähteenmäki <lihis lihis net>
+ *
+ * This program 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.
+ *
+ * This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#define G_LOG_DOMAIN "clang-format-buffer"
+
+#include "config.h"
+
+#include <json-glib/json-glib.h>
+#include <libide-code.h>
+#include <libide-editor.h>
+#include <sys/wait.h>
+
+#include "gb-clang-format-buffer-addin.h"
+
+struct _GbClangFormatBufferAddin
+{
+ GObject parent_instance;
+ gchar *working_directory;
+ gssize cursor_position;
+ IdePage *page;
+ gssize header_len;
+};
+
+typedef struct {
+ IdeBuffer *buffer;
+ IdePage *page;
+} Lookup;
+
+static void
+foreach_page_cb (GtkWidget *page,
+ gpointer user_data)
+{
+ Lookup *l = user_data;
+
+ if (l->page == NULL &&
+ IDE_IS_EDITOR_PAGE (page) &&
+ ide_editor_page_get_buffer (IDE_EDITOR_PAGE (page)) == l->buffer)
+ l->page = IDE_PAGE (page);
+}
+
+static IdePage *
+get_page (IdeBuffer *buffer)
+{
+ Lookup lookup = {buffer, NULL};
+ IdeContext *context = ide_buffer_ref_context (buffer);
+ IdeWorkbench *workbench = ide_workbench_from_context (context);
+ ide_workbench_foreach_page (workbench, foreach_page_cb, &lookup);
+
+ return lookup.page;
+}
+
+gboolean
+format_on_save_enabled (IdeBuffer *buffer)
+{
+ g_autoptr (GSettings) settings = g_settings_new ("org.gnome.builder");
+
+ return g_settings_get_boolean (settings, "format-on-save");
+}
+
+gboolean
+is_formattable_language (IdeBuffer *buffer)
+{
+ const gchar *lang_id;
+
+ lang_id = ide_buffer_get_language_id (buffer);
+ if (lang_id == NULL)
+ {
+ g_debug ("Language ID was NULL");
+ return FALSE;
+ }
+
+ if (strcmp (lang_id, "c") == 0 || strcmp (lang_id, "chdr") == 0 ||
+ strcmp (lang_id, "cpp") == 0 || strcmp (lang_id, "cpphdr") == 0 ||
+ strcmp (lang_id, "objc") == 0)
+ return TRUE;
+
+ return FALSE;
+}
+
+char *
+project_root_directory (IdeBuffer *buffer)
+{
+ IdeContext *context;
+ GFile *workdir;
+
+ context = ide_buffer_ref_context (buffer);
+ if (context == NULL)
+ {
+ g_warning ("Failed to get IdeContext");
+ return NULL;
+ }
+
+ workdir = ide_context_ref_workdir (context);
+ if (workdir == NULL)
+ {
+ g_warning ("Failed to get working directory");
+ return NULL;
+ }
+
+ return g_file_get_path (workdir);
+}
+
+gboolean
+clang_format_config_exists (GbClangFormatBufferAddin *self,
+ IdeBuffer *buffer)
+{
+ GFile *config;
+
+ config = g_file_new_build_filename (self->working_directory, ".clang-format", NULL);
+
+ return g_file_query_exists (config, NULL);
+}
+
+gint
+get_cursor_position (IdeBuffer *buffer)
+{
+ GtkTextBuffer *textbuffer;
+ gint cursor_position;
+
+ textbuffer = GTK_TEXT_BUFFER (buffer);
+ g_object_get (G_OBJECT (textbuffer), "cursor-position", &cursor_position, NULL);
+
+ return cursor_position;
+}
+
+gssize
+get_header_length (gchar *data)
+{
+ gssize len = g_utf8_strlen (data, G_MAXSSIZE);
+
+ for (gssize i = 0; i < len; ++i)
+ if (data[i] == '\n')
+ return (i + 1 <= len ? (i + 1) : i);
+
+ return 0;
+}
+
+gboolean
+parse_header (GbClangFormatBufferAddin *self,
+ gchar *data)
+{
+ JsonParser *parser;
+ gboolean ret;
+ GError *error;
+ JsonNode *root;
+ JsonObject *cursor;
+
+ self->header_len = get_header_length (data);
+ if (self->header_len <= 0)
+ {
+ g_warning ("Empty header");
+ return FALSE;
+ }
+
+ error = NULL;
+ parser = json_parser_new ();
+ ret = json_parser_load_from_data (parser, data, self->header_len, &error);
+ if (ret == FALSE)
+ {
+ g_warning ("Unable to parse JSON: %s", error->message);
+ g_error_free (error);
+ g_object_unref (parser);
+ return FALSE;
+ }
+
+ root = json_parser_get_root (parser);
+ cursor = json_node_get_object (root);
+ if (cursor == NULL)
+ {
+ g_warning ("clang-format didn't return cursor position");
+ g_object_unref (parser);
+ return FALSE;
+ }
+ self->cursor_position = json_object_get_int_member (cursor, "Cursor");
+
+ g_object_unref (parser);
+ return TRUE;
+}
+
+gchar *
+format_cursor_arg (gssize position)
+{
+ return g_strdup_printf ("--cursor=%zu", position);
+}
+
+IdeSubprocess *
+create_process (GbClangFormatBufferAddin *self)
+{
+ g_autoptr (IdeSubprocessLauncher) launcher = NULL;
+ IdeSubprocess *subprocess = NULL;
+ GPtrArray *args;
+ GError *error = NULL;
+ gchar *cursor_arg;
+
+ cursor_arg = format_cursor_arg (self->cursor_position);
+
+ args = g_ptr_array_new ();
+ g_ptr_array_add (args, (gchar *)"clang-format");
+ g_ptr_array_add (args, cursor_arg);
+ g_ptr_array_add (args, NULL);
+
+ launcher = ide_subprocess_launcher_new (G_SUBPROCESS_FLAGS_STDIN_PIPE
+ | G_SUBPROCESS_FLAGS_STDOUT_PIPE
+ | G_SUBPROCESS_FLAGS_STDERR_PIPE);
+ ide_subprocess_launcher_set_cwd (launcher, self->working_directory);
+ ide_subprocess_launcher_set_argv (launcher,
+ (const gchar *const *)args->pdata);
+ subprocess = ide_subprocess_launcher_spawn (launcher, NULL, &error);
+
+ g_ptr_array_free (args, TRUE);
+ g_free (cursor_arg);
+ return subprocess;
+}
+
+gboolean
+process_communicate (IdeSubprocess *process,
+ IdeBuffer *buffer,
+ gchar **stdout_buf)
+{
+ const gchar *stdin_buf;
+ gchar *stderr_buf = NULL;
+ GError *error = NULL;
+ gboolean ret;
+
+ stdin_buf = g_bytes_get_data (ide_buffer_dup_content (buffer), NULL);
+
+ ret = ide_subprocess_communicate_utf8 (process, stdin_buf, NULL, stdout_buf,
+ &stderr_buf, &error);
+ if (ret == FALSE)
+ {
+ g_warning ("clang-format failed: %s", error->message);
+ g_free (error);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+void
+run_clang_format (GbClangFormatBufferAddin *self,
+ IdeBuffer *buffer)
+{
+ IdeSubprocess *process;
+ gchar *stdout_buf = NULL;
+ gchar *data_start;
+ gsize data_len;
+ GtkTextIter cursor;
+
+ process = create_process (self);
+ if (process_communicate (process, buffer, &stdout_buf) == FALSE)
+ return;
+
+ if (parse_header (self, stdout_buf) == FALSE)
+ return;
+
+ data_start = stdout_buf + self->header_len;
+ data_len = strlen (data_start);
+ if (data_len <= 0)
+ {
+ g_warning ("No output");
+ return;
+ }
+
+ if (data_start[data_len - 1] == '\n')
+ data_len--;
+
+ gtk_text_buffer_begin_user_action (GTK_TEXT_BUFFER (buffer));
+
+ gtk_text_buffer_set_text (GTK_TEXT_BUFFER (buffer), data_start, data_len);
+
+ gtk_text_buffer_get_start_iter (GTK_TEXT_BUFFER (buffer), &cursor);
+ gtk_text_iter_set_offset (&cursor, self->cursor_position);
+ gtk_text_buffer_place_cursor (GTK_TEXT_BUFFER (buffer), &cursor);
+
+ gtk_text_buffer_end_user_action (GTK_TEXT_BUFFER (buffer));
+
+ if (self->page != NULL)
+ {
+ IdeSourceView *source_view = ide_editor_page_get_view (IDE_EDITOR_PAGE (self->page));
+ gtk_text_view_scroll_to_iter (GTK_TEXT_VIEW (source_view), &cursor, 0.25, FALSE, 0.5, 0.5);
+ }
+ else
+ g_warning ("Failed to get page");
+}
+
+static void
+gb_clang_format_buffer_addin_save_file (IdeBufferAddin *addin,
+ IdeBuffer *buffer,
+ GFile *file)
+{
+ GbClangFormatBufferAddin *self;
+
+ if (format_on_save_enabled (buffer) == FALSE)
+ return;
+
+ if (is_formattable_language (buffer) == FALSE)
+ return;
+
+ self = (GbClangFormatBufferAddin *)addin;
+ self->working_directory = project_root_directory (buffer);
+ if (self->working_directory == NULL)
+ {
+ g_warning ("Failed to get working directory");
+ return;
+ }
+
+ if (clang_format_config_exists (self, buffer) == FALSE)
+ {
+ g_debug ("No .clang-format");
+ return;
+ }
+ self->cursor_position = get_cursor_position (buffer);
+ self->page = get_page (buffer);
+
+ run_clang_format (self, buffer);
+}
+
+static void
+buffer_addin_iface_init (IdeBufferAddinInterface *iface)
+{
+ iface->load = NULL;
+ iface->unload = NULL;
+ iface->save_file = gb_clang_format_buffer_addin_save_file;
+ iface->file_loaded = NULL;
+}
+
+G_DEFINE_FINAL_TYPE_WITH_CODE (GbClangFormatBufferAddin, gb_clang_format_buffer_addin, G_TYPE_OBJECT,
+ G_IMPLEMENT_INTERFACE (IDE_TYPE_BUFFER_ADDIN, buffer_addin_iface_init))
+
+static void
+gb_clang_format_buffer_addin_class_init (GbClangFormatBufferAddinClass *klass)
+{
+}
+
+static void
+gb_clang_format_buffer_addin_init (GbClangFormatBufferAddin *self)
+{
+}
diff --git a/src/plugins/clang-format/gb-clang-format-buffer-addin.h
b/src/plugins/clang-format/gb-clang-format-buffer-addin.h
new file mode 100644
index 000000000..eb866373c
--- /dev/null
+++ b/src/plugins/clang-format/gb-clang-format-buffer-addin.h
@@ -0,0 +1,31 @@
+/* gb-clang-format-buffer-addin.h
+ *
+ * Copyright 2021 Tomi Lähteenmäki <lihis lihis net>
+ *
+ * This program 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.
+ *
+ * This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#pragma once
+
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+#define GB_TYPE_CLANG_FORMAT_BUFFER_ADDIN (gb_clang_format_buffer_addin_get_type())
+
+G_DECLARE_FINAL_TYPE (GbClangFormatBufferAddin, gb_clang_format_buffer_addin, GB, CLANG_FORMAT_BUFFER_ADDIN,
GObject)
+
+G_END_DECLS
diff --git a/src/plugins/clang-format/meson.build b/src/plugins/clang-format/meson.build
new file mode 100644
index 000000000..d084a2032
--- /dev/null
+++ b/src/plugins/clang-format/meson.build
@@ -0,0 +1,16 @@
+plugins_sources += files([
+ 'clang-format-plugin.c',
+ 'gb-clang-format-buffer-addin.c',
+])
+
+plugin_clang_format_resources = gnome.compile_resources(
+ 'gb-clang-format-resources',
+ 'clang-format.gresource.xml',
+ c_name: 'gb_clang_format',
+)
+
+plugins_sources += plugin_clang_format_resources
+
+if not find_program('clang-format', required: false).found()
+ message('Please install clang-format as runtime dependency')
+endif
diff --git a/src/plugins/meson.build b/src/plugins/meson.build
index fe7ee0ae9..582cd1727 100644
--- a/src/plugins/meson.build
+++ b/src/plugins/meson.build
@@ -45,6 +45,7 @@ subdir('buildui')
subdir('buffer-monitor')
subdir('cargo')
subdir('clang')
+subdir('clang-format')
subdir('cmake')
subdir('codespell')
subdir('code-index')
@@ -153,6 +154,7 @@ status += [
'C Pack ................ : @0@'.format(get_option('plugin_c_pack')),
'Cargo ................. : @0@'.format(get_option('plugin_cargo')),
'Clang ................. : @0@'.format(get_option('plugin_clang')),
+ 'ClangFormat ........... : @0@'.format(get_option('plugin_clang_format')),
'CMake ................. : @0@'.format(get_option('plugin_cmake')),
'Codespell ............. : @0@'.format(get_option('plugin_codespell')),
'Code Index ............ : @0@'.format(get_option('plugin_code_index')),
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]