note description: "Object that is able to auto complete code." legal: "See notice at end of class." status: "See notice at end of class." author: "$Author$" date: "$Date$" revision: "$Revision$" deferred class CODE_COMPLETABLE inherit ANY EV_ANY_HANDLER EV_SHARED_APPLICATION export {NONE} all end feature {NONE} -- Initialization initialize_code_complete -- Initialization do key_press_string_actions.extend (agent on_char) key_press_actions.extend (agent on_key_pressed) create precompletion_actions key_completable := agent can_complete create completion_timeout.make_with_interval (default_timer_interval) completion_timeout.actions.extend (agent complete_code) completion_timeout.actions.block ensure completion_timeout_not_void: completion_timeout /= Void can_complete_by_key_not_void: key_completable /= Void end feature -- Access choices: CODE_COMPLETION_WINDOW -- Completion choice window. once create Result.make end parent_window: detachable EV_WINDOW note option: stable attribute end -- Parent window, used to evaluate where the compilation list should be shown. possibilities_provider: detachable COMPLETION_POSSIBILITIES_PROVIDER -- Possibilities provider. key_completable : detachable FUNCTION [EV_KEY, BOOLEAN, BOOLEAN, BOOLEAN, BOOLEAN] -- EV_KEY can activate text completion? key_press_string_actions: EV_KEY_STRING_ACTION_SEQUENCE -- Actions to be performed when a char is entered. deferred ensure key_press_string_actions_not_void: Result /= Void end key_press_actions: EV_KEY_ACTION_SEQUENCE -- Actions to be performed when a keyboard key is pressed. deferred ensure key_press_actions_not_void: Result /= Void end save_list_position_action: detachable PROCEDURE [INTEGER, INTEGER, INTEGER, INTEGER] -- Action to save completion list position. -- [x_position, y_position, width, height] is_char_activator_character (a_char: CHARACTER_32): BOOLEAN do Result := completion_activator_characters.has (a_char) end feature -- Element change set_parent_window (a_window: attached like parent_window) -- Set parent window (used for screen location adjustments) to `a_window'. require a_window_attached: a_window /= Void do parent_window := a_window ensure parent_window_set: parent_window = a_window end feature -- Status change set_can_complete (a_fun: like key_completable) -- Set `key_completable' with `a_fun'. require a_fun_attached: a_fun /= Void do key_completable := a_fun ensure key_completable_not_void: key_completable /= Void end set_completion_possibilities_provider (a_provider: like possibilities_provider) -- Set `completion_possibilities_provider'. do possibilities_provider := a_provider end set_save_list_position_action (a_action: like save_list_position_action) -- Set `save_list_position_action' with `a_action'. do save_list_position_action := a_action ensure save_list_position_action_set: save_list_position_action = a_action end refresh -- Refresh current display. do end feature -- Status report is_completing: BOOLEAN -- Is completion currently being processed? is_focus_back_needed: BOOLEAN -- Should focus be set back after code completion? do Result := True end feature {NONE} -- Status report auto_complete_is_possible: BOOLEAN -- Is auto complete possible. do if attached possibilities_provider as l_provider then Result := l_provider.completion_possible end end completing_word: BOOLEAN -- Is in completing word mode? deferred end feature -- Text operation back_delete_char -- Back delete character. deferred end delete_char -- Delete char. deferred end insert_string (a_str: STRING_32) -- Insert `a_str' at cursor position. require a_str_attached: a_str /= Void deferred end insert_char (a_char: CHARACTER_32) -- Insert `a_char' at cursor position. deferred end feature -- Cursor place_post_cursor -- Place cursor after completion. deferred end feature {CODE_COMPLETION_WINDOW} -- Autocompletion from window complete_from_window (cmp: STRING_32; appended_character: CHARACTER_32; remainder: INTEGER) -- Insert `cmp' in the editor and switch to completion mode. -- `appended_character' is a character that should be appended after. '%U' if none. local completed: STRING_32 do completed := cmp if completed.is_empty then if appended_character /= '%U' then insert_char (appended_character) end else complete_call (completed, appended_character, remainder) place_post_cursor end refresh end complete_call (completed: STRING_32; appended_character: CHARACTER_32; remainder: INTEGER) -- Finish completion process by inserting the completed expression. local i: INTEGER do if attached possibilities_provider as l_provider and then attached l_provider.insertion as l_insertion and then not l_insertion.is_empty then if completed.as_string_32.item (1) = ' ' then back_delete_char end end if remainder > 0 then from i := 0 until i = remainder loop delete_char i := i + 1 end end insert_string (completed) if appended_character /= '%U' then insert_char (appended_character) end end feature -- Basic operation trigger_completion -- Start timer, let timer to start code completion. do if attached completion_timeout as t then t.reset_count t.actions.resume end end block_completion -- Stop timer, block code completion. do if attached completion_timeout as t then t.reset_count t.actions.block end end complete_code -- Prepare auto complete and show choice window directly. local retried: BOOLEAN do if not retried then if attached possibilities_provider as l_provider then if attached precompletion_actions as l_actions and then not l_actions.is_empty then l_actions.call (Void) end prepare_auto_complete if l_provider.completion_possible then block_focus_out_actions show_completion_list else block_completion end end block_completion end rescue retried := True if attached possibilities_provider as l_provider then l_provider.reset end resume_focus_out_actions block_completion retry end position_completion_choice_window -- Reposition the completion choice window require choices_not_void: choices /= Void local l_x, l_y: INTEGER l_width, l_height: INTEGER l_helpers: EVS_HELPERS l_coords: TUPLE [x, y: INTEGER] do l_width := calculate_completion_list_width l_height := calculate_completion_list_height l_x := calculate_completion_list_x_position l_y := calculate_completion_list_y_position if attached parent_window as p and then not p.is_destroyed and then p.is_show_requested then -- Reposition based on screen coords create l_helpers l_coords := l_helpers.suggest_pop_up_widget_location_with_size (p, l_x, l_y, l_width, l_height) if l_y >= l_coords.y then -- Adjust height to prevent the completion list from shift up and over the text. -- This is ok to do because `calculate_completion_list_y_position' determines if the -- list should be shown above or below the editor caret. If it's displayed below then -- we adjust the size of the list to remain on-screen. l_height := l_height - (l_y - l_coords.y) end end choices.set_size (l_width, l_height) choices.set_position (l_x, l_y) if shared_environment.is_gtk3_implementation then -- FIXME: hack to get the completion window well positioned [2021-12-06] -- with GTK3, for an unknown reason, often the window manager changes the position of the completion window -- to something different from the given l_x and l_y positions. ev_application.add_idle_action_kamikaze (agent choices.set_position (l_x, l_y)) end end exit_complete_mode -- Set mode to normal (not completion mode). do is_completing := False -- Invalidating cursor forces cursor to be updated. set_focus ensure not_is_completing: is_completing = false end set_focus -- Set focus. deferred end feature {CODE_COMPLETION_WINDOW} -- Basic operation block_focus_in_actions -- Block focus in actions deferred end resume_focus_in_actions -- Resume focus in actions deferred end block_focus_out_actions -- Block focus out actions. deferred end resume_focus_out_actions -- Resume focus out actions. deferred end feature {CODE_COMPLETION_WINDOW} -- Interact with code complete window. Completion_border_size: INTEGER = 5 -- Size in pixels that the completion list can go to (virtual border) unwanted_characters: SPECIAL [BOOLEAN] -- Unwanted characters: backspace, tabulation, carriage return and escape. local c: CHARACTER i: INTEGER once create Result.make_filled (False, 256) from i := 0 until i = 256 loop c := i.to_character_8 Result.put (c.is_control, i) i := i + 1 end end calculate_completion_list_width: INTEGER -- Determine the width the completion list should have local l_grid: EV_GRID l_screen: EV_SCREEN l_height: INTEGER l_count_to_calculate: INTEGER i: INTEGER do l_grid := choices.choice_list -- Calculate correct size to fit if not l_grid.is_destroyed and then l_grid.column_count >= 1 and then l_grid.row_count > 0 then Result := l_grid.column (1).required_width_of_item_span (1, l_grid.row_count) if Result = 0 then l_height := calculate_completion_list_height l_count_to_calculate := l_height // l_grid.row_height + 1 l_count_to_calculate := l_count_to_calculate.min (l_grid.row_count) from i := 1 until i > l_count_to_calculate loop if attached {NAME_FOR_COMPLETION} l_grid.row (i).data as l_name then Result := Result.max (l_name.grid_item.required_width) else check l_has_valid_name: False end end i := i + 1 end -- Make sure border and any potential vertical scrollbar is taken in to account. Result := Result + (completion_border_size * 2) + l_grid.vertical_scroll_bar.width end else Result := default_window_width end create l_screen Result := l_screen.width.min (Result) end handle_character (a_char: CHARACTER_32) -- Handle `a_char' deferred end handle_extended_ctrled_key (ev_key: EV_KEY) -- Process the push on Ctrl + an extended key. deferred end handle_extended_key (ev_key: EV_KEY) -- Process the push on an extended key. deferred end calculate_completion_list_x_position: INTEGER -- Determine the x position to display the completion list deferred end calculate_completion_list_y_position: INTEGER -- Determine the y position to display the completion list deferred end calculate_completion_list_height: INTEGER -- Determine the height the completion should list should have deferred end feature {NONE} -- Trigger completion on_key_pressed (a_key: EV_KEY) -- If `a_key' can activate text completion, activate it. require a_key_attached: a_key /= Void do if not is_completing then if attached key_completable as l_key_completable and then l_key_completable.item ([a_key, ctrled_key, alt_key, shifted_key]) then complete_code end end end on_char (character_string: STRING_32) -- If `a_key' can activate text completion, activate it. require a_key_attached: character_string /= Void do if not is_completing and then character_string.count = 1 then if is_char_activator_character (character_string.item (1)) then trigger_completion debug ("Auto_completion") print ("Completion triggered.%N") end else block_completion debug ("Auto_completion") print ("Completion blocked.%N") end end end end shifted_key: BOOLEAN -- Is any of the shift key pushed? do Result := ev_application.shift_pressed end ctrled_key: BOOLEAN -- Is any of the ctrl key pushed? do Result := ev_application.ctrl_pressed end alt_key: BOOLEAN -- Is any of the alt key pushed? do Result := ev_application.alt_pressed end can_complete (a_key: EV_KEY; a_ctrl: BOOLEAN; a_alt: BOOLEAN; a_shift: BOOLEAN): BOOLEAN -- `a_key' can activate text completion? require a_key_attached: a_key /= Void do end feature {NONE} -- Implementation show_completion_list -- Show completion window. local l_possibilities: detachable SORTABLE_ARRAY [NAME_FOR_COMPLETION] do if attached possibilities_provider as l_provider then l_possibilities := l_provider.completion_possibilities else create l_possibilities.make_empty end choices.common_initialization ( Current, name_part_to_be_completed, name_part_to_be_completed_remainder, l_possibilities, completing_word ) if choices.is_displayed then choices.hide end block_focus_out_actions if choices.show_needed then position_completion_choice_window is_completing := True choices.show end end prepare_auto_complete -- Prepare possibilities in provider. do if attached possibilities_provider as l_provider then l_provider.prepare_completion end end precompletion_actions: detachable EV_NOTIFY_ACTION_SEQUENCE -- Actions called before trying to complete completion_activator_characters: ARRAYED_LIST [CHARACTER_32] -- List of completion activating keys once create Result.make (1) Result.extend ('.') end feature {NONE} -- Complete essentials name_part_to_be_completed: detachable STRING_32 -- Word, which is being completed. do if attached possibilities_provider as l_provider then Result := l_provider.insertion end end name_part_to_be_completed_remainder: INTEGER -- Number of characters past the cursor on the token currenly being completed. do if attached possibilities_provider as l_provider then Result := l_provider.insertion_remainder end end feature -- Timer set_delay (l_time: INTEGER) -- Set timer interval. do if completion_timeout /= Void then completion_timeout.set_interval (l_time) end end feature {NONE} -- Timer completion_timeout: detachable EV_TIMEOUT note option: stable attribute end -- Timeout for showing completion list default_timer_interval: INTEGER -- Default completion window appearance time out interval once Result := 10 end default_window_width: INTEGER -- Default completion window width once Result := 300 end default_window_height: INTEGER -- Default completion window height once Result := 400 end note copyright: "Copyright (c) 1984-2021, Eiffel Software and others" license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)" licensing_options: "http://www.eiffel.com/licensing" copying: "[ This file is part of Eiffel Software's Eiffel Development Environment. Eiffel Software's Eiffel Development Environment is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2 of the License (available at the URL listed under "license" above). Eiffel Software's Eiffel Development Environment is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Eiffel Software's Eiffel Development Environment; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA ]" source: "[ Eiffel Software 5949 Hollister Ave., Goleta, CA 93117 USA Telephone 805-685-1006, Fax 805-685-6869 Website http://www.eiffel.com Customer support http://support.eiffel.com ]" end