package Kephra::Edit::Search; our $VERSION = '0.31'; use strict; use warnings; # internal and menu functions about find and replace text # drag n drop target class # internal functions my ($find_item, $found_pos, $old_pos, $replace_item); my $flags; my $history_refresh; my @find_history; my @replace_history; sub _config { Kephra::API::settings()->{search} } sub _attributes{ _config()->{attribute} } sub _history { _config()->{history} } sub _find_pos { $found_pos } sub _refresh_search_flags { my $attr = _attributes(); $flags = 0; $flags |= &Wx::wxSTC_FIND_MATCHCASE if defined $attr->{match_case} and $attr->{match_case}; if ( defined $attr->{match_whole_word} and $attr->{match_whole_word} ) { $flags |= &Wx::wxSTC_FIND_WHOLEWORD } else { $flags |= &Wx::wxSTC_FIND_WORDSTART if $attr->{match_word_begin} and $attr->{match_word_begin}; } $flags |= &Wx::wxSTC_FIND_REGEXP if defined $attr->{match_regex} and $attr->{match_regex}; } sub load_search_data { my $file = Kephra::Config::filepath( _config()->{data_file} ); my $config_tree = Kephra::Config::File::load($file); set_find_item( $config_tree->{find}{item} || '' ); set_replace_item( $config_tree->{replace}{item} || '' ); if (defined $config_tree->{find}{history}){ if (ref $config_tree->{find}{history} eq 'ARRAY'){ @find_history = @{ $config_tree->{find}{history} }; } else { $find_history[0] = $config_tree->{find}{history} } } if (defined $config_tree->{replace}{history}){ if (ref $config_tree->{replace}{history} eq 'ARRAY'){ @replace_history = @{ $config_tree->{replace}{history} }; } else { $replace_history[0] = $config_tree->{replace}{history} } } my $history = _history(); # remove dups and cut to the configured length if ( $history->{use} ) { my ( %seen1, %seen2 ); my @uniq = grep { !$seen1{$_}++ } @find_history; @find_history = splice @uniq, 0, $history->{length}; @uniq = grep { !$seen2{$_}++ } @replace_history; @replace_history = splice @uniq, 0, $history->{length}; } else { @find_history = (); @replace_history = (); } # search item is findable $found_pos = 0; Kephra::EventTable::trigger ('find.item.history.changed', 'replace.item.history.changed'); Kephra::Edit::Marker::restore_bookmarks( $config_tree->{bookmark} ); } sub save_search_data { my $file = Kephra::Config::filepath( _config()->{data_file} ); my $config_tree = Kephra::Config::File::load($file); $config_tree->{find}{item} = get_find_item(); $config_tree->{find}{history} = get_find_history(); $config_tree->{replace}{item} = get_replace_item(); $config_tree->{replace}{history} = get_replace_history(); $config_tree->{bookmark} = Kephra::Edit::Marker::get_bookmark_data(); Kephra::Config::File::store($file, $config_tree); Kephra::Edit::Marker::store(); } # sub get_find_item { $find_item || ''} sub set_find_item { my $old = $find_item; my $new = shift; if (defined $new and (not defined $old or $new ne $old)){ $find_item = $new; $found_pos = -1; Kephra::EventTable::trigger('find.item.changed'); } } sub set_selection_as_find_item { set_find_item( Kephra::App::EditPanel::_ref()->GetSelectedText ) } sub item_findable { _exist_find_item() } sub _exist_find_item { (defined $find_item and $find_item) ? 1 : 0 } sub _exist_replace_item { (defined $replace_item and $replace_item) ? 1 : 0 } sub get_replace_item { $replace_item || ''} sub set_replace_item { my $old = $replace_item; my $new = shift; if (defined $new and (not defined $old or $new ne $old)){ $replace_item = $new; Kephra::EventTable::trigger('replace.item.changed'); } } sub set_selection_as_replace_item { set_replace_item( Kephra::App::EditPanel::_ref()->GetSelectedText ) } sub get_find_history { \@find_history } sub get_replace_history { \@replace_history } sub refresh_find_history { my $found_match = shift; my $find_item = get_find_item(); my $history = _history(); return unless $history->{use}; # check if refresh needed return if $history->{remember_only_matched} and not $found_match; if ($find_item and $find_history[0] ne $find_item) { for ( 0 .. $#find_history ) { # delete the dup if ( $find_history[$_] eq $find_item ) { @find_history = @find_history[ 0 .. $_-1, $_+1 .. $#find_history ]; last; } pop @find_history if $_ == $history->{length} - 1; } unshift @find_history, $find_item; # insert new history item Kephra::EventTable::trigger('find.item.history.changed'); } } sub refresh_replace_history { my $replace_item = get_replace_item(); my $history = _history(); if ($replace_item) { for ( 0 .. $#replace_history ) { if ( $replace_history[$_] eq $replace_item ) { @replace_history = @replace_history[ 0 .. $_-1, $_+1 .. $#replace_history ]; last; } pop @replace_history if $_ == $history->{length} - 1; } unshift @replace_history, $replace_item; Kephra::EventTable::trigger('replace.item.history.changed'); } } sub _caret_2_sel_end { my $ep = Kephra::App::EditPanel::_ref(); my $pos = $ep->GetCurrentPos; my $sel_start = $ep->GetSelectionStart; my $sel_end = $ep->GetSelectionEnd; if ( $pos != $sel_end ) { $ep->SetCurrentPos($sel_end); $ep->SetSelectionStart($sel_start); } } # sub set_range{ _attributes()->{in} = shift } sub get_range{ _attributes()->{in} } sub get_attribute{ my $attr = shift; if ($attr eq 'match_case' or $attr eq 'match_word_begin'or $attr eq 'match_whole_word'or $attr eq 'match_regex' or $attr eq 'auto_wrap' or $attr eq 'incremental' ) { _attributes()->{$attr} } } sub switch_attribute{ my $attr = shift; if ($attr eq 'match_case' or $attr eq 'match_word_begin'or $attr eq 'match_whole_word'or $attr eq 'match_regex' or $attr eq 'auto_wrap' or $attr eq 'incremental' ) { unless (defined _attributes->{$attr}) { _attributes->{$attr} = 1 } else { _attributes->{$attr} ^= 1 } _refresh_search_flags() if substr($attr, 0, 1) eq 'm'; } } # find helper function sub replace_selection { Kephra::App::EditPanel::_ref()->ReplaceSelection( get_replace_item() ) } sub _find_next { my $ep = Kephra::App::EditPanel::_ref(); $ep->SearchAnchor; $found_pos = $ep->SearchNext( $flags, get_find_item() ); Kephra::EventTable::trigger('find'); return $found_pos; } sub _find_prev { my $ep = Kephra::App::EditPanel::_ref(); $ep->SearchAnchor; $found_pos = $ep->SearchPrev( $flags, get_find_item() ); Kephra::EventTable::trigger('find'); return $found_pos; } sub _find_first { Kephra::Edit::Goto::_pos(0); _find_next(); } sub _find_last { Kephra::Edit::Goto::_pos(-1); _find_prev(); } sub first_increment { my $ep = Kephra::App::EditPanel::_ref(); if ( _exist_find_item() ) { Kephra::Edit::_save_positions; if ( _find_first() > -1 ) { #_caret_2_sel_end(); Kephra::Edit::_center_caret; return 1; } } Kephra::Edit::Goto::_pos( $old_pos ) if defined $old_pos; return 0; } #sub next_increment {} # find related menu calls sub find_all { #Kephra::Dialog::msg_box(&Wx::wxUNICODE(), ''); my $ep = Kephra::App::EditPanel::_ref(); if ( _exist_find_item() ) { my $search_result = _find_first(); my ($sel_start, $sel_end); #Kephra::Dialog::msg_box( , ''); #$ep->IndicatorSetStyle(0, &Wx::wxSTC_INDIC_TT ); #$ep->IndicatorSetForeground(0, &Wx::Colour->new(0xff, 0x00, 0x00)); $ep->IndicatorSetStyle(1, &Wx::wxSTC_INDIC_TT ); $ep->IndicatorSetForeground(1, &Wx::Colour->new(0xff, 0x00, 0x00)); # ^= &Wx::wxSTC_INDIC_STRIKE; $ep->SetSelection(0,0); return 0 if $search_result == -1; while ($search_result > -1){ ($sel_start, $sel_end) = $ep->GetSelection; Kephra::Edit::Goto::_pos( $sel_end ); $ep->StartStyling($sel_start, 224);#224 $ep->SetStyleBytes($sel_end - $sel_start, 128); $search_result = _find_next(); } Kephra::Edit::Goto::_pos( $sel_end ); $ep->Colourise( 0, $sel_end); return 1; } else { Kephra::Edit::Goto::_pos( $old_pos ) if defined $old_pos; return 1; } } sub find_prev { my $ep = Kephra::App::EditPanel::_ref(); my $attr = _attributes(); my $return = -1; if ( _exist_find_item() ) { Kephra::Edit::_save_positions; Kephra::Edit::Goto::_pos( $ep->GetSelectionStart - 1 ); $return = _find_prev(); if ( $return == -1 ) { if ( get_range() eq 'document' ) { $return = _find_last() if $attr->{auto_wrap}; } elsif ( get_range() eq 'open_docs' ) { $Kephra::temp{dialog}{control} = 1; my $begin_doc = Kephra::Document::Data::current_nr(); while ( $return == -1 ) { Kephra::Edit::_restore_positions; last if ( ( Kephra::Document::Data::current_nr() == 0 ) and !$attr->{auto_wrap} ); Kephra::Document::Change::tab_left(); Kephra::Edit::_save_positions(); $return = _find_last(); last if ( Kephra::Document::Data::current_nr() == $begin_doc ); } $Kephra::temp{dialog}{control} = 0; } } if ( $return == -1 ) { Kephra::Edit::_restore_positions() } else { _caret_2_sel_end(); Kephra::Edit::_center_caret() } refresh_find_history($return); } $return; } sub find_next { my $ep = Kephra::App::EditPanel::_ref(); my $attr = _attributes(); my $return = -1; if ( _exist_find_item() ) { Kephra::Edit::_save_positions(); Kephra::Edit::Goto::_pos( $ep->GetSelectionEnd ); $return = _find_next(); if ( $return == -1 ) { if ( get_range() eq 'document' ) { $return = _find_first() if $attr->{auto_wrap}; } elsif ( get_range() eq 'open_docs' ) { $Kephra::temp{dialog}{control} = 1; my $begin_doc = Kephra::Document::Data::current_nr(); while ( $return == -1 ) { Kephra::Edit::_restore_positions(); last if Kephra::Document::Data::current_nr() == Kephra::Document::Data::last_nr() and not $attr->{auto_wrap}; Kephra::Document::Change::tab_right(); Kephra::Edit::_save_positions(); $return = _find_first(); last if ( Kephra::Document::Data::current_nr() == $begin_doc ); } $Kephra::temp{dialog}{control} = 0; } } if ( $return == -1 ) { Kephra::Edit::_restore_positions() } else { _caret_2_sel_end(); Kephra::Edit::_center_caret(); } refresh_find_history($return); } $return; } sub fast_back { my $ep = Kephra::App::EditPanel::_ref(); my $attr = _attributes(); my $return = -1; if (_exist_find_item()) { for ( 1 .. $attr->{fast_steps} ) { Kephra::Edit::_save_positions(); Kephra::Edit::Goto::_pos( $ep->GetSelectionStart - 1 ); $return = _find_prev(); if ( $return == -1 ) { if ( get_range() eq 'document' ) { $return = _find_last() if $attr->{auto_wrap}; } elsif ( get_range() eq 'open_docs' ) { $Kephra::temp{dialog}{control} = 1; my $begin_doc = Kephra::Document::Data::current_nr(); while ( $return == -1 ) { Kephra::Edit::_restore_positions(); last if Kephra::Document::Data::current_nr() == 0 and not $attr->{auto_wrap}; Kephra::Document::Change::tab_left(); Kephra::Edit::_save_positions(); $return = _find_last(); last if Kephra::Document::Data::current_nr() == $begin_doc; } $Kephra::temp{dialog}{control} = 0; } } refresh_find_history($return) if ( $_ == 1 ); if ( $return == -1 ) { Kephra::Edit::_restore_positions(); last; } else { _caret_2_sel_end(); Kephra::Edit::_center_caret(); } } } } sub fast_fore { my $ep = Kephra::App::EditPanel::_ref(); my $attr = _attributes(); my $return = -1; if (_exist_find_item()) { for ( 1 .. $attr->{fast_steps} ) { Kephra::Edit::_save_positions(); Kephra::Edit::Goto::_pos( $ep->GetSelectionEnd ); $return = _find_next(); if ( $return == -1 ) { if ( get_range() eq 'document' ) { $return = _find_first() if $attr->{auto_wrap}; } elsif ( get_range() eq 'open_docs' ) { $Kephra::temp{dialog}{control} = 1; my $begin_doc = Kephra::Document::Data::current_nr(); while ( $return == -1 ) { Kephra::Edit::_restore_positions(); last if Kephra::Document::Data::current_nr() == Kephra::Document::Data::last_nr() and not $attr->{auto_wrap}; Kephra::Document::Change::tab_right(); Kephra::Edit::_save_positions(); $return = _find_first(); last if Kephra::Document::Data::current_nr() == $begin_doc; } $Kephra::temp{dialog}{control} = 0; } } refresh_find_history($return) if $_ == 1; if ( $return == -1 ) { Kephra::Edit::_restore_positions(); last; } else { _caret_2_sel_end(); Kephra::Edit::_center_caret(); } } } } sub find_first { my $menu_call = shift; my $ep = Kephra::App::EditPanel::_ref(); my $attr = _attributes(); my ( $sel_begin, $sel_end ) = $ep->GetSelection; my $pos = $ep->GetCurrentPos; my $len = _exist_find_item(); my $return; if ( _exist_find_item() ) { Kephra::Edit::_save_positions(); if ($menu_call and $sel_begin != $sel_end and $sel_end - $sel_begin > $len ) { set_range('selection') } if ( get_range() eq 'selection' ) { Kephra::Edit::Goto::_pos($sel_begin); $return = _find_next(); if ($return > -1 and $ep->GetCurrentPos + $len <= $sel_end) { Kephra::Edit::_center_caret(); } else { Kephra::Edit::_restore_positions(); $return = -1; } } else { $return = _find_first(); if ( get_range() eq 'open_docs' and ($sel_begin == $ep->GetSelectionStart or $return == -1 ) ){ $Kephra::temp{dialog}{control} = 1; $return = -1; my $begin_doc = Kephra::Document::Data::current_nr(); while ( $return == -1 ) { Kephra::Edit::_restore_positions(); last if Kephra::Document::Data::current_nr() == 0 and not $attr->{auto_wrap}; Kephra::Document::Change::tab_left(); Kephra::Edit::_save_positions(); $return = _find_first(); last if ( Kephra::Document::Data::current_nr() == $begin_doc ); } $Kephra::temp{dialog}{control} = 0; } if ( $return > -1 ) { _caret_2_sel_end(); Kephra::Edit::_center_caret(); } else { Kephra::Edit::_restore_positions(); } } refresh_find_history($return); } $return; } sub find_last { my $menu_call = shift; my $ep = Kephra::App::EditPanel::_ref(); my $attr = _attributes(); my ( $sel_begin, $sel_end ) = $ep->GetSelection; my $pos = $ep->GetCurrentPos; my $len = _exist_find_item(); my $return; if (_exist_find_item()) { Kephra::Edit::_save_positions(); if ($menu_call and $sel_begin != $sel_end and $sel_end - $sel_begin > $len) { set_range('selection'); } if ( get_range() eq 'selection' ) { Kephra::Edit::Goto::_pos($sel_end); $return = _find_prev(); if ($return > -1 and $ep->GetCurrentPos >= $sel_begin) { Kephra::Edit::_center_caret(); } else { Kephra::Edit::_restore_positions(); $return = -1; } } else { $return = _find_last(); if (get_range() eq 'open_docs' and ($sel_begin == $ep->GetSelectionStart or $return == -1) ){ $Kephra::temp{dialog}{control} = 1; $return = -1; my $begin_doc = Kephra::Document::Data::current_nr(); while ( $return == -1 ) { Kephra::Edit::_restore_positions(); last if Kephra::Document::Data::current_nr() == Kephra::Document::Data::last_nr() and not $attr->{auto_wrap}; Kephra::Document::Change::tab_right(); Kephra::Edit::_save_positions(); $return = _find_last(); last if ( Kephra::Document::Data::current_nr() == $begin_doc ); } $Kephra::temp{dialog}{control} = 0; } if ( $return > -1 ) { _caret_2_sel_end(); Kephra::Edit::_center_caret(); } else { Kephra::Edit::_restore_positions(); } } refresh_find_history($return); } $return; } # replace sub replace_back { my $ep = Kephra::App::EditPanel::_ref(); if ( $ep->GetSelectionStart != $ep->GetSelectionEnd ) { replace_selection(); refresh_replace_history(); find_prev(); } } sub replace_fore { my $ep = Kephra::App::EditPanel::_ref(); if ( $ep->GetSelectionStart != $ep->GetSelectionEnd ) { replace_selection(); refresh_replace_history(); find_next(); } } sub replace_all { my $menu_call = shift; my $ep = Kephra::App::EditPanel::_ref(); my ($sel_begin, $sel_end ) = $ep->GetSelection; my $line = $ep->GetCurrentLine; my $len = _exist_find_item(); my $replace_string = get_replace_item(); #if ($len) { # forbid replace with nothing if ( $menu_call and $sel_begin != $sel_end and $sel_end - $sel_begin > $len ) { _attributes()->{in} = 'selection'; } if ( get_range() eq 'selection' ) { $ep->BeginUndoAction; Kephra::Edit::Goto::_pos($sel_begin); while ( _find_next() > -1 ) { last if ( $ep->GetCurrentPos + $len >= $sel_end ); $ep->ReplaceSelection($replace_string); } $ep->EndUndoAction; } elsif ( get_range() eq 'document' ) { $ep->BeginUndoAction; Kephra::Edit::Goto::_pos(0); while ( _find_next() > -1 ) { $ep->ReplaceSelection($replace_string); } $ep->EndUndoAction; } elsif ( get_range() eq 'open_docs' ) { my $begin_doc = Kephra::Document::Data::current_nr(); do { { Kephra::Edit::_save_positions(); $ep->BeginUndoAction; Kephra::Edit::Goto::_pos(0); while ( _find_next() > -1 ) { $ep->ReplaceSelection($replace_string); } $ep->EndUndoAction; Kephra::Edit::_restore_positions(); } } until ( Kephra::Document::Change::tab_right() == $begin_doc ); } $ep->GotoLine($line); refresh_replace_history; Kephra::Edit::_keep_focus(); #} # end of don't replace nothing } sub replace_confirm { if (_exist_find_item()) { my $ep = Kephra::App::EditPanel::_ref(); my $attr = _attributes(); my $line = $ep->LineFromPosition( $ep->GetCurrentPos ); my $len = _exist_find_item(); my $sel_begin = $ep->GetSelectionStart; my $sel_end = $ep->GetSelectionEnd; my $answer = &Wx::wxYES; my $menu_call = shift; set_range('selection') if $menu_call and $sel_begin != $sel_end and $sel_end - $sel_begin > $len; if (get_range() eq 'selection') { sniff_selection( $ep, $sel_begin, $sel_end, $len, $line ); } elsif (get_range() eq 'document') { sniff_selection( $ep, 0, $ep->GetTextLength, $len, $line ); } elsif (get_range() eq 'open_docs') { my $begin_doc = Kephra::Document::Data::current_nr(); do { { next if $answer == &Wx::wxCANCEL; Kephra::Edit::_save_positions(); $answer = sniff_selection ( $ep, 0, $ep->GetTextLength, $len, $line ); Kephra::Edit::_restore_positions(); } } until ( Kephra::Document::Change::tab_right() == $begin_doc ); } } sub sniff_selection { my ( $ep, $sel_begin, $sel_end, $len, $line ) = @_; my $l10n = Kephra::Config::Localisation::strings()->{dialog}{search}{confirm}; my $answer; Kephra::Edit::Goto::_pos($sel_begin); $ep->BeginUndoAction(); while ( _find_next() > -1 ) { last if $ep->GetCurrentPos + $len >= $sel_end; Kephra::Edit::_center_caret(); $answer = Kephra::Dialog::get_confirm_3 ($l10n->{text}, $l10n->{title}, 100, 100); last if $answer == &Wx::wxCANCEL; if ($answer == &Wx::wxYES) {replace_selection()} else {$ep->SetCurrentPos( $ep->GetCurrentPos + 1 )} } $ep->EndUndoAction; Kephra::Edit::Goto::_pos( $ep->PositionFromLine($line) ); Kephra::Edit::_center_caret(); $answer; } refresh_replace_history(); Kephra::Edit::_keep_focus(); } 1; =head1 NAME Kephra::Edit::Search - find and replace functions =head1 DESCRIPTION =cut