/* SPDX-FileCopyrightText: 2025 - Sébastien Wilmet
 * SPDX-License-Identifier: LGPL-3.0-or-later
 */

#include "tepl-code-comment-view.h"
#include "tepl/tepl-signal-group.h"

/**
 * SECTION:code-comment-view
 * @Title: TeplCodeCommentView
 * @Short_description: Code comment - GtkSourceView class extension
 *
 * #TeplCodeCommentView extends the #GtkSourceView class with the code comment
 * and uncomment feature.
 */

struct _TeplCodeCommentViewPrivate
{
	/* Weak ref: a subclass of GtkSourceView can potentially own a strong
	 * ref to a TeplCodeCommentView object.
	 */
	GtkSourceView *source_view;

	TeplSignalGroup *buffer_signal_group;
};

enum
{
	PROP_0,
	PROP_SOURCE_VIEW,
	PROP_CODE_COMMENT_IS_SUPPORTED,
	N_PROPERTIES
};

static GParamSpec *properties[N_PROPERTIES];

G_DEFINE_TYPE_WITH_PRIVATE (TeplCodeCommentView, tepl_code_comment_view, G_TYPE_OBJECT)

static gboolean
str_is_empty (const gchar *str)
{
	return (str == NULL || str[0] == '\0');
}

static gboolean
str_contains_newline_char (const gchar *str)
{
	g_assert (str != NULL);

	return (strchr (str, '\n') != NULL ||
		strchr (str, '\r') != NULL);
}

static gboolean
str_contains_leading_or_trailing_whitespace (const gchar *str,
					     gboolean     leading)
{
	gchar *stripped_str;
	gboolean ret;

	g_assert (str != NULL);

	stripped_str = g_strdup (str);

	if (leading)
	{
		g_strchug (stripped_str);
	}
	else
	{
		g_strchomp (stripped_str);
	}

	ret = g_strcmp0 (stripped_str, str) != 0;

	g_free (stripped_str);
	return ret;
}

static gboolean
str_contains_leading_whitespace (const gchar *str)
{
	return str_contains_leading_or_trailing_whitespace (str, TRUE);
}

static gboolean
str_contains_trailing_whitespace (const gchar *str)
{
	return str_contains_leading_or_trailing_whitespace (str, FALSE);
}

static gboolean
metadata_line_comment_start_is_valid (const gchar *str)
{
	if (str_is_empty (str) ||
	    str_contains_newline_char (str) ||
	    str_contains_leading_whitespace (str))
	{
		return FALSE;
	}

	return TRUE;
}

static gboolean
metadata_block_comment_start_is_valid (const gchar *str)
{
	return metadata_line_comment_start_is_valid (str);
}

static gboolean
metadata_block_comment_end_is_valid (const gchar *str)
{
	if (str_is_empty (str) ||
	    str_contains_newline_char (str) ||
	    str_contains_trailing_whitespace (str))
	{
		return FALSE;
	}

	return TRUE;
}

static void
warn_if_invalid_metadata (GtkSourceLanguage *language,
			  const gchar       *metadata_name,
			  const gchar       *metadata_value,
			  gboolean           metadata_value_is_valid)
{
	if (metadata_value != NULL && !metadata_value_is_valid)
	{
		g_warning ("TeplCodeCommentView: language “%s”: %s metadata “%s” is not supported.",
			   gtk_source_language_get_id (language),
			   metadata_name,
			   metadata_value);
	}
}

static void
get_sanitized_metadata (GtkSourceLanguage  *language,
			const gchar       **line_comment_start,
			const gchar       **block_comment_start,
			const gchar       **block_comment_end)
{
	gboolean line_comment_start_is_valid;
	gboolean block_comment_start_is_valid;
	gboolean block_comment_end_is_valid;

	*line_comment_start = gtk_source_language_get_metadata (language, "line-comment-start");
	*block_comment_start = gtk_source_language_get_metadata (language, "block-comment-start");
	*block_comment_end = gtk_source_language_get_metadata (language, "block-comment-end");

	line_comment_start_is_valid = metadata_line_comment_start_is_valid (*line_comment_start);
	block_comment_start_is_valid = metadata_block_comment_start_is_valid (*block_comment_start);
	block_comment_end_is_valid = metadata_block_comment_end_is_valid (*block_comment_end);

	warn_if_invalid_metadata (language,
				  "line-comment-start",
				  *line_comment_start,
				  line_comment_start_is_valid);
	warn_if_invalid_metadata (language,
				  "block-comment-start",
				  *block_comment_start,
				  block_comment_start_is_valid);
	warn_if_invalid_metadata (language,
				  "block-comment-end",
				  *block_comment_end,
				  block_comment_end_is_valid);

	if (!line_comment_start_is_valid)
	{
		*line_comment_start = NULL;
	}

	if (!block_comment_start_is_valid ||
	    !block_comment_end_is_valid)
	{
		*block_comment_start = NULL;
		*block_comment_end = NULL;
	}
}

