Files
VSS/testsuite/text/performance/src_editor_buffer.adb
Vadim Godunko 120eea9eac Revert "Merge branch 'topic/vadim/text' into 'master'"
This reverts merge request !394
2025-08-21 11:03:15 +00:00

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;