mirror of
https://github.com/AdaCore/VSS.git
synced 2026-02-12 13:06:25 -08:00
9781 lines
303 KiB
Ada
9781 lines
303 KiB
Ada
------------------------------------------------------------------------------
|
|
-- GNAT Studio --
|
|
-- --
|
|
-- Copyright (C) 2001-2025, AdaCore --
|
|
-- --
|
|
-- This is free software; you can redistribute it and/or modify it under --
|
|
-- terms of the GNU General Public License as published by the Free Soft- --
|
|
-- ware Foundation; either version 3, or (at your option) any later ver- --
|
|
-- sion. This software is distributed in the hope that it will be useful, --
|
|
-- but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHAN- --
|
|
-- TABILITY 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 distributed with this software; see file --
|
|
-- COPYING3. If not, go to http://www.gnu.org/licenses for a complete copy --
|
|
-- of the license. --
|
|
------------------------------------------------------------------------------
|
|
|
|
with Ada.Calendar; use Ada.Calendar;
|
|
with Ada.Characters.Conversions;
|
|
with Ada.Text_IO;
|
|
with System.Address_To_Access_Conversions;
|
|
|
|
pragma Warnings (Off, ".*is an internal GNAT unit");
|
|
with Ada.Strings.Unbounded.Aux;
|
|
pragma Warnings (On, ".*is an internal GNAT unit");
|
|
with Ada.Strings.Maps; use Ada.Strings.Maps;
|
|
|
|
with Interfaces.C.Strings; use Interfaces.C.Strings;
|
|
with System.Address_Image;
|
|
with GNAT.Expect; use GNAT.Expect;
|
|
with GNAT.Regpat; use GNAT.Regpat;
|
|
with GNAT.SHA1;
|
|
|
|
with GNATCOLL.Arg_Lists; use GNATCOLL.Arg_Lists;
|
|
with GNATCOLL.Paragraph_Filling; use GNATCOLL.Paragraph_Filling;
|
|
with GNATCOLL.Projects; use GNATCOLL.Projects;
|
|
with GNATCOLL.Symbols; use GNATCOLL.Symbols;
|
|
with GNATCOLL.Traces; use GNATCOLL.Traces;
|
|
with GNATCOLL.Utils; use GNATCOLL.Utils;
|
|
with GNATCOLL.VFS; use GNATCOLL.VFS;
|
|
|
|
with VSS.Strings.Conversions;
|
|
|
|
with Gdk.Keyval;
|
|
with Gdk.RGBA; use Gdk.RGBA;
|
|
with Gdk.Types.Keysyms; use Gdk.Types.Keysyms;
|
|
with Glib.Convert;
|
|
with Glib.Error; use Glib.Error;
|
|
with Glib.Object; use Glib.Object;
|
|
with Glib.Properties; use Glib.Properties;
|
|
with Glib.Unicode; use Glib.Unicode;
|
|
with Glib.Values; use Glib.Values;
|
|
|
|
with Gtk; use Gtk;
|
|
with Gtk.Enums; use Gtk.Enums;
|
|
with Gtk.Handlers; use Gtk.Handlers;
|
|
with Gtk.Text_Buffer; use Gtk.Text_Buffer;
|
|
with Gtk.Text_Iter; use Gtk.Text_Iter;
|
|
with Gtk.Text_Tag; use Gtk.Text_Tag;
|
|
with Gtk.Text_Tag_Table; use Gtk.Text_Tag_Table;
|
|
with Gtk.Text_View; use Gtk.Text_View;
|
|
|
|
with Gtkada.Dialogs; use Gtkada.Dialogs;
|
|
with Gtkada.MDI; use Gtkada.MDI;
|
|
with Gtkada.Types; use Gtkada.Types;
|
|
|
|
with Pango.Enums; use Pango.Enums;
|
|
|
|
with Commands.Editor; use Commands.Editor;
|
|
with Completion_Module; use Completion_Module;
|
|
with Default_Preferences; use Default_Preferences;
|
|
with GPS.Default_Styles; use GPS.Default_Styles;
|
|
with GPS.Dialogs; use GPS.Dialogs;
|
|
with GPS.Intl; use GPS.Intl;
|
|
with GPS.Kernel; use GPS.Kernel;
|
|
with GPS.Kernel.Charsets; use GPS.Kernel.Charsets;
|
|
with GPS.Kernel.Clipboard; use GPS.Kernel.Clipboard;
|
|
with GPS.Kernel.Contexts; use GPS.Kernel.Contexts;
|
|
with GPS.Kernel.Hooks; use GPS.Kernel.Hooks;
|
|
with GPS.Kernel.MDI; use GPS.Kernel.MDI;
|
|
with GPS.Kernel.Messages; use GPS.Kernel.Messages;
|
|
with GPS.Kernel.Messages.Simple; use GPS.Kernel.Messages.Simple;
|
|
with GPS.Kernel.Modules; use GPS.Kernel.Modules;
|
|
with GPS.Kernel.Preferences; use GPS.Kernel.Preferences;
|
|
with GPS.Kernel.Properties; use GPS.Kernel.Properties;
|
|
with GPS.Kernel.Project; use GPS.Kernel.Project;
|
|
with GPS.Kernel.Scripts; use GPS.Kernel.Scripts;
|
|
with GPS.Kernel.Task_Manager;
|
|
with GPS.Properties;
|
|
with GUI_Utils; use GUI_Utils;
|
|
with Language; use Language;
|
|
with Language.Unknown; use Language.Unknown;
|
|
with Language_Handlers; use Language_Handlers;
|
|
with Src_Editor_Box; use Src_Editor_Box;
|
|
with Src_Editor_Buffer.Blocks;
|
|
with Src_Editor_Buffer.Line_Information;
|
|
with Src_Editor_Buffer.Hooks; use Src_Editor_Buffer.Hooks;
|
|
with Src_Editor_Buffer.Cursors; use Src_Editor_Buffer.Cursors;
|
|
with Src_Editor_Buffer.Text_Handling;
|
|
with Src_Editor_Module; use Src_Editor_Module;
|
|
with Src_Editor_Module.Editors; use Src_Editor_Module.Editors;
|
|
with Src_Editor_Module.Line_Highlighting;
|
|
with Src_Editor_Status_Bar; use Src_Editor_Status_Bar;
|
|
with Src_Highlighting; use Src_Highlighting;
|
|
with String_Utils; use String_Utils;
|
|
with Gtk.Window; use Gtk.Window;
|
|
|
|
package body Src_Editor_Buffer is
|
|
|
|
type Gtk_TB_Access is access all Gtk_Text_Buffer_Record;
|
|
|
|
use Src_Editor_Buffer.Blocks;
|
|
use Src_Editor_Module.Line_Highlighting;
|
|
use Src_Editor_Buffer.Line_Information;
|
|
|
|
use type Basic_Types.Visible_Column_Type;
|
|
use type Glib.Main.G_Source_Id;
|
|
use type GNAT.Strings.String_Access;
|
|
use type System.Address;
|
|
|
|
type Src_Editor_Factory_Access is
|
|
access all Src_Editor_Module.Editors.Src_Editor_Buffer_Factory;
|
|
|
|
Editors_Factory : Src_Editor_Factory_Access;
|
|
|
|
Me : constant Trace_Handle := Create ("GPS.Source_Editor.Buffer");
|
|
|
|
Me_Formatters : constant Trace_Handle :=
|
|
Create ("GPS.SOURCE_EDITOR.BUFFER.FORMATTERS", Off);
|
|
|
|
Prevent_Align : constant Trace_Handle :=
|
|
Create ("GPS.INTERNAL.PREVENT_ALIGN_ON_TAB", On);
|
|
|
|
Auto_Save_No_Reload : constant Trace_Handle :=
|
|
Create ("DIALOG_AUTO_SAVE_NO_RELOAD", Off);
|
|
|
|
Auto_Save_Reload : constant Trace_Handle :=
|
|
Create ("DIALOG_AUTO_SAVE_RELOAD", Off);
|
|
|
|
pragma Unreferenced (Prevent_Align);
|
|
-- This trace is setup here for the benefit of tab.py
|
|
|
|
Buffer_Recompute_Interval : constant Guint := 200;
|
|
-- The interval at which to check whether the buffer should be reparsed,
|
|
-- in milliseconds.
|
|
|
|
Buffer_Recompute_Delay : constant Duration := 1.0;
|
|
-- The delay between the last edit and the re-parsing of the buffer,
|
|
-- in seconds.
|
|
|
|
Src_Editor_Message_Flags : constant Message_Flags := Side_And_Locations;
|
|
|
|
package Buffer_Timeout is new Glib.Main.Generic_Sources (Source_Buffer);
|
|
|
|
function Strlen (Str : Gtkada.Types.Chars_Ptr) return Interfaces.C.size_t;
|
|
pragma Import (C, Strlen);
|
|
-- Import Strlen directly, for efficiency
|
|
|
|
type Delimiter_Type is (Opening, Closing);
|
|
-- ??? missing doc
|
|
|
|
Delimiters : constant array (1 .. 3, Delimiter_Type'Range) of Character :=
|
|
(('(', ')'), ('[', ']'), ('{', '}'));
|
|
-- ??? missing doc
|
|
-- ??? Should we get that from the language ?
|
|
|
|
Strip_Blanks_Property_Name : constant String := "strip-blanks";
|
|
Strip_Lines_Property_Name : constant String := "strip-blanks-lines";
|
|
|
|
package Iter_Access_Address_Conversions is new
|
|
System.Address_To_Access_Conversions (Gtk_Text_Iter);
|
|
|
|
--------------------
|
|
-- Signal Support --
|
|
--------------------
|
|
|
|
Class_Record : Ada_GObject_Class := Uninitialized_Class;
|
|
-- A pointer to the 'class record'
|
|
|
|
Signals : constant Interfaces.C.Strings.chars_ptr_array :=
|
|
(1 => New_String (String (Signal_Cursor_Position_Changed)),
|
|
2 => New_String (String (Signal_Side_Column_Changed)),
|
|
3 => New_String (String (Signal_Side_Column_Configuration_Changed)),
|
|
4 => New_String (String (Signal_Line_Highlights_Changed)),
|
|
5 => New_String (String (Signal_Status_Changed)),
|
|
6 => New_String (String (Signal_Filename_Changed)),
|
|
7 => New_String (String (Signal_Buffer_Information_Changed)),
|
|
8 => New_String (String (Signal_Closed)));
|
|
-- The list of new signals supported by this GObject
|
|
|
|
Signal_Parameters : constant Glib.Object.Signal_Parameter_Types :=
|
|
(1 => (GType_Int, GType_Int),
|
|
2 => (GType_None, GType_None),
|
|
3 => (GType_None, GType_None),
|
|
4 => (GType_None, GType_None),
|
|
5 => (GType_None, GType_None),
|
|
6 => (GType_None, GType_None),
|
|
7 => (GType_None, GType_None),
|
|
8 => (GType_None, GType_None));
|
|
-- The parameters associated to each new signal
|
|
|
|
package Buffer_Callback is new
|
|
Gtk.Handlers.Callback (Widget_Type => Source_Buffer_Record);
|
|
|
|
--------------------------
|
|
-- Forward declarations --
|
|
--------------------------
|
|
|
|
generic
|
|
with function Line_Length (Iter : Gtk_Text_Iter) return Gint;
|
|
with procedure Set_Pos (Iter : in out Gtk_Text_Iter; Pos : Gint);
|
|
procedure Generic_Valid_Position
|
|
(Buffer : Source_Buffer;
|
|
Iter : out Gtk_Text_Iter;
|
|
Found : out Boolean;
|
|
Line : Gint;
|
|
Column : Gint := 0);
|
|
-- Generic version of Is_Valid_Position
|
|
|
|
procedure Changed_Handler (Buffer : access Source_Buffer_Record'Class);
|
|
-- This procedure is used to signal to the clients that the insert
|
|
-- cursor position may have changed by emitting the
|
|
-- "cursor_position_changed" signal.
|
|
|
|
procedure Mark_Set_Handler
|
|
(Buffer : access Source_Buffer_Record'Class;
|
|
Params : Glib.Values.GValues);
|
|
-- This procedure is used to signal to the clients that the insert
|
|
-- cursor position has changed by emitting the "cursor_position_changed"
|
|
-- signal. This signal is emitted only when the mark changed is the
|
|
-- Insert_Mark.
|
|
|
|
procedure Insert_Text_Cb
|
|
(Buffer : access Source_Buffer_Record'Class;
|
|
End_Insert_Iter : Gtk.Text_Iter.Gtk_Text_Iter);
|
|
-- This procedure recomputes the syntax-highlighting of the buffer
|
|
-- in a semi-optimized manor, based on syntax-highlighting already
|
|
-- done before the insertion and the text added.
|
|
--
|
|
-- This procedure assumes that the language has been set.
|
|
|
|
procedure After_Insert_Text
|
|
(Buffer : access Source_Buffer_Record'Class;
|
|
Params : Glib.Values.GValues);
|
|
-- This procedure is just a proxy between the "insert_text" signal
|
|
-- and the Insert_Text_Cb callback. It extracts the parameters from
|
|
-- Params and then call Insert_Text_Cb. For efficiency reasons, the
|
|
-- signal is processed only when Lang is not null and the language
|
|
-- supports syntax highlighting.
|
|
--
|
|
-- Note that this handler is designed to be connected "after", in which
|
|
-- case the Insert_Iter iterator is located at the end of the inserted
|
|
-- text.
|
|
|
|
procedure Before_Insert_Text
|
|
(Buffer : access Source_Buffer_Record'Class;
|
|
Params : Glib.Values.GValues);
|
|
-- First handler connected to the "insert_text" signal
|
|
|
|
procedure Delete_Range_Cb
|
|
(Buffer : access Source_Buffer_Record'Class; Iter : Gtk_Text_Iter);
|
|
-- This procedure recomputes the syntax-highlighting of the buffer
|
|
-- in a semi-optimized manor, based on syntax-highlighting already
|
|
-- done before the deletion and the location of deletion.
|
|
--
|
|
-- This procedure assumes that the language has been set.
|
|
|
|
procedure Before_Delete_Range
|
|
(Buffer : access Source_Buffer_Record'Class;
|
|
Params : Glib.Values.GValues);
|
|
-- Handler connected to the "delete_range" signal, but which occurs before
|
|
-- the actual deletion.
|
|
|
|
procedure After_Delete_Range
|
|
(Buffer : access Source_Buffer_Record'Class;
|
|
Params : Glib.Values.GValues);
|
|
-- This procedure is just a proxy between the "delete_range" signal
|
|
-- and the Delete_Range_Cb callback. It extracts the parameters from
|
|
-- Params and then call Delete_Range_Cb. For efficiency reasons, the
|
|
-- signal is processed only when Lang is not null and the language
|
|
-- supports syntax highlighting.
|
|
--
|
|
-- Note that this handler is designed to be connected "after", in which
|
|
-- case the Start and End iterators are equal, since the text between
|
|
-- these iterators have already been deleted.
|
|
|
|
procedure Buffer_Destroy (Data : System.Address; Buf : System.Address);
|
|
pragma Convention (C, Buffer_Destroy);
|
|
-- Called when the buffer is being destroyed
|
|
|
|
function Automatic_Save (Buffer : Source_Buffer) return Boolean;
|
|
-- Handle automatic save of the buffer, using a timeout
|
|
|
|
procedure Internal_Save_To_File
|
|
(Buffer : Source_Buffer;
|
|
Filename : GNATCOLL.VFS.Virtual_File;
|
|
Internal : Boolean;
|
|
Success : out Boolean;
|
|
Force : Boolean := False);
|
|
-- Low level save function. Only writes the buffer contents on disk,
|
|
-- with no modification on the buffer's settings.
|
|
-- If Internal is True, do not emit kernel signals. This is used notably
|
|
-- for automatic saves.
|
|
|
|
type On_Pref_Changed is new Preferences_Hooks_Function with record
|
|
Buffer : Source_Buffer;
|
|
end record;
|
|
overriding
|
|
procedure Execute
|
|
(Self : On_Pref_Changed;
|
|
Kernel : not null access Kernel_Handle_Record'Class;
|
|
Pref : Preference);
|
|
-- Called when the preferences have changed
|
|
|
|
type On_Project_Changed is new Simple_Hooks_Function with record
|
|
Buffer : Source_Buffer;
|
|
end record;
|
|
overriding
|
|
procedure Execute
|
|
(Self : On_Project_Changed;
|
|
Kernel : not null access Kernel_Handle_Record'Class);
|
|
-- Called when the project has changed
|
|
|
|
type On_Loc_Changed is new File_Location_Hooks_Function with record
|
|
Buffer : Source_Buffer;
|
|
end record;
|
|
overriding
|
|
procedure Execute
|
|
(Self : On_Loc_Changed;
|
|
Kernel : not null access Kernel_Handle_Record'Class;
|
|
File : Virtual_File;
|
|
Line, Column : Integer;
|
|
Project : Project_Type);
|
|
-- Called when the current editor reaches a new location
|
|
|
|
procedure Cursor_Move_Hook (Buffer : access Source_Buffer_Record'Class);
|
|
-- Actions that must be executed whenever the cursor moves
|
|
|
|
procedure User_Edit_Hook (Buffer : access Source_Buffer_Record'Class);
|
|
-- Actions that must be executed whenever the user inserts or deletes text
|
|
|
|
procedure Edit_Hook (Buffer : access Source_Buffer_Record'Class);
|
|
-- Actions that must be executed whenever the buffer text is changed
|
|
|
|
procedure Lines_Remove_Hook_Before
|
|
(Buffer : access Source_Buffer_Record'Class;
|
|
Start_Line : Buffer_Line_Type;
|
|
Count : Buffer_Line_Type);
|
|
-- Actions that must be executed whenever a line is removed.
|
|
-- This is called before the lines are actually removed from the buffer.
|
|
|
|
procedure Lines_Remove_Hook_After
|
|
(Buffer : access Source_Buffer_Record'Class;
|
|
Start_Line : Buffer_Line_Type;
|
|
End_Line : Buffer_Line_Type);
|
|
-- Same as above, but occurs after the lines have been removed
|
|
|
|
procedure Lines_Add_Hook
|
|
(Buffer : access Source_Buffer_Record'Class;
|
|
Start : Buffer_Line_Type;
|
|
Number : Buffer_Line_Type);
|
|
-- Actions that must be executed whenever a line is added.
|
|
-- Start is the number of the line just before the lines were inserted,
|
|
-- Number is the number of lines that were inserted.
|
|
|
|
procedure End_Action_Hook (Buffer : access Source_Buffer_Record'Class);
|
|
-- Actions that must be executed whenever an action is ended
|
|
|
|
procedure Destroy_Hook (Buffer : access Source_Buffer_Record'Class);
|
|
-- Actions that must be executed when the buffer is destroyed
|
|
|
|
procedure Initialize_Hook (Buffer : access Source_Buffer_Record'Class);
|
|
-- Actions that must be executed after initialization
|
|
|
|
procedure Get_Iter_At_Screen_Position
|
|
(Buffer : access Source_Buffer_Record;
|
|
Iter : out Gtk_Text_Iter;
|
|
Line : Gint;
|
|
Column : Visible_Column_Type);
|
|
-- Return the iter at position (Line, Column), tab expansion included.
|
|
-- ??? This function should be removed in the long term, replaced by
|
|
-- the version of Get_Iter_At_Screen_Position that supports blank lines.
|
|
|
|
function Edition_Timeout (Buffer : Source_Buffer) return Boolean;
|
|
-- Timeout called in a timeout after the user has finished editing
|
|
|
|
procedure Free_Column_Info (Column_Info : Columns_Config_Access);
|
|
-- Free the info contained in Column_Info
|
|
|
|
procedure Get_Selection_Bounds
|
|
(Buffer : access Source_Buffer_Record;
|
|
Start_Line : out Gint;
|
|
Start_Column : out Gint;
|
|
End_Line : out Gint;
|
|
End_Column : out Gint;
|
|
Found : out Boolean);
|
|
-- If a portion of the buffer is currently selected, then return the
|
|
-- position of the beginning and the end of the selection. Otherwise,
|
|
-- Found is set to False and the positions returned both point to the
|
|
-- begining of the buffer.
|
|
|
|
procedure Insert
|
|
(Buffer : access Source_Buffer_Record;
|
|
Line : Gint;
|
|
Column : Gint;
|
|
Text : String;
|
|
Enable_Undo : Boolean := True);
|
|
-- Insert the given text in at the specified position.
|
|
--
|
|
-- The validity of the given position must be verified before invoking this
|
|
-- procedure. An incorrect position will cause an Assertion_Failure when
|
|
-- compiled with assertion checks, or an undefined behavior
|
|
-- otherwise.
|
|
-- If Enable_Undo is True, then the insertion action will be
|
|
-- stored in the undo/redo queue.
|
|
|
|
procedure Insert_Text
|
|
(Buffer : access Source_Buffer_Record;
|
|
From_File : Virtual_File;
|
|
Lang_Autodetect : Boolean;
|
|
Is_Auto_Save : Boolean;
|
|
File_Is_New : Boolean;
|
|
Success : out Boolean);
|
|
-- Replace the buffer with the contents of the file. This is undoable.
|
|
|
|
function Compare_SHA1
|
|
(File : Virtual_File; Autosave : Virtual_File) return Boolean;
|
|
-- Compare the SHA1 of File and its autosaved file.
|
|
-- Delete the autosaved file and return False if they are the same.
|
|
|
|
procedure Replace_Slice_Real
|
|
(Buffer : access Source_Buffer_Record;
|
|
Start_Line : Gint;
|
|
Start_Column : Gint;
|
|
End_Line : Gint;
|
|
End_Column : Gint;
|
|
Text : String;
|
|
Enable_Undo : Boolean := True);
|
|
-- Replace the text between the start and end positions by Text.
|
|
--
|
|
-- The validity of the given positions must be verified before invoking
|
|
-- this procedure. An incorrect position will cause an Assertion_Failure
|
|
-- when compiled with assertion checks, or an undefined behavior otherwise.
|
|
|
|
procedure Delete
|
|
(Buffer : access Source_Buffer_Record;
|
|
Line : Gint;
|
|
Column : Gint;
|
|
Length : Gint;
|
|
Enable_Undo : Boolean := True);
|
|
-- Delete Length caracters after the specified position.
|
|
--
|
|
-- The validity of the given position must be verified before invoking this
|
|
-- procedure. An incorrect position will cause an Assertion_Failure when
|
|
-- compiled with assertion checks, or an undefined behavior
|
|
-- otherwise.
|
|
-- If Enable_Undo is True, then the deletion action will be
|
|
-- stored in the undo/redo queue.
|
|
|
|
function Conversion_Error_Message
|
|
(Charset : String) return VSS.Strings.Virtual_String;
|
|
-- Return the location category corresponding to errors when converting
|
|
-- to Charset.
|
|
|
|
procedure On_Paste_Done (Buffer : access Source_Buffer_Record'Class);
|
|
-- Disable the "paste-done" signal introduced in gtk+ 2.18, which breaks
|
|
-- the handling of multiple views
|
|
|
|
procedure Update_Logical_Timestamp
|
|
(Buffer : access Source_Buffer_Record'Class);
|
|
pragma Inline (Update_Logical_Timestamp);
|
|
-- Update the logical timestamp
|
|
|
|
procedure Update_All_Column_Memory
|
|
(Buffer : access Source_Buffer_Record'Class);
|
|
-- Update column memory for every cursor, the main one and every existing
|
|
-- multi cursor
|
|
|
|
function Get_Current_Command
|
|
(Buffer : access Source_Buffer_Record'Class) return Editor_Command
|
|
is (if Buffer.Cursors_Sync.Mode = Manual_Slave
|
|
then Editor_Command (Buffer.Cursors_Sync.MC.Current_Command)
|
|
else Editor_Command (Buffer.Current_Command));
|
|
|
|
procedure Set_Current_Command
|
|
(Buffer : access Source_Buffer_Record'Class; Command : Editor_Command);
|
|
|
|
function Source_Lines_Context
|
|
(Buffer : access Source_Buffer_Record;
|
|
Project : GNATCOLL.Projects.Project_Type;
|
|
Start_Line : Editable_Line_Type;
|
|
End_Line : Editable_Line_Type) return Selection_Context;
|
|
-- Create a selection context for the given lines
|
|
|
|
procedure Set_Trailing_Space_Policy
|
|
(Buffer : access Source_Buffer_Record;
|
|
File : GNATCOLL.VFS.Virtual_File;
|
|
Trailing_Spaces_Found : Boolean);
|
|
-- Detect and set strip trailing space policy for Buffer
|
|
|
|
procedure Set_Trailing_Lines_Policy
|
|
(Buffer : access Source_Buffer_Record;
|
|
File : GNATCOLL.VFS.Virtual_File;
|
|
Trailing_Lines_Found : Boolean);
|
|
-- Detect and set strip trailing empty lines policy for Buffer
|
|
|
|
function Autosaved_File
|
|
(File : GNATCOLL.VFS.Virtual_File) return GNATCOLL.VFS.Virtual_File;
|
|
-- Return the autosaved file corresponding to File.
|
|
-- See also Is_Auto_Save
|
|
|
|
function Is_Editor (Ctxt : Selection_Context) return Boolean;
|
|
-- Return True if the context was created from a source editor
|
|
|
|
function Get_First_Non_Blank_Column
|
|
(Buffer : access Source_Buffer_Record; Line : Editable_Line_Type)
|
|
return Visible_Column_Type;
|
|
-- Return the first column with a non-whitespace character
|
|
|
|
-----------
|
|
-- Hooks --
|
|
-----------
|
|
|
|
type On_File_Deleted is new File_Hooks_Function with record
|
|
Buffer : Source_Buffer;
|
|
end record;
|
|
overriding
|
|
procedure Execute
|
|
(Self : On_File_Deleted;
|
|
Kernel : not null access Kernel_Handle_Record'Class;
|
|
File : Virtual_File);
|
|
-- Callback for the "file_deleted" hook
|
|
|
|
type On_File_Renamed is new File2_Hooks_Function with record
|
|
Buffer : Source_Buffer;
|
|
end record;
|
|
overriding
|
|
procedure Execute
|
|
(Self : On_File_Renamed;
|
|
Kernel : not null access Kernel_Handle_Record'Class;
|
|
File, Renamed : Virtual_File);
|
|
-- Callback for the "file_renamed" hook
|
|
|
|
type On_Semantic_Tree_Updated is new File_Hooks_Function with record
|
|
Buffer : Source_Buffer;
|
|
end record;
|
|
overriding
|
|
procedure Execute
|
|
(Self : On_Semantic_Tree_Updated;
|
|
Kernel : not null access Kernel_Handle_Record'Class;
|
|
File : GNATCOLL.VFS.Virtual_File);
|
|
|
|
procedure Reset_Slave_Cursors_Commands (Buffer : Source_Buffer);
|
|
|
|
procedure Emit_File_Edited
|
|
(Buffer : not null access Source_Buffer_Record'Class;
|
|
Filename : Virtual_File);
|
|
-- Emit the File_Edited hook and call File_Edited on the listeners
|
|
|
|
procedure Emit_File_Closed
|
|
(Buffer : not null access Source_Buffer_Record'Class;
|
|
Filename : Virtual_File);
|
|
-- Emit the File_Closed hook and call File_Closed on the listeners
|
|
|
|
procedure Emit_File_Renamed
|
|
(Buffer : not null access Source_Buffer_Record'Class;
|
|
From : Virtual_File;
|
|
To : Virtual_File);
|
|
-- Emit the File_Renamed hook and call File_Renamed on the listeners
|
|
|
|
procedure Adjust_Tab_Width (Buffer : access Source_Buffer_Record'Class);
|
|
-- Set Tab_Width based on the Language preferences
|
|
|
|
procedure Save_Cursor
|
|
(Buffer : Source_Buffer;
|
|
Mark : Gtk_Text_Mark;
|
|
Cursor_Line : out Gint;
|
|
Cursor_Offset : out Gint);
|
|
-- Save the location of the Mark
|
|
|
|
procedure Place_Cursor
|
|
(Buffer : Source_Buffer;
|
|
Mark : Gtk_Text_Mark;
|
|
Cursor_Line : Gint;
|
|
Cursor_Offset : Gint);
|
|
-- Move the mark to the new location
|
|
|
|
procedure Range_Formatting
|
|
(Buffer : Source_Buffer;
|
|
Mark : Gtk_Text_Mark;
|
|
From, To : Gtk_Text_Iter;
|
|
Force : Boolean := False);
|
|
-- Execute provider selected in the preferences to do range formatting
|
|
|
|
procedure On_Type_Formatting
|
|
(Buffer : Source_Buffer;
|
|
Mark : Gtk_Text_Mark;
|
|
From, To : Gtk_Text_Iter;
|
|
Force : Boolean := False);
|
|
-- Execute provider selected in the preferences to do formatting on enter
|
|
|
|
-----------
|
|
-- Utils --
|
|
-----------
|
|
|
|
procedure Unchecked_Free is new
|
|
Ada.Unchecked_Deallocation
|
|
(Source_Highlighter_Record'Class,
|
|
Source_Highlighter);
|
|
|
|
----------------------
|
|
-- Adjust_Tab_Width --
|
|
----------------------
|
|
|
|
procedure Adjust_Tab_Width (Buffer : access Source_Buffer_Record'Class) is
|
|
begin
|
|
if Buffer.Lang /= null then
|
|
Buffer.Tab_Width := Buffer.Lang.Get_Indentation_Level;
|
|
end if;
|
|
end Adjust_Tab_Width;
|
|
|
|
-------------------------
|
|
-- Set_Current_Command --
|
|
-------------------------
|
|
|
|
procedure Set_Current_Command
|
|
(Buffer : access Source_Buffer_Record'Class; Command : Editor_Command) is
|
|
begin
|
|
if Buffer.Cursors_Sync.Mode = Manual_Slave then
|
|
Buffer.Cursors_Sync.MC.Current_Command := Command_Access (Command);
|
|
else
|
|
Buffer.Current_Command := Command_Access (Command);
|
|
end if;
|
|
end Set_Current_Command;
|
|
|
|
----------------------------------
|
|
-- Reset_Slave_Cursors_Commands --
|
|
----------------------------------
|
|
|
|
procedure Reset_Slave_Cursors_Commands (Buffer : Source_Buffer) is
|
|
begin
|
|
for Cursor of Buffer.Slave_Cursors_List loop
|
|
if not Is_Null_Command (Editor_Command (Cursor.Current_Command)) then
|
|
Cursor.Current_Command := null;
|
|
end if;
|
|
end loop;
|
|
end Reset_Slave_Cursors_Commands;
|
|
|
|
---------------------
|
|
-- Paste_Clipboard --
|
|
---------------------
|
|
|
|
overriding
|
|
procedure Paste_Clipboard
|
|
(Buffer : not null access Source_Buffer_Record;
|
|
Clipboard :
|
|
not null access Gtk.Clipboard.Gtk_Clipboard_Record'Class;
|
|
Default_Editable : Boolean := True)
|
|
is
|
|
Iter : Gtk_Text_Iter;
|
|
S : Loc_T;
|
|
From, To : Gtk_Text_Iter;
|
|
Success : Boolean;
|
|
pragma Unreferenced (Success);
|
|
|
|
G : Group_Block := New_Group (Buffer.Queue);
|
|
begin
|
|
-- This procedure is called from GPS.Kernel.Clipboard.Paste_Clipboard
|
|
if Buffer.Has_MC_Clipboard then
|
|
for C of Get_Cursors (Source_Buffer (Buffer)) loop
|
|
if not C.Is_Main_Cursor then
|
|
Set_Manual_Sync (C);
|
|
Buffer.Get_Iter_At_Mark (Iter, Get_Mark (C));
|
|
Buffer.Insert (Iter, To_String (C.Cursor.Clipboard));
|
|
end if;
|
|
end loop;
|
|
|
|
Set_Manual_Sync (Get_Main_Cursor (Source_Buffer (Buffer)));
|
|
end if;
|
|
|
|
Get_Mark_Position (Source_Buffer (Buffer), Buffer.Get_Insert, S);
|
|
Paste_Clipboard (Gtk_TB_Access (Buffer), Clipboard, Default_Editable);
|
|
Get_Iter_At_Screen_Position (Buffer, From, S.Line, S.Col);
|
|
Buffer.Get_Iter_At_Mark (To, Buffer.Get_Insert);
|
|
|
|
Set_Manual_Sync (Get_Main_Cursor (+Buffer));
|
|
|
|
if Get_Line (From) /= Get_Line (To)
|
|
and then Auto_Indent_On_Paste.Get_Pref
|
|
then
|
|
Success := On_Indent_Action (Source_Buffer (Buffer), From, To);
|
|
end if;
|
|
|
|
Set_Cursors_Auto_Sync (Source_Buffer (Buffer));
|
|
end Paste_Clipboard;
|
|
|
|
-------------------
|
|
-- Cut_Clipboard --
|
|
-------------------
|
|
|
|
overriding
|
|
procedure Cut_Clipboard
|
|
(Buffer : not null access Source_Buffer_Record;
|
|
Clipboard :
|
|
not null access Gtk.Clipboard.Gtk_Clipboard_Record'Class;
|
|
Default_Editable : Boolean)
|
|
is
|
|
Start_Iter, End_Iter : Gtk_Text_Iter;
|
|
begin
|
|
Set_Manual_Sync (Get_Main_Cursor (+Buffer));
|
|
Cut_Clipboard (Gtk_TB_Access (Buffer), Clipboard, Default_Editable);
|
|
|
|
for C of Get_Cursors (Source_Buffer (Buffer)) loop
|
|
if not C.Is_Main_Cursor then
|
|
Buffer.Has_MC_Clipboard := True;
|
|
|
|
Set_Manual_Sync (C);
|
|
Buffer.Get_Iter_At_Mark (Start_Iter, C.Cursor.Sel_Mark);
|
|
Buffer.Get_Iter_At_Mark (End_Iter, C.Cursor.Mark);
|
|
C.Cursor.Clipboard :=
|
|
To_Unbounded_String
|
|
(Buffer.Get_Text
|
|
(Start_Iter, End_Iter, Include_Hidden_Chars => True));
|
|
Buffer.Delete (Start_Iter, End_Iter);
|
|
end if;
|
|
end loop;
|
|
|
|
Update_MC_Selection (Source_Buffer (Buffer));
|
|
|
|
Set_Cursors_Auto_Sync (Source_Buffer (Buffer));
|
|
end Cut_Clipboard;
|
|
|
|
--------------------
|
|
-- Copy_Clipboard --
|
|
--------------------
|
|
|
|
overriding
|
|
procedure Copy_Clipboard
|
|
(Buffer : not null access Source_Buffer_Record;
|
|
Clipboard : not null access Gtk.Clipboard.Gtk_Clipboard_Record'Class)
|
|
is
|
|
Start_Iter, End_Iter : Gtk_Text_Iter;
|
|
Success : Boolean;
|
|
begin
|
|
Set_Manual_Sync (Get_Main_Cursor (+Buffer));
|
|
Buffer.Get_Selection_Bounds
|
|
(Start => Start_Iter, The_End => End_Iter, Result => Success);
|
|
|
|
if Success then
|
|
Clipboard.Set_Text
|
|
(Text =>
|
|
Buffer.Get_Text
|
|
(Start => Start_Iter,
|
|
The_End => End_Iter,
|
|
Include_Hidden_Chars => True));
|
|
end if;
|
|
|
|
for C of Get_Cursors (Source_Buffer (Buffer)) loop
|
|
if not C.Is_Main_Cursor then
|
|
Buffer.Has_MC_Clipboard := True;
|
|
Set_Manual_Sync (C);
|
|
Buffer.Get_Iter_At_Mark (Start_Iter, C.Cursor.Sel_Mark);
|
|
Buffer.Get_Iter_At_Mark (End_Iter, C.Cursor.Mark);
|
|
C.Cursor.Clipboard :=
|
|
To_Unbounded_String
|
|
(Buffer.Get_Text
|
|
(Start_Iter, End_Iter, Include_Hidden_Chars => True));
|
|
end if;
|
|
end loop;
|
|
|
|
Set_Cursors_Auto_Sync (Source_Buffer (Buffer));
|
|
end Copy_Clipboard;
|
|
|
|
-------------
|
|
-- Execute --
|
|
-------------
|
|
|
|
overriding
|
|
procedure Execute
|
|
(Self : On_File_Deleted;
|
|
Kernel : not null access Kernel_Handle_Record'Class;
|
|
File : Virtual_File)
|
|
is
|
|
pragma Unreferenced (Kernel);
|
|
Edited : constant GNATCOLL.VFS.Virtual_File := Self.Buffer.Filename;
|
|
Need_Action : Boolean := False;
|
|
begin
|
|
if Edited /= GNATCOLL.VFS.No_File then
|
|
if Is_Directory (File) and then Is_Parent (File, Edited) then
|
|
Need_Action := True;
|
|
elsif not Is_Directory (File) and then File = Edited then
|
|
Need_Action := True;
|
|
end if;
|
|
end if;
|
|
|
|
if Need_Action then
|
|
Self.Buffer.Saved_Position := -1;
|
|
Self.Buffer.Status_Changed;
|
|
end if;
|
|
end Execute;
|
|
|
|
-------------
|
|
-- Execute --
|
|
-------------
|
|
|
|
overriding
|
|
procedure Execute
|
|
(Self : On_Semantic_Tree_Updated;
|
|
Kernel : not null access Kernel_Handle_Record'Class;
|
|
File : Virtual_File)
|
|
is
|
|
pragma Unreferenced (Kernel);
|
|
Edited : constant GNATCOLL.VFS.Virtual_File := Self.Buffer.Filename;
|
|
begin
|
|
if Edited /= GNATCOLL.VFS.No_File and then File = Edited then
|
|
-- The semantic tree for this buffer has been updated.
|
|
-- Parse the blocks.
|
|
|
|
if Self.Buffer.Parse_Blocks then
|
|
Compute_Blocks (Self.Buffer, Immediate => False);
|
|
Self.Buffer.Blocks_Exact := True;
|
|
end if;
|
|
end if;
|
|
end Execute;
|
|
|
|
-------------
|
|
-- Execute --
|
|
-------------
|
|
|
|
overriding
|
|
procedure Execute
|
|
(Self : On_File_Renamed;
|
|
Kernel : not null access Kernel_Handle_Record'Class;
|
|
File, Renamed : Virtual_File)
|
|
is
|
|
pragma Unreferenced (Kernel);
|
|
Edited : constant Virtual_File := Self.Buffer.Filename;
|
|
Dest : GNATCOLL.VFS.Virtual_File;
|
|
begin
|
|
-- If we are renaming a directory (and not a file), we need to update
|
|
-- the internal name of the file to take the new directory name into
|
|
-- account.
|
|
|
|
if Is_Directory (File) then
|
|
if Is_Parent (File, Edited) then
|
|
Dest :=
|
|
Create_From_Dir (Renamed.Dir, Relative_Path (Edited, File));
|
|
Self.Buffer.Filename := Dest;
|
|
Self.Buffer.Filename_Changed;
|
|
end if;
|
|
|
|
-- Else if we are renaming the current file from elsewhere, we need
|
|
-- to update the buffer filename
|
|
|
|
elsif Edited /= GNATCOLL.VFS.No_File and then Edited = File then
|
|
Self.Buffer.Filename := Renamed;
|
|
Self.Buffer.Filename_Changed;
|
|
end if;
|
|
end Execute;
|
|
|
|
-------------
|
|
-- Execute --
|
|
-------------
|
|
|
|
overriding
|
|
procedure Execute
|
|
(Self : On_Loc_Changed;
|
|
Kernel : not null access Kernel_Handle_Record'Class;
|
|
File : Virtual_File;
|
|
Line, Column : Integer;
|
|
Project : Project_Type)
|
|
is
|
|
pragma Unreferenced (Kernel, File, Line, Column, Project);
|
|
begin
|
|
if Self.Buffer.Highlighter /= null then
|
|
-- Highlight the cursor delimiters
|
|
Self.Buffer.Highlighter.Highlight_Parenthesis;
|
|
end if;
|
|
end Execute;
|
|
|
|
----------------------
|
|
-- Free_Column_Info --
|
|
----------------------
|
|
|
|
procedure Free_Column_Info (Column_Info : Columns_Config_Access) is
|
|
begin
|
|
if Column_Info /= null then
|
|
if Column_Info.all /= null then
|
|
for J in Column_Info.all'Range loop
|
|
GNAT.Strings.Free (Column_Info.all (J).Identifier);
|
|
Unchecked_Free (Column_Info.all (J));
|
|
end loop;
|
|
|
|
Unchecked_Free (Column_Info.all);
|
|
end if;
|
|
|
|
Column_Info.all := null;
|
|
end if;
|
|
end Free_Column_Info;
|
|
|
|
----------
|
|
-- Free --
|
|
----------
|
|
|
|
procedure Free (S : in out Src_String) is
|
|
function To_chars_ptr is new
|
|
Ada.Unchecked_Conversion
|
|
(Unchecked_String_Access,
|
|
Gtkada.Types.Chars_Ptr);
|
|
begin
|
|
if not S.Read_Only then
|
|
-- was returned by Gtk.Text_Buffer.Get_Text
|
|
-- should be released using Gtkada.Types.g_free
|
|
g_free (To_chars_ptr (S.Contents));
|
|
end if;
|
|
S.Contents := null;
|
|
end Free;
|
|
|
|
-----------------------
|
|
-- Reset_Blocks_Info --
|
|
-----------------------
|
|
|
|
procedure Reset_Blocks_Info (Buffer : access Source_Buffer_Record'Class) is
|
|
begin
|
|
Buffer.Blocks_Exact := False;
|
|
end Reset_Blocks_Info;
|
|
|
|
---------------------
|
|
-- Get_String_Line --
|
|
---------------------
|
|
|
|
function Get_String_At_Line
|
|
(Buffer : Source_Buffer;
|
|
Line : Editable_Line_Type;
|
|
Start_Column : Character_Offset_Type := 1;
|
|
End_Column : Character_Offset_Type := 0;
|
|
Include_Hidden_Chars : Boolean := True;
|
|
Include_Last : Boolean := False) return Src_String
|
|
is
|
|
Start_Iter, End_Iter : Gtk_Text_Iter;
|
|
Success : Boolean;
|
|
Result : Src_String;
|
|
Chars : Gtkada.Types.Chars_Ptr;
|
|
|
|
begin
|
|
if Line not in 1 .. Buffer.Last_Editable_Line
|
|
or else Start_Column = End_Column
|
|
then
|
|
return Result;
|
|
end if;
|
|
|
|
Get_Iter_At_Line_Offset
|
|
(Buffer,
|
|
Start_Iter,
|
|
Gint (Buffer.Editable_Lines (Line) - 1),
|
|
Gint (Start_Column - 1));
|
|
|
|
if End_Column /= 0 then
|
|
Get_Iter_At_Line_Offset
|
|
(Buffer,
|
|
End_Iter,
|
|
Gint (Buffer.Editable_Lines (Line) - 1),
|
|
Gint (End_Column - 1));
|
|
else
|
|
Copy (Start_Iter, End_Iter);
|
|
Forward_To_Line_End (End_Iter, Success);
|
|
end if;
|
|
|
|
if Get_Line (Start_Iter) /= Get_Line (End_Iter) then
|
|
-- If Line is empty then Forward_To_Line_End will move
|
|
-- End_Iter to the end of Line + 1.
|
|
-- In this case End_Iter should just point to Start_Iter.
|
|
Copy (Start_Iter, End_Iter);
|
|
end if;
|
|
|
|
if Include_Last then
|
|
Forward_Char (End_Iter, Success);
|
|
end if;
|
|
|
|
Chars := Get_Text (Buffer, Start_Iter, End_Iter, Include_Hidden_Chars);
|
|
Result.Contents := To_Unchecked_String (Chars);
|
|
Result.Length := Integer (Strlen (Chars));
|
|
|
|
return Result;
|
|
end Get_String_At_Line;
|
|
|
|
--------------
|
|
-- Get_Text --
|
|
--------------
|
|
|
|
function Get_Text
|
|
(Buffer : access Source_Buffer_Record;
|
|
Start_Line : Editable_Line_Type := 1;
|
|
Start_Column : Character_Offset_Type := 1;
|
|
End_Line : Editable_Line_Type := 0;
|
|
End_Column : Character_Offset_Type := 0;
|
|
Include_Hidden_Chars : Boolean := True;
|
|
Include_Last : Boolean := False)
|
|
return GNAT.Strings.String_Access
|
|
is
|
|
Start_Iter : Gtk_Text_Iter;
|
|
End_Iter : Gtk_Text_Iter;
|
|
Result : GNAT.Strings.String_Access;
|
|
Success : Boolean;
|
|
|
|
begin
|
|
if Lines_Are_Real (Buffer) then
|
|
Get_Iter_At_Line_Offset
|
|
(Buffer,
|
|
Start_Iter,
|
|
Gint (Get_Buffer_Line (Buffer, Start_Line) - 1),
|
|
Gint (Start_Column - 1));
|
|
|
|
if End_Line /= 0 then
|
|
if End_Column /= 0 then
|
|
Get_Iter_At_Line_Offset
|
|
(Buffer,
|
|
End_Iter,
|
|
Gint (Get_Buffer_Line (Buffer, End_Line) - 1),
|
|
Gint (End_Column - 1));
|
|
else
|
|
Get_Iter_At_Line
|
|
(Buffer,
|
|
End_Iter,
|
|
Gint (Get_Buffer_Line (Buffer, End_Line) - 1));
|
|
Forward_To_Line_End (End_Iter, Success);
|
|
end if;
|
|
else
|
|
Get_End_Iter (Buffer, End_Iter);
|
|
end if;
|
|
|
|
if Include_Last then
|
|
Forward_Char (End_Iter, Success);
|
|
end if;
|
|
|
|
Result :=
|
|
GUI_Utils.Get_Text
|
|
(Buffer, Start_Iter, End_Iter, Include_Hidden_Chars);
|
|
|
|
return Result;
|
|
|
|
else
|
|
if End_Line /= 0 then
|
|
return
|
|
Get_Buffer_Lines
|
|
(Buffer => Buffer,
|
|
Start_Line => Start_Line,
|
|
End_Line => End_Line,
|
|
Start_Column => Start_Column,
|
|
End_Column => End_Column,
|
|
Include_Hidden_Chars => Include_Hidden_Chars,
|
|
Include_Last => Include_Last);
|
|
else
|
|
return
|
|
Get_Buffer_Lines
|
|
(Buffer => Buffer,
|
|
Start_Line => Start_Line,
|
|
End_Line => Buffer.Last_Editable_Line,
|
|
Start_Column => Start_Column,
|
|
Include_Hidden_Chars => Include_Hidden_Chars,
|
|
Include_Last => Include_Last);
|
|
end if;
|
|
end if;
|
|
end Get_Text;
|
|
|
|
---------------
|
|
-- To_String --
|
|
---------------
|
|
|
|
function To_String (S : Src_String) return String is
|
|
begin
|
|
if S.Length = 0 then
|
|
return "";
|
|
else
|
|
return S.Contents (1 .. S.Length);
|
|
end if;
|
|
end To_String;
|
|
|
|
-------------------------------
|
|
-- Get_Strip_Trailing_Blanks --
|
|
-------------------------------
|
|
|
|
function Get_Strip_Trailing_Blanks
|
|
(Buffer : access Source_Buffer_Record) return Boolean is
|
|
begin
|
|
return Buffer.Strip_Trailing_Blanks;
|
|
end Get_Strip_Trailing_Blanks;
|
|
|
|
-------------------------------
|
|
-- Set_Strip_Trailing_Blanks --
|
|
-------------------------------
|
|
|
|
procedure Set_Strip_Trailing_Blanks
|
|
(Buffer : access Source_Buffer_Record; Value : Boolean) is
|
|
begin
|
|
Buffer.Strip_Trailing_Blanks := Value;
|
|
|
|
if Buffer.Filename /= GNATCOLL.VFS.No_File then
|
|
Set_Property
|
|
(Kernel => Buffer.Kernel,
|
|
File => Buffer.Filename,
|
|
Name => Strip_Blanks_Property_Name,
|
|
Property => new GPS.Properties.Boolean_Property'(Value => Value),
|
|
Persistent => True);
|
|
end if;
|
|
end Set_Strip_Trailing_Blanks;
|
|
|
|
------------------------------
|
|
-- Get_Strip_Trailing_Lines --
|
|
------------------------------
|
|
|
|
function Get_Strip_Trailing_Lines
|
|
(Buffer : access Source_Buffer_Record) return Boolean is
|
|
begin
|
|
return Buffer.Strip_Trailing_Lines;
|
|
end Get_Strip_Trailing_Lines;
|
|
|
|
------------------------------
|
|
-- Set_Strip_Trailing_Lines --
|
|
------------------------------
|
|
|
|
procedure Set_Strip_Trailing_Lines
|
|
(Buffer : access Source_Buffer_Record; Value : Boolean) is
|
|
begin
|
|
Buffer.Strip_Trailing_Lines := Value;
|
|
|
|
if Buffer.Filename /= GNATCOLL.VFS.No_File then
|
|
Set_Property
|
|
(Kernel => Buffer.Kernel,
|
|
File => Buffer.Filename,
|
|
Name => Strip_Lines_Property_Name,
|
|
Property => new GPS.Properties.Boolean_Property'(Value => Value),
|
|
Persistent => True);
|
|
end if;
|
|
end Set_Strip_Trailing_Lines;
|
|
|
|
----------------------
|
|
-- Get_Buffer_Lines --
|
|
----------------------
|
|
|
|
function Get_Buffer_Lines
|
|
(Buffer : access Source_Buffer_Record'Class;
|
|
Start_Line : Editable_Line_Type;
|
|
End_Line : Editable_Line_Type;
|
|
Start_Column : Character_Offset_Type := 1;
|
|
End_Column : Character_Offset_Type := 0;
|
|
Include_Hidden_Chars : Boolean := True;
|
|
Include_Last : Boolean := False)
|
|
return GNAT.Strings.String_Access
|
|
is
|
|
-- If the caller is retrieving the text between two iters and there are
|
|
-- special lines. Then End_Line can be greater than Last_Editable_Line
|
|
-- => An extra ASCII.LF will be added at the end for each of the special
|
|
-- lines.
|
|
Real_End : constant Editable_Line_Type :=
|
|
Editable_Line_Type'Min (Buffer.Last_Editable_Line, End_Line);
|
|
Lines : array (Start_Line .. Real_End) of Src_String;
|
|
Len : Integer := 0;
|
|
Index : Integer := 1;
|
|
Output : GNAT.Strings.String_Access;
|
|
begin
|
|
for J in Lines'Range loop
|
|
-- Retrieve the content of the lines: handle the special case
|
|
-- of the first and last lines.
|
|
Lines (J) :=
|
|
Get_String_At_Line
|
|
(Source_Buffer (Buffer),
|
|
Line => J,
|
|
Start_Column =>
|
|
(if J = Lines'First then Start_Column else 1),
|
|
End_Column =>
|
|
(if J = Lines'Last then End_Column else 0),
|
|
Include_Hidden_Chars => Include_Hidden_Chars,
|
|
Include_Last => J /= Lines'Last or else Include_Last);
|
|
Len := Len + Lines (J).Length;
|
|
end loop;
|
|
|
|
Output := new String (1 .. Len);
|
|
|
|
for J in Lines'Range loop
|
|
Len := Lines (J).Length;
|
|
|
|
if Len /= 0 then
|
|
Output (Index .. Index + Len - 1) := Lines (J).Contents (1 .. Len);
|
|
end if;
|
|
|
|
Index := Index + Len;
|
|
|
|
Free (Lines (J));
|
|
end loop;
|
|
|
|
return Output;
|
|
end Get_Buffer_Lines;
|
|
|
|
--------------------
|
|
-- Get_Byte_Index --
|
|
--------------------
|
|
|
|
function Get_Byte_Index (Iter : Gtk_Text_Iter) return Natural is
|
|
Index : Natural := 0;
|
|
begin
|
|
for J in
|
|
0
|
|
.. Get_Editable_Line
|
|
(Source_Buffer (Get_Buffer (Iter)),
|
|
Buffer_Line_Type (Get_Line (Iter)))
|
|
- 1
|
|
loop
|
|
-- Increment the index by the size of the string + 1 (for EOL).
|
|
-- Gtk lines are 0-based, Editable_Lines 1-based, hence the J + 1
|
|
|
|
declare
|
|
Str : Src_String :=
|
|
Get_String_At_Line (Source_Buffer (Get_Buffer (Iter)), J + 1);
|
|
begin
|
|
Index := Index + Str.Length;
|
|
Index := Index + 1;
|
|
|
|
Free (Str);
|
|
end;
|
|
end loop;
|
|
|
|
Index := Index + Natural (Get_Line_Index (Iter));
|
|
|
|
return Index;
|
|
end Get_Byte_Index;
|
|
|
|
---------------------
|
|
-- Edition_Timeout --
|
|
---------------------
|
|
|
|
function Edition_Timeout (Buffer : Source_Buffer) return Boolean is
|
|
CL : Arg_List;
|
|
Start_Iter : Gtk_Text_Iter;
|
|
End_Iter : Gtk_Text_Iter;
|
|
|
|
------------------------------
|
|
-- Run_Highlight_Range_Hook --
|
|
------------------------------
|
|
|
|
procedure Run_Highlight_Range_Hook;
|
|
|
|
procedure Run_Highlight_Range_Hook is
|
|
begin
|
|
Get_Iter_At_Mark
|
|
(Buffer, Start_Iter, Buffer.Highlighter.First_Highlight_Mark);
|
|
Get_Iter_At_Mark
|
|
(Buffer, End_Iter, Buffer.Highlighter.Last_Highlight_Mark);
|
|
Highlight_Range_Hook.Run
|
|
(Kernel => Buffer.Kernel,
|
|
File => Buffer.Filename,
|
|
From_Line => Natural (Get_Line (Start_Iter) + 1),
|
|
To_Line => Natural (Get_Line (End_Iter) + 1));
|
|
end Run_Highlight_Range_Hook;
|
|
|
|
begin
|
|
if Buffer.In_Destruction
|
|
or else Clock
|
|
< Buffer.Blocks_Request_Timestamp + Buffer_Recompute_Delay
|
|
then
|
|
return True;
|
|
end if;
|
|
|
|
-- Re-highlight the highlight region if needed
|
|
if Buffer.Highlighter.Use_Highlighting_Hook then
|
|
Run_Highlight_Range_Hook;
|
|
|
|
else
|
|
Buffer.Highlighter.Highlight_Region;
|
|
end if;
|
|
|
|
-- Perform on-the-fly style check
|
|
|
|
if Buffer.Auto_Syntax_Check then
|
|
CL := Create ("File");
|
|
Append_Argument (CL, +Full_Name (Buffer.Filename), One_Arg);
|
|
Execute_GPS_Shell_Command (Buffer.Kernel, CL);
|
|
Execute_GPS_Shell_Command
|
|
(Buffer.Kernel,
|
|
Parse_String ("File.shadow_check_syntax %1", Separate_Args));
|
|
end if;
|
|
|
|
-- Emit the Buffer_Modifed hook
|
|
if Buffer.Get_Version > 0 then
|
|
Buffer_Modified (Buffer);
|
|
end if;
|
|
|
|
-- Request an asynchronous update of the semantic tree
|
|
if Buffer.Filename /= No_File then
|
|
declare
|
|
Command : constant Update_Async_Access :=
|
|
new Update_Async_Record'
|
|
(Root_Command
|
|
with Kernel => Buffer.Kernel, Filename => Buffer.Filename);
|
|
begin
|
|
GPS.Kernel.Task_Manager.Launch_Background_Command
|
|
(Buffer.Kernel,
|
|
Command_Access (Command),
|
|
Active => True,
|
|
Show_Bar => False,
|
|
Queue_Id => "semantic tree",
|
|
Block_Exit => False);
|
|
end;
|
|
end if;
|
|
|
|
-- Unregister the timeout
|
|
|
|
Buffer.Blocks_Timeout_Registered := False;
|
|
Buffer.Blocks_Timeout := Glib.Main.No_Source_Id;
|
|
return False;
|
|
|
|
exception
|
|
when E : others =>
|
|
Trace (Me, E);
|
|
Buffer.Blocks_Timeout_Registered := False;
|
|
Buffer.Blocks_Timeout := Glib.Main.No_Source_Id;
|
|
return False;
|
|
end Edition_Timeout;
|
|
|
|
--------------------
|
|
-- Get_Delimiters --
|
|
--------------------
|
|
|
|
procedure Get_Delimiters
|
|
(Buffer : access Source_Buffer_Record;
|
|
On_Cursor_Iter : Gtk_Text_Iter;
|
|
First_Delim_Iter : out Gtk_Text_Iter;
|
|
Last_Delim_Iter : out Gtk_Text_Iter;
|
|
Found : out Natural;
|
|
Counter_Max : Natural := 16_384)
|
|
is
|
|
Current : Gtk_Text_Iter;
|
|
|
|
Success : Boolean;
|
|
Counter : Natural;
|
|
|
|
Stack : Natural;
|
|
String_Tag : Boolean;
|
|
C : Character;
|
|
|
|
Delimiter : Integer;
|
|
|
|
Language : constant Language_Access := Get_Language (Buffer);
|
|
|
|
Highlight_Within_Comment : Boolean := False;
|
|
-- Set to True if the cursor is in a comment. In this case, we want to
|
|
-- highlight matching parentheses only within the current comment block.
|
|
|
|
function Check_Char (Forward : Boolean) return Boolean;
|
|
-- Check current character (C) and update current procedure state.
|
|
-- Returns False if parsing must stop (end of file reached for example)
|
|
|
|
----------------
|
|
-- Check_Char --
|
|
----------------
|
|
|
|
function Check_Char (Forward : Boolean) return Boolean is
|
|
Val : constant array (Boolean) of Integer := (1, -1);
|
|
Tmp : Gtk_Text_Iter;
|
|
C2 : Character;
|
|
Success : Boolean;
|
|
|
|
procedure Move_Char;
|
|
pragma Inline (Move_Char);
|
|
-- Move one character backward or forward
|
|
|
|
---------------
|
|
-- Move_Char --
|
|
---------------
|
|
|
|
procedure Move_Char is
|
|
begin
|
|
if Forward then
|
|
Forward_Char (Tmp, Success);
|
|
else
|
|
Backward_Char (Tmp, Success);
|
|
end if;
|
|
end Move_Char;
|
|
|
|
In_Comment : constant Boolean :=
|
|
Is_In_Comment (Source_Buffer (Buffer), Current);
|
|
|
|
begin
|
|
-- If we are looking to highlight only within the current comment,
|
|
-- and the character we are looking at is not in a comment, exit.
|
|
|
|
if Highlight_Within_Comment
|
|
and then not (Is_Blank (C) or else In_Comment)
|
|
then
|
|
return False;
|
|
end if;
|
|
|
|
if C = Delimiters (Delimiter, Closing)
|
|
and then not String_Tag
|
|
and then (not In_Comment or else Highlight_Within_Comment)
|
|
then
|
|
Stack := Stack + Val (Forward);
|
|
|
|
elsif C = Delimiters (Delimiter, Opening)
|
|
and then not String_Tag
|
|
and then (not In_Comment or else Highlight_Within_Comment)
|
|
then
|
|
Stack := Stack - Val (Forward);
|
|
|
|
elsif C = '"' then
|
|
String_Tag := not String_Tag;
|
|
|
|
elsif C = ''' then
|
|
-- Check if this is a character
|
|
Copy (Current, Tmp);
|
|
|
|
Move_Char;
|
|
|
|
if not Success then
|
|
return False;
|
|
end if;
|
|
|
|
Move_Char;
|
|
|
|
if not Success then
|
|
return False;
|
|
end if;
|
|
|
|
C2 := Get_Char (Tmp);
|
|
|
|
if C2 = ''' then
|
|
Copy (Tmp, Current);
|
|
end if;
|
|
end if;
|
|
|
|
return True;
|
|
end Check_Char;
|
|
|
|
begin
|
|
-- Find a closing delimiter
|
|
|
|
Found := 0;
|
|
Delimiter := -1;
|
|
Copy (On_Cursor_Iter, Current);
|
|
Backward_Char (Current, Success);
|
|
|
|
if Language /= null
|
|
and then Language /= Unknown_Lang
|
|
and then Is_In_Comment (Source_Buffer (Buffer), Current)
|
|
then
|
|
-- The current character is in a comment: set the corresponding flag
|
|
Highlight_Within_Comment := True;
|
|
end if;
|
|
|
|
if Success then
|
|
C := Get_Char (Current);
|
|
|
|
for J in Delimiters'Range (1) loop
|
|
if Delimiters (J, Closing) = C then
|
|
Delimiter := J;
|
|
exit;
|
|
end if;
|
|
end loop;
|
|
end if;
|
|
|
|
if Delimiter in Delimiters'Range (1) then
|
|
Counter := 0;
|
|
Stack := 1;
|
|
String_Tag := False;
|
|
|
|
Backward_Char (Current, Success);
|
|
|
|
while Success and then Counter < Counter_Max loop
|
|
C := Get_Char (Current);
|
|
|
|
Success := Check_Char (Forward => False);
|
|
exit when not Success;
|
|
|
|
if Stack = 0 and then not String_Tag then
|
|
Copy (Current, First_Delim_Iter);
|
|
Copy (On_Cursor_Iter, Last_Delim_Iter);
|
|
|
|
Found := Found + 1;
|
|
exit;
|
|
end if;
|
|
|
|
Counter := Counter + 1;
|
|
Backward_Char (Current, Success);
|
|
end loop;
|
|
end if;
|
|
|
|
-- Highlight next parenthesis, if necessary
|
|
|
|
Delimiter := -1;
|
|
Copy (On_Cursor_Iter, Current);
|
|
C := Get_Char (On_Cursor_Iter);
|
|
|
|
for J in Delimiters'Range (1) loop
|
|
if Delimiters (J, Opening) = C then
|
|
Delimiter := J;
|
|
exit;
|
|
end if;
|
|
end loop;
|
|
|
|
if Delimiter in Delimiters'Range (1) then
|
|
Counter := 0;
|
|
Stack := 1;
|
|
String_Tag := False;
|
|
|
|
Forward_Char (Current, Success);
|
|
|
|
while Success and then Counter < Counter_Max loop
|
|
C := Get_Char (Current);
|
|
|
|
Success := Check_Char (Forward => True);
|
|
exit when not Success;
|
|
|
|
if Stack = 0 and then not String_Tag then
|
|
if Found = 0 then
|
|
Copy (On_Cursor_Iter, First_Delim_Iter);
|
|
end if;
|
|
|
|
Forward_Char (Current, Success);
|
|
Copy (Current, Last_Delim_Iter);
|
|
|
|
Found := Found + 1;
|
|
exit;
|
|
end if;
|
|
|
|
Counter := Counter + 1;
|
|
Forward_Char (Current, Success);
|
|
end loop;
|
|
end if;
|
|
end Get_Delimiters;
|
|
|
|
---------------------------
|
|
-- Register_Edit_Timeout --
|
|
---------------------------
|
|
|
|
procedure Register_Edit_Timeout (Buffer : access Source_Buffer_Record'Class)
|
|
is
|
|
Timeout : Gint;
|
|
begin
|
|
Buffer.Blocks_Request_Timestamp := Clock;
|
|
|
|
if Buffer.In_Destruction then
|
|
return;
|
|
end if;
|
|
|
|
if not Buffer.Blocks_Timeout_Registered
|
|
and then Buffer.Blocks_Timeout = Glib.Main.No_Source_Id
|
|
then
|
|
Buffer.Blocks_Timeout_Registered := True;
|
|
Buffer.Blocks_Timeout :=
|
|
Buffer_Timeout.Timeout_Add
|
|
(Buffer_Recompute_Interval,
|
|
Edition_Timeout'Access,
|
|
Source_Buffer (Buffer));
|
|
end if;
|
|
|
|
Timeout := Gint (Integer'(Periodic_Save.Get_Pref));
|
|
if not Buffer.Timeout_Registered and then Timeout > 0 then
|
|
Buffer.Timeout_Id :=
|
|
Buffer_Timeout.Timeout_Add
|
|
(Guint (Timeout) * 1000,
|
|
Automatic_Save'Access,
|
|
Buffer.all'Access);
|
|
Buffer.Timeout_Registered := True;
|
|
end if;
|
|
end Register_Edit_Timeout;
|
|
|
|
---------------------
|
|
-- Get_Buffer_Line --
|
|
---------------------
|
|
|
|
function Get_Buffer_Line
|
|
(Buffer : access Source_Buffer_Record; Line : Editable_Line_Type)
|
|
return Buffer_Line_Type is
|
|
begin
|
|
if not Buffer.Original_Text_Inserted then
|
|
return Buffer_Line_Type (Line);
|
|
end if;
|
|
|
|
if Buffer.Editable_Lines /= null
|
|
and then Line in Buffer.Editable_Lines'Range
|
|
then
|
|
return Buffer.Editable_Lines (Line);
|
|
end if;
|
|
|
|
return 0;
|
|
end Get_Buffer_Line;
|
|
|
|
-----------------------
|
|
-- Get_Editable_Line --
|
|
-----------------------
|
|
|
|
function Get_Editable_Line
|
|
(Buffer : access Source_Buffer_Record'Class; Line : Buffer_Line_Type)
|
|
return Editable_Line_Type is
|
|
begin
|
|
if Buffer.Line_Data /= null and then Line in Buffer.Line_Data'Range then
|
|
return Buffer.Line_Data (Line).Editable_Line;
|
|
end if;
|
|
|
|
return 0;
|
|
end Get_Editable_Line;
|
|
|
|
--------------------
|
|
-- Automatic_Save --
|
|
--------------------
|
|
|
|
function Automatic_Save (Buffer : Source_Buffer) return Boolean is
|
|
Success : Boolean;
|
|
begin
|
|
if Buffer.Modified_Auto and then Buffer.Filename /= No_File then
|
|
Internal_Save_To_File
|
|
(Buffer, Autosaved_File (Buffer.Filename), True, Success);
|
|
Buffer.Modified_Auto := False;
|
|
end if;
|
|
|
|
-- timeout will be restarted when the bufer is changed
|
|
Buffer.Timeout_Registered := False;
|
|
Buffer.Timeout_Id := Glib.Main.No_Source_Id;
|
|
return False;
|
|
end Automatic_Save;
|
|
|
|
----------------------
|
|
-- Cursor_Move_Hook --
|
|
----------------------
|
|
|
|
procedure Cursor_Move_Hook (Buffer : access Source_Buffer_Record'Class) is
|
|
begin
|
|
-- Clear the completion data if we are not already completing
|
|
|
|
if not Buffer.Inserting then
|
|
Reset_Completion_Data;
|
|
End_Action (Buffer);
|
|
Reset_Slave_Cursors_Commands (Source_Buffer (Buffer));
|
|
end if;
|
|
|
|
Location_Changed (Source_Buffer (Buffer));
|
|
end Cursor_Move_Hook;
|
|
|
|
---------------
|
|
-- Edit_Hook --
|
|
---------------
|
|
|
|
procedure Edit_Hook (Buffer : access Source_Buffer_Record'Class) is
|
|
begin
|
|
-- Request re-parsing of the blocks
|
|
|
|
if Buffer.Modifying_Editable_Lines then
|
|
Buffer.Reset_Blocks_Info;
|
|
end if;
|
|
|
|
Register_Edit_Timeout (Buffer);
|
|
end Edit_Hook;
|
|
|
|
--------------------
|
|
-- User_Edit_Hook --
|
|
--------------------
|
|
|
|
procedure User_Edit_Hook (Buffer : access Source_Buffer_Record'Class) is
|
|
pragma Unreferenced (Buffer);
|
|
begin
|
|
Reset_Completion_Data;
|
|
end User_Edit_Hook;
|
|
|
|
------------------
|
|
-- Destroy_Hook --
|
|
------------------
|
|
|
|
procedure Destroy_Hook (Buffer : access Source_Buffer_Record'Class) is
|
|
begin
|
|
-- ??? Must remove the line information column
|
|
|
|
-- Unregister the blocks timeout
|
|
|
|
if Buffer.Blocks_Timeout /= Glib.Main.No_Source_Id then
|
|
Buffer.Blocks_Timeout_Registered := False;
|
|
Glib.Main.Remove (Buffer.Blocks_Timeout);
|
|
Buffer.Blocks_Timeout := Glib.Main.No_Source_Id;
|
|
end if;
|
|
|
|
if Buffer.Hightlight_Messages_Idle /= Glib.Main.No_Source_Id then
|
|
Glib.Main.Remove (Buffer.Hightlight_Messages_Idle);
|
|
Buffer.Hightlight_Messages_Idle := Glib.Main.No_Source_Id;
|
|
end if;
|
|
|
|
GNAT.Strings.Free (Buffer.Forced_Title);
|
|
end Destroy_Hook;
|
|
|
|
----------
|
|
-- Free --
|
|
----------
|
|
|
|
procedure Free (Info : in out Extra_Information_Access) is
|
|
procedure Unchecked_Free is new
|
|
Ada.Unchecked_Deallocation
|
|
(Extra_Information_Record,
|
|
Extra_Information_Access);
|
|
begin
|
|
if Info /= null then
|
|
GNAT.Strings.Free (Info.Identifier);
|
|
Free (Info.Info);
|
|
GNAT.Strings.Free (Info.Tooltip);
|
|
GNAT.Strings.Free (Info.Icon);
|
|
Unchecked_Free (Info);
|
|
end if;
|
|
end Free;
|
|
|
|
--------------------
|
|
-- Buffer_Destroy --
|
|
--------------------
|
|
|
|
procedure Buffer_Destroy (Data : System.Address; Buf : System.Address) is
|
|
|
|
procedure Free (X : in out Line_Info_Width_Array);
|
|
-- Free memory associated to X
|
|
|
|
Stub : Source_Buffer_Record;
|
|
pragma Unreferenced (Data);
|
|
pragma Warnings (Off, Stub);
|
|
|
|
Buffer : constant Source_Buffer :=
|
|
Source_Buffer (Get_User_Data (Buf, Stub));
|
|
|
|
----------
|
|
-- Free --
|
|
----------
|
|
|
|
procedure Free (X : in out Line_Info_Width_Array) is
|
|
begin
|
|
for J in X'Range loop
|
|
Free (Buffer, X (J), Free_Messages => False);
|
|
end loop;
|
|
end Free;
|
|
|
|
begin
|
|
Trace
|
|
(Me,
|
|
"Destroying Buffer buffer="
|
|
& System.Address_Image (Buffer.all'Address)
|
|
& " widget="
|
|
& System.Address_Image (Buf));
|
|
Buffer.In_Destruction := True;
|
|
|
|
-- Destroying listeners
|
|
|
|
while not Buffer.Listeners.Is_Empty loop
|
|
declare
|
|
procedure Unchecked_Free is new
|
|
Ada.Unchecked_Deallocation
|
|
(Editor_Listener'Class,
|
|
Editor_Listener_Access);
|
|
|
|
Listener : Editor_Listener_Access :=
|
|
Buffer.Listeners.First_Element;
|
|
|
|
begin
|
|
Listener.Finalize;
|
|
Unchecked_Free (Listener);
|
|
Buffer.Listeners.Delete_First;
|
|
end;
|
|
end loop;
|
|
|
|
-- We do not free memory associated to Buffer.Current_Command, since
|
|
-- this command is already freed when freeing Buffer.Queue.
|
|
|
|
Destroy_Hook (Buffer);
|
|
|
|
if Buffer.Timeout_Registered then
|
|
Glib.Main.Remove (Buffer.Timeout_Id);
|
|
Buffer.Timeout_Registered := False;
|
|
|
|
if Buffer.Filename /= GNATCOLL.VFS.No_File then
|
|
Buffer.Delete_Autosaved_File (Buffer.Filename);
|
|
end if;
|
|
end if;
|
|
|
|
-- Remove the undo redo queue if it is indeed the currently registered
|
|
-- queue. It may happen that the focus is given to another editor
|
|
-- before closing this one, in which case we do not want to reset
|
|
-- the undo/redo here.
|
|
|
|
if Get_Undo_Redo_Queue = Buffer.Queue then
|
|
Remove_Controls (Buffer);
|
|
end if;
|
|
Empty_Queue (Buffer.Queue);
|
|
|
|
Free_File_Information (Buffer);
|
|
|
|
Free_Column_Info (Buffer.Editable_Line_Info_Columns);
|
|
Unchecked_Free (Buffer.Editable_Line_Info_Columns);
|
|
|
|
if Buffer.Editable_Lines /= null then
|
|
Reset_Blocks_Info (Buffer);
|
|
Unchecked_Free (Buffer.Editable_Lines);
|
|
end if;
|
|
|
|
for J in Buffer.Line_Data'Range loop
|
|
for K in Highlight_Location loop
|
|
Unchecked_Free (Buffer.Line_Data (J).Highlighting (K).Enabled);
|
|
end loop;
|
|
|
|
if Buffer.Line_Data (J).Side_Info_Data /= null then
|
|
Free (Buffer.Line_Data (J).Side_Info_Data.all);
|
|
Unchecked_Free (Buffer.Line_Data (J).Side_Info_Data);
|
|
end if;
|
|
end loop;
|
|
|
|
Unchecked_Free (Buffer.Line_Data);
|
|
GNAT.Strings.Free (Buffer.Charset);
|
|
|
|
Unref (Buffer.Non_Editable_Tag);
|
|
Unchecked_Free (Buffer.Highlighter);
|
|
Unref (Buffer.Hyper_Mode_Tag);
|
|
|
|
Reset_Completion_Data;
|
|
end Buffer_Destroy;
|
|
|
|
---------------------
|
|
-- Changed_Handler --
|
|
---------------------
|
|
|
|
procedure Changed_Handler (Buffer : access Source_Buffer_Record'Class) is
|
|
begin
|
|
Emit_New_Cursor_Position (Buffer);
|
|
|
|
if Buffer.Modifying_Editable_Lines then
|
|
Buffer.Modified_Auto := True;
|
|
end if;
|
|
|
|
exception
|
|
when E : others =>
|
|
Trace (Me, E);
|
|
end Changed_Handler;
|
|
|
|
----------------------
|
|
-- Mark_Set_Handler --
|
|
----------------------
|
|
|
|
procedure Mark_Set_Handler
|
|
(Buffer : access Source_Buffer_Record'Class; Params : Glib.Values.GValues)
|
|
is
|
|
Mark : constant Gtk_Text_Mark :=
|
|
Get_Text_Mark (Glib.Values.Nth (Params, 2));
|
|
Iter : Gtk_Text_Iter;
|
|
begin
|
|
-- Emit the new cursor position if it is the Insert_Mark that was
|
|
-- changed.
|
|
|
|
if Buffer.Setting_Mark then
|
|
return;
|
|
end if;
|
|
|
|
Buffer.Setting_Mark := True;
|
|
|
|
-- If the mark being moved corresponds to the selection_bound (which is
|
|
-- moved each time the insert cursor or the selection changes), emit
|
|
-- the corresponding signal.
|
|
|
|
if Mark = Buffer.Insert_Mark then
|
|
|
|
if Buffer.Cursors_Sync.Mode = Auto then
|
|
Remove_All_Slave_Cursors (Source_Buffer (Buffer));
|
|
end if;
|
|
|
|
-- If we are not currently in an undo/redo group, moving the
|
|
-- cursor should break the grouping of actions. This way, if you
|
|
-- are typing 'a' then clicking then typing 'b', GNAT Studio should
|
|
-- require two undos to remove 'a' then 'b'.
|
|
if not Buffer.Inserting then
|
|
Change_Group (Buffer.Queue);
|
|
end if;
|
|
|
|
Emit_New_Cursor_Position (Buffer);
|
|
Cursor_Move_Hook (Buffer);
|
|
Buffer.Get_Iter_At_Mark (Iter, Mark);
|
|
Buffer.Cursor_Column_Memory := Get_Line_Offset (Iter);
|
|
|
|
declare
|
|
Editable_Line : Editable_Line_Type;
|
|
Visible_Column : Visible_Column_Type;
|
|
begin
|
|
Get_Iter_Position
|
|
(Source_Buffer (Buffer), Iter, Editable_Line, Visible_Column);
|
|
|
|
for Listener of Buffer.Listeners loop
|
|
Listener.After_Cursor_Moved
|
|
(Buffer.Editor_Buffer.New_Location
|
|
(Integer (Editable_Line), Visible_Column),
|
|
not Buffer.Inserting);
|
|
end loop;
|
|
end;
|
|
end if;
|
|
|
|
for Cursor of Buffer.Slave_Cursors_List loop
|
|
if Cursor.Mark = Mark then
|
|
Buffer.Get_Iter_At_Mark (Iter, Mark);
|
|
Cursor.Column_Memory := Get_Line_Offset (Iter);
|
|
end if;
|
|
end loop;
|
|
|
|
Buffer.Setting_Mark := False;
|
|
|
|
exception
|
|
when E : others =>
|
|
Trace (Me, E);
|
|
end Mark_Set_Handler;
|
|
|
|
--------------------
|
|
-- Insert_Text_Cb --
|
|
--------------------
|
|
|
|
procedure Insert_Text_Cb
|
|
(Buffer : access Source_Buffer_Record'Class;
|
|
End_Insert_Iter : Gtk.Text_Iter.Gtk_Text_Iter) is
|
|
begin
|
|
if not Buffer.Modifying_Editable_Lines then
|
|
return;
|
|
end if;
|
|
|
|
Source_Buffer (Buffer).Highlighter.Update_Highlight_Region
|
|
(End_Insert_Iter);
|
|
end Insert_Text_Cb;
|
|
|
|
-----------------------
|
|
-- After_Insert_Text --
|
|
-----------------------
|
|
|
|
procedure After_Insert_Text
|
|
(Buffer : access Source_Buffer_Record'Class; Params : Glib.Values.GValues)
|
|
is
|
|
Text : constant Unchecked_String_Access :=
|
|
To_Unchecked_String (Get_Chars (Nth (Params, 2)));
|
|
Length : constant Integer := Integer (Get_Int (Nth (Params, 3)));
|
|
Start : Buffer_Line_Type;
|
|
Iter : Gtk_Text_Iter;
|
|
Start_Iter : Gtk_Text_Iter;
|
|
Start_Line : Editable_Line_Type;
|
|
Start_Column : Visible_Column_Type;
|
|
Ignored : Boolean;
|
|
Number : Buffer_Line_Type := 0;
|
|
begin
|
|
Update_Logical_Timestamp (Buffer);
|
|
|
|
Get_Text_Iter (Nth (Params, 1), Iter);
|
|
|
|
-- Get the coordinates of the start
|
|
Copy (Iter, Start_Iter);
|
|
Backward_Chars
|
|
(Start_Iter, Gint (UTF8_Strlen (Text (1 .. Length))), Ignored);
|
|
Get_Iter_Position
|
|
(Source_Buffer (Buffer), Start_Iter, Start_Line, Start_Column);
|
|
|
|
-- After a text insertion, both the selection mark and the insertion
|
|
-- mark are at the location where the insertion ended. Update the
|
|
-- corresponding editor command.
|
|
|
|
declare
|
|
C : constant Editor_Command := Get_Current_Command (Buffer);
|
|
begin
|
|
if C /= null
|
|
and then not Buffer.Inserting
|
|
and then Buffer.Cursors_Sync.Mode = Manual_Slave
|
|
then
|
|
C.Set_End_Location (Iter);
|
|
end if;
|
|
end;
|
|
|
|
-- Call Add_Lines, to compute added lines for the side column
|
|
|
|
Start := Buffer_Line_Type (Get_Line (Iter) + 1);
|
|
|
|
for J in 1 .. Length loop
|
|
if Text (J) = ASCII.LF then
|
|
Number := Number + 1;
|
|
end if;
|
|
end loop;
|
|
|
|
if Number > 0 then
|
|
Lines_Add_Hook (Buffer, Start - Number, Number);
|
|
|
|
Emit_New_Cursor_Position (Buffer);
|
|
-- This is already done when the cursor is moved but it is too early
|
|
-- when new lines are inserted at the end of the buffer: the fact
|
|
-- that lines have been added as to be reflected in the buffer data
|
|
-- before the status bar is refereshed.
|
|
-- When indentation is enabled "cursor_position_changed" is emitted
|
|
-- twice (once after line information has been recomputed) and the
|
|
-- status bar is properly refreshed. This is not the case when
|
|
-- auto indentation is not performed (preference disabled or
|
|
-- buffer language unknown). As a result, the status bar report a
|
|
-- cursor on line 0.
|
|
|
|
end if;
|
|
|
|
-- Perform insertion for every multi cursor
|
|
-- If we are in auto mode
|
|
if Buffer.Cursors_Sync.Mode = Auto then
|
|
declare
|
|
Iter : Gtk_Text_Iter;
|
|
G : Group_Block := Current_Group (Buffer.Queue);
|
|
begin
|
|
for C of Get_Cursors (Source_Buffer (Buffer)) loop
|
|
if not C.Is_Main_Cursor then
|
|
-- Perform insertion for the multi cursor
|
|
Set_Manual_Sync (C);
|
|
Buffer.Get_Iter_At_Mark (Iter, C.Cursor.Mark);
|
|
Buffer.Insert (Iter, Text (1 .. Length));
|
|
end if;
|
|
end loop;
|
|
|
|
Set_Cursors_Auto_Sync (Source_Buffer (Buffer));
|
|
|
|
declare
|
|
Iter_Acc : Gtk_Text_Iter renames
|
|
Iter_Access_Address_Conversions.To_Pointer
|
|
(Get_Address (Nth (Params, 1))).all;
|
|
|
|
begin
|
|
Buffer.Get_Iter_At_Mark (Iter_Acc, Buffer.Insert_Mark);
|
|
end;
|
|
end;
|
|
end if;
|
|
|
|
Update_All_Column_Memory (Buffer);
|
|
|
|
if Buffer.Modifying_Editable_Lines then
|
|
for Listener of Buffer.Listeners loop
|
|
Listener.After_Insert_Text
|
|
(Buffer.Editor_Buffer.New_Location
|
|
(Integer (Start_Line), Start_Column),
|
|
Text (1 .. Length),
|
|
not Buffer.Inserting);
|
|
end loop;
|
|
end if;
|
|
|
|
-- Emit the Character_Added hook. Do this only if we are appending only
|
|
-- one character. Eliminate the obvious cases when we are writing more
|
|
-- than one character, so as not to have to perform UTF8 computations
|
|
-- in these cases.
|
|
|
|
if Number = 0 and then Length < 4 then
|
|
declare
|
|
Index : Natural;
|
|
begin
|
|
Index := UTF8_Find_Next_Char (Text (1 .. Length), Text'First);
|
|
|
|
if Index > Length then
|
|
Character_Added
|
|
(Source_Buffer (Buffer),
|
|
UTF8_Get_Char (Text (1 .. Length)),
|
|
Interactive => not Buffer.Inserting);
|
|
end if;
|
|
end;
|
|
end if;
|
|
|
|
-- We might have invalidate the original iter, so retrieve a new one
|
|
-- at the insert mark's location, forwarding it by the number of
|
|
-- chars that are being added, to highlight the inserted text.
|
|
|
|
declare
|
|
End_Iter : Gtk_Text_Iter;
|
|
Success : Boolean := False;
|
|
begin
|
|
Src_Editor_Buffer.Text_Handling.Get_Iter
|
|
(Buffer => Buffer,
|
|
Iter => End_Iter,
|
|
Line => Start_Line,
|
|
Column =>
|
|
Collapse_Tabs
|
|
(Buffer => Buffer,
|
|
Line => Start_Line,
|
|
Column => Start_Column));
|
|
Forward_Chars
|
|
(Iter => End_Iter, Count => Gint (Length), Result => Success);
|
|
|
|
if Buffer.Lang /= null
|
|
and then Get_Language_Context (Buffer.Lang).Syntax_Highlighting
|
|
then
|
|
Insert_Text_Cb (Buffer => Buffer, End_Insert_Iter => End_Iter);
|
|
end if;
|
|
end;
|
|
|
|
declare
|
|
Address : constant System.Address :=
|
|
Get_Address (Nth (Params, 1));
|
|
Insert_Iter : Gtk_Text_Iter;
|
|
Original : Gtk_Text_Iter
|
|
with Import, Address => Address;
|
|
begin
|
|
Buffer.Get_Iter_At_Mark (Insert_Iter, Buffer.Insert_Mark);
|
|
|
|
-- From Gtk "insert-text" signal documentation:
|
|
-- if your handler runs before the default handler it must not
|
|
-- invalidate the location iter (or has to revalidate it).
|
|
-- Let's revalidate original TextIter:
|
|
Copy (Insert_Iter, Dest => Original);
|
|
end;
|
|
|
|
exception
|
|
when E : others =>
|
|
Trace (Me, E);
|
|
end After_Insert_Text;
|
|
|
|
------------------------
|
|
-- Before_Insert_Text --
|
|
------------------------
|
|
|
|
procedure Before_Insert_Text
|
|
(Buffer : access Source_Buffer_Record'Class; Params : Glib.Values.GValues)
|
|
is
|
|
|
|
procedure Update_Insert_Command
|
|
(Buffer : Source_Buffer;
|
|
User_Action : Action_Type;
|
|
Command : out Editor_Command;
|
|
Pos : Gtk_Text_Iter;
|
|
Sel_Pos : Gtk_Text_Iter;
|
|
Text : String;
|
|
Is_Main_Action : Boolean := True);
|
|
-- Update the command with the given action
|
|
|
|
procedure Update_Insert_Command
|
|
(Buffer : Source_Buffer;
|
|
User_Action : Action_Type;
|
|
Command : out Editor_Command;
|
|
Pos : Gtk_Text_Iter;
|
|
Sel_Pos : Gtk_Text_Iter;
|
|
Text : String;
|
|
Is_Main_Action : Boolean := True)
|
|
is
|
|
Line, Sel_Line : Editable_Line_Type;
|
|
Col, Sel_Col : Character_Offset_Type;
|
|
|
|
procedure End_Action;
|
|
procedure End_Action is
|
|
begin
|
|
if Is_Main_Action then
|
|
End_Action (Buffer);
|
|
end if;
|
|
end End_Action;
|
|
|
|
procedure Create_And_Enqueue_Command;
|
|
procedure Create_And_Enqueue_Command is
|
|
C : constant Src_Editor_Buffer.Cursors.Cursor :=
|
|
(if Buffer.Cursors_Sync.Mode = Manual_Slave
|
|
then
|
|
Src_Editor_Buffer.Cursors.Create
|
|
(Buffer.Cursors_Sync.MC, Buffer)
|
|
else Get_Main_Cursor (+Buffer));
|
|
begin
|
|
Create
|
|
(Item => Command,
|
|
Mode => Insertion,
|
|
Buffer => Buffer,
|
|
User_Executed => False,
|
|
Cursor_Loc => (Line, Col),
|
|
Sel_Loc => (Sel_Line, Sel_Col),
|
|
C => C);
|
|
Enqueue (Buffer, Command_Access (Command), User_Action);
|
|
end Create_And_Enqueue_Command;
|
|
|
|
begin
|
|
Get_Iter_Position (Buffer, Pos, Line, Col);
|
|
Get_Iter_Position (Buffer, Sel_Pos, Sel_Line, Sel_Col);
|
|
|
|
if Is_Null_Command (Command) then
|
|
Create_And_Enqueue_Command;
|
|
elsif Get_Mode (Command) = Insertion then
|
|
if (User_Action = Insert_Spaces
|
|
and then Buffer.Last_User_Action /= Insert_Spaces)
|
|
or else (User_Action = Insert_Line
|
|
and then Buffer.Last_User_Action /= Insert_Line)
|
|
then
|
|
End_Action;
|
|
Create_And_Enqueue_Command;
|
|
end if;
|
|
else
|
|
End_Action;
|
|
Create_And_Enqueue_Command;
|
|
end if;
|
|
|
|
Add_Text (Command, Text);
|
|
|
|
Buffer.Set_Current_Command (Command);
|
|
end Update_Insert_Command;
|
|
|
|
Text : constant Unchecked_String_Access :=
|
|
To_Unchecked_String (Get_Chars (Nth (Params, 2)));
|
|
Length : constant Integer :=
|
|
Integer (Get_Int (Nth (Params, 3)));
|
|
Pos, Sel_Pos : Gtk_Text_Iter;
|
|
Command : Editor_Command := Get_Current_Command (Buffer);
|
|
Line : Editable_Line_Type;
|
|
User_Action : Action_Type;
|
|
Sel_Mark : Gtk_Text_Mark := Buffer.Get_Selection_Bound;
|
|
Cursor_Previously_Held : Boolean;
|
|
|
|
begin
|
|
if Buffer.Inserting_Count > 0 then
|
|
-- Setting Insertion_Position by default (old behavior),
|
|
-- in other words do not use settings from previous
|
|
-- (non internal) insert.
|
|
Buffer.Inserting_Position := At_Begin;
|
|
return;
|
|
end if;
|
|
|
|
-- If in multi cursors manual slave mode, update corresponding command
|
|
-- and sel mark
|
|
if Buffer.Cursors_Sync.Mode = Manual_Slave then
|
|
Sel_Mark := Buffer.Cursors_Sync.MC.Sel_Mark;
|
|
end if;
|
|
|
|
Get_Text_Iter (Nth (Params, 1), Pos);
|
|
Line :=
|
|
Get_Editable_Line (Buffer, Buffer_Line_Type (Get_Line (Pos) + 1));
|
|
|
|
if Starts_Line (Pos) then
|
|
Buffer.Inserting_Position := At_Begin;
|
|
|
|
elsif Ends_Line (Pos) then
|
|
Buffer.Inserting_Position := At_End;
|
|
|
|
else
|
|
Buffer.Inserting_Position := Other;
|
|
end if;
|
|
|
|
-- Move mark of start of re-highlight area into insertion position
|
|
Move_Mark (Buffer, Buffer.Highlighter.First_Highlight_Mark, Pos);
|
|
|
|
if Line = 0 then
|
|
-- In a special line: we simply stop propagation
|
|
Emit_Stop_By_Name (Object => Buffer, Name => "insert_text");
|
|
return;
|
|
end if;
|
|
|
|
if Buffer.Prevent_CR_Insertion then
|
|
Buffer.Prevent_CR_Insertion := False;
|
|
|
|
declare
|
|
T : String (1 .. Length);
|
|
Last : Integer := 1;
|
|
CR_Found : Boolean := False;
|
|
Ignore : Boolean;
|
|
pragma Unreferenced (Ignore);
|
|
|
|
begin
|
|
-- Copy Text in T, replacing CRLF by LF and CR by LF
|
|
for J in 1 .. Length loop
|
|
if Text (J) = ASCII.CR then
|
|
CR_Found := True;
|
|
|
|
if J = Length or else Text (J + 1) /= ASCII.LF then
|
|
T (Last) := ASCII.LF;
|
|
Last := Last + 1;
|
|
end if;
|
|
else
|
|
T (Last) := Text (J);
|
|
Last := Last + 1;
|
|
end if;
|
|
end loop;
|
|
|
|
Last := Last - 1;
|
|
|
|
if CR_Found then
|
|
-- If we have found a CR in the text, block the current
|
|
-- insertion and write the stripped text instead.
|
|
|
|
Emit_Stop_By_Name (Object => Buffer, Name => "insert_text");
|
|
Ignore :=
|
|
Insert_Interactive_At_Cursor (Buffer, T (1 .. Last), True);
|
|
return;
|
|
end if;
|
|
end;
|
|
end if;
|
|
|
|
-- We are editing characters on a line: unfold the block below, so
|
|
-- that the folding data remains in sync even if we remove the
|
|
-- information that justified the folding.
|
|
--
|
|
-- For instance, if the text is
|
|
--
|
|
-- 1 procedure hello is
|
|
-- 2 begin
|
|
-- 3 null;
|
|
-- 3 end hello;
|
|
--
|
|
-- If the block is folded and the user inserts a line break in line 1,
|
|
-- there is no longer a reason that there should be folded data
|
|
-- below this line.
|
|
|
|
if not Lines_Are_Real (Buffer) then
|
|
declare
|
|
Result, Ignore : Boolean := False;
|
|
pragma Unreferenced (Ignore);
|
|
begin
|
|
-- The line which receives the insert_text event is necessarily
|
|
-- folded: we want to unfold the text starting with the following
|
|
-- line to effectively unfold the block.
|
|
if Line < Buffer.Last_Editable_Line then
|
|
Result := Fold_Unfold_Line (Buffer, Line + 1, False);
|
|
end if;
|
|
|
|
if Result then
|
|
-- We did unfold a block: stop propagation and insert the
|
|
-- new text at the cursor position.
|
|
Emit_Stop_By_Name (Object => Buffer, Name => "insert_text");
|
|
|
|
Ignore :=
|
|
Insert_Interactive_At_Cursor
|
|
(Buffer, Text (1 .. Length), True);
|
|
return;
|
|
end if;
|
|
end;
|
|
end if;
|
|
|
|
Edit_Hook (Buffer);
|
|
Cursor_Move_Hook (Buffer);
|
|
|
|
if Buffer.Inserting then
|
|
return;
|
|
end if;
|
|
|
|
-- Past this point, we know we are dealing with an user action
|
|
|
|
User_Edit_Hook (Buffer);
|
|
|
|
if Length = 1 and then (Text (1) = ' ' or else Text (1) = ASCII.HT) then
|
|
User_Action := Insert_Spaces;
|
|
elsif Length = 1 and then Text (1) = ASCII.LF then
|
|
User_Action := Insert_Line;
|
|
else
|
|
User_Action := Insert_Text;
|
|
end if;
|
|
|
|
Cursor_Previously_Held := Buffer.No_Cursor_Move_On_Changes;
|
|
|
|
-- If there is a selection and we are inserting, this might be a drag
|
|
-- and drop action - in this case, prevent cursor changes in reaction to
|
|
-- insertion, as they would result in losing the selection, and Gtk+
|
|
-- needs the selection to know which text to delete after the drop.
|
|
if Buffer.Selection_Exists then
|
|
Buffer.No_Cursor_Move_On_Changes := True;
|
|
end if;
|
|
|
|
Buffer.Get_Iter_At_Mark (Sel_Pos, Sel_Mark);
|
|
Update_Insert_Command
|
|
(Source_Buffer (Buffer),
|
|
User_Action,
|
|
Command,
|
|
Pos,
|
|
Sel_Pos,
|
|
Text (1 .. Length),
|
|
Is_Main_Action => Buffer.Cursors_Sync.Mode /= Manual_Slave);
|
|
|
|
Buffer.No_Cursor_Move_On_Changes := Cursor_Previously_Held;
|
|
|
|
exception
|
|
when E : others =>
|
|
Trace (Me, E);
|
|
end Before_Insert_Text;
|
|
|
|
---------------------
|
|
-- Delete_Range_Cb --
|
|
---------------------
|
|
|
|
procedure Delete_Range_Cb
|
|
(Buffer : access Source_Buffer_Record'Class; Iter : Gtk_Text_Iter) is
|
|
begin
|
|
Source_Buffer (Buffer).Highlighter.Update_Highlight_Region (Iter);
|
|
end Delete_Range_Cb;
|
|
|
|
------------------------
|
|
-- After_Delete_Range --
|
|
------------------------
|
|
|
|
procedure After_Delete_Range
|
|
(Buffer : access Source_Buffer_Record'Class; Params : Glib.Values.GValues)
|
|
is
|
|
Start_Iter : Gtk_Text_Iter;
|
|
End_Iter : Gtk_Text_Iter;
|
|
begin
|
|
Update_Logical_Timestamp (Buffer);
|
|
|
|
Get_Text_Iter (Nth (Params, 1), Start_Iter);
|
|
Get_Text_Iter (Nth (Params, 2), End_Iter);
|
|
|
|
-- Move mark of start of re-highlight area into insertion position
|
|
|
|
Move_Mark (Buffer, Buffer.Highlighter.First_Highlight_Mark, Start_Iter);
|
|
|
|
declare
|
|
C : constant Editor_Command := Get_Current_Command (Buffer);
|
|
begin
|
|
if C /= null
|
|
and then (not Buffer.Inserting
|
|
or else Buffer.Cursors_Sync.Mode = Manual_Slave)
|
|
then
|
|
C.Set_End_Location (Start_Iter);
|
|
end if;
|
|
end;
|
|
|
|
if Buffer.Lang /= null
|
|
and then Get_Language_Context (Buffer.Lang).Syntax_Highlighting
|
|
then
|
|
Delete_Range_Cb (Buffer, Start_Iter);
|
|
end if;
|
|
|
|
if Buffer.First_Removed_Line > 0 then
|
|
Lines_Remove_Hook_After
|
|
(Buffer, Buffer.First_Removed_Line, Buffer.Last_Removed_Line);
|
|
Buffer.First_Removed_Line := 0;
|
|
end if;
|
|
|
|
if Buffer.Modifying_Editable_Lines then
|
|
for Listener of Buffer.Listeners loop
|
|
Listener.After_Delete_Range (not Buffer.Inserting);
|
|
end loop;
|
|
end if;
|
|
|
|
Character_Added
|
|
(Source_Buffer (Buffer), 8, Interactive => not Buffer.Inserting);
|
|
|
|
if Buffer.Cursors_Delete_Offset /= 0
|
|
and then Buffer.Cursors_Sync.Mode = Auto
|
|
then
|
|
declare
|
|
Iter_1, Iter_2 : Gtk_Text_Iter;
|
|
G : Group_Block := Current_Group (Buffer.Queue);
|
|
begin
|
|
for C of Get_Cursors (Source_Buffer (Buffer)) loop
|
|
if not C.Is_Main_Cursor then
|
|
Set_Manual_Sync (C);
|
|
Buffer.Get_Iter_At_Mark (Iter_1, C.Cursor.Mark);
|
|
Buffer.Get_Iter_At_Offset
|
|
(Iter_2,
|
|
Get_Offset (Iter_1) + Buffer.Cursors_Delete_Offset);
|
|
Buffer.Delete (Iter_1, Iter_2);
|
|
end if;
|
|
end loop;
|
|
Set_Cursors_Auto_Sync (Source_Buffer (Buffer));
|
|
end;
|
|
end if;
|
|
|
|
Update_All_Column_Memory (Buffer);
|
|
|
|
Buffer.Get_Iter_At_Mark (Start_Iter, Buffer.Insert_Mark);
|
|
|
|
if Buffer_Line_Type (Get_Line (Start_Iter)) in Buffer.Line_Data'Range
|
|
and then not Starts_Line (Start_Iter)
|
|
and then not Ends_Line (Start_Iter)
|
|
then
|
|
-- Start line was not removed completely, check if all messages
|
|
-- still have corresponding locations in source code
|
|
|
|
declare
|
|
use Message_Reference_List;
|
|
Src : Line_Info_Width_Array_Access renames
|
|
Buffer.Line_Data (Buffer_Line_Type (Get_Line (Start_Iter)))
|
|
.Side_Info_Data;
|
|
|
|
Msg : Message_Access;
|
|
For_Deleting : Message_Reference_List.List;
|
|
|
|
begin
|
|
for Index in Src'Range loop
|
|
for Ref of Src (Index).Messages loop
|
|
Msg := Message (Ref);
|
|
if Msg /= null and then not Msg.Get_Editor_Mark.Is_Present
|
|
then
|
|
For_Deleting.Append (Ref);
|
|
end if;
|
|
end loop;
|
|
end loop;
|
|
|
|
while not For_Deleting.Is_Empty loop
|
|
Msg := Message (For_Deleting.First_Element);
|
|
if Msg /= null then
|
|
Remove (Msg);
|
|
end if;
|
|
For_Deleting.Delete_First;
|
|
end loop;
|
|
end;
|
|
end if;
|
|
|
|
exception
|
|
when E : others =>
|
|
Trace (Me, E);
|
|
end After_Delete_Range;
|
|
|
|
-------------------------
|
|
-- Before_Delete_Range --
|
|
-------------------------
|
|
|
|
procedure Before_Delete_Range
|
|
(Buffer : access Source_Buffer_Record'Class; Params : Glib.Values.GValues)
|
|
is
|
|
Start_Iter : Gtk_Text_Iter;
|
|
End_Iter : Gtk_Text_Iter;
|
|
Command : Editor_Command := Get_Current_Command (Buffer);
|
|
Direction : Direction_Type := Extended;
|
|
Line, Column : Gint;
|
|
Line_Start : Gint;
|
|
Column_Start : Gint;
|
|
Line_End : Gint;
|
|
Column_End : Gint;
|
|
Line_Count : Gint;
|
|
Editable_Line_Start : Editable_Line_Type;
|
|
Editable_Line_End : Editable_Line_Type;
|
|
Delete_Offset : Gint := 0;
|
|
Sel_Mark : Gtk_Text_Mark := Buffer.Get_Selection_Bound;
|
|
Sel_Pos_Iter : Gtk_Text_Iter;
|
|
Sel_Pos : Loc_T;
|
|
|
|
First_Buffer_Line_To_Remove : Buffer_Line_Type;
|
|
Last_Buffer_Line_To_Remove : Buffer_Line_Type;
|
|
|
|
procedure Get_Current_Cursor_Position
|
|
(Line : out Gint; Column : out Gint);
|
|
-- Same as get cursor position, but takes multi-cursors into account
|
|
|
|
Cursor_Pos : Loc_T;
|
|
|
|
procedure Get_Current_Cursor_Position
|
|
(Line : out Gint; Column : out Gint)
|
|
is
|
|
Mark : Gtk_Text_Mark;
|
|
Iter : Gtk_Text_Iter;
|
|
begin
|
|
|
|
if Buffer.Cursors_Sync.Mode = Manual_Slave then
|
|
Mark := Buffer.Cursors_Sync.MC.Mark;
|
|
else
|
|
Mark := Buffer.Insert_Mark;
|
|
end if;
|
|
|
|
Get_Iter_At_Mark (Buffer, Iter, Mark);
|
|
Line := Get_Line (Iter);
|
|
Column := Get_Line_Offset (Iter);
|
|
|
|
Get_Iter_Position
|
|
(Source_Buffer (Buffer), Iter, Cursor_Pos.Line, Cursor_Pos.Col);
|
|
end Get_Current_Cursor_Position;
|
|
|
|
begin
|
|
-- If in multi cursors manual slave mode, update corresponding command
|
|
if Buffer.Cursors_Sync.Mode = Manual_Slave then
|
|
Sel_Mark := Buffer.Cursors_Sync.MC.Sel_Mark;
|
|
end if;
|
|
|
|
Get_Text_Iter (Nth (Params, 1), Start_Iter);
|
|
Get_Text_Iter (Nth (Params, 2), End_Iter);
|
|
|
|
-- Determine the direction mode for the delete action
|
|
|
|
Get_Current_Cursor_Position (Line, Column);
|
|
|
|
Line_Start := Get_Line (Start_Iter);
|
|
Column_Start := Get_Line_Offset (Start_Iter);
|
|
Line_End := Get_Line (End_Iter);
|
|
Column_End := Get_Line_Offset (End_Iter);
|
|
|
|
if Line = Line_Start and then Column = Column_Start then
|
|
Direction := Backward;
|
|
end if;
|
|
|
|
if Line = Line_End and then Column = Column_End then
|
|
Direction := Forward;
|
|
end if;
|
|
|
|
if Buffer.Cursors_Sync.Mode = Auto then
|
|
Delete_Offset := (Get_Offset (End_Iter) - Get_Offset (Start_Iter));
|
|
Delete_Offset :=
|
|
Delete_Offset
|
|
* (case Direction is
|
|
when Forward => -1,
|
|
when Backward => 1,
|
|
when Extended => 0);
|
|
Buffer.Cursors_Delete_Offset := Delete_Offset;
|
|
end if;
|
|
|
|
Editable_Line_Start :=
|
|
Get_Editable_Line (Buffer, Buffer_Line_Type (Line_Start + 1));
|
|
|
|
Editable_Line_End :=
|
|
Get_Editable_Line (Buffer, Buffer_Line_Type (Line_End + 1));
|
|
|
|
Buffer.Get_Iter_At_Mark (Sel_Pos_Iter, Sel_Mark);
|
|
Get_Iter_Position
|
|
(Source_Buffer (Buffer), Sel_Pos_Iter, Sel_Pos.Line, Sel_Pos.Col);
|
|
|
|
-- If there are non-editable lines in the range, intercept the deletion
|
|
|
|
if not Buffer.Inserting
|
|
and then Has_Special_Lines
|
|
(Buffer,
|
|
Buffer_Line_Type (Line_Start + 1),
|
|
Buffer_Line_Type (Line_End + 1))
|
|
then
|
|
-- Intercept default propagation, we want to flatten the area
|
|
-- before continuing.
|
|
Emit_Stop_By_Name (Buffer, "delete_range");
|
|
|
|
-- If we are just deleting in one special line, ignore
|
|
|
|
if Editable_Line_Start = 0 and then Editable_Line_End = 0 then
|
|
return;
|
|
end if;
|
|
|
|
while Editable_Line_Start = 0 loop
|
|
Line_Start := Line_Start - 1;
|
|
exit when Line_Start <= 0;
|
|
|
|
Editable_Line_Start :=
|
|
Get_Editable_Line (Buffer, Buffer_Line_Type (Line_Start + 1));
|
|
end loop;
|
|
|
|
if Editable_Line_Start = 0 then
|
|
First_Buffer_Line_To_Remove := 1;
|
|
Editable_Line_Start := 1;
|
|
else
|
|
First_Buffer_Line_To_Remove := Buffer_Line_Type (Line_Start + 1);
|
|
end if;
|
|
|
|
Line_Count := Buffer.Get_Line_Count;
|
|
|
|
while Editable_Line_End = 0 loop
|
|
Line_End := Line_End + 1;
|
|
|
|
Editable_Line_End :=
|
|
Get_Editable_Line (Buffer, Buffer_Line_Type (Line_End + 1));
|
|
|
|
exit when Line_End = Line_Count;
|
|
end loop;
|
|
|
|
Last_Buffer_Line_To_Remove := Buffer_Line_Type (Line_End + 1);
|
|
|
|
declare
|
|
Expanded : Boolean;
|
|
M_Start, M_End : Gtk_Text_Mark;
|
|
I_Start, I_End : Gtk_Text_Iter;
|
|
Ignored : Boolean;
|
|
begin
|
|
M_Start := Buffer.Create_Mark (Where => Start_Iter);
|
|
M_End := Buffer.Create_Mark (Where => End_Iter);
|
|
|
|
Expanded :=
|
|
Flatten_Area
|
|
(Buffer => Buffer,
|
|
Start_Line => Editable_Line_Start,
|
|
End_Line => Editable_Line_End,
|
|
Start_Buffer_Line => First_Buffer_Line_To_Remove,
|
|
End_Buffer_Line => Last_Buffer_Line_To_Remove);
|
|
|
|
Buffer.Get_Iter_At_Mark (I_Start, M_Start);
|
|
Buffer.Get_Iter_At_Mark (I_End, M_End);
|
|
|
|
Delete_Mark (Buffer, M_Start);
|
|
Delete_Mark (Buffer, M_End);
|
|
|
|
-- Re-launch the deletion unless we are expanding a folded line,
|
|
-- in which case do nothing.
|
|
if not Expanded then
|
|
Delete_Interactive
|
|
(Buffer => Buffer,
|
|
Start_Iter => I_Start,
|
|
End_Iter => I_End,
|
|
Default_Editable => True,
|
|
Result => Ignored);
|
|
end if;
|
|
return;
|
|
end;
|
|
end if;
|
|
|
|
if Buffer.Modifying_Editable_Lines then
|
|
for Listener of Buffer.Listeners loop
|
|
Listener.Before_Delete_Range
|
|
(Buffer.Editor_Buffer.New_Location
|
|
(Integer (Editable_Line_Start),
|
|
Visible_Column_Type (Column_Start + 1)),
|
|
Buffer.Editor_Buffer.New_Location
|
|
(Integer (Editable_Line_End),
|
|
Visible_Column_Type (Column_End + 1)),
|
|
not Buffer.Inserting);
|
|
end loop;
|
|
end if;
|
|
|
|
-- Remove the lines in the side information column
|
|
declare
|
|
From, To, Count : Buffer_Line_Type;
|
|
begin
|
|
From := Buffer_Line_Type (Line_Start + 1);
|
|
To := Buffer_Line_Type (Line_End + 1);
|
|
|
|
if From /= To then
|
|
Count := To - From;
|
|
|
|
if Starts_Line (Start_Iter) then
|
|
-- The Start_Iter is on the start of line, so we need
|
|
-- to remove this line.
|
|
From := From - 1;
|
|
end if;
|
|
|
|
Lines_Remove_Hook_Before (Buffer, From, Count);
|
|
|
|
else
|
|
-- We are editing characters on a line: unfold the block below, so
|
|
-- that the folding data remains in sync even if we remove the
|
|
-- information that justified the folding.
|
|
--
|
|
-- For instance, if the text is
|
|
--
|
|
-- 1 procedure hello is
|
|
-- 2 begin
|
|
-- 3 null;
|
|
-- 3 end hello;
|
|
--
|
|
-- If the block is folded and the user removes the "is" in line 1,
|
|
-- there is no longer a reason that there should be folded data
|
|
-- below this line.
|
|
|
|
if not Lines_Are_Real (Buffer) then
|
|
declare
|
|
Result : Boolean;
|
|
M1 : Gtk_Text_Mark;
|
|
M2 : Gtk_Text_Mark;
|
|
begin
|
|
M1 := Create_Mark (Buffer, "", Start_Iter);
|
|
M2 := Create_Mark (Buffer, "", End_Iter);
|
|
|
|
Result :=
|
|
Fold_Unfold_Line (Buffer, Editable_Line_Start, False);
|
|
if Result then
|
|
-- We have changed the buffer:
|
|
-- stop propagation and reemit
|
|
Emit_Stop_By_Name (Buffer, "delete_range");
|
|
|
|
Get_Iter_At_Mark (Buffer, Start_Iter, M1);
|
|
Get_Iter_At_Mark (Buffer, End_Iter, M2);
|
|
|
|
Delete_Mark (Buffer, M1);
|
|
Delete_Mark (Buffer, M2);
|
|
Delete (Buffer, Start_Iter, End_Iter);
|
|
return;
|
|
end if;
|
|
end;
|
|
end if;
|
|
end if;
|
|
end;
|
|
|
|
Edit_Hook (Buffer);
|
|
Cursor_Move_Hook (Buffer);
|
|
|
|
if Buffer.Inserting then
|
|
return;
|
|
end if;
|
|
|
|
User_Edit_Hook (Buffer);
|
|
|
|
if not Is_Null_Command (Command)
|
|
and then (Get_Mode (Command) /= Deletion
|
|
or else Get_Direction (Command) /= Direction)
|
|
then
|
|
End_Action (Buffer);
|
|
Command := Get_Current_Command (Buffer);
|
|
end if;
|
|
|
|
declare
|
|
Slice : constant Basic_Types.UTF8_String :=
|
|
Get_Slice (Buffer, Start_Iter, End_Iter);
|
|
User_Action : Action_Type;
|
|
begin
|
|
|
|
if Slice = "" & ASCII.LF then
|
|
User_Action := Delete_Line;
|
|
elsif Slice = " " or else Slice = "" & ASCII.HT then
|
|
User_Action := Delete_Spaces;
|
|
else
|
|
User_Action := Delete_Text;
|
|
end if;
|
|
|
|
if Is_Null_Command (Command) then
|
|
Create
|
|
(Command,
|
|
Deletion,
|
|
Source_Buffer (Buffer),
|
|
True,
|
|
Cursor_Loc => Cursor_Pos,
|
|
Sel_Loc => Sel_Pos,
|
|
Direction => Direction,
|
|
C =>
|
|
(if Buffer.Cursors_Sync.Mode = Manual_Slave
|
|
then Create (Buffer.Cursors_Sync.MC, Source_Buffer (Buffer))
|
|
else Get_Main_Cursor (+Buffer)));
|
|
|
|
Enqueue (Buffer, Command_Access (Command), User_Action);
|
|
|
|
Add_Text (Command, Slice);
|
|
else
|
|
if Direction = Forward then
|
|
Add_Text
|
|
(Command,
|
|
Slice,
|
|
Get_Editable_Line
|
|
(Buffer, Buffer_Line_Type (Line_Start + 1)),
|
|
Character_Offset_Type (Column_Start + 1));
|
|
else
|
|
Add_Text (Command, Slice);
|
|
end if;
|
|
end if;
|
|
end;
|
|
|
|
Buffer.Set_Current_Command (Command);
|
|
|
|
exception
|
|
when E : others =>
|
|
Trace (Me, E);
|
|
end Before_Delete_Range;
|
|
|
|
-----------------------------
|
|
-- Line_Highlights_Changed --
|
|
-----------------------------
|
|
|
|
procedure Line_Highlights_Changed
|
|
(Buffer : access Source_Buffer_Record'Class) is
|
|
begin
|
|
Emit_By_Name
|
|
(Get_Object (Buffer), Signal_Line_Highlights_Changed & ASCII.NUL);
|
|
end Line_Highlights_Changed;
|
|
|
|
--------------------------------
|
|
-- Buffer_Information_Changed --
|
|
--------------------------------
|
|
|
|
procedure Buffer_Information_Changed
|
|
(Buffer : access Source_Buffer_Record'Class) is
|
|
begin
|
|
Emit_By_Name
|
|
(Get_Object (Buffer), Signal_Buffer_Information_Changed & ASCII.NUL);
|
|
end Buffer_Information_Changed;
|
|
|
|
--------------------
|
|
-- Status_Changed --
|
|
--------------------
|
|
|
|
procedure Status_Changed (Buffer : access Source_Buffer_Record'Class) is
|
|
begin
|
|
Emit_By_Name (Get_Object (Buffer), Signal_Status_Changed & ASCII.NUL);
|
|
end Status_Changed;
|
|
|
|
----------------------
|
|
-- Filename_Changed --
|
|
----------------------
|
|
|
|
procedure Filename_Changed (Buffer : access Source_Buffer_Record'Class) is
|
|
begin
|
|
Emit_By_Name (Get_Object (Buffer), Signal_Filename_Changed & ASCII.NUL);
|
|
end Filename_Changed;
|
|
|
|
---------------------
|
|
-- Set_Last_Status --
|
|
---------------------
|
|
|
|
procedure Set_Last_Status
|
|
(Buffer : access Source_Buffer_Record'Class; Status : Status_Type) is
|
|
begin
|
|
if Status /= Buffer.Current_Status then
|
|
Buffer.Current_Status := Status;
|
|
Status_Changed (Buffer);
|
|
end if;
|
|
end Set_Last_Status;
|
|
|
|
------------------------------
|
|
-- Emit_New_Cursor_Position --
|
|
------------------------------
|
|
|
|
procedure Emit_New_Cursor_Position
|
|
(Buffer : access Source_Buffer_Record'Class)
|
|
is
|
|
procedure Emit_By_Name
|
|
(Object : System.Address; Name : String; Line : Gint; Column : Gint);
|
|
pragma Import (C, Emit_By_Name, "ada_g_signal_emit_by_name_int_int");
|
|
|
|
L, C : Gint;
|
|
begin
|
|
if Buffer.Do_Not_Move_Cursor then
|
|
return;
|
|
end if;
|
|
|
|
Get_Screen_Position (Buffer, L, C);
|
|
|
|
Emit_By_Name
|
|
(Get_Object (Buffer),
|
|
"cursor_position_changed" & ASCII.NUL,
|
|
Line => Gint (Get_Editable_Line (Buffer, Buffer_Line_Type (L + 1))),
|
|
Column => C + 1);
|
|
|
|
Buffer.Highlighter.Remove_Delimiters_Highlighting;
|
|
end Emit_New_Cursor_Position;
|
|
|
|
----------------------------
|
|
-- Generic_Valid_Position --
|
|
----------------------------
|
|
|
|
procedure Generic_Valid_Position
|
|
(Buffer : Source_Buffer;
|
|
Iter : out Gtk_Text_Iter;
|
|
Found : out Boolean;
|
|
Line : Gint;
|
|
Column : Gint := 0) is
|
|
begin
|
|
-- First check that Line does not exceed the number of lines
|
|
-- in the buffer.
|
|
|
|
if Column < 0 or else Line >= Get_Line_Count (Buffer) then
|
|
Found := False;
|
|
return;
|
|
end if;
|
|
|
|
Get_Iter_At_Line_Offset (Buffer, Iter, Line, 0);
|
|
|
|
if Column = 0 then
|
|
Found := True;
|
|
elsif Line_Length (Iter) >= Column then
|
|
Set_Pos (Iter, Column);
|
|
Found := True;
|
|
else
|
|
Found := False;
|
|
end if;
|
|
end Generic_Valid_Position;
|
|
|
|
------------------
|
|
-- Is_Valid_Pos --
|
|
------------------
|
|
|
|
procedure Is_Valid_Pos is new
|
|
Generic_Valid_Position (Get_Chars_In_Line, Set_Line_Offset);
|
|
-- Column should be given in characters, not in bytes
|
|
|
|
-----------------------
|
|
-- Is_Valid_Position --
|
|
-----------------------
|
|
|
|
function Is_Valid_Position
|
|
(Buffer : access Source_Buffer_Record; Line : Gint; Column : Gint := 0)
|
|
return Boolean
|
|
is
|
|
Iter : Gtk_Text_Iter;
|
|
Found : Boolean;
|
|
begin
|
|
Is_Valid_Pos (Source_Buffer (Buffer), Iter, Found, Line, Column);
|
|
return Found;
|
|
end Is_Valid_Position;
|
|
|
|
function Is_Valid_Position
|
|
(Buffer : access Source_Buffer_Record;
|
|
Line : Editable_Line_Type;
|
|
Column : Character_Offset_Type := 1) return Boolean
|
|
is
|
|
Buffer_Line : constant Buffer_Line_Type :=
|
|
Get_Buffer_Line (Buffer, Line);
|
|
|
|
begin
|
|
if Buffer_Line = 0 then
|
|
if Line in 1 .. Buffer.Last_Editable_Line then
|
|
return True;
|
|
else
|
|
Trace (Me, "Invalid Buffer Line");
|
|
return False;
|
|
end if;
|
|
|
|
else
|
|
return
|
|
Is_Valid_Position
|
|
(Buffer, Gint (Buffer_Line - 1), Gint (Column - 1));
|
|
end if;
|
|
end Is_Valid_Position;
|
|
|
|
function Is_Valid_Position
|
|
(Buffer : access Source_Buffer_Record;
|
|
Line : Editable_Line_Type;
|
|
Column : Visible_Column_Type) return Boolean is
|
|
begin
|
|
return
|
|
Is_Valid_Position (Buffer, Line, Collapse_Tabs (Buffer, Line, Column));
|
|
end Is_Valid_Position;
|
|
|
|
---------------------------
|
|
-- Ensure_Valid_Position --
|
|
---------------------------
|
|
|
|
procedure Ensure_Valid_Position
|
|
(Buffer : access Source_Buffer_Record;
|
|
Line : Editable_Line_Type;
|
|
Column : Character_Offset_Type := 1) is
|
|
begin
|
|
if not Is_Valid_Position (Buffer, Line, Column) then
|
|
raise Location_Exception with (-"Invalid Buffer Line");
|
|
end if;
|
|
end Ensure_Valid_Position;
|
|
|
|
procedure Ensure_Valid_Position
|
|
(Buffer : access Source_Buffer_Record;
|
|
Line : Editable_Line_Type;
|
|
Column : Visible_Column_Type) is
|
|
begin
|
|
if not Is_Valid_Position (Buffer, Line, Column) then
|
|
raise Location_Exception with (-"Invalid Buffer Line");
|
|
end if;
|
|
end Ensure_Valid_Position;
|
|
|
|
-------------
|
|
-- Gtk_New --
|
|
-------------
|
|
|
|
procedure Gtk_New
|
|
(Buffer : out Source_Buffer;
|
|
Kernel : GPS.Kernel.Kernel_Handle;
|
|
Lang : Language.Language_Access := null) is
|
|
begin
|
|
Buffer := new Source_Buffer_Record;
|
|
Initialize (Buffer, Kernel, Lang);
|
|
end Gtk_New;
|
|
|
|
---------------------
|
|
-- Initialize_Hook --
|
|
---------------------
|
|
|
|
procedure Initialize_Hook (Buffer : access Source_Buffer_Record'Class) is
|
|
Iter : Gtk_Text_Iter;
|
|
begin
|
|
Reset_Completion_Data;
|
|
|
|
Get_Start_Iter (Buffer, Iter);
|
|
|
|
-- Initialize the data for timeout highlighting
|
|
|
|
Buffer.Highlighter.First_Highlight_Mark :=
|
|
Create_Mark (Buffer, "", Iter);
|
|
Buffer.Highlighter.Last_Highlight_Mark :=
|
|
Create_Mark (Buffer, "", Iter, False);
|
|
|
|
-- Initialize the line info
|
|
|
|
Buffer.Editable_Lines := new Editable_Line_Array (1 .. 1);
|
|
Buffer.Editable_Lines (1) := 1;
|
|
|
|
-- ??? create line info (above)
|
|
|
|
Buffer.Line_Data := new Line_Data_Array (1 .. 1);
|
|
Buffer.Line_Data (1) := New_Line_Data;
|
|
Buffer.Line_Data (1).Editable_Line := 1;
|
|
Create_Side_Info (Buffer, 1);
|
|
|
|
-- Compute the block information
|
|
|
|
Register_Edit_Timeout (Buffer);
|
|
|
|
-- Show the line number information, if needed
|
|
Recalculate_Side_Column_Width (Buffer);
|
|
end Initialize_Hook;
|
|
|
|
-------------------
|
|
-- On_Paste_Done --
|
|
-------------------
|
|
|
|
procedure On_Paste_Done (Buffer : access Source_Buffer_Record'Class) is
|
|
begin
|
|
-- Workaround a bug in gtk+ 2.18: when a paste was completed, the
|
|
-- paste-done signal is emitted, and results in scrolling _all_ views
|
|
-- to the cursor location (which breaks the handling of multiple views).
|
|
-- The simplest workaround is simply to not emit that signal at all
|
|
-- (GNAT Studio takes care of the scrolling for the active view anyway).
|
|
-- The RH for this signal in gtk+ is:
|
|
-- Add a "paste-done" signal and use it to properly scroll the
|
|
-- view at the end of the pasted text in the case of an async paste
|
|
|
|
Emit_Stop_By_Name (Buffer, "paste-done");
|
|
|
|
On_Paste_Done (Get_Clipboard (Buffer.Kernel), Buffer);
|
|
end On_Paste_Done;
|
|
|
|
----------------
|
|
-- Initialize --
|
|
----------------
|
|
|
|
procedure Initialize
|
|
(Buffer : access Source_Buffer_Record'Class;
|
|
Kernel : GPS.Kernel.Kernel_Handle;
|
|
Lang : Language.Language_Access := null)
|
|
is
|
|
use Pango.Enums.Underline_Properties;
|
|
|
|
Command : Check_Modified_State;
|
|
P_Hook : Preferences_Hooks_Function_Access;
|
|
|
|
begin
|
|
Glib.Object.Initialize_Class_Record
|
|
(Ancestor => Gtkada.Text_Buffer.Get_Type,
|
|
Signals => Signals,
|
|
Class_Record => Class_Record,
|
|
Type_Name => "GPSSourceBuffer",
|
|
Parameters => Signal_Parameters);
|
|
Glib.Object.G_New (Buffer, Class_Record);
|
|
Gtkada.Text_Buffer.Initialize (Buffer);
|
|
|
|
Buffer.Lang := Lang;
|
|
Buffer.Kernel := Kernel;
|
|
|
|
-- Create the Hidden_Text_Tag and save it into the source buffer tag
|
|
-- table.
|
|
Gtk_New (Buffer.Hidden_Text_Tag, "hidden-text");
|
|
Set_Property (Buffer.Hidden_Text_Tag, Invisible_Property, True);
|
|
|
|
-- Create the Hyper Mode Tag
|
|
|
|
Gtk_New (Buffer.Hyper_Mode_Tag);
|
|
Set_Property
|
|
(Buffer.Hyper_Mode_Tag,
|
|
Gtk.Text_Tag.Underline_Property,
|
|
Pango_Underline_Single);
|
|
Set_Property
|
|
(Buffer.Hyper_Mode_Tag,
|
|
Foreground_Rgba_Property,
|
|
Hyper_Links_Style.Get_Pref_Fg);
|
|
|
|
if Hyper_Links_Style.Get_Pref_Bg /= Gdk.RGBA.White_RGBA then
|
|
Set_Property
|
|
(Buffer.Hyper_Mode_Tag,
|
|
Background_Rgba_Property,
|
|
Hyper_Links_Style.Get_Pref_Bg);
|
|
end if;
|
|
|
|
-- Create the Non Editable Tag
|
|
|
|
Gtk_New (Buffer.Non_Editable_Tag);
|
|
Set_Property
|
|
(Buffer.Non_Editable_Tag, Gtk.Text_Tag.Editable_Property, False);
|
|
|
|
Buffer.Highlighter :=
|
|
new Source_Highlighter_Record (Source_Buffer (Buffer));
|
|
|
|
-- Preference changed hook
|
|
|
|
P_Hook :=
|
|
new On_Pref_Changed'
|
|
(Hook_Function with Buffer => Source_Buffer (Buffer));
|
|
Preferences_Changed_Hook.Add (Obj => P_Hook, Watch => Buffer);
|
|
P_Hook.Execute (Kernel, null);
|
|
|
|
-- Project recomputed hook
|
|
Project_View_Changed_Hook.Add
|
|
(Obj =>
|
|
new On_Project_Changed'
|
|
(Hook_Function with Buffer => Source_Buffer (Buffer)),
|
|
Watch => Buffer);
|
|
|
|
-- File hooks
|
|
File_Deleted_Hook.Add
|
|
(Obj =>
|
|
new On_File_Deleted'
|
|
(Hook_Function with Buffer => Source_Buffer (Buffer)),
|
|
Watch => Buffer);
|
|
|
|
-- Renamed_Hook.Execute will change the buffer's filename:
|
|
-- Add it with Last=>True so that other modules have a chance to react
|
|
-- on the editor before it is renamed
|
|
File_Renamed_Hook.Add
|
|
(Obj =>
|
|
new On_File_Renamed'
|
|
(Hook_Function with Buffer => Source_Buffer (Buffer)),
|
|
Watch => Buffer,
|
|
Last => True);
|
|
|
|
Semantic_Tree_Updated_Hook.Add
|
|
(Obj =>
|
|
new On_Semantic_Tree_Updated'
|
|
(Hook_Function with Buffer => Source_Buffer (Buffer)),
|
|
Watch => Buffer);
|
|
|
|
Location_Changed_Hook.Add_Debounce
|
|
(Obj =>
|
|
new On_Loc_Changed'
|
|
(Hook_Function with Buffer => Source_Buffer (Buffer)),
|
|
Watch => Buffer);
|
|
|
|
-- Save the insert mark for fast retrievals, since we will need to
|
|
-- access it very often.
|
|
|
|
Buffer.Insert_Mark := Get_Insert (Buffer);
|
|
|
|
-- Initialize the queue for editor commands
|
|
|
|
Buffer.Queue := New_Queue;
|
|
|
|
-- Workaround a bug in gtk+ 2.18: when a paste was completed, the
|
|
-- paste-done signal is emitted, and results in scrolling _all_ views
|
|
-- to the cursor location (which breaks the handling of multiple views).
|
|
-- The simplest workaround is simply to not emit that signal at all
|
|
-- (GNAT Studio takes care of the scrolling for the active view anyway).
|
|
-- The RH for this signal in gtk+ is:
|
|
-- Add a "paste-done" signal and use it to properly scroll the
|
|
-- view at the end of the pasted text in the case of an async paste
|
|
|
|
Buffer_Callback.Connect
|
|
(Buffer,
|
|
"paste-done",
|
|
Marsh => Buffer_Callback.To_Marshaller (On_Paste_Done'Access),
|
|
After => False);
|
|
|
|
-- And finally, connect ourselves to the interesting signals
|
|
|
|
Weak_Ref (Buffer, Buffer_Destroy'Access);
|
|
|
|
Buffer_Callback.Connect
|
|
(Buffer, Signal_Changed, Changed_Handler'Access, After => True);
|
|
Buffer_Callback.Connect
|
|
(Buffer,
|
|
Signal_Mark_Set,
|
|
Cb => Mark_Set_Handler'Access,
|
|
After => True);
|
|
Buffer_Callback.Connect
|
|
(Buffer, Signal_Insert_Text, Cb => Before_Insert_Text'Access);
|
|
Buffer_Callback.Connect
|
|
(Buffer,
|
|
Signal_Insert_Text,
|
|
Cb => After_Insert_Text'Access,
|
|
After => True);
|
|
Buffer_Callback.Connect
|
|
(Buffer,
|
|
Signal_Delete_Range,
|
|
Cb => After_Delete_Range'Access,
|
|
After => True);
|
|
Buffer_Callback.Connect
|
|
(Buffer,
|
|
Signal_Delete_Range,
|
|
Cb => Before_Delete_Range'Access,
|
|
After => False);
|
|
|
|
Buffer.Editable_Line_Info_Columns :=
|
|
new Line_Info_Display_Array_Access'(null);
|
|
|
|
Initialize_Hook (Buffer);
|
|
|
|
-- Create the queue change hook that will be called every
|
|
-- time the state of the queue associated to the buffer changes.
|
|
|
|
Create (Command, Source_Buffer (Buffer));
|
|
|
|
Add_Queue_Change_Hook
|
|
(Buffer.Queue, Command_Access (Command), "State_Check");
|
|
|
|
Buffer.First_Removed_Line := 0;
|
|
|
|
End_Action (Buffer);
|
|
|
|
-- Create the default column for line information (block folding,
|
|
-- compiler error messages, etc).
|
|
Create_Line_Information_Column
|
|
(Buffer, Default_Column, False, Empty_Line_Information);
|
|
Buffer.Block_Highlighting_Column :=
|
|
Buffer.Editable_Line_Info_Columns.all'Last;
|
|
|
|
if Editors_Factory = null then
|
|
Editors_Factory :=
|
|
new Src_Editor_Module.Editors.Src_Editor_Buffer_Factory'
|
|
(Src_Editor_Module.Editors.Create (Kernel));
|
|
end if;
|
|
|
|
Buffer.Editor_Buffer :=
|
|
new GPS.Editors.Editor_Buffer'Class'(Editors_Factory.Get (Buffer));
|
|
|
|
-- Initialize every listener from factories
|
|
for Factory of Listener_Factories loop
|
|
Buffer.Listeners.Append
|
|
(Factory.Create
|
|
(Buffer.Editor_Buffer.all,
|
|
Editor_Buffer_Factory (Editors_Factory.all),
|
|
Core_Kernel (Kernel)));
|
|
end loop;
|
|
|
|
Buffer.Folding_Provider := Folding_Provider;
|
|
end Initialize;
|
|
|
|
-------------------
|
|
-- Is_In_Comment --
|
|
-------------------
|
|
|
|
function Is_In_Comment
|
|
(Buffer : Source_Buffer; Iter : Gtk_Text_Iter) return Boolean
|
|
is
|
|
Pos : Gtk_Text_Iter;
|
|
Success : Boolean;
|
|
begin
|
|
Copy (Iter, Pos);
|
|
|
|
-- If the position is past the end of buffer move backward one char
|
|
-- otherwise the Has_Tag check won't work properly.
|
|
|
|
if Is_End (Pos) and not Is_Start (Pos) then
|
|
Backward_Char (Pos, Success);
|
|
end if;
|
|
|
|
return Buffer.Highlighter.Is_Comment_Tag (Pos);
|
|
end Is_In_Comment;
|
|
|
|
-------------------
|
|
-- Is_In_Comment --
|
|
-------------------
|
|
|
|
function Is_In_Comment
|
|
(Buffer : Source_Buffer;
|
|
Line : Editable_Line_Type;
|
|
Column : Visible_Column_Type) return Boolean
|
|
is
|
|
Iter : Gtk_Text_Iter;
|
|
begin
|
|
Buffer.Get_Iter_At_Screen_Position
|
|
(Iter => Iter, Line => Line, Column => Column);
|
|
|
|
return Is_In_Comment (Buffer, Iter);
|
|
end Is_In_Comment;
|
|
|
|
------------------
|
|
-- Is_In_String --
|
|
------------------
|
|
|
|
function Is_In_String
|
|
(Buffer : Source_Buffer;
|
|
Iter : Gtk_Text_Iter;
|
|
Added_Character : Glib.Gunichar := 0;
|
|
Check_Interpolation : Boolean := False) return Boolean
|
|
is
|
|
Lang : constant Language_Access := Buffer.Lang;
|
|
Lang_Context : Language_Context_Access;
|
|
C1, C2 : Character;
|
|
Added_C : Character;
|
|
Pos : Gtk_Text_Iter;
|
|
Quoted : Boolean := False;
|
|
Result : Boolean;
|
|
Skip_First : Boolean := False;
|
|
begin
|
|
if Lang = null then
|
|
return False;
|
|
end if;
|
|
|
|
Lang_Context := Get_Language_Context (Lang);
|
|
|
|
Copy (Iter, Pos);
|
|
Backward_Char (Pos, Result);
|
|
|
|
if not Result then
|
|
-- Start of buffer
|
|
return Quoted;
|
|
end if;
|
|
|
|
C2 := Get_Char (Pos);
|
|
|
|
if Added_Character /= 0 then
|
|
declare
|
|
The_Char : String (1 .. 6);
|
|
-- Unichar_To_UTF8 requires a buffer of size 6
|
|
Last : Natural;
|
|
begin
|
|
Unichar_To_UTF8 (Added_Character, The_Char, Last);
|
|
Added_C := The_Char (Last);
|
|
Skip_First := True;
|
|
end;
|
|
end if;
|
|
|
|
if Check_Interpolation
|
|
and then Lang.Is_Interpolation_Char
|
|
(Ada.Characters.Conversions.To_Wide_Wide_Character (C2))
|
|
then
|
|
-- In case of interpolation character, consider that we are in
|
|
-- a string if the next character is in string.
|
|
Forward_Char (Pos, Result);
|
|
if Result then
|
|
return
|
|
Is_In_String (Buffer, Pos)
|
|
xor (Added_Character /= 0
|
|
and then C2 /= Lang_Context.Quote_Character
|
|
and then Added_C = Lang_Context.String_Delimiter);
|
|
end if;
|
|
end if;
|
|
|
|
if Added_Character /= 0 then
|
|
-- For the first loop iteration use the added character
|
|
-- which is not in the buffer yet
|
|
C2 := Added_C;
|
|
Skip_First := True;
|
|
end if;
|
|
|
|
while C2 /= ASCII.LF loop
|
|
if Skip_First then
|
|
Skip_First := False;
|
|
else
|
|
Backward_Char (Pos, Result);
|
|
exit when not Result;
|
|
end if;
|
|
|
|
C1 := C2;
|
|
C2 := Get_Char (Pos);
|
|
|
|
-- Take care of '"' case
|
|
if C1 = Lang_Context.Constant_Character
|
|
and then C2 = Lang_Context.String_Delimiter
|
|
then
|
|
Backward_Char (Pos, Result);
|
|
|
|
if not Result then
|
|
-- The string delimiter is the first character
|
|
Quoted := not Quoted;
|
|
exit;
|
|
end if;
|
|
|
|
C2 := Get_Char (Pos);
|
|
|
|
-- Ignore string delimiters in Constant_Character quotes
|
|
if C2 /= Lang_Context.Constant_Character
|
|
and then C2 /= Lang_Context.Quote_Character
|
|
then
|
|
Quoted := not Quoted;
|
|
end if;
|
|
|
|
elsif C1 = Lang_Context.String_Delimiter
|
|
and then not (C2 = Lang_Context.Quote_Character)
|
|
then
|
|
Quoted := not Quoted;
|
|
end if;
|
|
end loop;
|
|
|
|
return Quoted;
|
|
end Is_In_String;
|
|
|
|
-------------
|
|
-- Execute --
|
|
-------------
|
|
|
|
overriding
|
|
procedure Execute
|
|
(Self : On_Project_Changed;
|
|
Kernel : not null access Kernel_Handle_Record'Class)
|
|
is
|
|
Buffer : constant Source_Buffer := Self.Buffer;
|
|
begin
|
|
-- The project has changed: if this buffer has a file and the language
|
|
-- is unknown, it is possible that the new project knows which language
|
|
-- this file is.
|
|
|
|
if (Buffer.Lang = null or else Buffer.Lang = Unknown_Lang)
|
|
and then Buffer.Filename /= No_File
|
|
then
|
|
Set_Language
|
|
(Buffer,
|
|
Get_Language_From_File
|
|
(Get_Language_Handler (Kernel), Buffer.Filename));
|
|
end if;
|
|
end Execute;
|
|
|
|
-------------
|
|
-- Execute --
|
|
-------------
|
|
|
|
overriding
|
|
procedure Execute
|
|
(Self : On_Pref_Changed;
|
|
Kernel : not null access Kernel_Handle_Record'Class;
|
|
Pref : Preference)
|
|
is
|
|
pragma Unreferenced (Kernel, Pref);
|
|
B : constant Source_Buffer := Self.Buffer;
|
|
Prev : Boolean;
|
|
Prev_Highlighting : GPS.Kernel.Preferences.External_Highlighting;
|
|
Prev_Folding : Folding_Preferences_Values;
|
|
|
|
begin
|
|
-- Connect timeout, to handle automatic saving of buffer
|
|
|
|
B.Highlighter.Update_Use_Highlighting_Hook;
|
|
|
|
Prev := B.Block_Highlighting;
|
|
B.Block_Highlighting := Block_Highlighting.Get_Pref;
|
|
|
|
if Prev /= B.Block_Highlighting then
|
|
Register_Edit_Timeout (B);
|
|
end if;
|
|
|
|
Prev_Highlighting := B.LSP_Highlighting;
|
|
B.LSP_Highlighting := Use_External_Highlighting.Get_Pref;
|
|
if Prev_Highlighting /= B.LSP_Highlighting then
|
|
Register_Edit_Timeout (B);
|
|
end if;
|
|
|
|
Prev_Folding := B.Block_Folding;
|
|
B.Block_Folding :=
|
|
(Block_Folding.Get_Pref,
|
|
Fold_With_Use_Blocks.Get_Pref,
|
|
Fold_Comments.Get_Pref,
|
|
Autofold_Comment_Blocks.Get_Pref,
|
|
To_Unbounded_String (Fold_Comment_Reg1.Get_Pref),
|
|
To_Unbounded_String (Fold_Comment_Reg2.Get_Pref),
|
|
To_Unbounded_String (Fold_Comment_Reg3.Get_Pref));
|
|
|
|
if Prev_Folding /= B.Block_Folding then
|
|
Register_Edit_Timeout (B);
|
|
end if;
|
|
|
|
if not B.Block_Folding.Block_Folding and then Prev_Folding.Block_Folding
|
|
then
|
|
Unfold_All (B);
|
|
Remove_Block_Folding_Commands (B);
|
|
end if;
|
|
|
|
Prev := B.Parse_Blocks;
|
|
B.Parse_Blocks :=
|
|
B.Block_Folding.Block_Folding
|
|
or else B.Block_Highlighting
|
|
or else Display_Subprogram_Names.Get_Pref;
|
|
|
|
if Prev /= B.Parse_Blocks then
|
|
Buffer_Information_Changed (B);
|
|
end if;
|
|
|
|
if not Prev and then B.Parse_Blocks then
|
|
Register_Edit_Timeout (B);
|
|
end if;
|
|
|
|
B.Auto_Syntax_Check := Automatic_Syntax_Check.Get_Pref;
|
|
B.Adjust_Tab_Width;
|
|
|
|
B.Highlighter.Highlight_Delimiters := Highlight_Delimiters.Get_Pref;
|
|
end Execute;
|
|
|
|
---------------------
|
|
-- Load_Empty_File --
|
|
---------------------
|
|
|
|
procedure Load_Empty_File (Buffer : access Source_Buffer_Record) is
|
|
begin
|
|
Buffer.Original_Text_Inserted := True;
|
|
Set_Trailing_Lines_Policy (Buffer, No_File, False);
|
|
Set_Trailing_Space_Policy (Buffer, No_File, False);
|
|
end Load_Empty_File;
|
|
|
|
-------------------------------
|
|
-- Set_Trailing_Space_Policy --
|
|
-------------------------------
|
|
|
|
procedure Set_Trailing_Space_Policy
|
|
(Buffer : access Source_Buffer_Record;
|
|
File : GNATCOLL.VFS.Virtual_File;
|
|
Trailing_Spaces_Found : Boolean)
|
|
is
|
|
Found : Boolean;
|
|
Prop : GPS.Properties.Boolean_Property;
|
|
Default : constant Strip_Trailing_Blanks_Policy := Strip_Blanks.Get_Pref;
|
|
begin
|
|
if File /= GNATCOLL.VFS.No_File then
|
|
GPS.Properties.Get_Property
|
|
(Prop, File, Strip_Blanks_Property_Name, Found);
|
|
|
|
if Found then
|
|
Buffer.Strip_Trailing_Blanks := Prop.Value;
|
|
|
|
return;
|
|
end if;
|
|
end if;
|
|
|
|
case Default is
|
|
when Always =>
|
|
Set_Strip_Trailing_Blanks (Buffer, True);
|
|
|
|
when Never =>
|
|
Set_Strip_Trailing_Blanks (Buffer, False);
|
|
|
|
when Autodetect =>
|
|
Set_Strip_Trailing_Blanks (Buffer, not Trailing_Spaces_Found);
|
|
end case;
|
|
end Set_Trailing_Space_Policy;
|
|
|
|
-------------------------------
|
|
-- Set_Trailing_Lines_Policy --
|
|
-------------------------------
|
|
|
|
procedure Set_Trailing_Lines_Policy
|
|
(Buffer : access Source_Buffer_Record;
|
|
File : GNATCOLL.VFS.Virtual_File;
|
|
Trailing_Lines_Found : Boolean)
|
|
is
|
|
Found : Boolean;
|
|
Prop : GPS.Properties.Boolean_Property;
|
|
Default : constant Strip_Trailing_Blanks_Policy := Strip_Lines.Get_Pref;
|
|
begin
|
|
if File /= GNATCOLL.VFS.No_File then
|
|
GPS.Properties.Get_Property
|
|
(Prop, File, Strip_Lines_Property_Name, Found);
|
|
|
|
if Found then
|
|
Buffer.Strip_Trailing_Lines := Prop.Value;
|
|
return;
|
|
end if;
|
|
end if;
|
|
|
|
case Default is
|
|
when Always =>
|
|
Set_Strip_Trailing_Lines (Buffer, True);
|
|
|
|
when Never =>
|
|
Set_Strip_Trailing_Lines (Buffer, False);
|
|
|
|
when Autodetect =>
|
|
Set_Strip_Trailing_Lines (Buffer, not Trailing_Lines_Found);
|
|
end case;
|
|
end Set_Trailing_Lines_Policy;
|
|
|
|
--------------------
|
|
-- Autosaved_File --
|
|
--------------------
|
|
|
|
function Autosaved_File (File : Virtual_File) return Virtual_File is
|
|
begin
|
|
-- Implementation must be in sync with Is_Auto_Save below
|
|
return Create_From_Dir (Dir (File), ".#" & Base_Name (File) & "#");
|
|
exception
|
|
when VFS_Invalid_File_Error =>
|
|
Trace
|
|
(Me,
|
|
"Failed to create Autosaved File for " & Display_Full_Name (File));
|
|
return No_File;
|
|
end Autosaved_File;
|
|
|
|
------------------
|
|
-- Is_Auto_Save --
|
|
------------------
|
|
|
|
function Is_Auto_Save (File : GNATCOLL.VFS.Virtual_File) return Boolean is
|
|
Base : constant String := +Base_Name (File);
|
|
begin
|
|
return
|
|
Base'Length >= 2
|
|
and then Base (Base'First .. Base'First + 1) = ".#"
|
|
and then Base (Base'Last) = '#';
|
|
end Is_Auto_Save;
|
|
|
|
----------------------------
|
|
-- Check_Auto_Saved_Files --
|
|
----------------------------
|
|
|
|
procedure Check_Auto_Saved_Files (Kernel : GPS.Kernel.Kernel_Handle) is
|
|
Saved_Files : File_Sets.Set;
|
|
Dialog : GPS.Dialogs.GPS_Dialog;
|
|
Ignore : Boolean;
|
|
begin
|
|
if Active (Testsuite_Handle) and then Active (Auto_Save_No_Reload) then
|
|
return;
|
|
end if;
|
|
|
|
for File of Kernel.Opened_Files loop
|
|
declare
|
|
Saved : constant Virtual_File := Autosaved_File (File);
|
|
begin
|
|
if Saved.Is_Regular_File and then Compare_SHA1 (File, Saved) then
|
|
Saved_Files.Include (File);
|
|
end if;
|
|
end;
|
|
end loop;
|
|
|
|
if Saved_Files.Is_Empty then
|
|
return;
|
|
end if;
|
|
|
|
Reload_Files_Dialog
|
|
(Kernel => Kernel,
|
|
To_Update => Saved_Files,
|
|
Title => "Autosaved files",
|
|
Description => "Select the autosaved files which will be loaded",
|
|
Extra_Widget => null,
|
|
Force =>
|
|
Active (Testsuite_Handle) and then Active (Auto_Save_Reload),
|
|
Monitored => False,
|
|
Dialog => Dialog);
|
|
|
|
if Dialog /= null then
|
|
Dialog.Destroy;
|
|
end if;
|
|
|
|
for File of Saved_Files loop
|
|
declare
|
|
Buffer : constant access Source_Buffer_Record :=
|
|
Get (Kernel, File);
|
|
begin
|
|
if Buffer /= null then
|
|
-- Replace File by its autosaved counterpart
|
|
Insert_Text
|
|
(Buffer => Buffer,
|
|
From_File => Autosaved_File (File),
|
|
Lang_Autodetect => True,
|
|
Is_Auto_Save => True,
|
|
File_Is_New => False,
|
|
Success => Ignore);
|
|
end if;
|
|
end;
|
|
end loop;
|
|
end Check_Auto_Saved_Files;
|
|
|
|
-----------------
|
|
-- Insert_Text --
|
|
-----------------
|
|
|
|
procedure Insert_Text
|
|
(Buffer : access Source_Buffer_Record;
|
|
From_File : Virtual_File;
|
|
Lang_Autodetect : Boolean;
|
|
Is_Auto_Save : Boolean;
|
|
File_Is_New : Boolean;
|
|
Success : out Boolean)
|
|
is
|
|
|
|
procedure Reset_Buffer (Buffer : access Source_Buffer_Record);
|
|
-- Reset all data associated with Buffer
|
|
|
|
------------------
|
|
-- Reset_Buffer --
|
|
------------------
|
|
|
|
procedure Reset_Buffer (Buffer : access Source_Buffer_Record) is
|
|
begin
|
|
-- Clear the buffer
|
|
|
|
-- The presence of folded lines stops propagation of delete_range:
|
|
-- unfold all lines before clearing.
|
|
Unfold_All (Buffer);
|
|
Clear (Buffer);
|
|
|
|
Buffer.Original_Text_Inserted := False;
|
|
|
|
if Buffer.Editable_Lines /= null then
|
|
Unchecked_Free (Buffer.Editable_Lines);
|
|
end if;
|
|
|
|
for J in Buffer.Line_Data'Range loop
|
|
for K in Highlight_Location loop
|
|
Unchecked_Free (Buffer.Line_Data (J).Highlighting (K).Enabled);
|
|
end loop;
|
|
|
|
if Buffer.Line_Data (J).Side_Info_Data /= null then
|
|
for I in Buffer.Line_Data (J).Side_Info_Data'Range loop
|
|
Free
|
|
(Buffer,
|
|
Buffer.Line_Data (J).Side_Info_Data (I),
|
|
Free_Messages => False);
|
|
end loop;
|
|
Unchecked_Free (Buffer.Line_Data (J).Side_Info_Data);
|
|
end if;
|
|
end loop;
|
|
|
|
Unchecked_Free (Buffer.Line_Data);
|
|
|
|
Initialize_Hook (Buffer);
|
|
|
|
Buffer.First_Removed_Line := 0;
|
|
Buffer.Last_Removed_Line := 0;
|
|
Buffer.Last_Editable_Line := 1;
|
|
|
|
-- Unregister the blocks timeout
|
|
|
|
if Buffer.Blocks_Timeout /= Glib.Main.No_Source_Id then
|
|
Buffer.Blocks_Timeout_Registered := False;
|
|
Glib.Main.Remove (Buffer.Blocks_Timeout);
|
|
Buffer.Blocks_Timeout := Glib.Main.No_Source_Id;
|
|
end if;
|
|
|
|
-- Unregister the cursor timeout
|
|
|
|
Buffer.Blank_Lines := 0;
|
|
Buffer.Hidden_Lines := 0;
|
|
Buffer.Block_Highlighting_Column := -1;
|
|
end Reset_Buffer;
|
|
|
|
Text : VSS.Strings.Virtual_String;
|
|
Props : File_Props;
|
|
|
|
begin
|
|
Trace
|
|
(Me,
|
|
"Loading "
|
|
& From_File.Display_Full_Name
|
|
& " as autosave ? "
|
|
& Is_Auto_Save'Img
|
|
& " is new ? "
|
|
& File_Is_New'Img);
|
|
Success := True;
|
|
Read_File_With_Charset (From_File, Text, Props);
|
|
|
|
if Text.Is_Null then
|
|
-- The file does not exist on disk, this is a new file that has
|
|
-- never been saved.
|
|
Trace
|
|
(Me,
|
|
"Load_File: Couldn't read contents of "
|
|
& From_File.Display_Full_Name);
|
|
Success := False;
|
|
return;
|
|
end if;
|
|
|
|
if Props.Bidirectional_Unicode then
|
|
Buffer.Kernel.Insert
|
|
("Warning: the file contains bidirectional Unicode text that may be"
|
|
& " interpreted or compiled differently than what it looks in the"
|
|
& " editor.",
|
|
Mode => GPS.Kernel.Error);
|
|
end if;
|
|
|
|
if Props.NUL_Found then
|
|
Buffer.Kernel.Insert
|
|
((-"Warning: NUL characters stripped from ")
|
|
& From_File.Display_Full_Name,
|
|
Mode => GPS.Kernel.Error);
|
|
end if;
|
|
|
|
if Props.Invalid_UTF8 then
|
|
Buffer.Kernel.Insert
|
|
((-"Warning: invalid characters stripped from ")
|
|
& From_File.Display_Full_Name,
|
|
Mode => GPS.Kernel.Error);
|
|
end if;
|
|
|
|
declare
|
|
G : Group_Block := New_Group (Buffer.Queue);
|
|
begin
|
|
if not Is_Auto_Save then
|
|
if not File_Is_New then
|
|
Emit_By_Name (Get_Object (Buffer), Signal_Closed & ASCII.NUL);
|
|
Reset_Buffer (Buffer);
|
|
else
|
|
Buffer.Start_Inserting; -- no undo should be available
|
|
end if;
|
|
|
|
if Lang_Autodetect then
|
|
Set_Language
|
|
(Buffer,
|
|
Get_Language_From_File
|
|
(Get_Language_Handler (Buffer.Kernel), From_File));
|
|
end if;
|
|
|
|
Set_Charset (Buffer, Get_File_Charset (From_File));
|
|
Set_Trailing_Space_Policy
|
|
(Buffer, From_File, Props.Trailing_Spaces_Found);
|
|
Set_Trailing_Lines_Policy
|
|
(Buffer, From_File, Props.Trailing_Lines_Found);
|
|
|
|
if Props.CR_Found then
|
|
Buffer.Line_Terminator := CR_LF;
|
|
else
|
|
Buffer.Line_Terminator := LF;
|
|
end if;
|
|
|
|
else
|
|
Reset_Buffer (Buffer);
|
|
end if;
|
|
|
|
-- Insert the new text
|
|
|
|
declare
|
|
use type Ada.Strings.Unbounded.Aux.Big_String_Access;
|
|
|
|
U : constant Unbounded_String :=
|
|
VSS.Strings.Conversions.To_Unbounded_UTF_8_String (Text);
|
|
S : Ada.Strings.Unbounded.Aux.Big_String_Access;
|
|
L : Natural;
|
|
|
|
begin
|
|
Ada.Strings.Unbounded.Aux.Get_String (U, S, L);
|
|
|
|
if S /= null then
|
|
Insert_At_Cursor (Buffer, S (1 .. L));
|
|
end if;
|
|
|
|
exception
|
|
when E : others =>
|
|
Trace (Me, E);
|
|
end;
|
|
|
|
if not Is_Auto_Save and then File_Is_New then
|
|
Buffer.End_Inserting; -- reenable undo
|
|
|
|
end if;
|
|
end;
|
|
|
|
if Is_Auto_Save then
|
|
Buffer.Saved_Position := 0;
|
|
Buffer.Set_Last_Status (Modified);
|
|
elsif File_Is_New then
|
|
Buffer.Saved_Position := 0;
|
|
else
|
|
Buffer.Saved_Position := Get_Position (Buffer.Queue);
|
|
Buffer.Set_Last_Status (Saved);
|
|
end if;
|
|
end Insert_Text;
|
|
|
|
------------------
|
|
-- Compare_SHA1 --
|
|
------------------
|
|
|
|
function Compare_SHA1
|
|
(File : Virtual_File; Autosave : Virtual_File) return Boolean
|
|
is
|
|
Str1, Str2 : GNAT.Strings.String_Access;
|
|
Success : Boolean;
|
|
begin
|
|
Str1 := File.Read_File;
|
|
Str2 := Autosave.Read_File;
|
|
|
|
-- Return immediately if one of the files to be compared
|
|
-- can't be read.
|
|
if Str1 = null or else Str2 = null then
|
|
return True;
|
|
end if;
|
|
|
|
if GNAT.SHA1.Digest (Str1.all) = GNAT.SHA1.Digest (Str2.all) then
|
|
-- same sha1, don't bother the user
|
|
Trace (Me, "Auto-save file has same sha1");
|
|
GNAT.Strings.Free (Str1);
|
|
GNAT.Strings.Free (Str2);
|
|
Autosave.Delete (Success);
|
|
return False;
|
|
end if;
|
|
|
|
GNAT.Strings.Free (Str1);
|
|
GNAT.Strings.Free (Str2);
|
|
return True;
|
|
end Compare_SHA1;
|
|
|
|
---------------
|
|
-- Load_File --
|
|
---------------
|
|
|
|
procedure Load_File
|
|
(Buffer : access Source_Buffer_Record;
|
|
Filename : GNATCOLL.VFS.Virtual_File;
|
|
Lang_Autodetect : Boolean := True;
|
|
Success : out Boolean;
|
|
Is_Load_Desktop : Boolean := False)
|
|
is
|
|
File_Is_New : constant Boolean := not Buffer.Original_Text_Inserted;
|
|
|
|
procedure Check_Auto_Saved_File;
|
|
-- Chech whether there exists an auto-saved file, and whether the user
|
|
-- would like to load it.
|
|
-- Loads the auto-save file if requested
|
|
|
|
---------------------------
|
|
-- Check_Auto_Saved_File --
|
|
---------------------------
|
|
|
|
procedure Check_Auto_Saved_File is
|
|
Autosave : constant Virtual_File := Autosaved_File (Filename);
|
|
Buttons : Message_Dialog_Buttons;
|
|
begin
|
|
if Autosave.Is_Regular_File then
|
|
|
|
if not Compare_SHA1 (Filename, Autosave) then
|
|
return;
|
|
end if;
|
|
|
|
Trace (Me, "Found auto-save file " & Autosave.Display_Full_Name);
|
|
|
|
if Active (Testsuite_Handle) then
|
|
-- In the testsuite, we test for two specific handles to
|
|
-- control the behavior of this dialog:
|
|
if Active (Auto_Save_Reload) then
|
|
Buttons := Button_Yes;
|
|
elsif Active (Auto_Save_No_Reload) then
|
|
Buttons := Button_No;
|
|
else
|
|
Buttons := Button_No;
|
|
-- test is not expecting the dialog, we should report it as
|
|
-- an error, but no need to block with a dialog
|
|
Trace
|
|
(Testsuite_Handle,
|
|
"Would have displayed the dialog about auto-saved file");
|
|
end if;
|
|
else
|
|
Buttons :=
|
|
GPS_Message_Dialog
|
|
(Msg =>
|
|
-"Found an auto-saved file named "
|
|
& Autosave.Display_Base_Name
|
|
& ASCII.LF
|
|
& (-"This usually means that your previous GNAT Studio"
|
|
& " session ")
|
|
& ASCII.LF
|
|
& (-"terminated unexpectedly with unsaved changes.")
|
|
& ASCII.LF
|
|
& ASCII.LF
|
|
& (-"Do you want to recover the contents of ")
|
|
& Filename.Display_Base_Name
|
|
& ASCII.LF
|
|
& (-"from this auto-saved file "
|
|
& " (this operation can be undone) ?"),
|
|
Dialog_Type => Warning,
|
|
Buttons => Button_Yes or Button_No,
|
|
Default_Button => Button_Yes,
|
|
Title => -"Found auto-saved file",
|
|
Justification => Justify_Left,
|
|
Parent => Get_Current_Window (Buffer.Kernel));
|
|
end if;
|
|
|
|
if Buttons = Button_Yes then
|
|
Insert_Text
|
|
(Buffer => Buffer,
|
|
From_File => Autosave,
|
|
Lang_Autodetect => Lang_Autodetect,
|
|
Is_Auto_Save => True,
|
|
File_Is_New => File_Is_New,
|
|
Success => Success);
|
|
end if;
|
|
|
|
-- Do not delete the auto-save file: it will be removed when the
|
|
-- user saves the file (or another auto-save takes place). In the
|
|
-- meantime, should GNAT Studio crash, we still want the user to
|
|
-- be able to restore it next time.
|
|
|
|
end if;
|
|
end Check_Auto_Saved_File;
|
|
|
|
begin
|
|
Insert_Text
|
|
(Buffer => Buffer,
|
|
From_File => Filename,
|
|
Lang_Autodetect => Lang_Autodetect,
|
|
Is_Auto_Save => False,
|
|
File_Is_New => File_Is_New,
|
|
Success => Success);
|
|
|
|
if not Success then
|
|
Buffer.Save_Complete := False;
|
|
return;
|
|
end if;
|
|
|
|
-- If there is an auto-save file that the user wants to reload, we do
|
|
-- this in a separate step, so that this operation can be undone.
|
|
-- We don't want to spawn multiple dialogs while loading a project's
|
|
-- desktop: a dialog with all the autosaved files will be opened later.
|
|
|
|
if not Is_Load_Desktop and then File_Is_New then
|
|
Check_Auto_Saved_File;
|
|
end if;
|
|
|
|
Buffer.Highlighter.On_Load_File;
|
|
|
|
Buffer.Modified_Auto := False;
|
|
|
|
-- Do not empty the undo/redo queue, so that reloading a file can be
|
|
-- undone.
|
|
-- Empty_Queue (Buffer.Queue);
|
|
-- Buffer.Current_Command := null;
|
|
|
|
Buffer.Set_Filename (Filename);
|
|
Buffer.Writable := Filename.Is_Writable;
|
|
|
|
-- Emit the "file_reloaded" hook so that clients know that this file
|
|
-- has just been reloaded from disk - for instance to reset file
|
|
-- markers that have just been sent to location 1,1 during the
|
|
-- buffer reset above.
|
|
if not File_Is_New then
|
|
File_Reloaded_Hook.Run (Buffer.Kernel, Filename);
|
|
end if;
|
|
|
|
exception
|
|
when E : others =>
|
|
Trace (Me, E);
|
|
Success := False;
|
|
end Load_File;
|
|
|
|
------------------------------
|
|
-- Conversion_Error_Message --
|
|
------------------------------
|
|
|
|
function Conversion_Error_Message
|
|
(Charset : String) return VSS.Strings.Virtual_String is
|
|
begin
|
|
return
|
|
VSS.Strings.Conversions.To_Virtual_String
|
|
(-"Error converting from UTF8 to " & Charset);
|
|
end Conversion_Error_Message;
|
|
|
|
---------------------------
|
|
-- Internal_Save_To_File --
|
|
---------------------------
|
|
|
|
procedure Internal_Save_To_File
|
|
(Buffer : Source_Buffer;
|
|
Filename : GNATCOLL.VFS.Virtual_File;
|
|
Internal : Boolean;
|
|
Success : out Boolean;
|
|
Force : Boolean := False)
|
|
is
|
|
FD : Writable_File;
|
|
Terminator : Line_Terminator_Style := Buffer.Line_Terminator;
|
|
-- Whether the file mode has been forced to writable
|
|
U_Buffer : Unbounded_String;
|
|
Has_Errors : Boolean := False;
|
|
Buttons : Message_Dialog_Buttons := Button_None;
|
|
|
|
procedure New_Line;
|
|
-- Append a new line on U_Buffer
|
|
|
|
--------------
|
|
-- New_Line --
|
|
--------------
|
|
|
|
procedure New_Line is
|
|
begin
|
|
case Terminator is
|
|
when CR_LF =>
|
|
Append (U_Buffer, (ASCII.CR & ASCII.LF));
|
|
|
|
when CR =>
|
|
Append (U_Buffer, ASCII.CR);
|
|
|
|
when Unknown | LF =>
|
|
Append (U_Buffer, ASCII.LF);
|
|
end case;
|
|
end New_Line;
|
|
|
|
begin
|
|
Trace (Me, "Internal_Save_To_File " & Filename.Display_Full_Name);
|
|
Success := True;
|
|
|
|
if not Needs_To_Be_Saved (Buffer)
|
|
and then Filename = Buffer.Get_Filename
|
|
and then Filename.Is_Regular_File
|
|
then
|
|
Trace (Me, "File not modified, nothing to do");
|
|
return;
|
|
end if;
|
|
|
|
-- When the user requested a save, interrupt the current action, so
|
|
-- that the "check_modified" command, which is called when the undo/redo
|
|
-- queue changes, has a chance to refresh the modified state.
|
|
End_Action (Buffer);
|
|
|
|
if not Internal then
|
|
-- Run the "before_file_saved" hook
|
|
Before_File_Saved_Hook.Run (Buffer.Kernel, Filename);
|
|
end if;
|
|
|
|
declare
|
|
Terminator_Pref : constant Line_Terminators :=
|
|
Line_Terminator.Get_Pref;
|
|
Bytes_Written : Integer;
|
|
pragma Unreferenced (Bytes_Written);
|
|
|
|
Index : Natural;
|
|
Error : GError_Access := new GError'(null);
|
|
|
|
procedure Unchecked_Free is new
|
|
Ada.Unchecked_Deallocation (GError, GError_Access);
|
|
|
|
Last_Line : Editable_Line_Type := 0;
|
|
begin
|
|
case Terminator_Pref is
|
|
when Unix =>
|
|
Terminator := LF;
|
|
|
|
when Windows =>
|
|
Terminator := CR_LF;
|
|
|
|
when Unchanged =>
|
|
null;
|
|
end case;
|
|
|
|
if Buffer.Strip_Trailing_Lines then
|
|
for Line in reverse
|
|
Buffer.Editable_Lines'First .. Buffer.Last_Editable_Line
|
|
loop
|
|
declare
|
|
Str : constant Src_String :=
|
|
Get_String_At_Line (Buffer, Line);
|
|
begin
|
|
if Str.Contents /= null
|
|
and then not Is_Blank_Line (Str.Contents (1 .. Str.Length))
|
|
then
|
|
Last_Line := Line;
|
|
exit;
|
|
end if;
|
|
end;
|
|
end loop;
|
|
else
|
|
Last_Line := Buffer.Last_Editable_Line;
|
|
end if;
|
|
|
|
for Line in Buffer.Editable_Lines'First .. Last_Line loop
|
|
declare
|
|
Str : Src_String := Get_String_At_Line (Buffer, Line);
|
|
begin
|
|
if Str.Length = 0 then
|
|
if Line /= Buffer.Last_Editable_Line then
|
|
New_Line;
|
|
end if;
|
|
|
|
else
|
|
Index := Str.Length;
|
|
|
|
if Buffer.Strip_Trailing_Blanks then
|
|
-- Safe to use 1 here as Str.Contents'First as this is
|
|
-- the type definition
|
|
for J in reverse 1 .. Str.Length loop
|
|
-- No need for special utf8 handling, as we are just
|
|
-- looking for spaces.
|
|
|
|
exit when
|
|
Str.Contents (J) /= ' '
|
|
and then Str.Contents (J) /= ASCII.HT;
|
|
|
|
-- J points to a blank character. Let's set Index to
|
|
-- the previous character in the string.
|
|
Index := J - 1;
|
|
end loop;
|
|
end if;
|
|
|
|
declare
|
|
Contents : constant String :=
|
|
Glib.Convert.Convert
|
|
(Str.Contents (Str.Contents'First .. Index),
|
|
Buffer.Charset.all,
|
|
"UTF-8",
|
|
Error);
|
|
|
|
begin
|
|
if Error.all = null then
|
|
Append (U_Buffer, Contents);
|
|
else
|
|
-- An error has occurred on this line
|
|
Has_Errors := True;
|
|
|
|
Create_Simple_Message
|
|
(Get_Messages_Container (Buffer.Kernel),
|
|
Conversion_Error_Message (Buffer.Charset.all),
|
|
Buffer.Filename,
|
|
Positive (Line),
|
|
1,
|
|
VSS.Strings.Conversions.To_Virtual_String
|
|
(Get_Message (Error.all)),
|
|
High,
|
|
Src_Editor_Message_Flags);
|
|
|
|
Error_Free (Error.all);
|
|
Error.all := null;
|
|
end if;
|
|
end;
|
|
|
|
New_Line;
|
|
end if;
|
|
|
|
Free (Str);
|
|
end;
|
|
end loop;
|
|
|
|
Unchecked_Free (Error);
|
|
end;
|
|
|
|
-- If we observed UTF-8 conversion errors, warn the user
|
|
|
|
if Has_Errors and then not Internal then
|
|
Buttons :=
|
|
GPS_Message_Dialog
|
|
(Msg =>
|
|
-("This buffer contains UTF-8 characters which"
|
|
& " could not be translated to ")
|
|
& Buffer.Charset.all
|
|
& "."
|
|
& ASCII.LF
|
|
& ASCII.LF
|
|
& (-("Some data may be missing in the saved file: check the"
|
|
& " Locations View."))
|
|
& ASCII.LF
|
|
& ASCII.LF
|
|
& (-("You may change the character set of this file through"
|
|
& " the ""Properties..."" contextual menu.")),
|
|
Dialog_Type => Warning,
|
|
Buttons => Button_OK,
|
|
Default_Button => Button_OK,
|
|
Title => -"Warning: Conversion Incomplete",
|
|
Justification => Justify_Left,
|
|
Parent => Get_Current_Window (Buffer.Kernel));
|
|
end if;
|
|
|
|
-- The file could not be opened, check whether it is read-only
|
|
|
|
if Is_Regular_File (Filename) and then not Is_Writable (Filename) then
|
|
if not Force then
|
|
Buttons :=
|
|
GPS_Message_Dialog
|
|
(Msg =>
|
|
-"The file "
|
|
& Display_Base_Name (Filename)
|
|
& ASCII.LF
|
|
& (-"is read-only. Do you want to overwrite it ?"),
|
|
Dialog_Type => Confirmation,
|
|
Buttons => Button_Yes or Button_No,
|
|
Default_Button => Button_No,
|
|
Title => -"File is read-only",
|
|
Justification => Justify_Left,
|
|
Parent => Get_Current_Window (Buffer.Kernel));
|
|
end if;
|
|
|
|
if Force or else Buttons = Button_Yes then
|
|
Make_File_Writable (Buffer.Kernel, Filename, True);
|
|
Mark_Buffer_Writable (Buffer, True);
|
|
end if;
|
|
end if;
|
|
|
|
declare
|
|
use type Ada.Strings.Unbounded.Aux.Big_String_Access;
|
|
|
|
S : Ada.Strings.Unbounded.Aux.Big_String_Access;
|
|
L : Natural;
|
|
|
|
begin
|
|
FD := Write_File (Filename);
|
|
|
|
if FD = Invalid_File then
|
|
Buffer.Kernel.Insert
|
|
(-"Could not open file for writing: "
|
|
& Display_Full_Name (Filename),
|
|
Mode => GPS.Kernel.Error);
|
|
Buffer.Kernel.Insert
|
|
(To_String (Error_String (FD)), Mode => GPS.Kernel.Error);
|
|
Success := False;
|
|
return;
|
|
end if;
|
|
|
|
Ada.Strings.Unbounded.Aux.Get_String (U_Buffer, S, L);
|
|
|
|
if S /= null then
|
|
Write (FD, S (1 .. L));
|
|
end if;
|
|
|
|
U_Buffer := Null_Unbounded_String;
|
|
|
|
Close (FD);
|
|
|
|
exception
|
|
when E : Ada.Text_IO.Use_Error =>
|
|
Trace (Me, E);
|
|
|
|
if not Internal then
|
|
Buttons :=
|
|
GPS_Message_Dialog
|
|
(Msg =>
|
|
-"The file "
|
|
& Display_Base_Name (Filename)
|
|
& ASCII.LF
|
|
& " could not be saved. This might be a transient disk"
|
|
& " problem.",
|
|
Dialog_Type => Warning,
|
|
Buttons => Button_OK,
|
|
Default_Button => Button_OK,
|
|
Title => -"File could not be saved",
|
|
Justification => Justify_Left,
|
|
Parent => Get_Current_Window (Buffer.Kernel));
|
|
end if;
|
|
return;
|
|
end;
|
|
|
|
-- If the file could be saved, emit the corresponding signal.
|
|
-- Emit the signal only if we are really saving to the buffer's file,
|
|
-- not to another filename (which happens for example when doing
|
|
-- automatic saves.
|
|
|
|
if not Internal then
|
|
Buffer.Save_Complete := not Has_Errors;
|
|
|
|
if Buffer.Filename /= Filename then
|
|
-- If we "save as" the buffer, we emit a closed for the previous
|
|
-- name, unless the file was an unnamed buffer
|
|
if Buffer.Filename /= GNATCOLL.VFS.No_File then
|
|
Emit_File_Closed (Buffer, Buffer.File_Identifier);
|
|
end if;
|
|
|
|
declare
|
|
Old : constant Virtual_File := Buffer.Filename;
|
|
begin
|
|
Buffer.Filename := Filename;
|
|
|
|
-- If we renamed the file, emit the corresponding hook
|
|
-- and notify listeners
|
|
if Old /= Buffer.Filename then
|
|
Emit_File_Renamed (Buffer, Old, Buffer.Filename);
|
|
end if;
|
|
end;
|
|
end if;
|
|
|
|
File_Saved_Hook.Run (Buffer.Kernel, Filename);
|
|
|
|
Buffer.Saved_Position := Get_Position (Buffer.Queue);
|
|
Buffer.Set_Last_Status (Saved);
|
|
end if;
|
|
|
|
exception
|
|
when E : others =>
|
|
Trace (Me, E);
|
|
|
|
-- To avoid consuming up all File Descriptors, we catch all
|
|
-- exceptions here, and close the current file descriptor.
|
|
|
|
if FD /= Invalid_File then
|
|
Close (FD);
|
|
end if;
|
|
end Internal_Save_To_File;
|
|
|
|
------------------
|
|
-- Save_To_File --
|
|
------------------
|
|
|
|
procedure Save_To_File
|
|
(Buffer : access Source_Buffer_Record;
|
|
Filename : GNATCOLL.VFS.Virtual_File;
|
|
Success : out Boolean;
|
|
Internal : Boolean := False;
|
|
Force : Boolean := False)
|
|
is
|
|
-- When a file is created through "Goto spec<->body", the file won't
|
|
-- exist on the disk yet, but Buffer.Filename will already be set. So
|
|
-- we need both tests below
|
|
Name_Changed : constant Boolean :=
|
|
Buffer.Filename /= Filename
|
|
or else not Buffer.Filename.Is_Regular_File;
|
|
|
|
Original_Filename : constant Virtual_File := Buffer.Filename;
|
|
begin
|
|
if not Internal then
|
|
Remove_Completion;
|
|
end if;
|
|
|
|
Internal_Save_To_File
|
|
(Source_Buffer (Buffer), Filename, Internal, Success, Force => Force);
|
|
|
|
if Success and then not Internal then
|
|
if Name_Changed then
|
|
-- Force an update of the persistent properties if need be
|
|
Set_Charset (Buffer, Get_Charset (Buffer));
|
|
Set_Language (Buffer, Get_Language (Buffer));
|
|
|
|
-- ??? The following is expensive, it would be nice to have a
|
|
-- simpler way to report a possible change in the list of sources
|
|
-- of a project.
|
|
Recompute_View (Buffer.Kernel);
|
|
|
|
-- Change the language when we have reparsed the project, so that
|
|
-- the naming scheme is correctly taken into account
|
|
Set_Language
|
|
(Buffer,
|
|
Get_Language_From_File
|
|
(Get_Language_Handler (Buffer.Kernel), Buffer.Filename));
|
|
|
|
-- Emit the "filename_changed" signal
|
|
Buffer.Filename_Changed;
|
|
end if;
|
|
|
|
if Original_Filename /= GNATCOLL.VFS.No_File then
|
|
Buffer.Delete_Autosaved_File (Original_Filename);
|
|
end if;
|
|
|
|
if Filename /= Original_Filename then
|
|
-- We have just "saved as" with a new file name: tell GNAT Studio
|
|
-- that this file is now open
|
|
Emit_File_Edited (Buffer, Filename);
|
|
end if;
|
|
|
|
Buffer.Modified_Auto := False;
|
|
end if;
|
|
|
|
exception
|
|
when E : others =>
|
|
Trace (Me, E);
|
|
end Save_To_File;
|
|
|
|
---------------------------
|
|
-- Delete_Autosaved_File --
|
|
---------------------------
|
|
|
|
procedure Delete_Autosaved_File
|
|
(Buffer : access Source_Buffer_Record; File : GNATCOLL.VFS.Virtual_File)
|
|
is
|
|
Autosaved : constant Virtual_File := Autosaved_File (File);
|
|
Dummy : Boolean;
|
|
begin
|
|
if Active (Me) then
|
|
Trace (Me, "Delete autosave file " & Autosaved.Display_Full_Name);
|
|
end if;
|
|
|
|
if Is_Regular_File (Autosaved) then
|
|
if not Is_Writable (Autosaved) then
|
|
Make_File_Writable (Buffer.Kernel, Autosaved, True);
|
|
end if;
|
|
|
|
Delete (Autosaved, Dummy);
|
|
end if;
|
|
exception
|
|
when E : others =>
|
|
Me.Trace
|
|
(E, "When deleting autosave file " & Autosaved.Display_Full_Name);
|
|
end Delete_Autosaved_File;
|
|
|
|
------------------
|
|
-- Set_Language --
|
|
------------------
|
|
|
|
procedure Set_Language
|
|
(Buffer : access Source_Buffer_Record; Lang : Language.Language_Access)
|
|
is
|
|
Buffer_Start_Iter : Gtk_Text_Iter;
|
|
Buffer_End_Iter : Gtk_Text_Iter;
|
|
begin
|
|
if Buffer.Lang /= Lang then
|
|
Buffer.Lang := Lang;
|
|
Get_Bounds (Buffer, Buffer_Start_Iter, Buffer_End_Iter);
|
|
|
|
Buffer.Highlighter.On_Set_Language
|
|
(Buffer_Start_Iter, Buffer_End_Iter);
|
|
|
|
Register_Edit_Timeout (Buffer);
|
|
|
|
Buffer_Information_Changed (Buffer);
|
|
end if;
|
|
|
|
if Buffer.Filename /= GNATCOLL.VFS.No_File then
|
|
if Lang
|
|
/= Get_Language_From_File
|
|
(Get_Language_Handler (Buffer.Kernel), Buffer.Filename)
|
|
then
|
|
if Lang = Language.Unknown.Unknown_Lang
|
|
or else Lang
|
|
= Get_Language_From_File
|
|
(Get_Language_Handler (Buffer.Kernel),
|
|
Buffer.Filename,
|
|
From_Project_Only => True)
|
|
then
|
|
-- If this is the same as the project => Do not save, so that
|
|
-- changing in the project correctly impacts this file
|
|
Set_Language_From_File (Buffer.Kernel, Buffer.Filename, "");
|
|
else
|
|
-- Note for the future which language should be used
|
|
Set_Language_From_File
|
|
(Buffer.Kernel, Buffer.Filename, Get_Name (Lang));
|
|
end if;
|
|
end if;
|
|
end if;
|
|
Buffer.Adjust_Tab_Width;
|
|
end Set_Language;
|
|
|
|
-----------------
|
|
-- Set_Charset --
|
|
-----------------
|
|
|
|
procedure Set_Charset
|
|
(Buffer : access Source_Buffer_Record; Charset : String)
|
|
is
|
|
Success : Boolean;
|
|
Ignore : Message_Dialog_Buttons;
|
|
pragma Unreferenced (Ignore);
|
|
|
|
begin
|
|
if Buffer.Filename /= GNATCOLL.VFS.No_File then
|
|
if Charset /= Get_File_Charset (Buffer.Filename) then
|
|
if Charset = Get_File_Charset (GNATCOLL.VFS.No_File) then
|
|
-- Since we are using the default charset, do not save in the
|
|
-- properties
|
|
Set_File_Charset (Buffer.Kernel, Buffer.Filename, "");
|
|
else
|
|
-- Else note for the future which charset should be used
|
|
Set_File_Charset (Buffer.Kernel, Buffer.Filename, Charset);
|
|
end if;
|
|
end if;
|
|
end if;
|
|
|
|
if Buffer.Charset /= null and then Buffer.Charset.all /= Charset then
|
|
-- The charset is being changed: remove from the Locations View
|
|
-- the category listing the conversion errors from that charset for
|
|
-- this file.
|
|
|
|
Get_Messages_Container (Buffer.Kernel).Remove_File
|
|
(Conversion_Error_Message (Buffer.Charset.all),
|
|
Buffer.Filename,
|
|
Src_Editor_Message_Flags);
|
|
|
|
GNAT.Strings.Free (Buffer.Charset);
|
|
Buffer.Charset := new String'(Charset);
|
|
|
|
if Get_Status (Buffer) = Modified
|
|
or else Get_Status (Buffer) = Unsaved
|
|
then
|
|
Ignore :=
|
|
GPS_Message_Dialog
|
|
(Msg =>
|
|
-("The character set has been modified."
|
|
& ASCII.LF
|
|
& "Since the file is currently modified, the new"
|
|
& ASCII.LF
|
|
& "character set will only apply when the file is"
|
|
& ASCII.LF
|
|
& " saved, the file will not be reloaded automatically"),
|
|
Dialog_Type => Warning,
|
|
Buttons => Button_OK,
|
|
Title => -"Warning: charset modified",
|
|
Parent => Get_Main_Window (Buffer.Kernel));
|
|
|
|
else
|
|
-- If the user has tried to save Buffer and got errors, do not
|
|
-- reload the file from the disk, as the user would then lose his
|
|
-- modifications that could not be saved.
|
|
if Buffer.Save_Complete then
|
|
Load_File (Buffer, Buffer.Filename, Success => Success);
|
|
end if;
|
|
end if;
|
|
else
|
|
GNAT.Strings.Free (Buffer.Charset);
|
|
Buffer.Charset := new String'(Charset);
|
|
end if;
|
|
end Set_Charset;
|
|
|
|
-----------------
|
|
-- Get_Charset --
|
|
-----------------
|
|
|
|
function Get_Charset (Buffer : access Source_Buffer_Record) return String is
|
|
begin
|
|
if Buffer.Charset = null then
|
|
return Get_File_Charset (GNATCOLL.VFS.No_File);
|
|
else
|
|
return Buffer.Charset.all;
|
|
end if;
|
|
end Get_Charset;
|
|
|
|
------------------
|
|
-- Get_Language --
|
|
------------------
|
|
|
|
function Get_Language
|
|
(Buffer : access Source_Buffer_Record) return Language.Language_Access is
|
|
begin
|
|
return Buffer.Lang;
|
|
end Get_Language;
|
|
|
|
-----------------------------
|
|
-- Should_Extend_Selection --
|
|
-----------------------------
|
|
|
|
function Should_Extend_Selection
|
|
(Buffer : not null access Source_Buffer_Record;
|
|
Extend_Selection : Boolean) return Boolean is
|
|
begin
|
|
return Extend_Selection or else Buffer.Extend_Existing_Selection;
|
|
end Should_Extend_Selection;
|
|
|
|
-----------------------------------
|
|
-- Set_Extend_Existing_Selection --
|
|
-----------------------------------
|
|
|
|
procedure Set_Extend_Existing_Selection
|
|
(Buffer : not null access Source_Buffer_Record; Extend : Boolean) is
|
|
begin
|
|
Buffer.Extend_Existing_Selection := Extend;
|
|
end Set_Extend_Existing_Selection;
|
|
|
|
-------------------------------
|
|
-- Extend_Existing_Selection --
|
|
-------------------------------
|
|
|
|
function Extend_Existing_Selection
|
|
(Buffer : not null access Source_Buffer_Record) return Boolean is
|
|
begin
|
|
return Buffer.Extend_Existing_Selection;
|
|
end Extend_Existing_Selection;
|
|
|
|
-------------------------
|
|
-- Set_Cursor_Position --
|
|
-------------------------
|
|
|
|
procedure Set_Cursor_Position
|
|
(Buffer : access Source_Buffer_Record;
|
|
Line : Gint;
|
|
Column : Gint;
|
|
Internal : Boolean;
|
|
Extend_Selection : Boolean := False)
|
|
is
|
|
Iter : Gtk_Text_Iter;
|
|
begin
|
|
if not Is_Valid_Position (Buffer, Line, Column) then
|
|
Trace
|
|
(Me,
|
|
"invalid position for Set_Cursor_Position "
|
|
& Get_Filename (Buffer).Display_Full_Name
|
|
& Line'Img
|
|
& Column'Img);
|
|
return;
|
|
end if;
|
|
|
|
if not Internal then
|
|
-- Any explicit (ie, forced programatically, as opposed to through
|
|
-- user typing) cursor movement should end the current action, and
|
|
-- destroy the smart completion window.
|
|
End_Action (Buffer);
|
|
Remove_Completion;
|
|
end if;
|
|
|
|
-- At this point, we know that the (Line, Column) position is
|
|
-- valid, so we can safely get the iterator at this position.
|
|
|
|
Get_Iter_At_Line_Offset (Buffer, Iter, Line, Column);
|
|
|
|
if Buffer.Should_Extend_Selection (Extend_Selection) then
|
|
Move_Mark (Buffer, Buffer.Insert_Mark, Iter);
|
|
else
|
|
Place_Cursor (Buffer, Iter);
|
|
end if;
|
|
end Set_Cursor_Position;
|
|
|
|
procedure Set_Cursor_Position
|
|
(Buffer : access Source_Buffer_Record;
|
|
Line : Editable_Line_Type;
|
|
Column : Character_Offset_Type;
|
|
Internal : Boolean;
|
|
Extend_Selection : Boolean := False)
|
|
is
|
|
Buffer_Line : Buffer_Line_Type;
|
|
begin
|
|
if Buffer.Do_Not_Move_Cursor then
|
|
return;
|
|
end if;
|
|
|
|
-- If the line is in a non-visible line, make the line visible
|
|
|
|
Unfold_Line (Buffer, Line);
|
|
Buffer_Line := Get_Buffer_Line (Buffer, Line);
|
|
|
|
Set_Cursor_Position
|
|
(Buffer,
|
|
Gint (Buffer_Line - 1),
|
|
Gint (Column - 1),
|
|
Internal => Internal,
|
|
Extend_Selection => Extend_Selection);
|
|
end Set_Cursor_Position;
|
|
|
|
---------------------------------
|
|
-- Get_Iter_At_Screen_Position --
|
|
---------------------------------
|
|
|
|
procedure Get_Iter_At_Screen_Position
|
|
(Buffer : access Source_Buffer_Record;
|
|
Iter : out Gtk_Text_Iter;
|
|
Line : Gint;
|
|
Column : Visible_Column_Type)
|
|
is
|
|
Tab_Len : constant Positive := Buffer.Tab_Width;
|
|
The_Column : constant Gint := Gint (Column - 1);
|
|
Result : Boolean := True;
|
|
Current : Gint := 0;
|
|
begin
|
|
if Is_Valid_Position (Buffer, Line, 0) then
|
|
Get_Iter_At_Line_Offset (Buffer, Iter, Line, 0);
|
|
else
|
|
Trace
|
|
(Me,
|
|
"Invalid position for Set_Screen_Position "
|
|
& Get_Filename (Buffer).Display_Full_Name
|
|
& Line'Img);
|
|
Get_End_Iter (Buffer, Iter);
|
|
end if;
|
|
|
|
-- We have to test Result, in case Iter was pointing after the end of
|
|
-- the buffer.
|
|
|
|
while Result and then Current < The_Column loop
|
|
if Get_Char (Iter) = ASCII.HT then
|
|
Current := Current + Gint (Tab_Len) - (Current mod Gint (Tab_Len));
|
|
else
|
|
Current := Current + 1;
|
|
end if;
|
|
|
|
exit when Ends_Line (Iter);
|
|
|
|
Forward_Char (Iter, Result);
|
|
end loop;
|
|
end Get_Iter_At_Screen_Position;
|
|
|
|
---------------------------------
|
|
-- Get_Iter_At_Screen_Position --
|
|
---------------------------------
|
|
|
|
procedure Get_Iter_At_Screen_Position
|
|
(Buffer : access Source_Buffer_Record;
|
|
Iter : out Gtk_Text_Iter;
|
|
Line : Editable_Line_Type;
|
|
Column : Visible_Column_Type)
|
|
is
|
|
Buffer_Line : Buffer_Line_Type;
|
|
begin
|
|
Buffer_Line := Get_Buffer_Line (Buffer, Line);
|
|
|
|
if Buffer_Line /= 0 then
|
|
Get_Iter_At_Screen_Position
|
|
(Buffer, Iter, Gint (Buffer_Line - 1), Column);
|
|
else
|
|
Get_Iter_At_Line_Offset (Buffer, Iter, 0, 0);
|
|
end if;
|
|
end Get_Iter_At_Screen_Position;
|
|
|
|
procedure Get_Iter_At_Screen_Position
|
|
(Buffer : access Source_Buffer_Record;
|
|
Iter : out Gtk.Text_Iter.Gtk_Text_Iter;
|
|
Line : Editable_Line_Type;
|
|
Column : Character_Offset_Type)
|
|
is
|
|
Buffer_Line : Buffer_Line_Type;
|
|
begin
|
|
Buffer_Line := Get_Buffer_Line (Buffer, Line);
|
|
|
|
if Buffer_Line /= 0 then
|
|
Get_Iter_At_Line_Offset
|
|
(Buffer, Iter, Gint (Buffer_Line - 1), Gint (Column - 1));
|
|
else
|
|
Get_Iter_At_Line_Offset (Buffer, Iter, 0, 0);
|
|
end if;
|
|
end Get_Iter_At_Screen_Position;
|
|
|
|
-------------------------
|
|
-- Get_Screen_Position --
|
|
-------------------------
|
|
|
|
procedure Get_Screen_Position
|
|
(Buffer : access Source_Buffer_Record;
|
|
Iter : Gtk_Text_Iter;
|
|
Line : out Gint;
|
|
Column : out Gint)
|
|
is
|
|
Start : Gtk_Text_Iter;
|
|
Result : Boolean := True;
|
|
Tab_Len : constant Positive := Buffer.Tab_Width;
|
|
|
|
begin
|
|
Line := Get_Line (Iter);
|
|
Column := 0;
|
|
Get_Iter_At_Line_Offset (Buffer, Start, Line, 0);
|
|
|
|
-- We have to test Result, in case Iter was pointing after the end of
|
|
-- the buffer.
|
|
|
|
while Result and then not Equal (Start, Iter) loop
|
|
if Get_Char (Start) = ASCII.HT then
|
|
Column := Column + Gint (Tab_Len) - (Column mod Gint (Tab_Len));
|
|
else
|
|
Column := Column + 1;
|
|
end if;
|
|
|
|
Forward_Char (Start, Result);
|
|
end loop;
|
|
end Get_Screen_Position;
|
|
|
|
procedure Get_Screen_Position
|
|
(Buffer : access Source_Buffer_Record; Line : out Gint; Column : out Gint)
|
|
is
|
|
Iter : Gtk_Text_Iter;
|
|
begin
|
|
Get_Iter_At_Mark (Buffer, Iter, Buffer.Insert_Mark);
|
|
Get_Screen_Position (Buffer, Iter, Line, Column);
|
|
end Get_Screen_Position;
|
|
|
|
-------------------------
|
|
-- Get_Cursor_Position --
|
|
-------------------------
|
|
|
|
procedure Get_Cursor_Position
|
|
(Buffer : access Source_Buffer_Record; Line : out Gint; Column : out Gint)
|
|
is
|
|
Iter : Gtk_Text_Iter;
|
|
begin
|
|
Get_Iter_At_Mark (Buffer, Iter, Buffer.Insert_Mark);
|
|
Line := Get_Line (Iter);
|
|
Column := Get_Line_Offset (Iter);
|
|
end Get_Cursor_Position;
|
|
|
|
-----------------------
|
|
-- Get_Iter_Position --
|
|
-----------------------
|
|
|
|
procedure Get_Iter_Position
|
|
(Buffer : Source_Buffer;
|
|
Iter : Gtk_Text_Iter;
|
|
Line : out Editable_Line_Type;
|
|
Column : out Character_Offset_Type) is
|
|
begin
|
|
Line :=
|
|
Get_Editable_Line (Buffer, Buffer_Line_Type (Get_Line (Iter) + 1));
|
|
Column := Character_Offset_Type (Get_Line_Offset (Iter) + 1);
|
|
end Get_Iter_Position;
|
|
|
|
-----------------------
|
|
-- Get_Iter_Position --
|
|
-----------------------
|
|
|
|
procedure Get_Iter_Position
|
|
(Buffer : Source_Buffer;
|
|
Iter : Gtk.Text_Iter.Gtk_Text_Iter;
|
|
Line : out Editable_Line_Type;
|
|
Column : out Visible_Column_Type)
|
|
is
|
|
Col : Character_Offset_Type;
|
|
begin
|
|
Get_Iter_Position (Buffer, Iter, Line, Col);
|
|
Column := Expand_Tabs (Buffer, Line, Col);
|
|
end Get_Iter_Position;
|
|
|
|
-----------------------
|
|
-- Get_Iter_Position --
|
|
-----------------------
|
|
|
|
procedure Get_Iter_Position
|
|
(Buffer : Source_Buffer;
|
|
Iter : Gtk.Text_Iter.Gtk_Text_Iter;
|
|
Loc : out Loc_T) is
|
|
begin
|
|
Get_Iter_Position (Buffer, Iter, Loc.Line, Loc.Col);
|
|
end Get_Iter_Position;
|
|
|
|
-----------------------
|
|
-- Get_Mark_Position --
|
|
-----------------------
|
|
|
|
procedure Get_Mark_Position
|
|
(Buffer : Source_Buffer;
|
|
Mark : Gtk.Text_Mark.Gtk_Text_Mark;
|
|
Loc : out Loc_T)
|
|
is
|
|
I : Gtk_Text_Iter;
|
|
begin
|
|
Buffer.Get_Iter_At_Mark (I, Mark);
|
|
Get_Iter_Position (Buffer, I, Loc);
|
|
end Get_Mark_Position;
|
|
|
|
-------------------------
|
|
-- Get_Cursor_Position --
|
|
-------------------------
|
|
|
|
procedure Get_Cursor_Position
|
|
(Buffer : access Source_Buffer_Record;
|
|
Iter : out Gtk.Text_Iter.Gtk_Text_Iter) is
|
|
begin
|
|
Get_Iter_At_Mark (Buffer, Iter, Buffer.Insert_Mark);
|
|
end Get_Cursor_Position;
|
|
|
|
-------------------------
|
|
-- Get_Cursor_Position --
|
|
-------------------------
|
|
|
|
procedure Get_Cursor_Position
|
|
(Buffer : access Source_Buffer_Record;
|
|
Line : out Editable_Line_Type;
|
|
Column : out Character_Offset_Type)
|
|
is
|
|
Iter : Gtk_Text_Iter;
|
|
begin
|
|
Get_Cursor_Position (Buffer, Iter);
|
|
Get_Iter_Position (Source_Buffer (Buffer), Iter, Line, Column);
|
|
end Get_Cursor_Position;
|
|
|
|
procedure Get_Cursor_Position
|
|
(Buffer : access Source_Buffer_Record;
|
|
Line : out Editable_Line_Type;
|
|
Column : out Visible_Column_Type)
|
|
is
|
|
Col : Character_Offset_Type;
|
|
begin
|
|
Get_Cursor_Position (Buffer, Line, Col);
|
|
Column := Expand_Tabs (Buffer, Line, Col);
|
|
end Get_Cursor_Position;
|
|
|
|
---------------
|
|
-- Ends_Word --
|
|
---------------
|
|
|
|
function Ends_Word
|
|
(Buffer : access Source_Buffer_Record; Iter : Gtk_Text_Iter)
|
|
return Boolean
|
|
is
|
|
Next : Gtk_Text_Iter;
|
|
Res : Boolean;
|
|
begin
|
|
if not Inside_Word (Buffer, Iter) then
|
|
return False;
|
|
end if;
|
|
|
|
Copy (Iter, Next);
|
|
Forward_Char (Next, Res);
|
|
|
|
if Res then
|
|
return not Inside_Word (Buffer, Next);
|
|
else
|
|
return True;
|
|
end if;
|
|
end Ends_Word;
|
|
|
|
-----------------
|
|
-- Starts_Word --
|
|
-----------------
|
|
|
|
function Starts_Word
|
|
(Buffer : access Source_Buffer_Record; Iter : Gtk.Text_Iter.Gtk_Text_Iter)
|
|
return Boolean
|
|
is
|
|
Prev : Gtk_Text_Iter;
|
|
Res : Boolean;
|
|
begin
|
|
if not Inside_Word (Buffer, Iter) then
|
|
return False;
|
|
end if;
|
|
|
|
Copy (Iter, Prev);
|
|
Backward_Char (Prev, Res);
|
|
|
|
if Res then
|
|
return not Inside_Word (Buffer, Prev);
|
|
else
|
|
return True;
|
|
end if;
|
|
end Starts_Word;
|
|
|
|
-----------------
|
|
-- Inside_Word --
|
|
-----------------
|
|
|
|
function Inside_Word
|
|
(Buffer : access Source_Buffer_Record; Iter : Gtk.Text_Iter.Gtk_Text_Iter)
|
|
return Boolean is
|
|
begin
|
|
if Buffer.Lang /= null then
|
|
return
|
|
Buffer.Lang.Is_Word_Char
|
|
(Wide_Wide_Character'Val (Gunichar'(Get_Char (Iter))));
|
|
else
|
|
return False;
|
|
end if;
|
|
end Inside_Word;
|
|
|
|
-------------------------
|
|
-- Select_Current_Word --
|
|
-------------------------
|
|
|
|
procedure Select_Current_Word (Buffer : access Source_Buffer_Record) is
|
|
Success : Boolean;
|
|
Start_Iter, End_Iter, Iter : Gtk_Text_Iter;
|
|
|
|
begin
|
|
Get_Selection_Bounds (Buffer, Start_Iter, End_Iter, Success);
|
|
|
|
if Success then
|
|
-- Check the current selection: if more than a single word is
|
|
-- selected then return.
|
|
|
|
Copy (Start_Iter, Iter);
|
|
Forward_Char (Iter, Success);
|
|
|
|
loop
|
|
exit when Equal (Iter, End_Iter);
|
|
|
|
if not Inside_Word (Iter) then
|
|
return;
|
|
end if;
|
|
|
|
Forward_Char (Iter, Success);
|
|
exit when not Success;
|
|
end loop;
|
|
|
|
Backward_Char (End_Iter, Success);
|
|
|
|
elsif not Inside_Word (Start_Iter) and then Get_Char (Start_Iter) /= '_'
|
|
then
|
|
return;
|
|
end if;
|
|
|
|
Success := True;
|
|
while not Ends_Word (Buffer, End_Iter) loop
|
|
Forward_Char (End_Iter, Success);
|
|
exit when not Success;
|
|
end loop;
|
|
|
|
if Success then
|
|
Forward_Char (End_Iter, Success);
|
|
end if;
|
|
|
|
while not Starts_Word (Buffer, Start_Iter) loop
|
|
Backward_Char (Start_Iter, Success);
|
|
exit when not Success;
|
|
end loop;
|
|
|
|
Buffer.Select_Range (End_Iter, Start_Iter);
|
|
end Select_Current_Word;
|
|
|
|
--------------
|
|
-- Get_Text --
|
|
--------------
|
|
|
|
function Get_Text
|
|
(Buffer : Source_Buffer;
|
|
Start_Line : Gint;
|
|
Start_Column : Gint;
|
|
End_Line : Gint;
|
|
End_Column : Gint;
|
|
Include_Hidden_Chars : Boolean := True;
|
|
Include_Last : Boolean := False) return Unbounded_String is
|
|
begin
|
|
return
|
|
Get_Text
|
|
(Buffer => Buffer,
|
|
Start_Line => Editable_Line_Type (Start_Line + 1),
|
|
Start_Column => Character_Offset_Type (Start_Column + 1),
|
|
End_Line => Editable_Line_Type (End_Line + 1),
|
|
End_Column => Character_Offset_Type (End_Column + 1),
|
|
Include_Hidden_Chars => Include_Hidden_Chars,
|
|
Include_Last => Include_Last);
|
|
end Get_Text;
|
|
|
|
--------------------------
|
|
-- Get_Selection_Bounds --
|
|
--------------------------
|
|
|
|
procedure Get_Selection_Bounds
|
|
(Buffer : access Source_Buffer_Record;
|
|
Start_Line : out Gint;
|
|
Start_Column : out Gint;
|
|
End_Line : out Gint;
|
|
End_Column : out Gint;
|
|
Found : out Boolean)
|
|
is
|
|
Start_Iter : Gtk_Text_Iter;
|
|
End_Iter : Gtk_Text_Iter;
|
|
begin
|
|
Get_Selection_Bounds (Buffer, Start_Iter, End_Iter, Found);
|
|
|
|
if Found then
|
|
Start_Line := Get_Line (Start_Iter);
|
|
Start_Column := Get_Line_Offset (Start_Iter);
|
|
End_Line := Get_Line (End_Iter);
|
|
End_Column := Get_Line_Offset (End_Iter);
|
|
else
|
|
Start_Line := 0;
|
|
Start_Column := 0;
|
|
End_Line := 0;
|
|
End_Column := 0;
|
|
end if;
|
|
end Get_Selection_Bounds;
|
|
|
|
procedure Get_Selection_Bounds
|
|
(Buffer : access Source_Buffer_Record;
|
|
Start_Line : out Editable_Line_Type;
|
|
Start_Column : out Character_Offset_Type;
|
|
End_Line : out Editable_Line_Type;
|
|
End_Column : out Character_Offset_Type;
|
|
Found : out Boolean)
|
|
is
|
|
SL, SC, EL, EC : Gint;
|
|
begin
|
|
Get_Selection_Bounds (Buffer, SL, SC, EL, EC, Found);
|
|
|
|
Start_Line := Get_Editable_Line (Buffer, Buffer_Line_Type (SL + 1));
|
|
End_Line := Get_Editable_Line (Buffer, Buffer_Line_Type (EL + 1));
|
|
Start_Column := Character_Offset_Type (SC + 1);
|
|
End_Column := Character_Offset_Type (EC + 1);
|
|
end Get_Selection_Bounds;
|
|
|
|
-------------------
|
|
-- Get_Selection --
|
|
-------------------
|
|
|
|
function Get_Selection (Buffer : access Source_Buffer_Record) return String
|
|
is
|
|
Start_Iter : Gtk_Text_Iter;
|
|
End_Iter : Gtk_Text_Iter;
|
|
Found : Boolean;
|
|
|
|
begin
|
|
Get_Selection_Bounds (Buffer, Start_Iter, End_Iter, Found);
|
|
|
|
if Found then
|
|
return
|
|
To_String
|
|
(Get_Text
|
|
(Source_Buffer (Buffer),
|
|
Get_Line (Start_Iter),
|
|
Get_Line_Offset (Start_Iter),
|
|
Get_Line (End_Iter),
|
|
Get_Line_Offset (End_Iter),
|
|
Include_Last => True));
|
|
else
|
|
return "";
|
|
end if;
|
|
end Get_Selection;
|
|
|
|
------------
|
|
-- Insert --
|
|
------------
|
|
|
|
procedure Insert
|
|
(Buffer : access Source_Buffer_Record;
|
|
Line : Gint;
|
|
Column : Gint;
|
|
Text : String;
|
|
Enable_Undo : Boolean := True)
|
|
is
|
|
Iter : Gtk_Text_Iter;
|
|
begin
|
|
pragma Assert (Is_Valid_Position (Buffer, Line, Column));
|
|
|
|
if not Buffer.Writable then
|
|
End_Action (Buffer);
|
|
return;
|
|
end if;
|
|
|
|
if not Enable_Undo then
|
|
Buffer.Start_Inserting;
|
|
end if;
|
|
|
|
Get_Iter_At_Line_Offset (Buffer, Iter, Line, Column);
|
|
Insert (Buffer, Iter, Text);
|
|
|
|
if not Enable_Undo then
|
|
Buffer.End_Inserting;
|
|
end if;
|
|
|
|
Register_Edit_Timeout (Buffer);
|
|
end Insert;
|
|
|
|
procedure Insert
|
|
(Buffer : access Source_Buffer_Record;
|
|
Line : Editable_Line_Type;
|
|
Column : Character_Offset_Type;
|
|
Text : String;
|
|
Enable_Undo : Boolean := True)
|
|
is
|
|
Buffer_Line : Buffer_Line_Type;
|
|
begin
|
|
if not Buffer.Writable then
|
|
End_Action (Buffer);
|
|
return;
|
|
end if;
|
|
|
|
Unfold_Line (Buffer, Line);
|
|
Buffer_Line := Get_Buffer_Line (Buffer, Line);
|
|
|
|
if Buffer_Line = 0 then
|
|
Trace (Me, "invalid buffer line");
|
|
return;
|
|
end if;
|
|
|
|
Insert
|
|
(Buffer, Gint (Buffer_Line - 1), Gint (Column - 1), Text, Enable_Undo);
|
|
end Insert;
|
|
|
|
------------
|
|
-- Delete --
|
|
------------
|
|
|
|
procedure Delete
|
|
(Buffer : access Source_Buffer_Record;
|
|
Line : Gint;
|
|
Column : Gint;
|
|
Length : Gint;
|
|
Enable_Undo : Boolean := True)
|
|
is
|
|
Iter : Gtk_Text_Iter;
|
|
End_Iter : Gtk_Text_Iter;
|
|
Result : Boolean;
|
|
begin
|
|
pragma Assert (Is_Valid_Position (Buffer, Line, Column));
|
|
|
|
if not Buffer.Inserting then
|
|
End_Action (Buffer);
|
|
end if;
|
|
|
|
if not Enable_Undo then
|
|
Buffer.Start_Inserting;
|
|
end if;
|
|
|
|
Get_Iter_At_Line_Offset (Buffer, Iter, Line, Column);
|
|
Copy (Iter, End_Iter);
|
|
|
|
-- If the file contains blank lines, we need to move step by step here
|
|
-- and ignore blank lines.
|
|
|
|
if Buffer.Blank_Lines = 0 then
|
|
Forward_Chars (End_Iter, Length, Result);
|
|
|
|
else
|
|
declare
|
|
Remaining : Gint := Length;
|
|
Success : Boolean;
|
|
Buff_Line : Gint;
|
|
begin
|
|
Buff_Line := Get_Line (End_Iter);
|
|
|
|
Advance_Char :
|
|
loop
|
|
exit Advance_Char when Remaining = 0;
|
|
|
|
Forward_Char (End_Iter, Success);
|
|
exit Advance_Char when not Success;
|
|
|
|
if Get_Line (End_Iter) /= Buff_Line then
|
|
Buff_Line := Get_Line (End_Iter);
|
|
|
|
-- We have moved the End_Iter to another line. We need to
|
|
-- check whether this line is a blank line, and in this case
|
|
-- we move to the next line until we have found a non-blank
|
|
-- line.
|
|
|
|
while Get_Editable_Line
|
|
(Buffer, Buffer_Line_Type (Buff_Line + 1))
|
|
= 0
|
|
loop
|
|
Forward_Line (End_Iter, Success);
|
|
exit Advance_Char when not Success;
|
|
end loop;
|
|
end if;
|
|
|
|
Remaining := Remaining - 1;
|
|
end loop Advance_Char;
|
|
end;
|
|
end if;
|
|
|
|
Delete_Interactive (Buffer, Iter, End_Iter, True, Result);
|
|
|
|
if not Enable_Undo then
|
|
Buffer.End_Inserting;
|
|
end if;
|
|
|
|
Register_Edit_Timeout (Buffer);
|
|
end Delete;
|
|
|
|
procedure Delete
|
|
(Buffer : access Source_Buffer_Record;
|
|
Line : Editable_Line_Type;
|
|
Column : Character_Offset_Type;
|
|
Length : Natural;
|
|
Enable_Undo : Boolean := True)
|
|
is
|
|
Buffer_Line : Buffer_Line_Type;
|
|
|
|
begin
|
|
if not Buffer.Writable then
|
|
End_Action (Buffer);
|
|
return;
|
|
end if;
|
|
|
|
Unfold_Line (Buffer, Line);
|
|
Buffer_Line := Get_Buffer_Line (Buffer, Line);
|
|
|
|
if Buffer_Line = 0 then
|
|
Trace (Me, "invalid buffer line");
|
|
return;
|
|
end if;
|
|
|
|
Delete
|
|
(Buffer,
|
|
Gint (Buffer_Line - 1),
|
|
Gint (Column - 1),
|
|
Gint (Length),
|
|
Enable_Undo);
|
|
end Delete;
|
|
|
|
-------------------------
|
|
-- Delete_Tab_Backward --
|
|
-------------------------
|
|
|
|
procedure Delete_Tab_Backward (Buffer : access Source_Buffer_Record) is
|
|
Cursor_Mark : Gtk_Text_Mark;
|
|
Iter, To : Gtk_Text_Iter;
|
|
Result : Boolean;
|
|
begin
|
|
for Cursor of Get_Cursors (Source_Buffer (Buffer)) loop
|
|
-- For all cursors
|
|
|
|
Cursor_Mark := Get_Mark (Cursor);
|
|
Set_Manual_Sync (Cursor);
|
|
Get_Iter_At_Mark (Buffer, To, Cursor_Mark);
|
|
|
|
if Get_Line_Offset (To) /= 0 then
|
|
-- Not at the beginning of the line
|
|
|
|
Copy (Source => To, Dest => Iter);
|
|
Backward_Char (Iter, Result);
|
|
for Pos in 1 .. Buffer.Tab_Width - 1 loop
|
|
-- Move backward until we reach Tab_Width or find not HT/space
|
|
exit when
|
|
not Result
|
|
or else Get_Line_Offset (Iter) = 0
|
|
or else Get_Char (Iter) /= ' ';
|
|
Backward_Char (Iter, Result);
|
|
end loop;
|
|
|
|
if Get_Char (Iter) /= Character'Pos (ASCII.HT)
|
|
and then Get_Char (Iter) /= ' '
|
|
then
|
|
-- Move forward when cursor is not on the space or <tab>
|
|
Forward_Char (Iter, Result);
|
|
end if;
|
|
Delete (Buffer, Iter, To);
|
|
end if;
|
|
end loop;
|
|
end Delete_Tab_Backward;
|
|
|
|
------------------------
|
|
-- Replace_Slice_Real --
|
|
------------------------
|
|
|
|
procedure Replace_Slice_Real
|
|
(Buffer : access Source_Buffer_Record;
|
|
Start_Line : Gint;
|
|
Start_Column : Gint;
|
|
End_Line : Gint;
|
|
End_Column : Gint;
|
|
Text : String;
|
|
Enable_Undo : Boolean := True)
|
|
is
|
|
Start_Iter : Gtk_Text_Iter;
|
|
End_Iter : Gtk_Text_Iter;
|
|
|
|
begin
|
|
Assert
|
|
(Me,
|
|
Is_Valid_Position (Buffer, Start_Line, Start_Column),
|
|
"Invalid start position " & Start_Line'Img & Start_Column'Img);
|
|
Assert
|
|
(Me,
|
|
Is_Valid_Position (Buffer, End_Line, End_Column),
|
|
"Invalid end position " & End_Line'Img & End_Column'Img);
|
|
|
|
if not Buffer.Inserting then
|
|
End_Action (Buffer);
|
|
end if;
|
|
|
|
if not Enable_Undo then
|
|
Buffer.Start_Inserting;
|
|
end if;
|
|
|
|
Get_Iter_At_Line_Offset (Buffer, Start_Iter, Start_Line, Start_Column);
|
|
Get_Iter_At_Line_Offset (Buffer, End_Iter, End_Line, End_Column);
|
|
|
|
-- Currently, Gtk_Text_Buffer does not export a service to replace
|
|
-- some text, so we delete the slice first, then insert the text...
|
|
Delete (Buffer, Start_Iter, End_Iter);
|
|
|
|
Get_Iter_At_Line_Offset (Buffer, Start_Iter, Start_Line, Start_Column);
|
|
|
|
Insert (Buffer, Start_Iter, Text);
|
|
|
|
if not Enable_Undo then
|
|
Buffer.End_Inserting;
|
|
end if;
|
|
|
|
Register_Edit_Timeout (Buffer);
|
|
end Replace_Slice_Real;
|
|
|
|
-------------------
|
|
-- Replace_Slice --
|
|
-------------------
|
|
|
|
procedure Replace_Slice
|
|
(Buffer : access Source_Buffer_Record;
|
|
Start_Line : Editable_Line_Type;
|
|
Start_Column : Character_Offset_Type;
|
|
End_Line : Editable_Line_Type;
|
|
End_Column : Character_Offset_Type;
|
|
Text : String;
|
|
Enable_Undo : Boolean := True)
|
|
is
|
|
Buffer_Start_Line, Buffer_End_Line : Buffer_Line_Type;
|
|
begin
|
|
if not Buffer.Writable then
|
|
End_Action (Buffer);
|
|
return;
|
|
end if;
|
|
|
|
for J in Start_Line .. End_Line loop
|
|
Unfold_Line (Buffer, J);
|
|
end loop;
|
|
|
|
Buffer_Start_Line := Get_Buffer_Line (Buffer, Start_Line);
|
|
Buffer_End_Line := Get_Buffer_Line (Buffer, End_Line);
|
|
|
|
if Buffer_Start_Line = 0 or else Buffer_End_Line = 0 then
|
|
Trace (Me, "invalid buffer line");
|
|
return;
|
|
end if;
|
|
|
|
Replace_Slice_Real
|
|
(Buffer,
|
|
Gint (Buffer_Start_Line - 1),
|
|
Gint (Start_Column - 1),
|
|
Gint (Buffer_End_Line - 1),
|
|
Gint (End_Column - 1),
|
|
Text,
|
|
Enable_Undo);
|
|
end Replace_Slice;
|
|
|
|
----------------
|
|
-- Select_All --
|
|
----------------
|
|
|
|
procedure Select_All (Buffer : access Source_Buffer_Record) is
|
|
Start_Iter : Gtk_Text_Iter;
|
|
End_Iter : Gtk_Text_Iter;
|
|
begin
|
|
End_Action (Buffer);
|
|
Get_Start_Iter (Buffer, Start_Iter);
|
|
Get_End_Iter (Buffer, End_Iter);
|
|
Select_Range (Buffer, Ins => End_Iter, Bound => Start_Iter);
|
|
end Select_All;
|
|
|
|
-------------------
|
|
-- Select_Region --
|
|
-------------------
|
|
|
|
procedure Select_Region
|
|
(Buffer : access Source_Buffer_Record;
|
|
Cursor_Iter : Gtk.Text_Iter.Gtk_Text_Iter;
|
|
Bound_Iter : Gtk.Text_Iter.Gtk_Text_Iter) is
|
|
begin
|
|
End_Action (Buffer);
|
|
Select_Range (Buffer, Ins => Cursor_Iter, Bound => Bound_Iter);
|
|
end Select_Region;
|
|
|
|
-------------------
|
|
-- Select_Region --
|
|
-------------------
|
|
|
|
procedure Select_Region
|
|
(Buffer : access Source_Buffer_Record;
|
|
Start_Line : Gint;
|
|
Start_Column : Gint;
|
|
End_Line : Gint;
|
|
End_Column : Gint)
|
|
is
|
|
Start_Iter : Gtk_Text_Iter;
|
|
End_Iter : Gtk_Text_Iter;
|
|
begin
|
|
if Start_Line = End_Line and then Start_Column = End_Column then
|
|
End_Action (Buffer);
|
|
Get_Iter_At_Mark (Buffer, Start_Iter, Get_Insert (Buffer));
|
|
Place_Cursor (Buffer, Start_Iter);
|
|
|
|
else
|
|
if not Is_Valid_Position (Buffer, Start_Line, Start_Column) then
|
|
Trace
|
|
(Me,
|
|
"invalid start position in Select_Region, aborting:"
|
|
& Start_Line'Img
|
|
& Start_Column'Img);
|
|
return;
|
|
|
|
elsif not Is_Valid_Position (Buffer, End_Line, End_Column) then
|
|
Trace
|
|
(Me,
|
|
"invalid end position in Select_Region, aborting:"
|
|
& End_Line'Img
|
|
& End_Column'Img);
|
|
return;
|
|
end if;
|
|
|
|
End_Action (Buffer);
|
|
Get_Iter_At_Line_Offset
|
|
(Buffer, Start_Iter, Start_Line, Start_Column);
|
|
Get_Iter_At_Line_Offset (Buffer, End_Iter, End_Line, End_Column);
|
|
Select_Range (Buffer, Ins => End_Iter, Bound => Start_Iter);
|
|
end if;
|
|
end Select_Region;
|
|
|
|
-------------------
|
|
-- Select_Region --
|
|
-------------------
|
|
|
|
procedure Select_Region
|
|
(Buffer : access Source_Buffer_Record;
|
|
Start_Line : Editable_Line_Type;
|
|
Start_Column : Character_Offset_Type;
|
|
End_Line : Editable_Line_Type;
|
|
End_Column : Character_Offset_Type) is
|
|
begin
|
|
for J in Start_Line .. End_Line loop
|
|
Unfold_Line (Buffer, J);
|
|
end loop;
|
|
|
|
Select_Region
|
|
(Buffer,
|
|
Gint (Get_Buffer_Line (Buffer, Start_Line) - 1),
|
|
Gint (Start_Column - 1),
|
|
Gint (Get_Buffer_Line (Buffer, End_Line) - 1),
|
|
Gint (End_Column - 1));
|
|
end Select_Region;
|
|
|
|
-------------------
|
|
-- Select_Region --
|
|
-------------------
|
|
|
|
procedure Select_Region
|
|
(Buffer : access Source_Buffer_Record;
|
|
Start_Line : Editable_Line_Type;
|
|
Start_Column : Visible_Column_Type;
|
|
End_Line : Editable_Line_Type;
|
|
End_Column : Visible_Column_Type)
|
|
is
|
|
Start_Col, End_Col : Character_Offset_Type;
|
|
begin
|
|
Start_Col := Collapse_Tabs (Buffer, Start_Line, Start_Column);
|
|
End_Col := Collapse_Tabs (Buffer, End_Line, End_Column);
|
|
|
|
Select_Region (Buffer, Start_Line, Start_Col, End_Line, End_Col);
|
|
end Select_Region;
|
|
|
|
--------------------
|
|
-- Lines_Add_Hook --
|
|
--------------------
|
|
|
|
procedure Lines_Add_Hook
|
|
(Buffer : access Source_Buffer_Record'Class;
|
|
Start : Buffer_Line_Type;
|
|
Number : Buffer_Line_Type) is
|
|
begin
|
|
-- Resynchronize the arrays that need to be synchronized with line
|
|
-- numbers.
|
|
Add_Lines (Buffer, Start, Number);
|
|
|
|
-- Parse the block information
|
|
Register_Edit_Timeout (Buffer);
|
|
end Lines_Add_Hook;
|
|
|
|
------------------------------
|
|
-- Lines_Remove_Hook_Before --
|
|
------------------------------
|
|
|
|
procedure Lines_Remove_Hook_Before
|
|
(Buffer : access Source_Buffer_Record'Class;
|
|
Start_Line : Buffer_Line_Type;
|
|
Count : Buffer_Line_Type) is
|
|
begin
|
|
Buffer_Before_Delete_Lines_Hook.Run
|
|
(Buffer.Kernel,
|
|
Buffer.Filename,
|
|
Integer (Start_Line + 1),
|
|
Integer (Count));
|
|
|
|
-- Resynchronize the arrays that need to be synchronized with line
|
|
-- numbers.
|
|
Remove_Lines (Buffer, Start_Line, Count);
|
|
|
|
-- It is necessary to set the fields Buffer.First_Removed_Line and
|
|
-- Buffer.Last_Removed_Line because the parameters for the
|
|
-- "delete_range" signal are invalid when we connect after the actual
|
|
-- deletion has been done.
|
|
|
|
Buffer.First_Removed_Line := Start_Line + 1;
|
|
Buffer.Last_Removed_Line := Start_Line + Count;
|
|
end Lines_Remove_Hook_Before;
|
|
|
|
-----------------------------
|
|
-- Lines_Remove_Hook_After --
|
|
-----------------------------
|
|
|
|
procedure Lines_Remove_Hook_After
|
|
(Buffer : access Source_Buffer_Record'Class;
|
|
Start_Line : Buffer_Line_Type;
|
|
End_Line : Buffer_Line_Type)
|
|
is
|
|
pragma Unreferenced (Start_Line, End_Line);
|
|
begin
|
|
-- Parse the block information
|
|
Register_Edit_Timeout (Buffer);
|
|
end Lines_Remove_Hook_After;
|
|
|
|
---------------------
|
|
-- End_Action_Hook --
|
|
---------------------
|
|
|
|
procedure End_Action_Hook (Buffer : access Source_Buffer_Record'Class) is
|
|
pragma Unreferenced (Buffer);
|
|
begin
|
|
Reset_Completion_Data;
|
|
end End_Action_Hook;
|
|
|
|
----------------
|
|
-- End_Action --
|
|
----------------
|
|
|
|
procedure End_Action (Buffer : access Source_Buffer_Record'Class) is
|
|
Command : constant Editor_Command :=
|
|
Editor_Command (Buffer.Current_Command);
|
|
begin
|
|
End_Action_Hook (Buffer);
|
|
|
|
if not Is_Null_Command (Command) then
|
|
Buffer.Current_Command := null;
|
|
end if;
|
|
end End_Action;
|
|
|
|
-------------------------
|
|
-- External_End_Action --
|
|
-------------------------
|
|
|
|
procedure External_End_Action (Buffer : access Source_Buffer_Record) is
|
|
Command : constant Editor_Command :=
|
|
Editor_Command (Buffer.Current_Command);
|
|
|
|
begin
|
|
if not Is_Null_Command (Command) then
|
|
End_Action (Buffer);
|
|
end if;
|
|
end External_End_Action;
|
|
|
|
----------
|
|
-- Redo --
|
|
----------
|
|
|
|
procedure Redo (Buffer : access Source_Buffer_Record) is
|
|
Command : constant Editor_Command :=
|
|
Editor_Command (Buffer.Current_Command);
|
|
begin
|
|
if not Is_Null_Command (Command) then
|
|
End_Action (Buffer);
|
|
end if;
|
|
|
|
Redo (Buffer.Queue);
|
|
|
|
-- Undo and Redo clear the current group: start a new group
|
|
Change_Group (Buffer.Queue);
|
|
end Redo;
|
|
|
|
----------
|
|
-- Undo --
|
|
----------
|
|
|
|
procedure Undo (Buffer : access Source_Buffer_Record) is
|
|
Command : constant Editor_Command :=
|
|
Editor_Command (Buffer.Current_Command);
|
|
begin
|
|
if not Is_Null_Command (Command) then
|
|
End_Action (Buffer);
|
|
end if;
|
|
|
|
Undo (Buffer.Queue);
|
|
|
|
-- Undo and Redo clear the current group: start a new group
|
|
Change_Group (Buffer.Queue);
|
|
end Undo;
|
|
|
|
--------------
|
|
-- Can_Undo --
|
|
--------------
|
|
|
|
function Can_Undo (Buffer : access Source_Buffer_Record) return Boolean is
|
|
begin
|
|
return Can_Undo (Buffer.Queue);
|
|
end Can_Undo;
|
|
|
|
-------------
|
|
-- Enqueue --
|
|
-------------
|
|
|
|
procedure Enqueue
|
|
(Buffer : access Source_Buffer_Record;
|
|
Command : Command_Access;
|
|
User_Action : Action_Type)
|
|
is
|
|
Drag_N_Drop_Deletion : constant Boolean :=
|
|
Buffer.In_Text_Drag_N_Drop and then User_Action = Delete_Text;
|
|
begin
|
|
Buffer.Start_Inserting;
|
|
|
|
if Buffer.Saved_Position > Get_Position (Buffer.Queue) then
|
|
Buffer.Saved_Position := -1;
|
|
end if;
|
|
|
|
Buffer.Current_Command := null;
|
|
|
|
-- Decide whether we should group this action with the previous actions.
|
|
-- Don't change the group if we are enqueuing a drag n drop deletion: we
|
|
-- want it to be in the same group as the drag n drop insertion.
|
|
if User_Action /= Buffer.Last_User_Action
|
|
and then not Drag_N_Drop_Deletion
|
|
then
|
|
Change_Group (Buffer.Queue);
|
|
end if;
|
|
|
|
if User_Action in No_Action .. Delete_Line then
|
|
Buffer.Last_User_Action := User_Action;
|
|
end if;
|
|
|
|
Src_Editor_Module.Set_Global_Command (null);
|
|
Enqueue (Buffer.Queue, Command);
|
|
Buffer.End_Inserting;
|
|
|
|
-- Reset the drag n drop flag when we know that it's finished
|
|
if Drag_N_Drop_Deletion then
|
|
Buffer.In_Text_Drag_N_Drop := False;
|
|
end if;
|
|
end Enqueue;
|
|
|
|
-----------------------------
|
|
-- Notify_Text_Drag_N_Drop --
|
|
-----------------------------
|
|
|
|
procedure Notify_Text_Drag_N_Drop
|
|
(Buffer : not null access Source_Buffer_Record) is
|
|
begin
|
|
Buffer.In_Text_Drag_N_Drop := True;
|
|
end Notify_Text_Drag_N_Drop;
|
|
|
|
----------------
|
|
-- Get_Kernel --
|
|
----------------
|
|
|
|
function Get_Kernel
|
|
(Buffer : access Source_Buffer_Record) return GPS.Kernel.Kernel_Handle is
|
|
begin
|
|
return Buffer.Kernel;
|
|
end Get_Kernel;
|
|
|
|
------------------
|
|
-- Get_Filename --
|
|
------------------
|
|
|
|
function Get_Filename
|
|
(Buffer : access Source_Buffer_Record) return GNATCOLL.VFS.Virtual_File is
|
|
begin
|
|
return Buffer.Filename;
|
|
end Get_Filename;
|
|
|
|
------------------
|
|
-- Set_Filename --
|
|
------------------
|
|
|
|
procedure Set_Filename
|
|
(Buffer : access Source_Buffer_Record; Name : Virtual_File)
|
|
is
|
|
Old : constant Virtual_File := Buffer.Filename;
|
|
begin
|
|
Buffer.Filename := Name;
|
|
if not Is_Regular_File (Name) then
|
|
Buffer.Saved_Position := -1;
|
|
Buffer.Save_Complete := False;
|
|
end if;
|
|
if Old /= Name then
|
|
-- Listeners are created before the editor is given
|
|
-- a filename: here is their chance to get the new
|
|
-- filename.
|
|
for Listener of Buffer.Listeners loop
|
|
Listener.File_Renamed (Old, Name);
|
|
end loop;
|
|
end if;
|
|
end Set_Filename;
|
|
|
|
---------------------
|
|
-- Set_Initial_Dir --
|
|
---------------------
|
|
|
|
procedure Set_Initial_Dir
|
|
(Buffer : access Source_Buffer_Record; Name : GNATCOLL.VFS.Virtual_File)
|
|
is
|
|
begin
|
|
Buffer.Initial_Dir := Name;
|
|
end Set_Initial_Dir;
|
|
|
|
---------------------
|
|
-- Get_Initial_Dir --
|
|
---------------------
|
|
|
|
function Get_Initial_Dir
|
|
(Buffer : access Source_Buffer_Record) return GNATCOLL.VFS.Virtual_File is
|
|
begin
|
|
return Buffer.Initial_Dir;
|
|
end Get_Initial_Dir;
|
|
|
|
-------------------------
|
|
-- Get_File_Identifier --
|
|
-------------------------
|
|
|
|
function Get_File_Identifier
|
|
(Buffer : access Source_Buffer_Record) return GNATCOLL.VFS.Virtual_File is
|
|
begin
|
|
return Buffer.File_Identifier;
|
|
end Get_File_Identifier;
|
|
|
|
-------------------------
|
|
-- Set_File_Identifier --
|
|
-------------------------
|
|
|
|
procedure Set_File_Identifier
|
|
(Buffer : access Source_Buffer_Record; Name : Virtual_File) is
|
|
begin
|
|
Buffer.File_Identifier := Name;
|
|
end Set_File_Identifier;
|
|
|
|
-------------------------
|
|
-- Source_Lines_Folded --
|
|
-------------------------
|
|
|
|
procedure Source_Lines_Folded
|
|
(Buffer : access Source_Buffer_Record;
|
|
Start_Line : Editable_Line_Type;
|
|
End_Line : Editable_Line_Type)
|
|
is
|
|
Context : constant Selection_Context :=
|
|
Source_Lines_Context (Buffer, No_Project, Start_Line, End_Line);
|
|
begin
|
|
Source_Lines_Folded_Hook.Run
|
|
(Buffer.Kernel, Context, Natural (Start_Line), Natural (End_Line));
|
|
end Source_Lines_Folded;
|
|
|
|
---------------------------
|
|
-- Source_Lines_Unfolded --
|
|
---------------------------
|
|
|
|
procedure Source_Lines_Unfolded
|
|
(Buffer : access Source_Buffer_Record;
|
|
Start_Line : Editable_Line_Type;
|
|
End_Line : Editable_Line_Type)
|
|
is
|
|
Context : constant Selection_Context :=
|
|
Source_Lines_Context (Buffer, No_Project, Start_Line, End_Line);
|
|
begin
|
|
Source_Lines_Unfolded_Hook.Run
|
|
(Buffer.Kernel, Context, Natural (Start_Line), Natural (End_Line));
|
|
end Source_Lines_Unfolded;
|
|
|
|
--------------------------
|
|
-- Source_Lines_Context --
|
|
--------------------------
|
|
|
|
function Source_Lines_Context
|
|
(Buffer : access Source_Buffer_Record;
|
|
Project : GNATCOLL.Projects.Project_Type;
|
|
Start_Line : Editable_Line_Type;
|
|
End_Line : Editable_Line_Type) return Selection_Context
|
|
is
|
|
Context : Selection_Context :=
|
|
New_Context (Buffer.Kernel, Src_Editor_Module_Id);
|
|
The_Project : Project_Type;
|
|
begin
|
|
if Project = No_Project then
|
|
declare
|
|
Child : MDI_Child;
|
|
begin
|
|
Child :=
|
|
Find_Editor
|
|
(Kernel => Buffer.Kernel,
|
|
File => Buffer.Filename,
|
|
Project => No_Project);
|
|
|
|
if Child /= null then
|
|
The_Project := Src_Editor_Module.Get_Project (Child);
|
|
end if;
|
|
end;
|
|
end if;
|
|
|
|
if Buffer.Filename /= GNATCOLL.VFS.No_File then
|
|
Set_File_Information
|
|
(Context,
|
|
Files => (1 => Buffer.Filename),
|
|
Project => The_Project,
|
|
Publish_Project => False);
|
|
|
|
elsif Buffer.File_Identifier /= GNATCOLL.VFS.No_File then
|
|
Set_File_Information
|
|
(Context,
|
|
Files => (1 => Buffer.File_Identifier),
|
|
Project => The_Project,
|
|
Publish_Project => False);
|
|
end if;
|
|
|
|
Set_Area_Information
|
|
(Context, "", Integer (Start_Line), Integer (End_Line));
|
|
|
|
return Context;
|
|
end Source_Lines_Context;
|
|
|
|
-------------------
|
|
-- Register_View --
|
|
-------------------
|
|
|
|
procedure Register_View
|
|
(Buffer : access Source_Buffer_Record; Add : Boolean) is
|
|
begin
|
|
if Add then
|
|
Buffer.Number_Of_Views := Buffer.Number_Of_Views + 1;
|
|
else
|
|
Buffer.Number_Of_Views := Buffer.Number_Of_Views - 1;
|
|
end if;
|
|
|
|
if Buffer.Number_Of_Views = 0 then
|
|
Emit_By_Name (Get_Object (Buffer), Signal_Closed & ASCII.NUL);
|
|
|
|
if Buffer.Filename /= GNATCOLL.VFS.No_File then
|
|
Emit_File_Closed (Buffer, Buffer.Filename);
|
|
|
|
elsif Buffer.File_Identifier /= GNATCOLL.VFS.No_File then
|
|
Emit_File_Closed (Buffer, Buffer.File_Identifier);
|
|
end if;
|
|
end if;
|
|
end Register_View;
|
|
|
|
----------------------------------
|
|
-- Avoid_Cursor_Move_On_Changes --
|
|
----------------------------------
|
|
|
|
function Avoid_Cursor_Move_On_Changes
|
|
(Buffer : access Source_Buffer_Record) return Boolean is
|
|
begin
|
|
return Buffer.No_Cursor_Move_On_Changes;
|
|
end Avoid_Cursor_Move_On_Changes;
|
|
|
|
--------------------------------------
|
|
-- Set_Avoid_Cursor_Move_On_Changes --
|
|
--------------------------------------
|
|
|
|
procedure Set_Avoid_Cursor_Move_On_Changes
|
|
(Buffer : access Source_Buffer_Record; Value : Boolean) is
|
|
begin
|
|
Buffer.No_Cursor_Move_On_Changes := Value;
|
|
end Set_Avoid_Cursor_Move_On_Changes;
|
|
|
|
------------------
|
|
-- Add_Controls --
|
|
------------------
|
|
|
|
procedure Add_Controls (Buffer : access Source_Buffer_Record) is
|
|
begin
|
|
Change_Undo_Redo_Queue (Buffer.Queue);
|
|
end Add_Controls;
|
|
|
|
---------------------
|
|
-- Remove_Controls --
|
|
---------------------
|
|
|
|
procedure Remove_Controls (Buffer : access Source_Buffer_Record) is
|
|
pragma Unreferenced (Buffer);
|
|
begin
|
|
Change_Undo_Redo_Queue (Null_Command_Queue);
|
|
end Remove_Controls;
|
|
|
|
----------------------------
|
|
-- Get_Total_Column_Width --
|
|
----------------------------
|
|
|
|
function Get_Total_Column_Width
|
|
(Buffer : access Source_Buffer_Record) return Natural is
|
|
begin
|
|
return Buffer.Total_Column_Width;
|
|
end Get_Total_Column_Width;
|
|
|
|
------------------------
|
|
-- Line_Needs_Refresh --
|
|
------------------------
|
|
|
|
function Line_Needs_Refresh
|
|
(Buffer : access Source_Buffer_Record; Line : Buffer_Line_Type)
|
|
return Boolean is
|
|
begin
|
|
if Buffer.Line_Data (Line).Side_Info_Data /= null then
|
|
for J in Buffer.Line_Data (Line).Side_Info_Data'Range loop
|
|
if not Buffer.Line_Data (Line).Side_Info_Data (J).Set then
|
|
return True;
|
|
end if;
|
|
end loop;
|
|
end if;
|
|
|
|
return False;
|
|
end Line_Needs_Refresh;
|
|
|
|
----------------------
|
|
-- Create_Side_Info --
|
|
----------------------
|
|
|
|
procedure Create_Side_Info
|
|
(Buffer : access Source_Buffer_Record; Line : Buffer_Line_Type)
|
|
is
|
|
Columns_Config : Line_Info_Display_Array_Access renames
|
|
Buffer.Editable_Line_Info_Columns.all;
|
|
begin
|
|
if Columns_Config /= null then
|
|
if Buffer.Line_Data (Line).Side_Info_Data = null then
|
|
Buffer.Line_Data (Line).Side_Info_Data :=
|
|
new Line_Info_Width_Array (Columns_Config'Range);
|
|
|
|
for K in Columns_Config'Range loop
|
|
Buffer.Line_Data (Line).Side_Info_Data (K) :=
|
|
(Message_Reference_List.Empty_List,
|
|
Action => null,
|
|
Set => not Columns_Config (K).Every_Line);
|
|
end loop;
|
|
end if;
|
|
end if;
|
|
end Create_Side_Info;
|
|
|
|
-----------------------
|
|
-- Needs_To_Be_Saved --
|
|
-----------------------
|
|
|
|
function Needs_To_Be_Saved
|
|
(Buffer : access Source_Buffer_Record'Class) return Boolean is
|
|
begin
|
|
-- Only modified and non-empty unsaved buffers need to be saved
|
|
return
|
|
Get_Status (Buffer) = Modified
|
|
or else (Get_Status (Buffer) = Unsaved
|
|
and then Get_Char_Count (Buffer) > 0);
|
|
end Needs_To_Be_Saved;
|
|
|
|
--------------------
|
|
-- Has_Been_Saved --
|
|
--------------------
|
|
|
|
function Has_Been_Saved
|
|
(Buffer : access Source_Buffer_Record'Class) return Boolean is
|
|
begin
|
|
return Buffer.Save_Complete;
|
|
end Has_Been_Saved;
|
|
|
|
----------------
|
|
-- Get_Status --
|
|
----------------
|
|
|
|
function Get_Status
|
|
(Buffer : access Source_Buffer_Record) return Status_Type is
|
|
begin
|
|
-- If the buffer has an empty queue (no modifications performed),
|
|
-- and a saved position = 0 (loaded from a file) => unmodified
|
|
-- Else, if saved position = current queue position => saved
|
|
-- Else, if the queue is not empty => modified
|
|
-- Else => unsaved (queue empty, and not loaded from a file, not saved).
|
|
if (Undo_Queue_Empty (Buffer.Queue)
|
|
and then Redo_Queue_Empty (Buffer.Queue)
|
|
and then Buffer.Saved_Position /= -1)
|
|
or else (Buffer.Saved_Position = Get_Position (Buffer.Queue)
|
|
and then Buffer.Saved_Position = 0)
|
|
then
|
|
return Unmodified;
|
|
|
|
elsif Buffer.Saved_Position = Get_Position (Buffer.Queue) then
|
|
return Saved;
|
|
|
|
elsif not Undo_Queue_Empty (Buffer.Queue)
|
|
or else not Redo_Queue_Empty (Buffer.Queue)
|
|
then
|
|
return Modified;
|
|
|
|
else
|
|
return Unsaved;
|
|
end if;
|
|
end Get_Status;
|
|
|
|
---------------------------
|
|
-- Get_Extra_Information --
|
|
---------------------------
|
|
|
|
function Get_Extra_Information
|
|
(Buffer : Source_Buffer) return Extra_Information_Array_Access is
|
|
begin
|
|
return Buffer.Extra_Information;
|
|
end Get_Extra_Information;
|
|
|
|
---------------------
|
|
-- Get_Highlighter --
|
|
---------------------
|
|
|
|
function Get_Highlighter
|
|
(Editor : access Source_Buffer_Record) return Source_Highlighter is
|
|
begin
|
|
return Editor.Highlighter;
|
|
end Get_Highlighter;
|
|
|
|
--------------------------------
|
|
-- Get_First_Non_Blank_Column --
|
|
--------------------------------
|
|
|
|
function Get_First_Non_Blank_Column
|
|
(Buffer : access Source_Buffer_Record; Line : Editable_Line_Type)
|
|
return Visible_Column_Type
|
|
is
|
|
Str : Src_String := Get_String_At_Line (Source_Buffer (Buffer), Line);
|
|
Index : Natural := 1;
|
|
begin
|
|
if Str.Contents /= null
|
|
and then not Is_Blank_Line (Str.Contents (1 .. Str.Length))
|
|
then
|
|
Skip_Blanks (Str.Contents (1 .. Str.Length), Index);
|
|
end if;
|
|
Free (Str);
|
|
return Visible_Column_Type (Index);
|
|
end Get_First_Non_Blank_Column;
|
|
|
|
---------------
|
|
-- Get_Block --
|
|
---------------
|
|
|
|
function Get_Block
|
|
(Editor : access Source_Buffer_Record;
|
|
Line : Editable_Line_Type;
|
|
Update_Immediately : Boolean;
|
|
Filter : Language.Tree.Category_Array :=
|
|
Language.Tree.Null_Category_Array;
|
|
Column : Visible_Column_Type := 0) return Block_Record is
|
|
begin
|
|
if Line = 0 then
|
|
return New_Block;
|
|
end if;
|
|
|
|
-- If we are inserting internally (for instance blank lines) then
|
|
-- we should not update the contents at this stage: the editor does
|
|
-- not know yet that the lines that we have just inserted are
|
|
-- special
|
|
if Editor.Inserting then
|
|
return New_Block;
|
|
end if;
|
|
|
|
declare
|
|
Tree : Semantic_Tree'Class :=
|
|
Editor.Kernel.Get_Abstract_Tree_For_File ("EDIT", Editor.Filename);
|
|
|
|
begin
|
|
if Update_Immediately then
|
|
Tree.Update;
|
|
end if;
|
|
|
|
if Tree.Is_Ready or else Update_Immediately then
|
|
-- Take the first possible project. This should not impact block
|
|
-- computation, which does not need xref information
|
|
declare
|
|
Real_Column : constant Visible_Column_Type :=
|
|
(if Column /= 0
|
|
then Column
|
|
else Get_First_Non_Blank_Column (Editor, Line));
|
|
Node : constant Semantic_Node'Class :=
|
|
Tree.Node_At
|
|
((Line => Integer (Line),
|
|
Column => Real_Column,
|
|
others => <>),
|
|
Filter);
|
|
begin
|
|
if Node = No_Semantic_Node then
|
|
return New_Block;
|
|
else
|
|
return
|
|
Block_Record'
|
|
(Indentation_Level => 0,
|
|
Offset_Start => Integer (Node.Sloc_Start.Column),
|
|
Stored_Offset => 0,
|
|
First_Line =>
|
|
Editable_Line_Type (Node.Sloc_Start.Line),
|
|
Last_Line =>
|
|
Editable_Line_Type (Node.Sloc_End.Line),
|
|
Name => Node.Name,
|
|
Block_Type => Node.Category,
|
|
Color => Null_RGBA);
|
|
end if;
|
|
end;
|
|
else
|
|
return New_Block;
|
|
end if;
|
|
end;
|
|
end Get_Block;
|
|
|
|
-----------------------
|
|
-- Get_Current_Block --
|
|
-----------------------
|
|
|
|
function Get_Current_Block
|
|
(Editor : access Source_Buffer_Record; Absolute : Boolean := False)
|
|
return Block_Record
|
|
is
|
|
Line : Editable_Line_Type;
|
|
Column : Visible_Column_Type;
|
|
begin
|
|
Editor.Get_Cursor_Position (Line, Column);
|
|
return
|
|
Editor.Get_Block
|
|
(Line => Line,
|
|
Update_Immediately => False,
|
|
Column => (if Absolute then Column else 0));
|
|
end Get_Current_Block;
|
|
|
|
--------------------------
|
|
-- Get_Subprogram_Block --
|
|
--------------------------
|
|
|
|
function Get_Subprogram_Block
|
|
(Editor : access Source_Buffer_Record;
|
|
Line : Editable_Line_Type;
|
|
Update_Tree : Boolean := False) return Block_Record is
|
|
begin
|
|
return
|
|
Get_Block
|
|
(Editor,
|
|
Line,
|
|
Update_Tree,
|
|
Filter =>
|
|
(Cat_Package,
|
|
Cat_Namespace,
|
|
Cat_Task,
|
|
Cat_Procedure,
|
|
Cat_Function,
|
|
Cat_Constructor,
|
|
Cat_Method,
|
|
Cat_Destructor,
|
|
Cat_Protected,
|
|
Cat_Entry,
|
|
Cat_Class,
|
|
Cat_Structure,
|
|
Cat_Union));
|
|
end Get_Subprogram_Block;
|
|
|
|
-------------------------
|
|
-- Get_Subprogram_Name --
|
|
-------------------------
|
|
|
|
function Get_Subprogram_Name
|
|
(Editor : access Source_Buffer_Record; Line : Editable_Line_Type)
|
|
return String
|
|
is
|
|
Block : constant Block_Record := Get_Subprogram_Block (Editor, Line);
|
|
begin
|
|
if Block.Name /= No_Symbol then
|
|
return Get (Block.Name).all;
|
|
else
|
|
return "";
|
|
end if;
|
|
end Get_Subprogram_Name;
|
|
|
|
---------------------------
|
|
-- Has_Block_Information --
|
|
---------------------------
|
|
|
|
function Has_Block_Information
|
|
(Editor : access Source_Buffer_Record) return Boolean is
|
|
begin
|
|
return Editor.Parse_Blocks;
|
|
end Has_Block_Information;
|
|
|
|
-----------------
|
|
-- Save_Cursor --
|
|
-----------------
|
|
|
|
procedure Save_Cursor
|
|
(Buffer : Source_Buffer;
|
|
Mark : Gtk_Text_Mark;
|
|
Cursor_Line : out Gint;
|
|
Cursor_Offset : out Gint)
|
|
is
|
|
Cursor_Iter : Gtk_Text_Iter;
|
|
begin
|
|
Buffer.Do_Not_Move_Cursor := True;
|
|
Get_Iter_At_Mark (Buffer, Cursor_Iter, Mark);
|
|
Cursor_Line := Get_Line (Cursor_Iter);
|
|
Cursor_Offset := Get_Line_Offset (Cursor_Iter);
|
|
end Save_Cursor;
|
|
|
|
------------------
|
|
-- Place_Cursor --
|
|
------------------
|
|
|
|
procedure Place_Cursor
|
|
(Buffer : Source_Buffer;
|
|
Mark : Gtk_Text_Mark;
|
|
Cursor_Line : Gint;
|
|
Cursor_Offset : Gint)
|
|
is
|
|
Cursor_Move : Gint := Cursor_Offset;
|
|
Result : Boolean := True;
|
|
Offset : Gint := 0;
|
|
Iter : Gtk_Text_Iter;
|
|
begin
|
|
-- Can't move the cursor if the mark was deleted
|
|
if not Get_Move_Cursor_When_Formatting (Buffer.Get_Language)
|
|
or else Mark.Get_Deleted
|
|
then
|
|
return;
|
|
end if;
|
|
|
|
-- If the cursor was located before the first non-blank character,
|
|
-- move it to that character. This is more usual for Emacs users,
|
|
-- and more user friendly generally.
|
|
Get_Iter_At_Line (Buffer, Iter, Cursor_Line);
|
|
Set_Line_Offset (Iter, 0);
|
|
|
|
while Result
|
|
and then not Ends_Line (Iter)
|
|
and then Glib.Unicode.Is_Space (Get_Char (Iter))
|
|
loop
|
|
Forward_Char (Iter, Result);
|
|
Offset := Offset + 1;
|
|
end loop;
|
|
|
|
Cursor_Move := Cursor_Move - Offset;
|
|
|
|
if Cursor_Move > 0 then
|
|
Forward_Chars (Iter, Cursor_Move, Result);
|
|
end if;
|
|
|
|
Buffer.Do_Not_Move_Cursor := False;
|
|
if Buffer.Insert_Mark = Mark then
|
|
-- Move the main cursor
|
|
Place_Cursor (Buffer, Iter);
|
|
else
|
|
-- Move multiline cursor
|
|
Move_Mark (Buffer, Mark, Iter);
|
|
end if;
|
|
end Place_Cursor;
|
|
|
|
----------------------
|
|
-- Range_Formatting --
|
|
----------------------
|
|
|
|
procedure Range_Formatting
|
|
(Buffer : Source_Buffer;
|
|
Mark : Gtk_Text_Mark;
|
|
From, To : Gtk_Text_Iter;
|
|
Force : Boolean := False)
|
|
is
|
|
pragma Unreferenced (Force);
|
|
G : Group_Block := New_Group (Buffer.Queue);
|
|
|
|
Cursor_Line : Gint;
|
|
Cursor_Offset : Gint;
|
|
|
|
From_Column : Visible_Column_Type;
|
|
To_Column : Visible_Column_Type;
|
|
|
|
From_Line : Editable_Line_Type;
|
|
To_Line : Editable_Line_Type;
|
|
|
|
Provider : constant Editor_Formatting_Provider_Access :=
|
|
Src_Editor_Module.Get_Range_Formatting_Provider (Buffer.Get_Language);
|
|
begin
|
|
if Provider = null then
|
|
Trace (Me_Formatters, "rangeFormatting Provider is not defined.");
|
|
return;
|
|
else
|
|
Trace
|
|
(Me_Formatters,
|
|
"Found rangeFormatting Provider: " & Provider.Get_Name);
|
|
end if;
|
|
|
|
Save_Cursor
|
|
(Buffer => Buffer,
|
|
Mark => Mark,
|
|
Cursor_Line => Cursor_Line,
|
|
Cursor_Offset => Cursor_Offset);
|
|
|
|
if Src_Editor_Buffer.Line_Information.Lines_Are_Real (Buffer) then
|
|
Get_Iter_Position (Buffer, From, From_Line, From_Column);
|
|
Get_Iter_Position (Buffer, To, To_Line, To_Column);
|
|
else
|
|
Trace (Me_Formatters, "Unreal lines found when formatting");
|
|
declare
|
|
Search_Line : Gint;
|
|
Start_Line : Gint;
|
|
End_Line : Gint;
|
|
begin
|
|
Start_Line := Get_Line (From);
|
|
End_Line := Get_Line (To);
|
|
|
|
-- Gint and Buffer_Line_Type are off by 1
|
|
From_Line :=
|
|
Get_Editable_Line (Buffer, Buffer_Line_Type (Start_Line + 1));
|
|
To_Line :=
|
|
Get_Editable_Line (Buffer, Buffer_Line_Type (End_Line + 1));
|
|
|
|
-- Get_Editable_Line will return 0 for non-editable line.
|
|
-- Search for the first and last editable lines between
|
|
-- Start_Line and End_Line.
|
|
|
|
-- Search upward for the first editable line
|
|
-- We have tested Start_Line + 1, the next one is Start_Line + 2
|
|
Search_Line := Start_Line + 2;
|
|
while From_Line = 0 and then Search_Line <= End_Line loop
|
|
From_Line :=
|
|
Get_Editable_Line (Buffer, Buffer_Line_Type (Search_Line));
|
|
Search_Line := Search_Line + 1;
|
|
end loop;
|
|
|
|
-- Search downward for the last editable line
|
|
-- We have tested End_Line + 1, the previous one is End_Line
|
|
Search_Line := End_Line;
|
|
while To_Line = 0 and then Search_Line >= Start_Line loop
|
|
To_Line :=
|
|
Get_Editable_Line (Buffer, Buffer_Line_Type (Search_Line));
|
|
Search_Line := Search_Line - 1;
|
|
end loop;
|
|
|
|
Trace (Me, "End_Line: " & End_Line'Image);
|
|
Trace (Me, "Search_Line: " & Search_Line'Image);
|
|
Trace (Me, "To_Line: " & To_Line'Image);
|
|
|
|
-- Include all characters from the last line
|
|
To_Column :=
|
|
Buffer.Editor_Buffer.New_Location
|
|
(Line => Integer (To_Line), Column => 1)
|
|
.End_Of_Line
|
|
.Column;
|
|
end;
|
|
end if;
|
|
|
|
if not Provider.On_Range_Formatting
|
|
(Buffer.Editor_Buffer.New_Location (Integer (From_Line), 1),
|
|
Buffer.Editor_Buffer.New_Location
|
|
(Integer (To_Line), To_Column),
|
|
Cursor_Line => Natural (Cursor_Line) + 1,
|
|
Cursor_Move => Integer (Cursor_Offset))
|
|
then
|
|
Trace (Me_Formatters, "Formatting failed");
|
|
end if;
|
|
|
|
Place_Cursor
|
|
(Buffer => Buffer,
|
|
Mark => Mark,
|
|
Cursor_Line => Cursor_Line,
|
|
Cursor_Offset => Cursor_Offset);
|
|
end Range_Formatting;
|
|
|
|
------------------------
|
|
-- On_Type_Formatting --
|
|
------------------------
|
|
|
|
procedure On_Type_Formatting
|
|
(Buffer : Source_Buffer;
|
|
Mark : Gtk_Text_Mark;
|
|
From, To : Gtk_Text_Iter;
|
|
Force : Boolean := False)
|
|
is
|
|
pragma Unreferenced (Force);
|
|
Cursor_Line : Gint;
|
|
Cursor_Offset : Gint;
|
|
|
|
Start_Line, End_Line : Editable_Line_Type;
|
|
Start_Column, End_Column : Visible_Column_Type;
|
|
|
|
Provider : constant Editor_Formatting_Provider_Access :=
|
|
Src_Editor_Module.Get_On_Type_Formatting_Provider
|
|
(Buffer.Get_Language);
|
|
begin
|
|
if Provider = null then
|
|
Trace (Me_Formatters, "onTypeFormatting Provider is not defined.");
|
|
return;
|
|
else
|
|
Trace
|
|
(Me_Formatters,
|
|
"Found onTypeFormatting Provider: " & Provider.Get_Name);
|
|
end if;
|
|
|
|
Save_Cursor
|
|
(Buffer => Buffer,
|
|
Mark => Mark,
|
|
Cursor_Line => Cursor_Line,
|
|
Cursor_Offset => Cursor_Offset);
|
|
|
|
Get_Iter_Position (Buffer, From, Start_Line, Start_Column);
|
|
Get_Iter_Position (Buffer, To, End_Line, End_Column);
|
|
|
|
if not Provider.On_Type_Formatting
|
|
(Buffer.Editor_Buffer.New_Location
|
|
(Integer (Start_Line), Start_Column),
|
|
Buffer.Editor_Buffer.New_Location
|
|
(Integer (End_Line), End_Column),
|
|
Cursor_Line => Natural (Cursor_Line) + 1)
|
|
then
|
|
Trace (Me_Formatters, "Fails to format on new line");
|
|
end if;
|
|
|
|
Place_Cursor
|
|
(Buffer => Buffer,
|
|
Mark => Mark,
|
|
Cursor_Line => Cursor_Line,
|
|
Cursor_Offset => Cursor_Offset);
|
|
end On_Type_Formatting;
|
|
|
|
-------------------
|
|
-- Should_Indent --
|
|
-------------------
|
|
|
|
function Should_Indent (Buffer : Source_Buffer) return Boolean is
|
|
Lang : constant Language_Access := Get_Language (Buffer);
|
|
begin
|
|
return
|
|
Get_Language_Context (Lang).Can_Indent
|
|
and then Lang.Get_Indentation_Style /= None;
|
|
end Should_Indent;
|
|
|
|
--------------------
|
|
-- Do_Indentation --
|
|
--------------------
|
|
|
|
function On_Indent_Action
|
|
(Buffer : Source_Buffer;
|
|
Current_Line_Only : Boolean := False;
|
|
Force : Boolean := False) return Boolean
|
|
is
|
|
Iter, End_Pos : Gtk_Text_Iter;
|
|
Result : Boolean;
|
|
begin
|
|
if not Buffer.Writable then
|
|
End_Action (Buffer);
|
|
return False;
|
|
end if;
|
|
|
|
Get_Selection_Bounds (Buffer, Iter, End_Pos, Result);
|
|
|
|
if not Current_Line_Only and then Result then
|
|
-- Do not consider a line selected if only the first character
|
|
-- is selected.
|
|
|
|
if Get_Line_Offset (End_Pos) = 0 then
|
|
Backward_Char (End_Pos, Result);
|
|
end if;
|
|
|
|
-- Do not consider a line selected if only the last character is
|
|
-- selected.
|
|
|
|
if Ends_Line (Iter) then
|
|
Forward_Char (Iter, Result);
|
|
end if;
|
|
|
|
else
|
|
Get_Iter_At_Mark (Buffer, Iter, Buffer.Insert_Mark);
|
|
Copy (Iter, Dest => End_Pos);
|
|
end if;
|
|
|
|
return On_Indent_Action (Buffer, Iter, End_Pos, Force);
|
|
end On_Indent_Action;
|
|
|
|
--------------------
|
|
-- Do_Indentation --
|
|
--------------------
|
|
|
|
function On_Indent_Action
|
|
(Buffer : Source_Buffer;
|
|
From, To : Gtk_Text_Iter;
|
|
Force : Boolean := False) return Boolean
|
|
is
|
|
Lang : constant Language_Access := Get_Language (Buffer);
|
|
Indent_Style : Indentation_Kind;
|
|
End_Pos : Gtk_Text_Iter;
|
|
Iter : Gtk_Text_Iter;
|
|
pragma Suppress (Access_Check, Slice);
|
|
Result : Boolean;
|
|
Indent_Params : Indent_Parameters;
|
|
begin
|
|
if not Buffer.Writable then
|
|
Trace (Me, "Buffer not writable");
|
|
End_Action (Buffer);
|
|
return False;
|
|
end if;
|
|
|
|
if not Get_Language_Context (Lang).Can_Indent then
|
|
Trace (Me, "Language Can't Indent");
|
|
return False;
|
|
end if;
|
|
|
|
Get_Indentation_Parameters
|
|
(Lang => Lang, Params => Indent_Params, Indent_Style => Indent_Style);
|
|
|
|
if Indent_Style = None then
|
|
return False;
|
|
end if;
|
|
|
|
-- What should we indent (current line or current selection) ?
|
|
|
|
Copy (From, Dest => Iter);
|
|
Set_Line_Offset (Iter, 0);
|
|
Copy (To, Dest => End_Pos);
|
|
|
|
if not Ends_Line (End_Pos) then
|
|
Forward_To_Line_End (End_Pos, Result);
|
|
end if;
|
|
|
|
End_Action (Buffer);
|
|
|
|
Range_Formatting
|
|
(Buffer => Buffer,
|
|
Mark => Buffer.Insert_Mark,
|
|
From => Iter,
|
|
To => End_Pos,
|
|
Force => Force);
|
|
|
|
return True;
|
|
|
|
exception
|
|
when E : others =>
|
|
-- Stop propagation of exception, since doing nothing
|
|
-- in this callback is harmless.
|
|
|
|
Buffer.Do_Not_Move_Cursor := False;
|
|
|
|
Trace (Me, E);
|
|
return False;
|
|
end On_Indent_Action;
|
|
|
|
------------------------
|
|
-- Newline_And_Indent --
|
|
------------------------
|
|
|
|
procedure Newline_And_Indent
|
|
(Buffer : access Source_Buffer_Record; As_Is : Boolean)
|
|
is
|
|
Ignore, Result : Boolean;
|
|
|
|
procedure Strip_Blanks_From_Previous_Line;
|
|
-- Removes blanks from the end of the previous line
|
|
|
|
-------------------------------------
|
|
-- Strip_Blanks_From_Previous_Line --
|
|
-------------------------------------
|
|
|
|
procedure Strip_Blanks_From_Previous_Line is
|
|
Iter : Gtk.Text_Iter.Gtk_Text_Iter;
|
|
End_Iter : Gtk.Text_Iter.Gtk_Text_Iter;
|
|
Line : Gint;
|
|
Start_Offset : Gint;
|
|
End_Offset : Gint;
|
|
Char : Character;
|
|
Result : Boolean;
|
|
begin
|
|
-- Retrieve the cursor position (after insertion of the newline) and
|
|
-- backward from one char to retrieve the end of the previous line.
|
|
Buffer.Get_Cursor_Position (Iter);
|
|
Backward_Char (Iter, Result);
|
|
|
|
if Iter = Null_Text_Iter or else not Result then
|
|
return;
|
|
end if;
|
|
|
|
End_Offset := Get_Line_Offset (Iter);
|
|
|
|
-- Backward the iter while we find trailing blanks
|
|
while not Starts_Line (Iter) loop
|
|
Backward_Char (Iter, Result);
|
|
Char := Get_Char (Iter);
|
|
exit when Char /= ' ' and then Char /= ASCII.HT;
|
|
end loop;
|
|
|
|
Start_Offset := Get_Line_Offset (Iter);
|
|
|
|
-- Delete the trailing blanks, if any
|
|
if Start_Offset /= 0 and then End_Offset /= Start_Offset + 1 then
|
|
Line := Get_Line (Iter);
|
|
Buffer.Get_Iter_At_Line_Offset (Iter, Line, Start_Offset + 1);
|
|
Buffer.Get_Iter_At_Line_Offset (End_Iter, Line, End_Offset);
|
|
Buffer.Delete_Interactive (Iter, End_Iter, True, Result);
|
|
end if;
|
|
|
|
exception
|
|
when E : others =>
|
|
Trace (Me, E);
|
|
end Strip_Blanks_From_Previous_Line;
|
|
|
|
G : Group_Block := New_Group (Buffer.Queue);
|
|
begin
|
|
if not As_Is then
|
|
Word_Added
|
|
(Buffer => Source_Buffer (Buffer),
|
|
Character => Gdk.Keyval.To_Unicode (GDK_Return),
|
|
Interactive => not Buffer.Inserting);
|
|
end if;
|
|
|
|
-- If there is a selection, delete it
|
|
if Selection_Exists (Buffer) then
|
|
Ignore := Delete_Selection (Buffer, True, True);
|
|
|
|
External_End_Action (Buffer);
|
|
end if;
|
|
|
|
-- Insert the newline
|
|
Result := Insert_Interactive_At_Cursor (Buffer, (1 => ASCII.LF), True);
|
|
|
|
-- Now that the newline is inserted, strip the trailing blanks from the
|
|
-- previous line, if any.
|
|
if Buffer.Strip_Trailing_Blanks then
|
|
Strip_Blanks_From_Previous_Line;
|
|
end if;
|
|
|
|
if Should_Indent (+Buffer) and then Result then
|
|
declare
|
|
Params : Indent_Parameters;
|
|
Indent_Style : Indentation_Kind;
|
|
|
|
Current_Sync_Mode : constant Cursors_Sync_Type :=
|
|
Get_Cursors_Sync (+Buffer);
|
|
procedure Indent_Cursor (M : Gtk_Text_Mark);
|
|
procedure Indent_Cursor (M : Gtk_Text_Mark) is
|
|
S, L : Gtk_Text_Iter;
|
|
begin
|
|
Get_Iter_At_Mark (Buffer, L, M);
|
|
Copy (L, S);
|
|
|
|
if not As_Is then
|
|
Backward_Line (S, Ignore);
|
|
end if;
|
|
|
|
if not Ends_Line (L) then
|
|
Forward_To_Line_End (L, Ignore);
|
|
end if;
|
|
|
|
On_Type_Formatting
|
|
(Buffer => Source_Buffer (Buffer),
|
|
Mark => M,
|
|
From => S,
|
|
To => L);
|
|
end Indent_Cursor;
|
|
|
|
begin
|
|
Buffer.Get_Language.Get_Indentation_Parameters
|
|
(Params, Indent_Style);
|
|
|
|
for Cursor of Get_Cursors (+Buffer) loop
|
|
Set_Manual_Sync (Cursor);
|
|
Indent_Cursor (Get_Mark (Cursor));
|
|
end loop;
|
|
Set_Cursors_Sync (+Buffer, Current_Sync_Mode);
|
|
end;
|
|
end if;
|
|
end Newline_And_Indent;
|
|
|
|
---------------
|
|
-- Do_Refill --
|
|
---------------
|
|
|
|
function Do_Refill (Buffer : Source_Buffer) return Boolean is
|
|
type Comment_Kind is (None, Single_Line, Multiple_Lines);
|
|
-- We classify programming language comments in two main kinds:
|
|
-- (1) Single-line comments: they have a Begin-Comment delimiter and
|
|
-- implicitly terminate at newline.
|
|
-- (2) Multiple-line comments: they have Begin-Comment (BC) and
|
|
-- End-Comment (EC) delimiters.
|
|
-- There are languages which simultaneously support both kinds of
|
|
-- comments (for example, C++).
|
|
|
|
Single_Line_BC_Len : Natural := 0;
|
|
Single_Line_BC_Pattern : Pattern_Matcher_Access;
|
|
|
|
Multiple_Lines_BC_Len : Natural := 0;
|
|
Multiple_Lines_BC_Pattern : Pattern_Matcher_Access;
|
|
|
|
Multiple_Lines_EC_Len : Natural := 0;
|
|
Multiple_Lines_EC_Pattern : Pattern_Matcher_Access;
|
|
|
|
Lang_Context : constant Language_Context_Access :=
|
|
(if Buffer.Lang /= null
|
|
then Get_Language_Context (Buffer.Lang)
|
|
else null);
|
|
|
|
function Is_Empty (Line : Src_String) return Boolean;
|
|
-- Return True if Line is empty (no contents it has only spaces/HT)
|
|
|
|
procedure Refill_Comments
|
|
(From_Line : Editable_Line_Type; To_Line : Editable_Line_Type);
|
|
-- Scan the Buffer in the range From_Line .. To_Line and refill the
|
|
-- comments. Lines not containing comments are left unmodified.
|
|
|
|
procedure Refill_Plain_Text
|
|
(From_Line : Editable_Line_Type; To_Line : Editable_Line_Type);
|
|
-- Refill the contents of the buffer. It assumes that the contents of
|
|
-- the buffer is plain text.
|
|
|
|
procedure Scan_Comment
|
|
(Line : String; Kind : out Comment_Kind; Last : out Natural);
|
|
-- Search in Line for the Begin-Comment pattern of single-line and
|
|
-- multiple-line comment. If the pattern of a single line comment
|
|
-- is found then Kind is set to Single_Line; if the pattern of a
|
|
-- multiple-line comment is found then Kind is set to Multiple_Lines.
|
|
-- In both cases Last returns the position of the last character of
|
|
-- the comment prefix. Example: for " -- xxx", returns 4, pointing just
|
|
-- before the first x. If no comment is found then Kind is None and Last
|
|
-- is 0.
|
|
--
|
|
-- Special case: If the pattern to start a multiple-line comment and the
|
|
-- pattern to terminate the comment are simultaneously found in Line,
|
|
-- and the pattern to terminate the comment is found after the last
|
|
-- position of the pattern to begin the comment, then the comment is
|
|
-- not considered a candidate to be refilled and Kind is set to None
|
|
-- and Last is 0.
|
|
|
|
procedure Setup_Comment_Regexps;
|
|
-- Check if the language for the current file is known, and if it is
|
|
-- setup the regexps to use to find the bounds of comments.
|
|
|
|
procedure Find_Paragraph_Bounds
|
|
(From_Line, To_Line : out Editable_Line_Type);
|
|
-- Find the bounds of the region to refill (it will be the user
|
|
-- selection if available, or guessed automatically).
|
|
|
|
procedure Unchecked_Free is new
|
|
Ada.Unchecked_Deallocation (Pattern_Matcher, Pattern_Matcher_Access);
|
|
|
|
--------------
|
|
-- Is_Empty --
|
|
--------------
|
|
|
|
function Is_Empty (Line : Src_String) return Boolean is
|
|
|
|
function Only_Spaces return Boolean;
|
|
-- Return True is Line contains only spaces or HT
|
|
|
|
-----------------
|
|
-- Only_Spaces --
|
|
-----------------
|
|
|
|
function Only_Spaces return Boolean is
|
|
Result : Boolean := True;
|
|
|
|
begin
|
|
for K in 1 .. Line.Length loop
|
|
if Line.Contents (K) /= ' '
|
|
and then Line.Contents (K) /= ASCII.HT
|
|
then
|
|
Result := False;
|
|
end if;
|
|
end loop;
|
|
|
|
return Result;
|
|
end Only_Spaces;
|
|
|
|
begin
|
|
return Line.Contents = null or else Only_Spaces;
|
|
end Is_Empty;
|
|
|
|
---------------------
|
|
-- Refill_Comments --
|
|
---------------------
|
|
|
|
procedure Refill_Comments
|
|
(From_Line : Editable_Line_Type; To_Line : Editable_Line_Type)
|
|
is
|
|
Max_Line_Length : constant Positive := Highlight_Column.Get_Pref;
|
|
Tab_Width : constant Integer := Integer (Buffer.Tab_Width);
|
|
Comment : Unbounded_String;
|
|
In_ML_Comment : Boolean := False;
|
|
Max_Line : Natural := 0;
|
|
New_Text : Unbounded_String := To_Unbounded_String ("");
|
|
Prefix : Unbounded_String;
|
|
|
|
function ML_End_Comment_Last (Line : String) return Natural;
|
|
-- If the pattern that terminates a comment of a multi-line comment
|
|
-- is found then return the position of its last character. Example,
|
|
-- for " */" return 3. Return 0 if the pattern is not found.
|
|
|
|
procedure Refill_One_Comment;
|
|
-- Refill the current comment
|
|
|
|
-------------------------
|
|
-- ML_End_Comment_Last --
|
|
-------------------------
|
|
|
|
function ML_End_Comment_Last (Line : String) return Natural is
|
|
Matches : Match_Array (0 .. 0);
|
|
|
|
begin
|
|
pragma Assert (Multiple_Lines_EC_Len > 0);
|
|
Match (Multiple_Lines_EC_Pattern.all, Line, Matches);
|
|
|
|
if Matches (0) /= No_Match then
|
|
return Matches (0).Last;
|
|
end if;
|
|
|
|
return 0;
|
|
end ML_End_Comment_Last;
|
|
|
|
------------------------
|
|
-- Refill_One_Comment --
|
|
------------------------
|
|
|
|
procedure Refill_One_Comment is
|
|
Blanks_Prefix : constant String (1 .. Length (Prefix)) :=
|
|
(others => ' ');
|
|
Search_For : constant Character_Set := To_Set (ASCII.LF);
|
|
From : Positive;
|
|
Is_First_Line : Boolean;
|
|
Len : Natural;
|
|
New_Comment : Unbounded_String;
|
|
Pos : Natural;
|
|
|
|
begin
|
|
-- Terminate the last line of the current comment
|
|
|
|
Append (Comment, ASCII.LF);
|
|
|
|
-- Reformat this comment
|
|
|
|
New_Comment := Pretty_Fill (To_String (Comment), Max_Line);
|
|
|
|
-- For single-line comments add Prefix before each line of the
|
|
-- refilled comment. For multi-line comments Prefix is added only
|
|
-- to the first line of the comment and blanks are added for
|
|
-- subsequent lines. For example the C-style comment:
|
|
--
|
|
-- /* This is an example of refilling text */
|
|
--
|
|
-- can be refilled as follows (assumming Max_Line = 24):
|
|
--
|
|
-- /* This is an example
|
|
-- of refilling text */
|
|
|
|
Is_First_Line := True;
|
|
Len := Length (New_Comment);
|
|
From := 1;
|
|
|
|
while From <= Len loop
|
|
Pos := Index (New_Comment, Search_For, From);
|
|
|
|
if In_ML_Comment and then not Is_First_Line then
|
|
Append (New_Text, Blanks_Prefix);
|
|
else
|
|
Append (New_Text, To_String (Prefix));
|
|
end if;
|
|
|
|
Append (New_Text, Slice (New_Comment, From, Pos));
|
|
|
|
Is_First_Line := False;
|
|
From := Pos + 1;
|
|
end loop;
|
|
end Refill_One_Comment;
|
|
|
|
-- Local variables
|
|
|
|
In_Comment : Boolean := False;
|
|
BC_Last : Natural;
|
|
EC_Last : Natural;
|
|
Line : Src_String;
|
|
To_Length : Character_Offset_Type;
|
|
|
|
begin
|
|
-- Refill_Comments
|
|
for K in From_Line .. To_Line loop
|
|
Line := Get_String_At_Line (Buffer, K);
|
|
|
|
if K = To_Line then
|
|
To_Length := Character_Offset_Type (Line.Length);
|
|
end if;
|
|
|
|
-- Handle continuation of multi-line comment
|
|
|
|
if In_ML_Comment then
|
|
|
|
-- Empty line in multi-line comment; continue accumulating
|
|
-- all the text of the current comment
|
|
|
|
if Is_Empty (Line) then
|
|
Append (Comment, ASCII.LF);
|
|
|
|
-- Non-empty line. Check if this line has the end-comment
|
|
-- delimiter
|
|
|
|
else
|
|
declare
|
|
S : String renames Line.Contents (1 .. Line.Length);
|
|
|
|
begin
|
|
EC_Last := ML_End_Comment_Last (S);
|
|
|
|
-- End of multi-line comment not found yet; continue
|
|
-- acumulating all the text of the current comment
|
|
|
|
if EC_Last = 0 then
|
|
Append (Comment, To_Unbounded_String (ASCII.LF & S));
|
|
|
|
-- End of multi-line comment found
|
|
|
|
else
|
|
-- Add the last line of this comment (including the
|
|
-- end-comment delimiter) to the buffer of acumulated
|
|
-- comments and refill the whole comment
|
|
|
|
Append
|
|
(Comment,
|
|
To_Unbounded_String (ASCII.LF & S (1 .. EC_Last)));
|
|
Refill_One_Comment;
|
|
|
|
-- If there is some text after the end-comment
|
|
-- delimiter then move it to the next line
|
|
|
|
if EC_Last < Line.Length then
|
|
Append
|
|
(New_Text,
|
|
S (EC_Last + 1 .. Line.Length) & ASCII.LF);
|
|
end if;
|
|
|
|
In_ML_Comment := False;
|
|
In_Comment := False;
|
|
end if;
|
|
end;
|
|
end if;
|
|
|
|
-- Empty line
|
|
|
|
elsif Is_Empty (Line) then
|
|
|
|
-- For single-line comments an empty line means that we must
|
|
-- stop acumulating comments and we must refill the text of
|
|
-- the acumulated comments
|
|
|
|
if In_Comment then
|
|
pragma Assert (not In_ML_Comment);
|
|
Refill_One_Comment;
|
|
In_Comment := False;
|
|
end if;
|
|
|
|
Append (New_Text, ASCII.LF);
|
|
|
|
-- Line containing text
|
|
|
|
else
|
|
declare
|
|
S : String renames Line.Contents (1 .. Line.Length);
|
|
Kind : Comment_Kind;
|
|
|
|
begin
|
|
Scan_Comment (S, Kind, BC_Last);
|
|
|
|
-- The line does not have a begin-comment delimiter
|
|
|
|
if Kind = None then
|
|
pragma Assert (not In_ML_Comment);
|
|
|
|
-- If we were acumulating single-line comments and this
|
|
-- line does not have a begin-comment delimiter then it
|
|
-- is time to refill the accumulated comment.
|
|
|
|
if In_Comment then
|
|
Refill_One_Comment;
|
|
In_Comment := False;
|
|
end if;
|
|
|
|
Append (New_Text, To_Unbounded_String (S));
|
|
Append (New_Text, ASCII.LF);
|
|
|
|
-- The line has some begin-comment delimiter: let's start
|
|
-- acumulating comments.
|
|
|
|
elsif not In_Comment then
|
|
|
|
-- Calculate the length of the prefix taking into account
|
|
-- spaces and horizontal tabs
|
|
|
|
declare
|
|
Prefix_Length : Natural := 0;
|
|
|
|
begin
|
|
for K in 1 .. BC_Last loop
|
|
if S (K) = ASCII.HT then
|
|
Prefix_Length := Prefix_Length + Tab_Width;
|
|
else
|
|
Prefix_Length := Prefix_Length + 1;
|
|
end if;
|
|
end loop;
|
|
|
|
-- Calculate the maximum line length to refill
|
|
|
|
Max_Line := Max_Line_Length - Prefix_Length + 1;
|
|
end;
|
|
|
|
Prefix := To_Unbounded_String (S (1 .. BC_Last - 1));
|
|
In_ML_Comment := Kind = Multiple_Lines;
|
|
|
|
if In_ML_Comment then
|
|
EC_Last :=
|
|
ML_End_Comment_Last (S (BC_Last .. Line.Length));
|
|
else
|
|
EC_Last := 0;
|
|
end if;
|
|
|
|
-- Check if this line has the end-comment delimiter
|
|
|
|
if EC_Last = 0 then
|
|
Comment :=
|
|
To_Unbounded_String (S (BC_Last .. Line.Length));
|
|
In_Comment := True;
|
|
else
|
|
Comment :=
|
|
To_Unbounded_String (S (BC_Last .. EC_Last));
|
|
Refill_One_Comment;
|
|
|
|
-- If there is some text after the end-comment
|
|
-- delimiter then move it to the next line
|
|
|
|
if EC_Last < Line.Length then
|
|
Append
|
|
(New_Text,
|
|
S (EC_Last + 1 .. Line.Length) & ASCII.LF);
|
|
end if;
|
|
end if;
|
|
|
|
-- We are acumulating text of single-line comments; let's
|
|
-- continue acumulating comments
|
|
|
|
else
|
|
Append
|
|
(Comment,
|
|
To_Unbounded_String
|
|
(ASCII.LF & S (BC_Last .. Line.Length)));
|
|
end if;
|
|
end;
|
|
end if;
|
|
end loop;
|
|
|
|
if In_Comment then
|
|
Refill_One_Comment;
|
|
end if;
|
|
|
|
-- Replace selected text with the new one
|
|
|
|
if Get_Line_Count (Buffer) >= Gint (To_Line + 1) then
|
|
Replace_Slice
|
|
(Buffer, From_Line, 1, To_Line + 1, 1, To_String (New_Text));
|
|
else
|
|
Replace_Slice
|
|
(Buffer, From_Line, 1, To_Line, To_Length, To_String (New_Text));
|
|
end if;
|
|
|
|
declare
|
|
Iter : Gtk_Text_Iter;
|
|
Dummy : Boolean;
|
|
begin
|
|
Buffer.Get_Iter_At_Mark (Iter, Buffer.Get_Insert);
|
|
Backward_Line (Iter, Dummy);
|
|
Forward_To_Line_End (Iter, Dummy);
|
|
Buffer.Place_Cursor (Iter);
|
|
end;
|
|
|
|
exception
|
|
when E : others =>
|
|
Trace (Me, E);
|
|
end Refill_Comments;
|
|
|
|
-----------------------
|
|
-- Refill_Plain_Text --
|
|
-----------------------
|
|
|
|
procedure Refill_Plain_Text
|
|
(From_Line : Editable_Line_Type; To_Line : Editable_Line_Type)
|
|
is
|
|
Max_Line_Length : constant Positive := Highlight_Column.Get_Pref;
|
|
Tab_Width : constant Integer := Integer (Buffer.Tab_Width);
|
|
Comment : Unbounded_String;
|
|
Max_Line : Natural := 0;
|
|
New_Text : Unbounded_String := To_Unbounded_String ("");
|
|
Prefix : Unbounded_String;
|
|
|
|
procedure Refill_Text;
|
|
-- Refill the acumulated text
|
|
|
|
-----------------
|
|
-- Refill_Text --
|
|
-----------------
|
|
|
|
procedure Refill_Text is
|
|
Search_For : constant Character_Set := To_Set (ASCII.LF);
|
|
From : Positive;
|
|
Len : Natural;
|
|
New_Comment : Unbounded_String;
|
|
Pos : Natural;
|
|
|
|
begin
|
|
-- Terminate the last line of the current comment
|
|
|
|
Append (Comment, ASCII.LF);
|
|
|
|
-- Reformat this comment
|
|
|
|
New_Comment := Pretty_Fill (To_String (Comment), Max_Line);
|
|
|
|
-- Add Prefix before each line. Thus we ensure that all the
|
|
-- refilled text has the same left margin.
|
|
|
|
Len := Length (New_Comment);
|
|
From := 1;
|
|
|
|
while From <= Len loop
|
|
Pos := Index (New_Comment, Search_For, From);
|
|
Append (New_Text, To_String (Prefix));
|
|
Append (New_Text, Slice (New_Comment, From, Pos));
|
|
|
|
From := Pos + 1;
|
|
end loop;
|
|
end Refill_Text;
|
|
|
|
-- Local variables
|
|
|
|
Acumulating : Boolean := False;
|
|
Line : Src_String;
|
|
|
|
begin
|
|
-- Refill_Plain_Text
|
|
for K in From_Line .. To_Line loop
|
|
Line := Get_String_At_Line (Buffer, K);
|
|
|
|
-- Empty line
|
|
|
|
if Is_Empty (Line) then
|
|
|
|
-- For single-line comments an empty line means that we must
|
|
-- stop acumulating comments and we must refill the text of
|
|
-- the acumulated comments
|
|
|
|
if Acumulating then
|
|
Refill_Text;
|
|
Acumulating := False;
|
|
end if;
|
|
|
|
Append (New_Text, ASCII.LF);
|
|
|
|
-- Line containing text
|
|
|
|
else
|
|
declare
|
|
S : String renames Line.Contents (1 .. Line.Length);
|
|
|
|
begin
|
|
-- If we are acumulating text then let's continue
|
|
-- acumulating text to refill
|
|
|
|
if Acumulating then
|
|
Append (Comment, To_Unbounded_String (ASCII.LF & S));
|
|
|
|
else
|
|
-- Calculate the length of the prefix taking into account
|
|
-- spaces and horizontal tabs
|
|
|
|
declare
|
|
Prefix_Length : Natural := 0;
|
|
J : Positive := 1;
|
|
|
|
begin
|
|
while S (J) = ASCII.HT or else S (J) = ' ' loop
|
|
if S (J) = ASCII.HT then
|
|
Prefix_Length := Prefix_Length + Tab_Width;
|
|
else
|
|
Prefix_Length := Prefix_Length + 1;
|
|
end if;
|
|
|
|
J := J + 1;
|
|
end loop;
|
|
|
|
-- Calculate the maximum line length to refill
|
|
|
|
Max_Line := Max_Line_Length - Prefix_Length;
|
|
Prefix := To_Unbounded_String (S (1 .. J - 1));
|
|
Comment := To_Unbounded_String (S (J .. Line.Length));
|
|
end;
|
|
|
|
Acumulating := True;
|
|
end if;
|
|
end;
|
|
end if;
|
|
end loop;
|
|
|
|
if Acumulating then
|
|
Refill_Text;
|
|
end if;
|
|
|
|
-- Replace selected text by the new one
|
|
|
|
if Get_Line_Count (Buffer) >= Gint (To_Line + 1) then
|
|
Replace_Slice
|
|
(Buffer, From_Line, 1, To_Line + 1, 1, To_String (New_Text));
|
|
else
|
|
Replace_Slice
|
|
(Buffer,
|
|
From_Line,
|
|
1,
|
|
To_Line,
|
|
Character_Offset_Type (Length (New_Text)),
|
|
To_String (New_Text));
|
|
end if;
|
|
|
|
exception
|
|
when E : others =>
|
|
Trace (Me, E);
|
|
end Refill_Plain_Text;
|
|
|
|
------------------
|
|
-- Scan_Comment --
|
|
------------------
|
|
|
|
procedure Scan_Comment
|
|
(Line : String; Kind : out Comment_Kind; Last : out Natural)
|
|
is
|
|
Matches : Match_Array (0 .. 0);
|
|
Single_Line_BC : constant GNAT.Strings.String_Access :=
|
|
(if Lang_Context /= null
|
|
then Lang_Context.Syntax.New_Line_Comment_Start
|
|
else null);
|
|
begin
|
|
Kind := None;
|
|
Last := 0;
|
|
|
|
-- If the language supports single-line comments then search for its
|
|
-- pattern in the current line
|
|
|
|
if Single_Line_BC_Pattern /= null then
|
|
Match (Single_Line_BC_Pattern.all, Line, Matches);
|
|
|
|
if Matches (0) /= No_Match then
|
|
pragma Assert (Matches (0).First = Line'First);
|
|
|
|
-- Single-line comments that end with the characters sequence
|
|
-- used to begin the comment are not considered comments to be
|
|
-- refilled. Such comments are probably part of the copyright
|
|
-- header, which we don't want to reformat. For example:
|
|
|
|
-- This line is an example of a non-refilled comment --
|
|
|
|
if Single_Line_BC = null
|
|
or else Line (Line'Last - Single_Line_BC_Len + 1 .. Line'Last)
|
|
/= Single_Line_BC.all
|
|
then
|
|
Kind := Single_Line;
|
|
Last := Matches (0).Last;
|
|
end if;
|
|
|
|
return;
|
|
end if;
|
|
end if;
|
|
|
|
-- If the language supports multiple-line comments then search for
|
|
-- the pattern that begin a comment in the current line
|
|
|
|
if Multiple_Lines_BC_Len /= 0 then
|
|
Match (Multiple_Lines_BC_Pattern.all, Line, Matches);
|
|
|
|
if Matches (0) /= No_Match then
|
|
Kind := Multiple_Lines;
|
|
Last := Matches (0).Last;
|
|
end if;
|
|
end if;
|
|
end Scan_Comment;
|
|
|
|
---------------------------
|
|
-- Setup_Comment_Regexps --
|
|
---------------------------
|
|
|
|
procedure Setup_Comment_Regexps is
|
|
Start_Comment_Pattern : constant String := "^\s*";
|
|
-- Start of line, followed by zero or more spaces
|
|
|
|
End_Comment_Pattern : constant String := "\s\s?[^\s]";
|
|
-- One or two spaces, followed by a non-space. If there are more than
|
|
-- two spaces after the comment marker, then we don't recognize it as
|
|
-- a comment line (it's an indented comment, which should not be
|
|
-- reformatted). We also don't recognize it as a comment line if
|
|
-- there is no space after the command marker.
|
|
|
|
S : Unbounded_String;
|
|
|
|
function Filter_Metachars (S : String) return String;
|
|
-- Filter regexp metacharacters. Currently only '*' needs to be
|
|
-- filtered.
|
|
|
|
----------------------
|
|
-- Filter_Metachars --
|
|
----------------------
|
|
|
|
function Filter_Metachars (S : String) return String is
|
|
Result : String (1 .. 2 * S'Length);
|
|
Pos : Natural := 0;
|
|
|
|
begin
|
|
for J in S'Range loop
|
|
if S (J) = '*' then
|
|
Pos := Pos + 1;
|
|
Result (Pos) := '\';
|
|
end if;
|
|
|
|
Pos := Pos + 1;
|
|
Result (Pos) := S (J);
|
|
end loop;
|
|
|
|
return Result (1 .. Pos);
|
|
end Filter_Metachars;
|
|
|
|
-- Local variables
|
|
|
|
Single_Line_BC : constant GNAT.Strings.String_Access :=
|
|
(if Lang_Context /= null
|
|
then Lang_Context.Syntax.New_Line_Comment_Start
|
|
else null);
|
|
begin
|
|
|
|
-- Handle single-line comments
|
|
|
|
-- If the language comment start dectection has been provided via
|
|
-- a regexp, use it directly
|
|
if Lang_Context /= null
|
|
and then Lang_Context.Syntax.New_Line_Comment_Start_Regexp /= null
|
|
and then Lang_Context.Syntax.New_Line_Comment_Start_Regexp.all
|
|
/= Never_Match
|
|
then
|
|
Single_Line_BC_Pattern :=
|
|
new Pattern_Matcher'
|
|
(Lang_Context.Syntax.New_Line_Comment_Start_Regexp.all);
|
|
end if;
|
|
|
|
-- If the language comment start dectection has been provided via
|
|
-- a simple string, build the regexp from it instead.
|
|
if Single_Line_BC_Pattern = null and then Single_Line_BC /= null then
|
|
Single_Line_BC_Len := Single_Line_BC'Length;
|
|
|
|
S := To_Unbounded_String (Start_Comment_Pattern);
|
|
Append (S, Filter_Metachars (Single_Line_BC.all));
|
|
Append (S, End_Comment_Pattern);
|
|
|
|
Single_Line_BC_Pattern :=
|
|
new Pattern_Matcher'
|
|
(Compile (To_String (S), GNAT.Regpat.Single_Line));
|
|
end if;
|
|
|
|
-- Handle multi-line comments
|
|
|
|
if Lang_Context /= null
|
|
and then Lang_Context.Syntax.Comment_Start /= null
|
|
then
|
|
pragma Assert (Lang_Context.Syntax.Comment_End /= null);
|
|
|
|
Multiple_Lines_BC_Len := Lang_Context.Syntax.Comment_Start'Length;
|
|
Multiple_Lines_EC_Len := Lang_Context.Syntax.Comment_End'Length;
|
|
|
|
S := To_Unbounded_String (Start_Comment_Pattern);
|
|
Append
|
|
(S, Filter_Metachars (Lang_Context.Syntax.Comment_Start.all));
|
|
Append (S, End_Comment_Pattern);
|
|
|
|
Multiple_Lines_BC_Pattern :=
|
|
new Pattern_Matcher'(Compile (To_String (S)));
|
|
|
|
S :=
|
|
To_Unbounded_String
|
|
(Filter_Metachars (Lang_Context.Syntax.Comment_End.all));
|
|
Multiple_Lines_EC_Pattern :=
|
|
new Pattern_Matcher'(Compile (To_String (S)));
|
|
end if;
|
|
end Setup_Comment_Regexps;
|
|
|
|
---------------------------
|
|
-- Find_Paragraph_Bounds --
|
|
---------------------------
|
|
|
|
procedure Find_Paragraph_Bounds
|
|
(From_Line, To_Line : out Editable_Line_Type)
|
|
is
|
|
Success : Boolean;
|
|
Current : Editable_Line_Type;
|
|
Column : Character_Offset_Type;
|
|
From, To : Gtk_Text_Iter;
|
|
begin
|
|
Get_Selection_Bounds (Buffer, From, To, Success);
|
|
|
|
if not Success then
|
|
-- No user selection. If the current file knows about comments,
|
|
-- we try and find the bounds of the current comment paragraph by
|
|
-- searching forward and backward for empty lines (or non-comment
|
|
-- lines); if we are formatting a plain text file, we always
|
|
-- refill the current line.
|
|
|
|
Get_Cursor_Position (Buffer, Current, Column);
|
|
Find_Current_Comment_Paragraph
|
|
(Buffer, Current, From_Line, To_Line);
|
|
|
|
else
|
|
-- Do not consider a line selected if only the first character is
|
|
-- selected.
|
|
|
|
if Get_Line_Offset (To) = 0 then
|
|
Backward_Char (To, Success);
|
|
end if;
|
|
|
|
-- Do not consider a line selected if only the last character is
|
|
-- selected.
|
|
|
|
if Ends_Line (From) then
|
|
Forward_Char (From, Success);
|
|
end if;
|
|
|
|
-- Get editable lines
|
|
|
|
From_Line :=
|
|
Get_Editable_Line
|
|
(Buffer, Buffer_Line_Type (Get_Line (From) + 1));
|
|
|
|
To_Line :=
|
|
Get_Editable_Line (Buffer, Buffer_Line_Type (Get_Line (To) + 1));
|
|
end if;
|
|
end Find_Paragraph_Bounds;
|
|
|
|
-- Local variables
|
|
|
|
From_Line : Editable_Line_Type;
|
|
To_Line : Editable_Line_Type;
|
|
|
|
-- Start of processing for Do_Refill
|
|
|
|
begin
|
|
if not Buffer.Writable then
|
|
End_Action (Buffer);
|
|
return False;
|
|
end if;
|
|
|
|
declare
|
|
G : Group_Block := New_Group (Buffer.Queue);
|
|
begin
|
|
Setup_Comment_Regexps;
|
|
Find_Paragraph_Bounds (From_Line, To_Line);
|
|
|
|
if Single_Line_BC_Pattern /= null
|
|
or else Multiple_Lines_BC_Pattern /= null
|
|
then
|
|
-- We have a known syntax for comments
|
|
Refill_Comments (From_Line, To_Line);
|
|
else
|
|
Refill_Plain_Text (From_Line, To_Line);
|
|
end if;
|
|
|
|
-- Free allocated memory
|
|
|
|
if Single_Line_BC_Pattern /= null then
|
|
Unchecked_Free (Single_Line_BC_Pattern);
|
|
end if;
|
|
|
|
if Multiple_Lines_BC_Pattern /= null then
|
|
Unchecked_Free (Multiple_Lines_BC_Pattern);
|
|
Unchecked_Free (Multiple_Lines_EC_Pattern);
|
|
end if;
|
|
|
|
End_Action (Buffer);
|
|
end;
|
|
|
|
return True;
|
|
end Do_Refill;
|
|
|
|
------------------------------------
|
|
-- Find_Current_Comment_Paragraph --
|
|
------------------------------------
|
|
|
|
procedure Find_Current_Comment_Paragraph
|
|
(Buffer : not null access Source_Buffer_Record;
|
|
Line : Editable_Line_Type;
|
|
Start_Line, End_Line : out Editable_Line_Type)
|
|
is
|
|
Lang_Context : constant Language_Context_Access :=
|
|
(if Buffer.Lang /= null
|
|
then Get_Language_Context (Buffer.Lang)
|
|
else null);
|
|
Single_Line_BC : constant GNAT.Strings.String_Access :=
|
|
(if Lang_Context /= null
|
|
then Lang_Context.Syntax.New_Line_Comment_Start
|
|
else null);
|
|
Non_Empty_Comment_Re : Pattern_Matcher_Access;
|
|
|
|
Is_Empty_Re : Pattern_Matcher_Access;
|
|
Comment_Start_End_Re : Pattern_Matcher_Access;
|
|
|
|
function Is_Comment_Line (Line : Editable_Line_Type) return Boolean;
|
|
-- Whether Pos is anywhere on a comment line (not necessarily within the
|
|
-- comment itself).
|
|
|
|
function Is_Boundary (Line : Editable_Line_Type) return Boolean;
|
|
-- Whether Line is a boundary for a pagraph
|
|
|
|
---------------------
|
|
-- Is_Comment_Line --
|
|
---------------------
|
|
|
|
function Is_Comment_Line (Line : Editable_Line_Type) return Boolean is
|
|
L : Src_String;
|
|
begin
|
|
if Non_Empty_Comment_Re = null then
|
|
return False;
|
|
end if;
|
|
|
|
L := Get_String_At_Line (Source_Buffer (Buffer), Line);
|
|
return
|
|
L.Contents /= null
|
|
and then Match
|
|
(Non_Empty_Comment_Re.all, L.Contents (1 .. L.Length));
|
|
end Is_Comment_Line;
|
|
|
|
-----------------
|
|
-- Is_Boundary --
|
|
-----------------
|
|
|
|
function Is_Boundary (Line : Editable_Line_Type) return Boolean is
|
|
L : constant Src_String :=
|
|
Get_String_At_Line (Source_Buffer (Buffer), Line);
|
|
begin
|
|
return
|
|
L.Contents = null
|
|
or else Match (Is_Empty_Re.all, L.Contents (1 .. L.Length))
|
|
or else (Non_Empty_Comment_Re /= null
|
|
and then Match -- in a single line comment
|
|
(Non_Empty_Comment_Re.all,
|
|
L.Contents (1 .. L.Length)))
|
|
or else -- boundary of comment block
|
|
(Comment_Start_End_Re /= null
|
|
and then Match
|
|
(Comment_Start_End_Re.all, L.Contents (1 .. L.Length)));
|
|
end Is_Boundary;
|
|
|
|
begin
|
|
if Single_Line_BC /= null then
|
|
Non_Empty_Comment_Re :=
|
|
new Pattern_Matcher'
|
|
(Compile ("^\s*" & Single_Line_BC.all & "\s*\S"));
|
|
end if;
|
|
|
|
Start_Line := Line;
|
|
End_Line := Line;
|
|
|
|
if not Is_Comment_Line (Line) then
|
|
-- If we are not in a single line comment, we should simply search
|
|
-- for empty lines before and after to find the bounds of the
|
|
-- paragraph. We should also stop when we find a line that marks the
|
|
-- beginning of end of a comment.
|
|
|
|
Is_Empty_Re := new Pattern_Matcher'(Compile ("^\s*$"));
|
|
|
|
if Lang_Context /= null
|
|
and then Lang_Context.Syntax.Comment_Start /= null
|
|
then
|
|
Comment_Start_End_Re :=
|
|
new Pattern_Matcher'
|
|
(Compile
|
|
("("
|
|
& Quote (Lang_Context.Syntax.Comment_Start.all)
|
|
& "|"
|
|
& Quote (Lang_Context.Syntax.Comment_End.all)
|
|
& ")"));
|
|
end if;
|
|
|
|
while Start_Line > 1 and then not Is_Boundary (Start_Line - 1) loop
|
|
Start_Line := Start_Line - 1;
|
|
end loop;
|
|
|
|
while End_Line < Editable_Line_Type (Buffer.Get_Line_Count)
|
|
and then not Is_Boundary (End_Line + 1)
|
|
loop
|
|
End_Line := End_Line + 1;
|
|
end loop;
|
|
|
|
else
|
|
while Start_Line > 1 and then Is_Comment_Line (Start_Line - 1) loop
|
|
Start_Line := Start_Line - 1;
|
|
end loop;
|
|
|
|
while End_Line < Editable_Line_Type (Buffer.Get_Line_Count)
|
|
and then Is_Comment_Line (End_Line + 1)
|
|
loop
|
|
End_Line := End_Line + 1;
|
|
end loop;
|
|
end if;
|
|
|
|
Trace
|
|
(Me,
|
|
"Bounds"
|
|
& Start_Line'Img
|
|
& End_Line'Img
|
|
& " started from"
|
|
& Line'Img);
|
|
|
|
Unchecked_Free (Non_Empty_Comment_Re);
|
|
Unchecked_Free (Is_Empty_Re);
|
|
Unchecked_Free (Comment_Start_End_Re);
|
|
end Find_Current_Comment_Paragraph;
|
|
|
|
---------------
|
|
-- Is_Editor --
|
|
---------------
|
|
|
|
function Is_Editor (Ctxt : Selection_Context) return Boolean is
|
|
begin
|
|
-- Do not check the current focus widget ourselves. Instead, we know
|
|
-- it has been properly checked when the context was created, and we
|
|
-- just check the current module from there.
|
|
if Get_Kernel (Ctxt).Get_Contextual_Menu_Open then
|
|
return False;
|
|
else
|
|
declare
|
|
Creator : constant Module_ID := Module_ID (Get_Creator (Ctxt));
|
|
begin
|
|
return Creator = Src_Editor_Module_Id;
|
|
end;
|
|
end if;
|
|
end Is_Editor;
|
|
|
|
------------------------------
|
|
-- Filter_Matches_Primitive --
|
|
------------------------------
|
|
|
|
overriding
|
|
function Filter_Matches_Primitive
|
|
(Context : access Src_Editor_Action_Context; Ctxt : Selection_Context)
|
|
return Boolean
|
|
is
|
|
pragma Unreferenced (Context);
|
|
begin
|
|
return Is_Editor (Ctxt);
|
|
end Filter_Matches_Primitive;
|
|
|
|
------------------------------
|
|
-- Filter_Matched_Primitive --
|
|
------------------------------
|
|
|
|
overriding
|
|
function Filter_Matches_Primitive
|
|
(Context : access Completion_Context; Ctxt : GPS.Kernel.Selection_Context)
|
|
return Boolean
|
|
is
|
|
pragma Unreferenced (Ctxt);
|
|
begin
|
|
-- Disable Move to next/previous line when in completion
|
|
return
|
|
not (Context.Is_Line_Movement
|
|
and then Completion_Module.In_Smart_Completion);
|
|
end Filter_Matches_Primitive;
|
|
|
|
------------------------------
|
|
-- Filter_Matched_Primitive --
|
|
------------------------------
|
|
|
|
overriding
|
|
function Filter_Matches_Primitive
|
|
(Context : access Signature_Context; Ctxt : GPS.Kernel.Selection_Context)
|
|
return Boolean
|
|
is
|
|
pragma Unreferenced (Context);
|
|
begin
|
|
-- Disable Move to next/previous line when in Signature Help window
|
|
return not Get_Kernel (Ctxt).In_Signature_Help;
|
|
end Filter_Matches_Primitive;
|
|
|
|
------------------------------
|
|
-- Filter_Matches_Primitive --
|
|
------------------------------
|
|
|
|
overriding
|
|
function Filter_Matches_Primitive
|
|
(Context : access Writable_Src_Editor_Action_Context;
|
|
Ctxt : GPS.Kernel.Selection_Context) return Boolean
|
|
is
|
|
pragma Unreferenced (Context);
|
|
Box : Source_Editor_Box;
|
|
begin
|
|
if not Is_Editor (Ctxt) or else Completion_Module.In_Smart_Completion
|
|
then
|
|
return False;
|
|
end if;
|
|
|
|
Box := Get_Source_Box_From_MDI (Find_Current_Editor (Get_Kernel (Ctxt)));
|
|
|
|
return Box /= null and then Box.Get_Buffer.Get_Writable;
|
|
end Filter_Matches_Primitive;
|
|
|
|
------------------------------
|
|
-- Filter_Matches_Primitive --
|
|
------------------------------
|
|
|
|
overriding
|
|
function Filter_Matches_Primitive
|
|
(Context : access Last_Editor_Action_Context;
|
|
Ctxt : GPS.Kernel.Selection_Context) return Boolean
|
|
is
|
|
pragma Unreferenced (Context);
|
|
Editor : constant MDI_Child := Find_Current_Editor (Get_Kernel (Ctxt));
|
|
begin
|
|
return Editor /= null;
|
|
end Filter_Matches_Primitive;
|
|
|
|
----------
|
|
-- Free --
|
|
----------
|
|
|
|
procedure Free
|
|
(Buffer : access Source_Buffer_Record;
|
|
X : in out Line_Info_Width;
|
|
Free_Messages : Boolean) is
|
|
begin
|
|
Free (X.Action);
|
|
|
|
if Free_Messages then
|
|
for Reference of X.Messages loop
|
|
Remove_Message (Buffer, Reference);
|
|
end loop;
|
|
X.Messages.Clear;
|
|
end if;
|
|
end Free;
|
|
|
|
----------------------
|
|
-- Forward_Position --
|
|
----------------------
|
|
|
|
procedure Forward_Position
|
|
(Buffer : access Source_Buffer_Record;
|
|
Start_Line : Editable_Line_Type;
|
|
Start_Column : Character_Offset_Type;
|
|
Length : Integer;
|
|
End_Line : out Editable_Line_Type;
|
|
End_Column : out Character_Offset_Type)
|
|
is
|
|
Iter : Gtk_Text_Iter;
|
|
Success : Boolean;
|
|
|
|
Amount : Integer := Length;
|
|
|
|
Found : Boolean;
|
|
Forward : constant Boolean := Length > 0;
|
|
begin
|
|
End_Line := Start_Line;
|
|
End_Column := Start_Column;
|
|
|
|
if Length < 0 then
|
|
Amount := -Length;
|
|
end if;
|
|
|
|
Is_Valid_Pos
|
|
(Buffer => Source_Buffer (Buffer),
|
|
Iter => Iter,
|
|
Found => Found,
|
|
Line => Gint (Get_Buffer_Line (Buffer, Start_Line) - 1),
|
|
Column => Gint (Start_Column - 1));
|
|
|
|
if not Found then
|
|
return;
|
|
end if;
|
|
|
|
for J in 1 .. Amount loop
|
|
if Forward then
|
|
Forward_Char (Iter, Success);
|
|
else
|
|
Backward_Char (Iter, Success);
|
|
end if;
|
|
|
|
exit when
|
|
not Success
|
|
or else Buffer_Line_Type (Get_Line (Iter) + 1)
|
|
not in Buffer.Line_Data'Range;
|
|
|
|
while Buffer.Line_Data (Buffer_Line_Type (Get_Line (Iter) + 1))
|
|
.Editable_Line
|
|
= 0
|
|
loop
|
|
-- ??? Could be optimized by moving line by line
|
|
if Forward then
|
|
Forward_Char (Iter, Success);
|
|
else
|
|
Backward_Char (Iter, Success);
|
|
end if;
|
|
|
|
exit when
|
|
not Success
|
|
or else Buffer_Line_Type (Get_Line (Iter) + 1)
|
|
not in Buffer.Line_Data'Range;
|
|
end loop;
|
|
end loop;
|
|
|
|
if Buffer_Line_Type (Get_Line (Iter) + 1) not in Buffer.Line_Data'Range
|
|
then
|
|
return;
|
|
end if;
|
|
|
|
End_Line :=
|
|
Get_Editable_Line (Buffer, Buffer_Line_Type (Get_Line (Iter) + 1));
|
|
|
|
End_Column := Character_Offset_Type (Get_Line_Offset (Iter) + 1);
|
|
end Forward_Position;
|
|
|
|
--------------
|
|
-- Get_Text --
|
|
--------------
|
|
|
|
function Get_Text
|
|
(Buffer : access Source_Buffer_Record;
|
|
Start_Line : Editable_Line_Type;
|
|
Start_Column : Character_Offset_Type;
|
|
End_Line : Editable_Line_Type := 0;
|
|
End_Column : Character_Offset_Type := 0;
|
|
Include_Hidden_Chars : Boolean := True;
|
|
Include_Last : Boolean := False) return Unbounded_String
|
|
is
|
|
Text_Access : GNAT.Strings.String_Access :=
|
|
Get_Text
|
|
(Buffer => Buffer,
|
|
Start_Line => Start_Line,
|
|
Start_Column => Start_Column,
|
|
End_Line => End_Line,
|
|
End_Column => End_Column,
|
|
Include_Hidden_Chars => Include_Hidden_Chars,
|
|
Include_Last => Include_Last);
|
|
begin
|
|
return To_Unbounded_String (Text_Access);
|
|
end Get_Text;
|
|
|
|
--------------
|
|
-- Get_Text --
|
|
--------------
|
|
|
|
function Get_Text
|
|
(Buffer : access Source_Buffer_Record;
|
|
Start_Line : Editable_Line_Type;
|
|
Start_Column : Character_Offset_Type;
|
|
End_Line : Editable_Line_Type := 0;
|
|
End_Column : Character_Offset_Type := 0;
|
|
Include_Hidden_Chars : Boolean := True;
|
|
Include_Last : Boolean := False)
|
|
return VSS.Strings.Virtual_String
|
|
is
|
|
Text_Access : GNAT.Strings.String_Access :=
|
|
Get_Text
|
|
(Buffer => Buffer,
|
|
Start_Line => Start_Line,
|
|
Start_Column => Start_Column,
|
|
End_Line => End_Line,
|
|
End_Column => End_Column,
|
|
Include_Hidden_Chars => Include_Hidden_Chars,
|
|
Include_Last => Include_Last);
|
|
begin
|
|
return
|
|
Result : constant VSS.Strings.Virtual_String :=
|
|
VSS.Strings.Conversions.To_Virtual_String (Text_Access.all)
|
|
do
|
|
GNAT.Strings.Free (Text_Access);
|
|
end return;
|
|
end Get_Text;
|
|
|
|
-------------------------
|
|
-- Refresh_Side_Column --
|
|
-------------------------
|
|
|
|
procedure Refresh_Side_Column (Buffer : access Source_Buffer_Record) is
|
|
begin
|
|
Recalculate_Side_Column_Width (Buffer);
|
|
Side_Column_Configuration_Changed (Buffer);
|
|
Side_Column_Changed (Buffer);
|
|
end Refresh_Side_Column;
|
|
|
|
--------------------
|
|
-- In_Destruction --
|
|
--------------------
|
|
|
|
function In_Destruction
|
|
(Buffer : access Source_Buffer_Record'Class) return Boolean is
|
|
begin
|
|
return Buffer.In_Destruction;
|
|
end In_Destruction;
|
|
|
|
-----------------------
|
|
-- Get_Command_Queue --
|
|
-----------------------
|
|
|
|
function Get_Command_Queue
|
|
(Buffer : access Source_Buffer_Record'Class) return Command_Queue is
|
|
begin
|
|
return Buffer.Queue;
|
|
end Get_Command_Queue;
|
|
|
|
-------------
|
|
-- Convert --
|
|
-------------
|
|
|
|
function Convert (L : Natural) return Editable_Line_Type is
|
|
begin
|
|
return Editable_Line_Type (L);
|
|
end Convert;
|
|
|
|
function Convert (L : Editable_Line_Type) return Natural is
|
|
begin
|
|
return Natural (L);
|
|
end Convert;
|
|
|
|
-----------------
|
|
-- Expand_Tabs --
|
|
-----------------
|
|
|
|
function Expand_Tabs
|
|
(Buffer : access Source_Buffer_Record;
|
|
Line : Editable_Line_Type;
|
|
Column : Character_Offset_Type) return Visible_Column_Type
|
|
is
|
|
Iter : Gtk_Text_Iter;
|
|
Count : Character_Offset_Type := 1;
|
|
Current : Visible_Column_Type := 1;
|
|
Result : Boolean := True;
|
|
Tab_Len : constant Visible_Column_Type :=
|
|
Visible_Column_Type (Buffer.Tab_Width);
|
|
begin
|
|
if Buffer.Editable_Lines = null
|
|
or else Line not in 1 .. Buffer.Last_Editable_Line
|
|
then
|
|
return Current;
|
|
end if;
|
|
|
|
Get_Iter_At_Line (Buffer, Iter, Gint (Buffer.Editable_Lines (Line) - 1));
|
|
|
|
while Result and then Count < Column loop
|
|
if Get_Char (Iter) = ASCII.HT then
|
|
Current := Current + (Tab_Len - (Current - 1) mod Tab_Len);
|
|
else
|
|
Current := Current + 1;
|
|
end if;
|
|
|
|
Count := Count + 1;
|
|
Forward_Char (Iter, Result);
|
|
end loop;
|
|
|
|
return Current;
|
|
end Expand_Tabs;
|
|
|
|
-------------------
|
|
-- Collapse_Tabs --
|
|
-------------------
|
|
|
|
function Collapse_Tabs
|
|
(Buffer : access Source_Buffer_Record;
|
|
Line : Editable_Line_Type;
|
|
Column : Visible_Column_Type) return Character_Offset_Type
|
|
is
|
|
Iter : Gtk_Text_Iter;
|
|
Current : Visible_Column_Type := 1;
|
|
Count : Character_Offset_Type := 1;
|
|
Result : Boolean := True;
|
|
Tab_Len : constant Visible_Column_Type :=
|
|
Visible_Column_Type (Buffer.Tab_Width);
|
|
Buffer_Line : constant Buffer_Line_Type := Buffer.Get_Buffer_Line (Line);
|
|
begin
|
|
if Column = 0 or else Line not in 1 .. Buffer.Last_Editable_Line then
|
|
return 0;
|
|
end if;
|
|
|
|
if Buffer_Line /= 0 then
|
|
Get_Iter_At_Line (Buffer, Iter, Gint (Buffer_Line - 1));
|
|
|
|
while Result and then Current < Column loop
|
|
if Get_Char (Iter) = ASCII.HT then
|
|
Current := Current + Tab_Len - (Current - 1) mod Tab_Len;
|
|
else
|
|
Current := Current + 1;
|
|
end if;
|
|
|
|
Count := Count + 1;
|
|
Forward_Char (Iter, Result);
|
|
end loop;
|
|
end if;
|
|
|
|
return Count;
|
|
end Collapse_Tabs;
|
|
|
|
--------------
|
|
-- Get_Tree --
|
|
--------------
|
|
|
|
function Get_Tree
|
|
(Buffer : access Source_Buffer_Record) return Semantic_Tree'Class is
|
|
begin
|
|
return
|
|
Buffer.Kernel.Get_Abstract_Tree_For_File ("EDIT", Buffer.Filename);
|
|
end Get_Tree;
|
|
|
|
----------------------
|
|
-- Blocks_Are_Exact --
|
|
----------------------
|
|
|
|
function Blocks_Are_Exact
|
|
(Buffer : access Source_Buffer_Record) return Boolean is
|
|
begin
|
|
return Buffer.Blocks_Exact;
|
|
end Blocks_Are_Exact;
|
|
|
|
--------------------------
|
|
-- Mark_Buffer_Writable --
|
|
--------------------------
|
|
|
|
procedure Mark_Buffer_Writable
|
|
(Buffer : not null access Source_Buffer_Record; Writable : Boolean)
|
|
is
|
|
Views : constant Views_Array := Get_Views (Source_Buffer (Buffer));
|
|
begin
|
|
Buffer.Writable := Writable;
|
|
|
|
if Writable then
|
|
Add_Controls (Buffer);
|
|
else
|
|
Remove_Controls (Buffer);
|
|
end if;
|
|
|
|
for V in Views'Range loop
|
|
Set_Editable (Gtk_Text_View (Views (V).Get_Source_View), Writable);
|
|
Update_Status (Views (V).Get_Status_Bar);
|
|
|
|
-- Changing the class does not take into account the CSS
|
|
-- background-color, for some reason, although it does take other
|
|
-- attributes like "color" into account.
|
|
Views (V).Get_Source_View.Set_Background_Color;
|
|
end loop;
|
|
end Mark_Buffer_Writable;
|
|
|
|
------------------
|
|
-- Get_Writable --
|
|
------------------
|
|
|
|
function Get_Writable
|
|
(Buffer : not null access Source_Buffer_Record) return Boolean is
|
|
begin
|
|
return Buffer.Writable;
|
|
end Get_Writable;
|
|
|
|
--------------------------
|
|
-- Prevent_CR_Insertion --
|
|
--------------------------
|
|
|
|
procedure Prevent_CR_Insertion
|
|
(Buffer : access Source_Buffer_Record'Class; Prevent : Boolean := True) is
|
|
begin
|
|
Buffer.Prevent_CR_Insertion := Prevent;
|
|
end Prevent_CR_Insertion;
|
|
|
|
-----------------------
|
|
-- Set_In_Completion --
|
|
-----------------------
|
|
|
|
procedure Set_In_Completion
|
|
(Buffer : Source_Buffer; In_Completion : Boolean) is
|
|
begin
|
|
Buffer.In_Completion := In_Completion;
|
|
end Set_In_Completion;
|
|
|
|
-------------------
|
|
-- In_Completion --
|
|
-------------------
|
|
|
|
function In_Completion (Buffer : Source_Buffer) return Boolean is
|
|
begin
|
|
return Buffer.In_Completion;
|
|
end In_Completion;
|
|
|
|
-----------------
|
|
-- Buffer_List --
|
|
-----------------
|
|
|
|
function Buffer_List
|
|
(Kernel : access GPS.Kernel.Kernel_Handle_Record'Class)
|
|
return Source_Buffer_Array
|
|
is
|
|
Iter : Child_Iterator;
|
|
Child_Count : Natural := 0;
|
|
begin
|
|
if Get_MDI (Kernel) = null then
|
|
return (1 .. 0 => <>);
|
|
end if;
|
|
|
|
Iter := First_Child (Get_MDI (Kernel));
|
|
while Get (Iter) /= null loop
|
|
Child_Count := Child_Count + 1;
|
|
Next (Iter);
|
|
end loop;
|
|
|
|
declare
|
|
Buffers : Source_Buffer_Array (1 .. Child_Count);
|
|
Buffer : Source_Buffer;
|
|
Index : Integer := Buffers'First - 1;
|
|
Found : Boolean;
|
|
begin
|
|
Iter := First_Child (Get_MDI (Kernel));
|
|
while Get (Iter) /= null loop
|
|
if Is_Source_Box (Get (Iter)) then
|
|
Buffer := Get_Buffer (Get_Source_Box_From_MDI (Get (Iter)));
|
|
Found := False;
|
|
for J in Buffers'First .. Index loop
|
|
if Buffers (J) = Buffer then
|
|
Found := True;
|
|
exit;
|
|
end if;
|
|
end loop;
|
|
|
|
if not Found then
|
|
Index := Index + 1;
|
|
Buffers (Index) := Buffer;
|
|
end if;
|
|
end if;
|
|
Next (Iter);
|
|
end loop;
|
|
|
|
return Buffers (Buffers'First .. Index);
|
|
end;
|
|
end Buffer_List;
|
|
|
|
--------------------
|
|
-- Add_Typed_Char --
|
|
--------------------
|
|
|
|
procedure Add_Typed_Char
|
|
(Buffer : access Source_Buffer_Record'Class; C : Gunichar) is
|
|
begin
|
|
if Buffer.Typed_Char_Index < Buffer.Typed_Chars'Last then
|
|
Buffer.Typed_Char_Index := Buffer.Typed_Char_Index + 1;
|
|
Buffer.Typed_Chars (Buffer.Typed_Char_Index) := C;
|
|
end if;
|
|
end Add_Typed_Char;
|
|
|
|
----------------------------
|
|
-- Delete_Last_Typed_Char --
|
|
----------------------------
|
|
|
|
procedure Delete_Last_Typed_Char
|
|
(Buffer : access Source_Buffer_Record'Class) is
|
|
begin
|
|
if Buffer.Typed_Char_Index >= Buffer.Typed_Chars'First then
|
|
Buffer.Typed_Char_Index := Buffer.Typed_Char_Index - 1;
|
|
end if;
|
|
end Delete_Last_Typed_Char;
|
|
|
|
-----------------------
|
|
-- Clear_Typed_Chars --
|
|
-----------------------
|
|
|
|
procedure Clear_Typed_Chars (Buffer : access Source_Buffer_Record'Class) is
|
|
begin
|
|
Buffer.Typed_Char_Index := 0;
|
|
end Clear_Typed_Chars;
|
|
|
|
---------------------
|
|
-- Get_Typed_Chars --
|
|
---------------------
|
|
|
|
function Get_Typed_Chars
|
|
(Buffer : access Source_Buffer_Record'Class; N : Positive)
|
|
return Basic_Types.UTF8_String is
|
|
begin
|
|
if N > Buffer.Typed_Char_Index then
|
|
-- No enough characters in buffer, we cannot support conservative
|
|
-- casing.
|
|
return "";
|
|
|
|
else
|
|
declare
|
|
S : Basic_Types.UTF8_String (1 .. N * 6);
|
|
-- An UTF-8 character expand of max 6 bytes
|
|
Last : Natural := 0;
|
|
begin
|
|
for K in Buffer.Typed_Char_Index - N + 1 .. Buffer.Typed_Char_Index
|
|
loop
|
|
Unichar_To_UTF8
|
|
(Buffer.Typed_Chars (K), S (Last + 1 .. S'Last), Last);
|
|
end loop;
|
|
return S (1 .. Last);
|
|
end;
|
|
end if;
|
|
end Get_Typed_Chars;
|
|
|
|
-------------------
|
|
-- Get_Version --
|
|
-------------------
|
|
|
|
function Get_Version
|
|
(Buffer : access Source_Buffer_Record'Class) return Integer is
|
|
begin
|
|
return Buffer.Version;
|
|
end Get_Version;
|
|
|
|
------------------------------
|
|
-- Update_Logical_Timestamp --
|
|
------------------------------
|
|
|
|
procedure Update_Logical_Timestamp
|
|
(Buffer : access Source_Buffer_Record'Class) is
|
|
begin
|
|
if Buffer.Version = Integer'Last then
|
|
Buffer.Version := 0;
|
|
else
|
|
Buffer.Version := Buffer.Version + 1;
|
|
end if;
|
|
end Update_Logical_Timestamp;
|
|
|
|
------------------------------
|
|
-- Update_All_Column_Memory --
|
|
------------------------------
|
|
|
|
procedure Update_All_Column_Memory
|
|
(Buffer : access Source_Buffer_Record'Class)
|
|
is
|
|
Iter : Gtk_Text_Iter;
|
|
begin
|
|
|
|
Buffer.Get_Iter_At_Mark (Iter, Buffer.Insert_Mark);
|
|
Buffer.Cursor_Column_Memory := Get_Line_Offset (Iter);
|
|
|
|
for Cursor of Buffer.Slave_Cursors_List loop
|
|
Buffer.Get_Iter_At_Mark (Iter, Cursor.Mark);
|
|
Cursor.Column_Memory := Get_Line_Offset (Iter);
|
|
end loop;
|
|
|
|
end Update_All_Column_Memory;
|
|
pragma Unreferenced (Update_All_Column_Memory);
|
|
|
|
----------------------------------
|
|
-- Update_Use_Highlighting_Hook --
|
|
----------------------------------
|
|
|
|
procedure Update_Use_Highlighting_Hook
|
|
(Self : access Source_Highlighter_Record)
|
|
is
|
|
Prev : constant Boolean := Self.Use_Highlighting_Hook;
|
|
begin
|
|
Self.Use_Highlighting_Hook :=
|
|
Self.Buffer.Lang /= null
|
|
and then Self.Buffer.Lang.Get_Name = "Ada"
|
|
and then Use_External_Highlighting.Get_Pref /= None;
|
|
|
|
if Prev /= Self.Use_Highlighting_Hook and then Prev then
|
|
-- We don't use hook anymore, so should clear old highlighting
|
|
-- styles prodused by code connected to highlighting hook
|
|
|
|
Self.Call_Clear_Highlighting := True;
|
|
end if;
|
|
end Update_Use_Highlighting_Hook;
|
|
|
|
---------------
|
|
-- Inserting --
|
|
---------------
|
|
|
|
function Inserting
|
|
(Buffer : access Source_Buffer_Record'Class) return Boolean is
|
|
begin
|
|
return Buffer.Inserting_Count >= 1;
|
|
end Inserting;
|
|
|
|
-----------------------------
|
|
-- Is_Inserting_Internally --
|
|
-----------------------------
|
|
|
|
function Is_Inserting_Internally
|
|
(Buffer : access Source_Buffer_Record) return Boolean
|
|
is (Buffer.Inserting);
|
|
|
|
---------------------
|
|
-- Start_Inserting --
|
|
---------------------
|
|
|
|
procedure Start_Inserting (Buffer : access Source_Buffer_Record'Class) is
|
|
begin
|
|
Buffer.Inserting_Count := Buffer.Inserting_Count + 1;
|
|
end Start_Inserting;
|
|
|
|
-------------------
|
|
-- End_Inserting --
|
|
-------------------
|
|
|
|
procedure End_Inserting (Buffer : access Source_Buffer_Record'Class) is
|
|
begin
|
|
Buffer.Inserting_Count := Buffer.Inserting_Count - 1;
|
|
end End_Inserting;
|
|
|
|
----------------------
|
|
-- Start_Undo_Group --
|
|
----------------------
|
|
|
|
procedure Start_Undo_Group (Buffer : access Source_Buffer_Record'Class) is
|
|
begin
|
|
End_Action (Buffer);
|
|
Start_Group (Buffer.Queue);
|
|
end Start_Undo_Group;
|
|
|
|
-----------------------
|
|
-- Finish_Undo_Group --
|
|
-----------------------
|
|
|
|
procedure Finish_Undo_Group (Buffer : access Source_Buffer_Record'Class) is
|
|
begin
|
|
End_Action (Buffer);
|
|
End_Group (Buffer.Queue);
|
|
end Finish_Undo_Group;
|
|
|
|
------------------------
|
|
-- Current_Undo_Group --
|
|
------------------------
|
|
|
|
function Current_Undo_Group
|
|
(Buffer : access Source_Buffer_Record'Class) return Group_Block is
|
|
begin
|
|
End_Action (Buffer);
|
|
return Current_Group (Buffer.Queue);
|
|
end Current_Undo_Group;
|
|
|
|
--------------------
|
|
-- New_Undo_Group --
|
|
--------------------
|
|
|
|
function New_Undo_Group
|
|
(Buffer : access Source_Buffer_Record'Class) return Group_Block is
|
|
begin
|
|
End_Action (Buffer);
|
|
return New_Group (Buffer.Queue);
|
|
end New_Undo_Group;
|
|
|
|
--------------------------
|
|
-- Add_Listener_Factory --
|
|
--------------------------
|
|
|
|
procedure Add_Listener_Factory (Factory : Editor_Listener_Factory_Access) is
|
|
begin
|
|
Listener_Factories.Append (Factory);
|
|
end Add_Listener_Factory;
|
|
|
|
--------------------------
|
|
-- Set_Folding_Provider --
|
|
--------------------------
|
|
|
|
procedure Set_Folding_Provider (Provider : Editor_Folding_Provider_Access)
|
|
is
|
|
begin
|
|
Folding_Provider := Provider;
|
|
end Set_Folding_Provider;
|
|
|
|
-----------------------
|
|
-- Get_Editor_Buffer --
|
|
-----------------------
|
|
|
|
function Get_Editor_Buffer
|
|
(Buffer : access Source_Buffer_Record'Class) return Editor_Buffer_Access
|
|
is
|
|
begin
|
|
return Buffer.Editor_Buffer;
|
|
end Get_Editor_Buffer;
|
|
|
|
--------------------------------------
|
|
-- Get_Global_Editor_Buffer_Factory --
|
|
--------------------------------------
|
|
|
|
function Get_Global_Editor_Buffer_Factory
|
|
return access GPS.Editors.Editor_Buffer_Factory'Class is
|
|
begin
|
|
return Editors_Factory;
|
|
end Get_Global_Editor_Buffer_Factory;
|
|
|
|
---------------
|
|
-- Set_Title --
|
|
---------------
|
|
|
|
procedure Set_Title
|
|
(Buffer : not null access Source_Buffer_Record; Title : String) is
|
|
begin
|
|
GNAT.Strings.Free (Buffer.Forced_Title);
|
|
Buffer.Forced_Title := new String'(Title);
|
|
end Set_Title;
|
|
|
|
---------------
|
|
-- Get_Title --
|
|
---------------
|
|
|
|
function Get_Title
|
|
(Buffer : not null access Source_Buffer_Record) return String is
|
|
begin
|
|
if Buffer.Forced_Title /= null then
|
|
return Buffer.Forced_Title.all;
|
|
else
|
|
return "";
|
|
end if;
|
|
end Get_Title;
|
|
|
|
--------------------
|
|
-- Freeze_Context --
|
|
--------------------
|
|
|
|
procedure Freeze_Context (Self : not null access Source_Buffer_Record'Class)
|
|
is
|
|
begin
|
|
Self.Context_Frozen := Self.Context_Frozen + 1;
|
|
end Freeze_Context;
|
|
|
|
------------------
|
|
-- Thaw_Context --
|
|
------------------
|
|
|
|
procedure Thaw_Context (Self : not null access Source_Buffer_Record'Class)
|
|
is
|
|
begin
|
|
Self.Context_Frozen := Self.Context_Frozen - 1;
|
|
Assert
|
|
(Me,
|
|
Self.Context_Frozen >= 0,
|
|
"Calls to Thaw_Context doesn't match Freeze_Context");
|
|
end Thaw_Context;
|
|
|
|
-----------------------
|
|
-- Context_Is_Frozen --
|
|
-----------------------
|
|
|
|
function Context_Is_Frozen
|
|
(Self : not null access Source_Buffer_Record'Class) return Boolean
|
|
is (Self.Context_Frozen > 0);
|
|
|
|
-------------------------------
|
|
-- Source_Highlighter_Record --
|
|
-------------------------------
|
|
|
|
----------------
|
|
-- Initialize --
|
|
----------------
|
|
|
|
overriding
|
|
procedure Initialize (Self : in out Source_Highlighter_Record) is
|
|
Tags : Gtk_Text_Tag_Table;
|
|
begin
|
|
-- Save the newly created highlighting tags into the source buffer
|
|
-- tag table.
|
|
|
|
Tags := Get_Tag_Table (Self.Buffer);
|
|
|
|
for Entity_Kind in Standout_Language_Entity'Range loop
|
|
Self.Syntax_Tags (Entity_Kind) :=
|
|
Get_Tag (Language_Styles (Entity_Kind));
|
|
Text_Tag_Table.Add (Tags, Self.Syntax_Tags (Entity_Kind));
|
|
end loop;
|
|
|
|
-- Create Delimiter_Tag and save it into the source buffer tag table
|
|
Self.Delimiter_Tag := Get_Tag (Editor_Ephemeral_Highlighting_Simple);
|
|
|
|
Add (Tags, Self.Delimiter_Tag);
|
|
Add (Tags, Self.Buffer.Hyper_Mode_Tag);
|
|
Add (Tags, Self.Buffer.Non_Editable_Tag);
|
|
Add (Tags, Self.Buffer.Hidden_Text_Tag);
|
|
end Initialize;
|
|
|
|
--------------
|
|
-- Finalize --
|
|
--------------
|
|
|
|
overriding
|
|
procedure Finalize (Self : in out Source_Highlighter_Record) is
|
|
begin
|
|
Unref (Self.Syntax_Tags);
|
|
if Self.Delimiter_Tag /= null then
|
|
Unref (Self.Delimiter_Tag);
|
|
end if;
|
|
end Finalize;
|
|
|
|
-------------------------
|
|
-- Enable_Highlighting --
|
|
-------------------------
|
|
|
|
procedure Enable_Highlighting (Self : access Source_Highlighter_Record) is
|
|
begin
|
|
Self.Auto_Highlight_Enabled := True;
|
|
end Enable_Highlighting;
|
|
|
|
--------------------------
|
|
-- Disable_Highlighting --
|
|
--------------------------
|
|
|
|
procedure Disable_Highlighting (Self : access Source_Highlighter_Record) is
|
|
begin
|
|
Self.Auto_Highlight_Enabled := False;
|
|
end Disable_Highlighting;
|
|
|
|
---------------------------
|
|
-- Set_Line_Highlighting --
|
|
---------------------------
|
|
|
|
procedure Set_Line_Highlighting
|
|
(Self : access Source_Highlighter_Record;
|
|
Line : Buffer_Line_Type;
|
|
Style : not null Style_Access;
|
|
Set : Boolean;
|
|
Highlight_In : Highlight_Location_Array)
|
|
is
|
|
procedure Set_Highlighting
|
|
(Data : in out Highlighting_Data_Record;
|
|
Category : Natural;
|
|
Enabled : Boolean);
|
|
-- Sets highligting state and recompute active highlighting
|
|
|
|
----------------------
|
|
-- Set_Highlighting --
|
|
----------------------
|
|
|
|
procedure Set_Highlighting
|
|
(Data : in out Highlighting_Data_Record;
|
|
Category : Natural;
|
|
Enabled : Boolean)
|
|
is
|
|
Last_Index : constant Natural := Get_Last_Index;
|
|
|
|
begin
|
|
-- Reallocate data when necessary
|
|
|
|
if Data.Enabled = null then
|
|
-- If we are removing a highlight where no highlight is defined,
|
|
-- we can exit immediately.
|
|
|
|
if not Set then
|
|
return;
|
|
end if;
|
|
|
|
Data.Enabled := new Boolean_Array (1 .. Last_Index);
|
|
Data.Enabled.all := (others => False);
|
|
|
|
elsif Data.Enabled'Last < Last_Index then
|
|
declare
|
|
Aux : Boolean_Array_Access;
|
|
|
|
begin
|
|
Aux := new Boolean_Array (1 .. Last_Index);
|
|
Aux (1 .. Data.Enabled'Last) := Data.Enabled.all;
|
|
Aux (Data.Enabled'Last + 1 .. Last_Index) := (others => False);
|
|
Unchecked_Free (Data.Enabled);
|
|
Data.Enabled := Aux;
|
|
end;
|
|
end if;
|
|
|
|
-- Set new state of highlighting
|
|
|
|
Data.Enabled (Category) := Enabled;
|
|
|
|
-- Find out which category has priority for highlighting
|
|
|
|
for J in Data.Enabled'Range loop
|
|
if Data.Enabled (J) then
|
|
Data.Active := J;
|
|
|
|
return;
|
|
end if;
|
|
end loop;
|
|
|
|
-- If we reach this stage, no highlighting was found
|
|
|
|
Data.Active := 0;
|
|
end Set_Highlighting;
|
|
|
|
Category : Natural;
|
|
|
|
begin
|
|
if Line = 0 then
|
|
return;
|
|
end if;
|
|
|
|
Category := Lookup_Category (Style);
|
|
|
|
if Category = 0 then
|
|
Trace
|
|
(Me,
|
|
"Set_Line_Highlight Id="
|
|
& Get_Name (Style)
|
|
& " couldn't identify category, nothing done");
|
|
-- Could not identify highlighting category
|
|
return;
|
|
end if;
|
|
|
|
if Line < Self.Buffer.Line_Data'First
|
|
or else Line > Self.Buffer.Line_Data'Last
|
|
then
|
|
Trace (Me, "Wrong line number: " & Image (Integer (Line)));
|
|
return;
|
|
end if;
|
|
|
|
for K in Highlight_Location loop
|
|
if Highlight_In (K) then
|
|
Set_Highlighting
|
|
(Self.Buffer.Line_Data (Line).Highlighting (K), Category, Set);
|
|
end if;
|
|
end loop;
|
|
end Set_Line_Highlighting;
|
|
|
|
---------------------------
|
|
-- Add_Line_Highlighting --
|
|
---------------------------
|
|
|
|
procedure Add_Line_Highlighting
|
|
(Self : access Source_Highlighter_Record;
|
|
Line : Editable_Line_Type;
|
|
Style : not null Style_Access;
|
|
Highlight_In : Highlight_Location_Array)
|
|
is
|
|
The_Line : Buffer_Line_Type;
|
|
begin
|
|
if Line = 0 then
|
|
for J in Self.Buffer.Line_Data'Range loop
|
|
Self.Set_Line_Highlighting (J, Style, True, Highlight_In);
|
|
end loop;
|
|
else
|
|
The_Line := Get_Buffer_Line (Self.Buffer, Line);
|
|
|
|
if The_Line /= 0 then
|
|
Self.Set_Line_Highlighting (The_Line, Style, True, Highlight_In);
|
|
end if;
|
|
end if;
|
|
|
|
Line_Highlights_Changed (Self.Buffer);
|
|
end Add_Line_Highlighting;
|
|
|
|
---------------------
|
|
-- Highlight_Range --
|
|
---------------------
|
|
|
|
procedure Highlight_Range
|
|
(Self : access Source_Highlighter_Record;
|
|
Style : Style_Access;
|
|
Line : Editable_Line_Type;
|
|
Start_Col : Visible_Column_Type;
|
|
End_Col : Visible_Column_Type;
|
|
Remove : Boolean := False)
|
|
is
|
|
Start_Iter, End_Iter : Gtk_Text_Iter;
|
|
Result : Boolean;
|
|
The_Line : Gint;
|
|
begin
|
|
-- Here we test whether the buffer is in destruction. If it is the case
|
|
-- we simply return since it is not worth taking care of unhighlighting
|
|
-- lines. Furthermore this prevents GNAT Studio from crashing when
|
|
-- we close a source file used in a visual diff while the reference
|
|
-- file is still being displayed.
|
|
|
|
if Self.Buffer.In_Destruction then
|
|
return;
|
|
end if;
|
|
|
|
-- Get the boundaries of text to (un)highlight
|
|
|
|
if Line = 0 then
|
|
Get_Bounds (Self.Buffer, Start_Iter, End_Iter);
|
|
|
|
else
|
|
The_Line := Gint (Get_Buffer_Line (Self.Buffer, Line) - 1);
|
|
|
|
if The_Line < 0 then
|
|
return;
|
|
end if;
|
|
|
|
if Start_Col <= 0
|
|
or else not Is_Valid_Position (Self.Buffer, Line, Start_Col)
|
|
then
|
|
Get_Iter_At_Line (Self.Buffer, Start_Iter, The_Line);
|
|
else
|
|
Get_Iter_At_Screen_Position
|
|
(Self.Buffer, Start_Iter, Line, Start_Col);
|
|
|
|
if Ends_Line (Start_Iter) then
|
|
Backward_Char (Start_Iter, Result);
|
|
end if;
|
|
end if;
|
|
|
|
if End_Col <= 0
|
|
or else not Is_Valid_Position (Self.Buffer, Line, End_Col)
|
|
then
|
|
Copy (Start_Iter, End_Iter);
|
|
Forward_To_Line_End (End_Iter, Result);
|
|
else
|
|
Get_Iter_At_Screen_Position (Self.Buffer, End_Iter, Line, End_Col);
|
|
end if;
|
|
end if;
|
|
|
|
Self.Highlight_Range
|
|
(Style => Style,
|
|
Line => Line,
|
|
Start_Iter => Start_Iter,
|
|
End_Iter => End_Iter,
|
|
Remove => Remove);
|
|
end Highlight_Range;
|
|
|
|
---------------------
|
|
-- Highlight_Slice --
|
|
---------------------
|
|
|
|
procedure Highlight_Slice
|
|
(Self : access Source_Highlighter_Record;
|
|
Style : Style_Access;
|
|
Start_Line : Editable_Line_Type;
|
|
Start_Col : Visible_Column_Type;
|
|
End_Line : Editable_Line_Type;
|
|
End_Col : Visible_Column_Type;
|
|
Remove : Boolean := False)
|
|
is
|
|
Start_Iter, End_Iter : Gtk_Text_Iter;
|
|
begin
|
|
Get_Iter_At_Screen_Position
|
|
(Self.Buffer, Start_Iter, Start_Line, Start_Col);
|
|
Get_Iter_At_Screen_Position (Self.Buffer, End_Iter, End_Line, End_Col);
|
|
|
|
Highlight_Slice (Self, Style, Start_Iter, End_Iter, Remove);
|
|
end Highlight_Slice;
|
|
|
|
---------------------
|
|
-- Highlight_Slice --
|
|
---------------------
|
|
|
|
procedure Highlight_Slice
|
|
(Self : access Source_Highlighter_Record;
|
|
Style : Style_Access;
|
|
Start_Iter : Gtk.Text_Iter.Gtk_Text_Iter;
|
|
End_Iter : Gtk.Text_Iter.Gtk_Text_Iter;
|
|
Remove : Boolean := False)
|
|
is
|
|
Tag : Gtk_Text_Tag;
|
|
begin
|
|
-- Get the text tag, create it if necessary
|
|
Tag := Lookup (Get_Tag_Table (Self.Buffer), Get_Name (Style));
|
|
|
|
if Tag = null then
|
|
if Remove then
|
|
return;
|
|
else
|
|
-- Create the tag from the style
|
|
Tag := Get_Tag (Style);
|
|
Add (Get_Tag_Table (Self.Buffer), Tag);
|
|
end if;
|
|
end if;
|
|
|
|
declare
|
|
Start_Line : constant Gint := Get_Line (Start_Iter);
|
|
End_Line : constant Gint := Get_Line (End_Iter);
|
|
begin
|
|
|
|
-- Highlight/Unhighlight the text
|
|
if Remove then
|
|
Remove_Tag (Self.Buffer, Tag, Start_Iter, End_Iter);
|
|
else
|
|
Apply_Tag (Self.Buffer, Tag, Start_Iter, End_Iter);
|
|
end if;
|
|
|
|
if Get_In_Speedbar (Style) then
|
|
for Line in Start_Line .. End_Line loop
|
|
if Remove then
|
|
Self.Remove_Line_Highlighting
|
|
(Editable_Line_Type (Line), Style);
|
|
else
|
|
Self.Add_Line_Highlighting
|
|
(Editable_Line_Type (Line),
|
|
Style,
|
|
Highlight_In =>
|
|
(Highlight_Speedbar => True, others => False));
|
|
end if;
|
|
end loop;
|
|
end if;
|
|
end;
|
|
end Highlight_Slice;
|
|
|
|
---------------------
|
|
-- Highlight_Range --
|
|
---------------------
|
|
|
|
procedure Highlight_Range
|
|
(Self : access Source_Highlighter_Record;
|
|
Style : Style_Access;
|
|
Line : Editable_Line_Type;
|
|
Start_Iter : Gtk_Text_Iter;
|
|
End_Iter : Gtk_Text_Iter;
|
|
Remove : Boolean := False)
|
|
is
|
|
Tag : Gtk_Text_Tag;
|
|
begin
|
|
-- Get the text tag, create it if necessary
|
|
|
|
Tag := Lookup (Get_Tag_Table (Self.Buffer), Get_Name (Style));
|
|
|
|
if Tag = null then
|
|
if Remove then
|
|
return;
|
|
else
|
|
-- Create the tag from the style
|
|
Tag := Get_Tag (Style);
|
|
Add (Get_Tag_Table (Self.Buffer), Tag);
|
|
end if;
|
|
end if;
|
|
|
|
-- Highlight/Unhighlight the text
|
|
|
|
if Remove then
|
|
Remove_Tag (Self.Buffer, Tag, Start_Iter, End_Iter);
|
|
else
|
|
Apply_Tag (Self.Buffer, Tag, Start_Iter, End_Iter);
|
|
end if;
|
|
|
|
if Line /= 0 then
|
|
if Get_In_Speedbar (Style) then
|
|
if Remove then
|
|
Self.Remove_Line_Highlighting (Line, Style);
|
|
else
|
|
Self.Add_Line_Highlighting
|
|
(Line,
|
|
Style,
|
|
Highlight_In =>
|
|
(Highlight_Speedbar => True, others => False));
|
|
end if;
|
|
end if;
|
|
end if;
|
|
end Highlight_Range;
|
|
|
|
---------------------
|
|
-- Highlight_Slice --
|
|
---------------------
|
|
|
|
procedure Highlight_Slice
|
|
(Self : access Source_Highlighter_Record;
|
|
Start_Iter : Gtk.Text_Iter.Gtk_Text_Iter;
|
|
End_Iter : Gtk.Text_Iter.Gtk_Text_Iter)
|
|
is
|
|
procedure Is_Valid_Index is new
|
|
Generic_Valid_Position (Get_Bytes_In_Line, Set_Line_Index);
|
|
-- Column should be given in bytes, not characters
|
|
|
|
function Highlight_Cb
|
|
(Entity : Language_Entity;
|
|
Sloc_Start : Source_Location;
|
|
Sloc_End : Source_Location;
|
|
Partial_Entity : Boolean) return Boolean;
|
|
-- Function called by Language.Parse_Entities for each entity found
|
|
|
|
procedure Local_Highlight;
|
|
-- Highlight the region exactly located between Entity_Start and
|
|
-- Entity_End.
|
|
-- After this procedure is run, some variables are positioned to
|
|
-- the following values:
|
|
-- - Last_Entity is equal to the incomplete entity kind found inside
|
|
-- the given region, or to Normal_Text if all entities were complete
|
|
-- - Entity_Start is set to the begining of the incomplete region
|
|
-- found in the given buffer slice, if any.
|
|
|
|
Highlight_Complete : Boolean := False;
|
|
Entity_Start : Gtk_Text_Iter;
|
|
Entity_End : Gtk_Text_Iter;
|
|
Slice_Offset_Line : Buffer_Line_Type;
|
|
-- Offset between the beginning of the Source_Buffer and the beginning
|
|
-- of the string slice passed to Parse_Entities.
|
|
|
|
Tmp_Start, Tmp_End : Gtk_Text_Iter;
|
|
Slice_Offset_Column : Gint;
|
|
Result : Boolean;
|
|
Ignored : Boolean;
|
|
Entity_Kind : Language_Entity;
|
|
Slice : Unchecked_String_Access;
|
|
pragma Suppress (Access_Check, Slice);
|
|
|
|
------------------
|
|
-- Highlight_Cb --
|
|
------------------
|
|
|
|
function Highlight_Cb
|
|
(Entity : Language_Entity;
|
|
Sloc_Start : Source_Location;
|
|
Sloc_End : Source_Location;
|
|
Partial_Entity : Boolean) return Boolean
|
|
is
|
|
Success : Boolean;
|
|
Start : Natural;
|
|
Col, Line : Gint;
|
|
Offset : Gint;
|
|
Buffer_Line : Buffer_Line_Type;
|
|
End_Buffer_Line : Buffer_Line_Type;
|
|
|
|
begin
|
|
if Partial_Entity then
|
|
Highlight_Complete := False;
|
|
end if;
|
|
|
|
if Entity not in Standout_Language_Entity then
|
|
return False;
|
|
end if;
|
|
|
|
-- Some parsers currently leave line numbers to 0. Don't highlight in
|
|
-- this case, since we cannot use from the byte index due to
|
|
-- limitations in gtk+
|
|
|
|
if Sloc_Start.Line < 1 then
|
|
return False;
|
|
end if;
|
|
|
|
-- Don't need to take into account the offset column, unless we are
|
|
-- still on the same line that we started at.
|
|
|
|
if Sloc_Start.Line = 1 then
|
|
Offset := Slice_Offset_Column;
|
|
else
|
|
Offset := 0;
|
|
end if;
|
|
|
|
Col := Gint (Sloc_Start.Column) + Offset - 1;
|
|
Buffer_Line := Buffer_Line_Type (Sloc_Start.Line) + Slice_Offset_Line;
|
|
|
|
End_Buffer_Line :=
|
|
Buffer_Line_Type (Sloc_End.Line) + Slice_Offset_Line;
|
|
|
|
-- If the column is 0, the entity really ended on the end of the
|
|
-- previous line.
|
|
|
|
if End_Buffer_Line = 0 then
|
|
return False;
|
|
end if;
|
|
|
|
Line := Gint (Buffer_Line - 1);
|
|
Is_Valid_Index (Self.Buffer, Entity_Start, Success, Line, Col);
|
|
|
|
if not Success then
|
|
Trace (Me, "invalid position");
|
|
return False;
|
|
end if;
|
|
|
|
Line := Gint (End_Buffer_Line - 1);
|
|
|
|
if Sloc_End.Column = 0 then
|
|
Get_Iter_At_Line_Index (Self.Buffer, Entity_End, Line, 0);
|
|
Backward_Char (Entity_End, Success);
|
|
|
|
else
|
|
if Gint (Sloc_End.Line) = 1 then
|
|
Offset := Slice_Offset_Column;
|
|
else
|
|
Offset := 0;
|
|
end if;
|
|
|
|
if Slice (Sloc_End.Index) /= ASCII.LF then
|
|
Col := Gint (Sloc_End.Column) + Offset - 1;
|
|
|
|
-- Is_Valid_Index requires an index at the start of a character
|
|
-- while Sloc_End.Index points to the end of a character, so
|
|
-- adjust if needed.
|
|
|
|
Start :=
|
|
UTF8_Find_Prev_Char
|
|
(Slice (1 .. Sloc_End.Index + 1), Sloc_End.Index + 1);
|
|
|
|
if Start /= Sloc_End.Index then
|
|
Col := Col - Gint (Sloc_End.Index - Start);
|
|
end if;
|
|
|
|
Is_Valid_Index (Self.Buffer, Entity_End, Success, Line, Col);
|
|
|
|
if not Success then
|
|
Trace
|
|
(Me,
|
|
"invalid position """
|
|
& Self.Buffer.Filename.Display_Full_Name
|
|
& """"
|
|
& Line'Img
|
|
& Col'Img);
|
|
return False;
|
|
end if;
|
|
|
|
Forward_Char (Entity_End, Success);
|
|
|
|
else
|
|
Is_Valid_Index (Self.Buffer, Entity_End, Success, Line, 0);
|
|
if not Success then
|
|
Trace
|
|
(Me,
|
|
"invalid position """
|
|
& Self.Buffer.Filename.Display_Full_Name
|
|
& """"
|
|
& Line'Img
|
|
& " 0--");
|
|
return False;
|
|
end if;
|
|
|
|
if not Ends_Line (Entity_End) then
|
|
Forward_To_Line_End (Entity_End, Success);
|
|
end if;
|
|
end if;
|
|
end if;
|
|
|
|
if Entity in Aspect_Keyword_Text .. Aspect_Text then
|
|
-- We have an aspect here, store information about this for
|
|
-- lines
|
|
declare
|
|
Line : Buffer_Line_Type :=
|
|
Buffer_Line_Type (Get_Line (Entity_Start) + 1);
|
|
The_End : constant Buffer_Line_Type :=
|
|
Buffer_Line_Type (Get_Line (Entity_End) + 1);
|
|
begin
|
|
while Line <= The_End
|
|
and then Line in Self.Buffer.Line_Data'Range
|
|
loop
|
|
Self.Buffer.Line_Data (Line).Has_Aspect := True;
|
|
Line := Line + 1;
|
|
end loop;
|
|
end;
|
|
end if;
|
|
|
|
Apply_Tag
|
|
(Self.Buffer, Self.Syntax_Tags (Entity), Entity_Start, Entity_End);
|
|
|
|
return False;
|
|
end Highlight_Cb;
|
|
|
|
---------------------
|
|
-- Local_Highlight --
|
|
---------------------
|
|
|
|
procedure Local_Highlight is
|
|
UTF8 : constant Gtkada.Types.Chars_Ptr :=
|
|
Get_Slice (Entity_Start, Entity_End);
|
|
|
|
-- Can't use Get_Offset (Entity_End) - Get_Offset (Entity_Start)
|
|
-- since this would give the number of chars, not bytes.
|
|
|
|
Length : constant Integer := Integer (Strlen (UTF8));
|
|
|
|
begin
|
|
Slice := To_Unchecked_String (UTF8);
|
|
Highlight_Complete := True;
|
|
Slice_Offset_Line := Buffer_Line_Type (Get_Line (Entity_Start));
|
|
Slice_Offset_Column := Get_Line_Index (Entity_Start);
|
|
|
|
-- First, un-apply all the style tags...
|
|
|
|
Self.Kill_Highlighting (Entity_Start, Entity_End);
|
|
|
|
-- Now re-highlight the text...
|
|
|
|
if Self.Buffer.Lang /= null then
|
|
Parse_Entities
|
|
(Self.Buffer.Lang,
|
|
Slice (1 .. Length),
|
|
Highlight_Cb'Unrestricted_Access);
|
|
end if;
|
|
|
|
Slice := null;
|
|
g_free (UTF8);
|
|
end Local_Highlight;
|
|
|
|
begin
|
|
Copy (Source => Start_Iter, Dest => Entity_Start);
|
|
Copy (Source => End_Iter, Dest => Entity_End);
|
|
|
|
-- Checking whether we have an aspect on the first range line
|
|
-- and enlarging the range to the first aspect's line if needed
|
|
Slice_Offset_Line := Buffer_Line_Type (Get_Line (Start_Iter) + 1);
|
|
while Slice_Offset_Line > 1
|
|
and then Slice_Offset_Line in Self.Buffer.Line_Data'Range
|
|
and then Self.Buffer.Line_Data (Slice_Offset_Line).Has_Aspect
|
|
loop
|
|
Slice_Offset_Line := Slice_Offset_Line - 1;
|
|
end loop;
|
|
|
|
if Slice_Offset_Line < Buffer_Line_Type (Get_Line (Start_Iter) + 1) then
|
|
Self.Buffer.Get_Iter_At_Line (Entity_Start, Gint (Slice_Offset_Line));
|
|
else
|
|
-- Start from the beginning of the current line to handle special
|
|
-- language semantics requiring information from previous characters,
|
|
-- such as x.all'address in Ada...
|
|
|
|
Set_Line_Offset (Entity_Start, 0);
|
|
|
|
if Get_Line (Entity_Start) /= Get_Line (Start_Iter) then
|
|
Copy (Source => Start_Iter, Dest => Entity_Start);
|
|
end if;
|
|
end if;
|
|
|
|
if Self.Buffer.Lang /= null
|
|
and then Get_Language_Context (Self.Buffer.Lang).Use_Semicolon
|
|
then
|
|
-- ...and highlight from the previous semicolon, to handle multiple
|
|
-- line constructs such as C comments or Ada aspect clauses. Note:
|
|
-- this is a heuristic, since we could find a semicolon in the middle
|
|
-- of a comment, which wouldn't help us that much.
|
|
|
|
Backward_Search
|
|
(Iter => Entity_Start,
|
|
Str => ";",
|
|
Flags => 0,
|
|
Match_Start => Tmp_Start,
|
|
Match_End => Tmp_End,
|
|
Result => Result);
|
|
|
|
if Result then
|
|
Forward_Char (Tmp_Start, Result);
|
|
Entity_Start := Tmp_Start;
|
|
else
|
|
Set_Offset (Entity_Start, 0);
|
|
end if;
|
|
end if;
|
|
|
|
-- Highlight to the end of line, to avoid missing most of the partial
|
|
-- entities (strings, characters, ...). In case we have started typing
|
|
-- a string for instance, that provides a nice optimization over
|
|
-- rehighlighting the whole buffer...
|
|
|
|
Forward_To_Line_End (Entity_End, Result);
|
|
|
|
if Self.Buffer.Lang /= null
|
|
and then Get_Language_Context (Self.Buffer.Lang).Use_Semicolon
|
|
then
|
|
-- ...and go to next semicolon if any
|
|
Forward_Search
|
|
(Iter => Entity_End,
|
|
Str => ";",
|
|
Flags => 0,
|
|
Match_Start => Tmp_Start,
|
|
Match_End => Tmp_End,
|
|
Result => Result);
|
|
|
|
if Result then
|
|
Entity_End := Tmp_Start;
|
|
else
|
|
Forward_To_End (Entity_End);
|
|
end if;
|
|
end if;
|
|
|
|
-- Search the initial minimum area to re-highlight...
|
|
|
|
Entity_Kind := Normal_Text;
|
|
|
|
Entity_Kind_Search_Loop :
|
|
for Current_Entity in Standout_Language_Entity loop
|
|
if Has_Tag (Entity_Start, Self.Syntax_Tags (Current_Entity)) then
|
|
-- This means that we are in a highlighted region. The minimum
|
|
-- region to re-highlight starts from the begining of the
|
|
-- current region to the end of the following region.
|
|
|
|
Entity_Kind := Current_Entity;
|
|
|
|
Backward_To_Tag_Toggle
|
|
(Entity_Start,
|
|
Self.Syntax_Tags (Current_Entity),
|
|
Result => Ignored);
|
|
Forward_To_Tag_Toggle
|
|
(Entity_End, Self.Syntax_Tags (Entity_Kind), Ignored);
|
|
Forward_To_Tag_Toggle (Entity_End, Result => Ignored);
|
|
|
|
exit Entity_Kind_Search_Loop;
|
|
|
|
elsif Begins_Tag (Entity_End, Self.Syntax_Tags (Current_Entity))
|
|
or else Ends_Tag (Entity_Start, Self.Syntax_Tags (Current_Entity))
|
|
then
|
|
-- Case Begins_Tag:
|
|
-- This means that we inserted right at the begining of
|
|
-- a highlighted region... The minimum region to re-highlight
|
|
-- starts from the begining of the previous region to the
|
|
-- end of the current region.
|
|
-- Case Ends_Tag:
|
|
-- This means that we inserted right at the end of a
|
|
-- highlighted region. In this case, the minimum region to
|
|
-- re-highlight starts from the begining of the previous
|
|
-- region to the end of the current region.
|
|
-- In both cases, the processing is the same...
|
|
|
|
Entity_Kind := Current_Entity;
|
|
Backward_To_Tag_Toggle (Entity_Start, Result => Ignored);
|
|
Forward_To_Tag_Toggle (Entity_End, Result => Ignored);
|
|
|
|
exit Entity_Kind_Search_Loop;
|
|
end if;
|
|
end loop Entity_Kind_Search_Loop;
|
|
|
|
if Entity_Kind = Normal_Text then
|
|
-- We are inside a normal text region. Just re-highlight this
|
|
-- region.
|
|
|
|
Backward_To_Tag_Toggle (Entity_Start, Result => Ignored);
|
|
Forward_To_Tag_Toggle (Entity_End, Result => Ignored);
|
|
end if;
|
|
|
|
Local_Highlight;
|
|
|
|
if not Highlight_Complete then
|
|
-- In this case, we are in the middle of e.g a multi-line comment,
|
|
-- and we re-highlight the whole buffer since we do not know where
|
|
-- the comment started.
|
|
-- ??? would be nice to optimize here. We could for instance
|
|
-- highlight only from the beginning of the section instead of from
|
|
-- the start
|
|
|
|
Set_Offset (Entity_Start, 0);
|
|
Forward_To_End (Entity_End);
|
|
Local_Highlight;
|
|
end if;
|
|
end Highlight_Slice;
|
|
|
|
-----------------------------
|
|
-- Update_Highlight_Region --
|
|
-----------------------------
|
|
|
|
procedure Update_Highlight_Region
|
|
(Self : access Source_Highlighter_Record; Iter : Gtk_Text_Iter)
|
|
is
|
|
First_Mark_Iter : Gtk_Text_Iter;
|
|
Last_Mark_Iter : Gtk_Text_Iter;
|
|
|
|
begin
|
|
if not Self.Highlight_Needed then
|
|
Self.Highlight_Needed := True;
|
|
Move_Mark (Self.Buffer, Self.Last_Highlight_Mark, Iter);
|
|
|
|
else
|
|
Get_Iter_At_Mark
|
|
(Self.Buffer, First_Mark_Iter, Self.First_Highlight_Mark);
|
|
Get_Iter_At_Mark
|
|
(Self.Buffer, Last_Mark_Iter, Self.Last_Highlight_Mark);
|
|
|
|
if Get_Offset (First_Mark_Iter) > Get_Offset (Iter) then
|
|
Move_Mark (Self.Buffer, Self.First_Highlight_Mark, Iter);
|
|
end if;
|
|
|
|
if Get_Offset (Last_Mark_Iter) < Get_Offset (Iter) then
|
|
Move_Mark (Self.Buffer, Self.Last_Highlight_Mark, Iter);
|
|
end if;
|
|
end if;
|
|
|
|
if not Self.Buffer.Inserting and then Self.Auto_Highlight_Enabled then
|
|
Self.Highlight_Region;
|
|
end if;
|
|
end Update_Highlight_Region;
|
|
|
|
----------------------
|
|
-- Highlight_Region --
|
|
----------------------
|
|
|
|
procedure Highlight_Region (Self : access Source_Highlighter_Record) is
|
|
Start_Iter : Gtk_Text_Iter;
|
|
End_Iter : Gtk_Text_Iter;
|
|
begin
|
|
if Self.Call_Clear_Highlighting then
|
|
Self.Call_Clear_Highlighting := False;
|
|
|
|
Clear_Highlighting_Hook.Run
|
|
(Kernel => Self.Buffer.Kernel, File => Self.Buffer.Filename);
|
|
end if;
|
|
|
|
if not Self.Highlight_Needed then
|
|
return;
|
|
end if;
|
|
|
|
Get_Iter_At_Mark (Self.Buffer, Start_Iter, Self.First_Highlight_Mark);
|
|
Get_Iter_At_Mark (Self.Buffer, End_Iter, Self.Last_Highlight_Mark);
|
|
Self.Highlight_Slice (Start_Iter, End_Iter);
|
|
|
|
Self.Highlight_Needed := False;
|
|
end Highlight_Region;
|
|
|
|
------------------
|
|
-- On_Load_File --
|
|
------------------
|
|
|
|
procedure On_Load_File (Self : access Source_Highlighter_Record) is
|
|
F, L : Gtk_Text_Iter;
|
|
begin
|
|
-- Highlight the newly inserted text
|
|
if Self.Buffer.Lang = null
|
|
or else not Get_Language_Context (Self.Buffer.Lang).Syntax_Highlighting
|
|
then
|
|
return;
|
|
end if;
|
|
|
|
Get_Bounds (Self.Buffer, F, L);
|
|
Self.Highlight_Needed := True;
|
|
Move_Mark (Self.Buffer, Self.First_Highlight_Mark, F);
|
|
Move_Mark (Self.Buffer, Self.Last_Highlight_Mark, L);
|
|
Self.Highlight_Region;
|
|
end On_Load_File;
|
|
|
|
---------------------
|
|
-- On_Set_Language --
|
|
---------------------
|
|
|
|
procedure On_Set_Language
|
|
(Self : access Source_Highlighter_Record;
|
|
Start_Iter : Gtk_Text_Iter;
|
|
End_Iter : Gtk_Text_Iter) is
|
|
begin
|
|
Self.Update_Use_Highlighting_Hook;
|
|
|
|
-- Do not try to highlight an empty buffer
|
|
if not Is_End (Start_Iter) then
|
|
if Self.Buffer.Lang /= null
|
|
and then not Get_Language_Context (Self.Buffer.Lang)
|
|
.Syntax_Highlighting
|
|
then
|
|
Self.Kill_Highlighting (Start_Iter, End_Iter);
|
|
|
|
elsif Self.Use_Highlighting_Hook then
|
|
Highlight_Range_Hook.Run
|
|
(Kernel => Self.Buffer.Kernel,
|
|
File => Self.Buffer.Filename,
|
|
From_Line => Natural (Get_Line (Start_Iter) + 1),
|
|
To_Line => Natural (Get_Line (End_Iter) + 1));
|
|
else
|
|
Self.Highlight_Slice (Start_Iter, End_Iter);
|
|
end if;
|
|
end if;
|
|
end On_Set_Language;
|
|
|
|
------------------------------
|
|
-- Remove_Line_Highlighting --
|
|
------------------------------
|
|
|
|
procedure Remove_Line_Highlighting
|
|
(Self : access Source_Highlighter_Record;
|
|
Line : Editable_Line_Type;
|
|
Style : not null Style_Access)
|
|
is
|
|
The_Line : Buffer_Line_Type;
|
|
begin
|
|
if Self.Buffer.In_Destruction then
|
|
return;
|
|
end if;
|
|
|
|
if Line = 0 then
|
|
-- This procedure is called by Highlight_Range, but not when Line=0,
|
|
-- so there are no recursive calls here
|
|
Self.Highlight_Range (Style, 0, 1, 1, True);
|
|
|
|
for J in Self.Buffer.Line_Data'Range loop
|
|
Self.Set_Line_Highlighting (J, Style, False, (others => True));
|
|
end loop;
|
|
else
|
|
The_Line := Get_Buffer_Line (Self.Buffer, Line);
|
|
|
|
if The_Line /= 0 then
|
|
Self.Set_Line_Highlighting
|
|
(The_Line, Style, False, (others => True));
|
|
end if;
|
|
end if;
|
|
|
|
Line_Highlights_Changed (Self.Buffer);
|
|
end Remove_Line_Highlighting;
|
|
|
|
-------------------------
|
|
-- Remove_Highlighting --
|
|
-------------------------
|
|
|
|
procedure Remove_Highlighting
|
|
(Self : access Source_Highlighter_Record;
|
|
Style : Style_Access;
|
|
From_Line : Editable_Line_Type;
|
|
To_Line : Editable_Line_Type)
|
|
is
|
|
Start_Iter, End_Iter : Gtk_Text_Iter;
|
|
Ignore : Boolean;
|
|
One : constant Character_Offset_Type := 1;
|
|
Tag : Gtk_Text_Tag;
|
|
begin
|
|
Tag := Lookup (Get_Tag_Table (Self.Buffer), Get_Name (Style));
|
|
|
|
-- Remove tag-based highlighting
|
|
if Tag /= null then
|
|
Get_Iter_At_Screen_Position (Self.Buffer, Start_Iter, From_Line, One);
|
|
Get_Iter_At_Screen_Position (Self.Buffer, End_Iter, To_Line, One);
|
|
|
|
if not Ends_Line (End_Iter) then
|
|
Forward_To_Line_End (End_Iter, Ignore);
|
|
end if;
|
|
|
|
Remove_Tag (Self.Buffer, Tag, Start_Iter, End_Iter);
|
|
end if;
|
|
|
|
-- Remove line-based highlighting
|
|
if Get_In_Speedbar (Style) then
|
|
for Line in From_Line .. To_Line loop
|
|
Self.Remove_Line_Highlighting (Line, Style);
|
|
end loop;
|
|
end if;
|
|
end Remove_Highlighting;
|
|
|
|
-----------------------
|
|
-- Kill_Highlighting --
|
|
-----------------------
|
|
|
|
procedure Kill_Highlighting
|
|
(Self : access Source_Highlighter_Record;
|
|
From : Gtk_Text_Iter;
|
|
To : Gtk_Text_Iter)
|
|
is
|
|
Line : Buffer_Line_Type := Buffer_Line_Type (Get_Line (From) + 1);
|
|
The_End : constant Buffer_Line_Type :=
|
|
Buffer_Line_Type (Get_Line (To) + 1);
|
|
begin
|
|
while Line <= The_End and then Line in Self.Buffer.Line_Data'Range loop
|
|
-- Clearing 'aspect' information for lines
|
|
Self.Buffer.Line_Data (Line).Has_Aspect := False;
|
|
Line := Line + 1;
|
|
end loop;
|
|
|
|
for Entity_Kind in Standout_Language_Entity loop
|
|
Remove_Tag (Self.Buffer, Self.Syntax_Tags (Entity_Kind), From, To);
|
|
end loop;
|
|
end Kill_Highlighting;
|
|
|
|
-------------------------
|
|
-- Get_Highlight_Color --
|
|
-------------------------
|
|
|
|
function Get_Highlight_Color
|
|
(Self : access Source_Highlighter_Record;
|
|
Line : Buffer_Line_Type;
|
|
Context : Highlight_Location) return Gdk_RGBA is
|
|
begin
|
|
if Line = 0 then
|
|
return Null_RGBA;
|
|
end if;
|
|
|
|
if Self.Buffer.Line_Data /= null
|
|
and then Line <= Self.Buffer.Line_Data'Last
|
|
and then Self.Buffer.Line_Data (Line).Highlighting (Context).Active
|
|
/= 0
|
|
then
|
|
return
|
|
Get_Color
|
|
(Self.Buffer.Line_Data (Line).Highlighting (Context).Active);
|
|
|
|
else
|
|
return Null_RGBA;
|
|
end if;
|
|
end Get_Highlight_Color;
|
|
|
|
--------------------
|
|
-- Is_Comment_Tag --
|
|
--------------------
|
|
|
|
function Is_Comment_Tag
|
|
(Self : access Source_Highlighter_Record;
|
|
Pos : Gtk.Text_Iter.Gtk_Text_Iter) return Boolean is
|
|
begin
|
|
return
|
|
Has_Tag (Pos, Self.Syntax_Tags (Comment_Text))
|
|
or else Has_Tag (Pos, Self.Syntax_Tags (Aspect_Comment_Text))
|
|
or else Has_Tag (Pos, Self.Syntax_Tags (Annotated_Comment_Text))
|
|
or else Has_Tag (Pos, Self.Syntax_Tags (Annotated_Keyword_Text));
|
|
end Is_Comment_Tag;
|
|
|
|
---------------------------
|
|
-- Highlight_Parenthesis --
|
|
---------------------------
|
|
|
|
procedure Highlight_Parenthesis (Self : access Source_Highlighter_Record) is
|
|
On_Cursor_Iter : Gtk_Text_Iter;
|
|
First_Highlight_Iter : Gtk_Text_Iter;
|
|
Last_Highlight_Iter : Gtk_Text_Iter;
|
|
Current : Gtk_Text_Iter;
|
|
Found : Natural;
|
|
Success : Boolean;
|
|
begin
|
|
-- Highlight the cursor delimiters
|
|
if not Self.Highlight_Delimiters then
|
|
return;
|
|
end if;
|
|
|
|
Get_Iter_At_Mark (Self.Buffer, On_Cursor_Iter, Self.Buffer.Insert_Mark);
|
|
Get_Delimiters
|
|
(Self.Buffer,
|
|
On_Cursor_Iter,
|
|
First_Highlight_Iter,
|
|
Last_Highlight_Iter,
|
|
Found);
|
|
|
|
if Found >= 1 then
|
|
Copy (First_Highlight_Iter, Current);
|
|
Forward_Char (Current, Success);
|
|
if Success then
|
|
Apply_Tag
|
|
(Self.Buffer, Self.Delimiter_Tag, First_Highlight_Iter, Current);
|
|
end if;
|
|
|
|
Copy (Last_Highlight_Iter, Current);
|
|
Backward_Char (Current, Success);
|
|
if Success then
|
|
Apply_Tag
|
|
(Self.Buffer, Self.Delimiter_Tag, Current, Last_Highlight_Iter);
|
|
end if;
|
|
|
|
if Found = 2 then
|
|
Copy (On_Cursor_Iter, Current);
|
|
Backward_Char (Current, Success);
|
|
if Success then
|
|
Forward_Char (On_Cursor_Iter, Success);
|
|
if Success then
|
|
Apply_Tag
|
|
(Self.Buffer, Self.Delimiter_Tag, Current, On_Cursor_Iter);
|
|
end if;
|
|
end if;
|
|
end if;
|
|
|
|
Self.Start_Delimiters_Highlight :=
|
|
Create_Mark (Self.Buffer, "", First_Highlight_Iter);
|
|
Self.End_Delimiters_Highlight :=
|
|
Create_Mark (Self.Buffer, "", Last_Highlight_Iter);
|
|
|
|
Self.Has_Delimiters_Highlight := True;
|
|
end if;
|
|
end Highlight_Parenthesis;
|
|
|
|
------------------------------------
|
|
-- Remove_Delimiters_Highlighting --
|
|
------------------------------------
|
|
|
|
procedure Remove_Delimiters_Highlighting
|
|
(Self : access Source_Highlighter_Record) is
|
|
begin
|
|
if not Self.Has_Delimiters_Highlight then
|
|
return;
|
|
end if;
|
|
|
|
declare
|
|
From : Gtk_Text_Iter;
|
|
To : Gtk_Text_Iter;
|
|
begin
|
|
Get_Iter_At_Mark (Self.Buffer, From, Self.Start_Delimiters_Highlight);
|
|
Get_Iter_At_Mark (Self.Buffer, To, Self.End_Delimiters_Highlight);
|
|
|
|
Delete_Mark (Self.Buffer, Self.Start_Delimiters_Highlight);
|
|
Delete_Mark (Self.Buffer, Self.End_Delimiters_Highlight);
|
|
|
|
Remove_Tag (Self.Buffer, Self.Delimiter_Tag, From, To);
|
|
end;
|
|
|
|
Self.Has_Delimiters_Highlight := False;
|
|
end Remove_Delimiters_Highlighting;
|
|
|
|
----------------------
|
|
-- Emit_File_Edited --
|
|
----------------------
|
|
|
|
procedure Emit_File_Edited
|
|
(Buffer : not null access Source_Buffer_Record'Class;
|
|
Filename : Virtual_File) is
|
|
begin
|
|
File_Edited_Hook.Run (Buffer.Kernel, Filename);
|
|
for Listener of Buffer.Listeners loop
|
|
Listener.File_Edited (Filename);
|
|
end loop;
|
|
end Emit_File_Edited;
|
|
|
|
----------------------
|
|
-- Emit_File_Closed --
|
|
----------------------
|
|
|
|
procedure Emit_File_Closed
|
|
(Buffer : not null access Source_Buffer_Record'Class;
|
|
Filename : Virtual_File) is
|
|
begin
|
|
File_Closed_Hook.Run (Buffer.Kernel, Filename);
|
|
for Listener of Buffer.Listeners loop
|
|
Listener.File_Closed (Filename);
|
|
end loop;
|
|
end Emit_File_Closed;
|
|
|
|
-----------------------
|
|
-- Emit_File_Renamed --
|
|
-----------------------
|
|
|
|
procedure Emit_File_Renamed
|
|
(Buffer : not null access Source_Buffer_Record'Class;
|
|
From : Virtual_File;
|
|
To : Virtual_File) is
|
|
begin
|
|
File_Renamed_Hook.Run (Buffer.Kernel, From, To);
|
|
for Listener of Buffer.Listeners loop
|
|
Listener.File_Renamed (From, To);
|
|
end loop;
|
|
end Emit_File_Renamed;
|
|
|
|
------------------------------
|
|
-- Set_Opened_On_LSP_Server --
|
|
------------------------------
|
|
|
|
procedure Set_Opened_On_LSP_Server
|
|
(This : access Source_Buffer_Record; Value : Boolean) is
|
|
begin
|
|
This.LSP_Opened := Value;
|
|
end Set_Opened_On_LSP_Server;
|
|
|
|
-----------------------------
|
|
-- Is_Opened_On_LSP_Server --
|
|
-----------------------------
|
|
|
|
function Is_Opened_On_LSP_Server
|
|
(This : access Source_Buffer_Record) return Boolean is
|
|
begin
|
|
return This.LSP_Opened;
|
|
end Is_Opened_On_LSP_Server;
|
|
|
|
end Src_Editor_Buffer;
|