static void
language_notify_cb (GtkSourceBuffer     *buffer,
		    GParamSpec          *pspec,
		    TeplCodeCommentView *code_comment_view)
{
	g_object_notify_by_pspec (G_OBJECT (code_comment_view), properties[PROP_CODE_COMMENT_IS_SUPPORTED]);
}

static void
buffer_changed (TeplCodeCommentView *code_comment_view)
{
	GtkSourceBuffer *buffer;

	tepl_signal_group_clear (&code_comment_view->priv->buffer_signal_group);

	g_object_notify_by_pspec (G_OBJECT (code_comment_view), properties[PROP_CODE_COMMENT_IS_SUPPORTED]);

	if (code_comment_view->priv->source_view == NULL)
	{
		return;
	}

	buffer = GTK_SOURCE_BUFFER (gtk_text_view_get_buffer (GTK_TEXT_VIEW (code_comment_view->priv->source_view)));
	code_comment_view->priv->buffer_signal_group = tepl_signal_group_new (G_OBJECT (buffer));

	tepl_signal_group_add (code_comment_view->priv->buffer_signal_group,
			       g_signal_connect (buffer,
						 "notify::language",
						 G_CALLBACK (language_notify_cb),
						 code_comment_view));
}

static void
buffer_notify_cb (GtkTextView         *view,
		  GParamSpec          *pspec,
		  TeplCodeCommentView *code_comment_view)
{
	buffer_changed (code_comment_view);
}

static void
editable_notify_cb (GtkTextView         *view,
		    GParamSpec          *pspec,
		    TeplCodeCommentView *code_comment_view)
{
	g_object_notify_by_pspec (G_OBJECT (code_comment_view), properties[PROP_CODE_COMMENT_IS_SUPPORTED]);
}

static void
set_source_view (TeplCodeCommentView *code_comment_view,
		 GtkSourceView       *source_view)
{
	if (source_view == NULL)
	{
		return;
	}

	g_return_if_fail (GTK_SOURCE_IS_VIEW (source_view));

	g_assert (code_comment_view->priv->source_view == NULL);
	g_set_weak_pointer (&code_comment_view->priv->source_view, source_view);

	g_signal_connect_object (source_view,
				 "notify::buffer",
				 G_CALLBACK (buffer_notify_cb),
				 code_comment_view,
				 G_CONNECT_DEFAULT);

	g_signal_connect_object (source_view,
				 "notify::editable",
				 G_CALLBACK (editable_notify_cb),
				 code_comment_view,
				 G_CONNECT_DEFAULT);

	buffer_changed (code_comment_view);
}

static void
tepl_code_comment_view_get_property (GObject    *object,
                                     guint       prop_id,
                                     GValue     *value,
                                     GParamSpec *pspec)
{
	TeplCodeCommentView *code_comment_view = TEPL_CODE_COMMENT_VIEW (object);

	switch (prop_id)
	{
		case PROP_SOURCE_VIEW:
			g_value_set_object (value, tepl_code_comment_view_get_source_view (code_comment_view));
			break;

		case PROP_CODE_COMMENT_IS_SUPPORTED:
			g_value_set_boolean (value, tepl_code_comment_view_code_comment_is_supported (code_comment_view));
			break;

		default:
			G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
			break;
	}
}

static void
tepl_code_comment_view_set_property (GObject      *object,
                                     guint         prop_id,
                                     const GValue *value,
                                     GParamSpec   *pspec)
{
	TeplCodeCommentView *code_comment_view = TEPL_CODE_COMMENT_VIEW (object);

	switch (prop_id)
	{
		case PROP_SOURCE_VIEW:
			set_source_view (code_comment_view, g_value_get_object (value));
			break;

		default:
			G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
			break;
	}
}

static void
tepl_code_comment_view_dispose (GObject *object)
{
	TeplCodeCommentView *code_comment_view = TEPL_CODE_COMMENT_VIEW (object);

	g_clear_weak_pointer (&code_comment_view->priv->source_view);
	tepl_signal_group_clear (&code_comment_view->priv->buffer_signal_group);

	G_OBJECT_CLASS (tepl_code_comment_view_parent_class)->dispose (object);
}

static void
tepl_code_comment_view_class_init (TeplCodeCommentViewClass *klass)
{
	GObjectClass *object_class = G_OBJECT_CLASS (klass);

	object_class->get_property = tepl_code_comment_view_get_property;
	object_class->set_property = tepl_code_comment_view_set_property;
	object_class->dispose = tepl_code_comment_view_dispose;

	/**
	 * TeplCodeCommentView:source-view:
	 *
	 * The associated #GtkSourceView widget. #TeplCodeCommentView has a weak
	 * reference to the #GtkSourceView.
	 *
	 * Since: 6.14
	 */
	properties[PROP_SOURCE_VIEW] =
		g_param_spec_object ("source-view",
				     "source-view",
				     "",
				     GTK_SOURCE_TYPE_VIEW,
				     G_PARAM_READWRITE |
				     G_PARAM_CONSTRUCT_ONLY |
				     G_PARAM_STATIC_STRINGS);

	/**
	 * TeplCodeCommentView:code-comment-is-supported:
	 *
	 * Whether code comment/uncomment is supported.
	 *
	 * This property is %TRUE when all these conditions are met:
	 * - The necessary metadata is available, to know what character or
	 *   string to insert or remove.
	 * - The #GtkTextView:editable property is %TRUE.
	 *
	 * Since: 6.14
	 */
	properties[PROP_CODE_COMMENT_IS_SUPPORTED] =
		g_param_spec_boolean ("code-comment-is-supported",
				      "code-comment-is-supported",
				      "",
				      FALSE,
				      G_PARAM_READABLE |
				      G_PARAM_STATIC_STRINGS);

	g_object_class_install_properties (object_class, N_PROPERTIES, properties);
}

static void
tepl_code_comment_view_init (TeplCodeCommentView *code_comment_view)
{
	code_comment_view->priv = tepl_code_comment_view_get_instance_private (code_comment_view);
}

/**
 * tepl_code_comment_view_new:
 * @view: a #GtkSourceView widget.
 *
 * Returns: (transfer full): a new #TeplCodeCommentView object.
 * Since: 6.14
 */
TeplCodeCommentView *
tepl_code_comment_view_new (GtkSourceView *view)
{
	g_return_val_if_fail (GTK_SOURCE_IS_VIEW (view), NULL);

	return g_object_new (TEPL_TYPE_CODE_COMMENT_VIEW,
			     "source-view", view,
			     NULL);
}

/**
 * tepl_code_comment_view_get_source_view:
 * @code_comment_view: a #TeplCodeCommentView.
 *
 * Returns: (transfer none) (nullable): the #TeplCodeCommentView:source-view.
 * Since: 6.14
 */
GtkSourceView *
tepl_code_comment_view_get_source_view (TeplCodeCommentView *code_comment_view)
{
	g_return_val_if_fail (TEPL_IS_CODE_COMMENT_VIEW (code_comment_view), NULL);

	return code_comment_view->priv->source_view;
}

/**
 * tepl_code_comment_view_code_comment_is_supported:
 * @code_comment_view: a #TeplCodeCommentView.
 *
 * Returns: the current value of the
 *   #TeplCodeCommentView:code-comment-is-supported property.
 * Since: 6.14
 */
gboolean
tepl_code_comment_view_code_comment_is_supported (TeplCodeCommentView *code_comment_view)
{
	GtkSourceBuffer *buffer;
	GtkSourceLanguage *language;
	const gchar *line_comment_start;
	const gchar *block_comment_start;
	const gchar *block_comment_end;

	g_return_val_if_fail (TEPL_IS_CODE_COMMENT_VIEW (code_comment_view), FALSE);

	if (code_comment_view->priv->source_view == NULL)
	{
		return FALSE;
	}

	if (!gtk_text_view_get_editable (GTK_TEXT_VIEW (code_comment_view->priv->source_view)))
	{
		return FALSE;
	}

	buffer = GTK_SOURCE_BUFFER (gtk_text_view_get_buffer (GTK_TEXT_VIEW (code_comment_view->priv->source_view)));

	language = gtk_source_buffer_get_language (buffer);
	if (language == NULL)
	{
		return FALSE;
	}

	get_sanitized_metadata (language,
				&line_comment_start,
				&block_comment_start,
				&block_comment_end);

	return ((line_comment_start != NULL) ||
		(block_comment_start != NULL && block_comment_end != NULL));
}

static void
comment_single_line (GtkTextBuffer *buffer,
		     gint           line_num,
		     const gchar   *line_comment_start,
		     const gchar   *block_comment_start,
		     const gchar   *block_comment_end)
{
	GtkTextIter iter;

	gtk_text_buffer_get_iter_at_line (buffer, &iter, line_num);
	g_return_if_fail (gtk_text_iter_get_line (&iter) == line_num);

	if (line_comment_start != NULL)
	{
		gtk_text_buffer_insert (buffer, &iter, line_comment_start, -1);
	}
	/* Don't insert block-comment-start/end on empty lines. For example with
	 * XML/HTML it would clutter the view.
	 * On the other hand, for line-comment-start it's nice to have a
	 * vertical "line" of comment chars, to better see what has been
	 * commented out.
	 */
	else if (!gtk_text_iter_ends_line (&iter))
	{
		gtk_text_buffer_insert (buffer, &iter, block_comment_start, -1);

		gtk_text_iter_forward_to_line_end (&iter);
		g_return_if_fail (gtk_text_iter_get_line (&iter) == line_num);

		gtk_text_buffer_insert (buffer, &iter, block_comment_end, -1);
	}
}

/**
 * tepl_code_comment_view_comment_lines:
 * @code_comment_view: a #TeplCodeCommentView.
 * @start_iter: a #GtkTextIter.
 * @end_iter: (nullable): a #GtkTextIter, or %NULL to comment only the line at
 *   @start_iter.
 *
 * Comments the lines between @start_iter and @end_iter included.
 *
 * If @end_iter is %NULL, only a single line is commented.
 *
 * Ensure that code comment is supported before calling this function. See
 * tepl_code_comment_view_code_comment_is_supported().
 *
 * Since: 6.14
 */
void
tepl_code_comment_view_comment_lines (TeplCodeCommentView *code_comment_view,
				      const GtkTextIter   *start_iter,
				      const GtkTextIter   *end_iter)
{
	GtkSourceBuffer *buffer;
	GtkSourceLanguage *language;
	const gchar *line_comment_start;
	const gchar *block_comment_start;
	const gchar *block_comment_end;
	GtkTextIter my_start_iter;
	GtkTextIter my_end_iter;
	gint start_line;
	gint end_line;
	gint line_num;

	g_return_if_fail (TEPL_IS_CODE_COMMENT_VIEW (code_comment_view));

	if (code_comment_view->priv->source_view == NULL)
	{
		return;
	}

	buffer = GTK_SOURCE_BUFFER (gtk_text_view_get_buffer (GTK_TEXT_VIEW (code_comment_view->priv->source_view)));

	g_return_if_fail (start_iter != NULL);
	g_return_if_fail (gtk_text_iter_get_buffer (start_iter) == GTK_TEXT_BUFFER (buffer));
	g_return_if_fail (end_iter == NULL ||
			  gtk_text_iter_get_buffer (end_iter) == GTK_TEXT_BUFFER (buffer));
	g_return_if_fail (tepl_code_comment_view_code_comment_is_supported (code_comment_view));

	language = gtk_source_buffer_get_language (buffer);
	get_sanitized_metadata (language,
				&line_comment_start,
				&block_comment_start,
				&block_comment_end);

	my_start_iter = *start_iter;
	my_end_iter = end_iter != NULL ? *end_iter : *start_iter;

	gtk_text_iter_order (&my_start_iter, &my_end_iter);

	start_line = gtk_text_iter_get_line (&my_start_iter);
	end_line = gtk_text_iter_get_line (&my_end_iter);

	gtk_text_buffer_begin_user_action (GTK_TEXT_BUFFER (buffer));

	for (line_num = start_line; line_num <= end_line; line_num++)
	{
		comment_single_line (GTK_TEXT_BUFFER (buffer),
				     line_num,
				     line_comment_start,
				     block_comment_start,
				     block_comment_end);
	}

	gtk_text_buffer_end_user_action (GTK_TEXT_BUFFER (buffer));
}

/**
 * tepl_code_comment_view_comment_selected_lines:
 * @code_comment_view: a #TeplCodeCommentView.
 *
 * Comments the selected lines.
 *
 * Ensure that code comment is supported before calling this function. See
 * tepl_code_comment_view_code_comment_is_supported().
 *
 * Since: 6.14
 */
void
tepl_code_comment_view_comment_selected_lines (TeplCodeCommentView *code_comment_view)
{
	GtkTextBuffer *buffer;
	GtkTextIter start;
	GtkTextIter end;

	g_return_if_fail (TEPL_IS_CODE_COMMENT_VIEW (code_comment_view));

	if (code_comment_view->priv->source_view == NULL)
	{
		return;
	}

	buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (code_comment_view->priv->source_view));
	gtk_text_buffer_get_selection_bounds (buffer, &start, &end);
	tepl_code_comment_view_comment_lines (code_comment_view, &start, &end);
}

static gboolean
uncomment_line_with_line_comment_start (GtkTextBuffer *buffer,
					gint           line_num,
					const gchar   *line_comment_start)
{
	GtkTextIter iter;
	GtkTextIter leading_end;
	GtkTextIter match_end;

	gtk_text_buffer_get_iter_at_line (buffer, &iter, line_num);
	g_return_val_if_fail (gtk_text_iter_get_line (&iter) == line_num, FALSE);

	gtk_source_iter_get_leading_spaces_end_boundary (&iter, &leading_end);

	if (gtk_source_iter_starts_string (&leading_end, line_comment_start, &match_end) &&
	    gtk_text_iter_get_line (&match_end) == line_num)
	{
		gtk_text_buffer_delete (buffer, &leading_end, &match_end);
		return TRUE;
	}

	return FALSE;
}

static gboolean
uncomment_line_by_line_with_line_comment_start (GtkTextBuffer *buffer,
						gint           start_line,
						gint           end_line,
						const gchar   *line_comment_start)
{
	gint line_num;
	gboolean success = FALSE;

	if (line_comment_start == NULL)
	{
		return FALSE;
	}

	for (line_num = start_line; line_num <= end_line; line_num++)
	{
		if (uncomment_line_with_line_comment_start (buffer, line_num, line_comment_start))
		{
			success = TRUE;
		}
	}

	return success;
}

static gboolean
uncomment_line_with_block_comments (GtkTextBuffer *buffer,
				    gint           line_num,
				    const gchar   *block_comment_start,
				    const gchar   *block_comment_end)
{
	GtkTextIter at_start_of_line;
	GtkTextIter leading_end;
	GtkTextIter trailing_start;
	GtkTextIter end_of_block_comment_start;
	GtkTextIter start_of_block_comment_end;
	gboolean block_comment_start_found;
	gboolean block_comment_end_found;

	gtk_text_buffer_get_iter_at_line (buffer, &at_start_of_line, line_num);
	g_return_val_if_fail (gtk_text_iter_get_line (&at_start_of_line) == line_num, FALSE);

	gtk_source_iter_get_leading_spaces_end_boundary (&at_start_of_line, &leading_end);
	gtk_source_iter_get_trailing_spaces_start_boundary (&at_start_of_line, &trailing_start);

	block_comment_start_found = gtk_source_iter_starts_string (&leading_end,
								   block_comment_start,
								   &end_of_block_comment_start);
	block_comment_end_found = gtk_source_iter_ends_string (&trailing_start,
							       block_comment_end,
							       &start_of_block_comment_end);

	if (block_comment_start_found && block_comment_end_found)
	{
		GtkTextMark *mark_for_start_of_block_comment_end;
		GtkTextMark *mark_for_trailing_start;

		g_return_val_if_fail (gtk_text_iter_get_line (&leading_end) == line_num, FALSE);
		g_return_val_if_fail (gtk_text_iter_get_line (&end_of_block_comment_start) == line_num, FALSE);
		g_return_val_if_fail (gtk_text_iter_get_line (&start_of_block_comment_end) == line_num, FALSE);
		g_return_val_if_fail (gtk_text_iter_get_line (&trailing_start) == line_num, FALSE);

		g_return_val_if_fail (gtk_text_iter_compare (&leading_end, &end_of_block_comment_start) < 0, FALSE);
		g_return_val_if_fail (gtk_text_iter_compare (&start_of_block_comment_end, &trailing_start) < 0, FALSE);

		// Overlap. This can happen with /*/ in C :-)
		if (gtk_text_iter_compare (&end_of_block_comment_start, &start_of_block_comment_end) > 0)
		{
			return FALSE;
		}

		mark_for_start_of_block_comment_end = gtk_text_buffer_create_mark (buffer,
										   NULL,
										   &start_of_block_comment_end,
										   TRUE);
		mark_for_trailing_start = gtk_text_buffer_create_mark (buffer,
								       NULL,
								       &trailing_start,
								       TRUE);

		gtk_text_buffer_delete (buffer, &leading_end, &end_of_block_comment_start);

		gtk_text_buffer_get_iter_at_mark (buffer,
						  &start_of_block_comment_end,
						  mark_for_start_of_block_comment_end);
		gtk_text_buffer_get_iter_at_mark (buffer,
						  &trailing_start,
						  mark_for_trailing_start);

		gtk_text_buffer_delete_mark (buffer, mark_for_start_of_block_comment_end);
		gtk_text_buffer_delete_mark (buffer, mark_for_trailing_start);

		gtk_text_buffer_delete (buffer, &start_of_block_comment_end, &trailing_start);

		return TRUE;
	}

	return FALSE;
}

static gboolean
uncomment_line_by_line_with_block_comments (GtkTextBuffer *buffer,
					    gint           start_line,
					    gint           end_line,
					    const gchar   *block_comment_start,
					    const gchar   *block_comment_end)
{
	gint line_num;
	gboolean success = FALSE;

	if (block_comment_start == NULL || block_comment_end == NULL)
	{
		return FALSE;
	}

	for (line_num = start_line; line_num <= end_line; line_num++)
	{
		if (uncomment_line_with_block_comments (buffer,
							line_num,
							block_comment_start,
							block_comment_end))
		{
			success = TRUE;
		}
	}

	return success;
}

/**
 * tepl_code_comment_view_uncomment_selection:
 * @code_comment_view: a #TeplCodeCommentView.
 *
 * Uncomments the selected text.
 *
 * Ensure that code comment is supported before calling this function. See
 * tepl_code_comment_view_code_comment_is_supported().
 *
 * Since: 6.14
 */
void
tepl_code_comment_view_uncomment_selection (TeplCodeCommentView *code_comment_view)
{
	GtkSourceBuffer *buffer;
	GtkSourceLanguage *language;
	const gchar *line_comment_start;
	const gchar *block_comment_start;
	const gchar *block_comment_end;
	GtkTextIter selection_start;
	GtkTextIter selection_end;
	gint start_line;
	gint end_line;
	gboolean done;

	g_return_if_fail (TEPL_IS_CODE_COMMENT_VIEW (code_comment_view));

	if (code_comment_view->priv->source_view == NULL)
	{
		return;
	}

	g_return_if_fail (tepl_code_comment_view_code_comment_is_supported (code_comment_view));

	buffer = GTK_SOURCE_BUFFER (gtk_text_view_get_buffer (GTK_TEXT_VIEW (code_comment_view->priv->source_view)));

	language = gtk_source_buffer_get_language (buffer);
	get_sanitized_metadata (language,
				&line_comment_start,
				&block_comment_start,
				&block_comment_end);

	gtk_text_buffer_get_selection_bounds (GTK_TEXT_BUFFER (buffer), &selection_start, &selection_end);
	start_line = gtk_text_iter_get_line (&selection_start);
	end_line = gtk_text_iter_get_line (&selection_end);

	gtk_text_buffer_begin_user_action (GTK_TEXT_BUFFER (buffer));

	done = uncomment_line_by_line_with_line_comment_start (GTK_TEXT_BUFFER (buffer),
							       start_line,
							       end_line,
							       line_comment_start);

	if (!done)
	{
		uncomment_line_by_line_with_block_comments (GTK_TEXT_BUFFER (buffer),
							    start_line,
							    end_line,
							    block_comment_start,
							    block_comment_end);
	}

	gtk_text_buffer_end_user_action (GTK_TEXT_BUFFER (buffer));
}
