root/trunk/Padre/lib/Padre/Wx/Editor.pm @ 11120

Revision 11120, 42.1 KB (checked in by azawawi, 6 months ago)

Perl tidy (25 files)

Line 
1package Padre::Wx::Editor;
2
3use 5.008;
4use strict;
5use warnings;
6use YAML::Tiny                ();
7use Padre::Constant           ();
8use Padre::Util               ();
9use Padre::Current            ();
10use Padre::Wx                 ();
11use Padre::Wx::FileDropTarget ();
12use Padre::Logger;
13
14our $VERSION = '0.58';
15our @ISA     = 'Wx::StyledTextCtrl';
16
17# End-Of-Line modes:
18# MAC is actually Mac classic.
19# MAC OS X and later uses UNIX EOLs
20#
21# Please note that WIN32 is the API. DO NOT change it to that :)
22#
23our %mode = (
24        WIN  => Wx::wxSTC_EOL_CRLF,
25        MAC  => Wx::wxSTC_EOL_CR,
26        UNIX => Wx::wxSTC_EOL_LF,
27);
28
29# mapping for mime-type to the style name in the share/styles/default.yml file
30our %MIME_STYLE = (
31        'application/x-perl' => 'perl',
32        'application/x-psgi' => 'perl',
33        'text/x-perlxs'      => 'xs',   # should be in the plugin...
34        'text/x-patch'       => 'diff',
35        'text/x-makefile'    => 'make',
36        'text/x-yaml'        => 'yaml',
37        'text/css'           => 'css',
38        'application/x-php'  => 'perl', # temporary solution
39);
40
41# Karl
42# these are the allowed braces for brace highlighting and brace matching
43# this has to be subset of  ( ) [ ] { } < > since we use the scintilla
44# Brace* methods
45# always altern opening and starting braces in the constant
46my $BRACES               = '{}[]()';
47my $STC_INVALID_POSITION = Wx::wxSTC_INVALID_POSITION;
48
49my $data;
50my $data_name;
51my $data_private;
52my $width;
53my $Clipboard_Old = '';
54
55sub new {
56        my $class  = shift;
57        my $parent = shift;
58
59        # NOTE: This hack is only here because the Preferences dialog uses
60        # an editor object for their style preview thingy.
61        my $main = $parent;
62        while ( not $main->isa('Padre::Wx::Main') ) {
63                $main = $main->GetParent;
64        }
65
66        # Create the underlying Wx object
67        my $lock = $main->lock( 'UPDATE', 'refresh_windowlist' );
68        my $self = $class->SUPER::new($parent);
69
70        # TO DO: Make this suck less
71        my $config = $main->config;
72        $data = data( $config->editor_style );
73
74        # Set the code margins a little larger than the default.
75        # This seems to noticably reduce eye strain.
76        $self->SetMarginLeft(2);
77        $self->SetMarginRight(0);
78
79        # Clear out all the other margins
80        $self->SetMarginWidth( 0, 0 );
81        $self->SetMarginWidth( 1, 0 );
82        $self->SetMarginWidth( 2, 0 );
83
84        # Set word chars to match Perl variables
85        $self->SetWordChars( join '', ( '$@%&_:[]{}', 0 .. 9, 'A' .. 'Z', 'a' .. 'z' ) );
86
87        Wx::Event::EVT_RIGHT_DOWN( $self, \&on_right_down );
88        Wx::Event::EVT_LEFT_UP( $self, \&on_left_up );
89        Wx::Event::EVT_CHAR( $self, \&on_char );
90        Wx::Event::EVT_SET_FOCUS( $self, \&on_focus );
91        Wx::Event::EVT_MIDDLE_UP( $self, \&on_middle_up );
92
93        # Smart highlighting:
94        # Selecting a word or small block of text causes all other occurrences to be highlighted
95        # with a round box around each of them
96        my @styles = ();
97        $self->{styles} = \@styles;
98        $self->IndicatorSetStyle( 0, 7 );
99        Wx::Event::EVT_STC_DOUBLECLICK( $self, -1, \&on_smart_highlight_begin );
100        Wx::Event::EVT_LEFT_DOWN( $self, \&on_smart_highlight_end );
101        Wx::Event::EVT_KEY_DOWN( $self, \&on_smart_highlight_end );
102
103        # No more unsafe CTRL-L for you :)
104        # CTRL-L or line cut should only work when there is no empty line
105        # This prevents the accidental destruction of the clipboard
106        $self->CmdKeyClear( ord('L'), Wx::wxSTC_SCMOD_CTRL );
107
108        # Setup EVT_KEY_UP for smart highlighting and non-destructive CTRL-L
109        Wx::Event::EVT_KEY_UP( $self, \&on_key_up );
110
111        if ( $config->editor_wordwrap ) {
112                $self->SetWrapMode(Wx::wxSTC_WRAP_WORD);
113        }
114
115        $self->SetDropTarget( Padre::Wx::FileDropTarget->new( $self->main ) );
116
117        # Disable CTRL keypad -/+. These seem to emit wrong scan codes
118        # on some laptop keyboards. (e.g. CTRL-Caps lock is the same as CTRL -)
119        # Please see bug #790
120        $self->CmdKeyClear( Wx::wxSTC_KEY_SUBTRACT, Wx::wxSTC_SCMOD_CTRL );
121        $self->CmdKeyClear( Wx::wxSTC_KEY_ADD,      Wx::wxSTC_SCMOD_CTRL );
122
123        my $green = Wx::Colour->new("green");
124        my $red   = Wx::Colour->new("red");
125        my $blue  = Wx::Colour->new("blue");
126
127        #NOTE: DO NOT USE "orange" string since it is actually red on win32
128        my $orange = Wx::Colour->new( 255, 165, 0 );
129
130        $self->MarkerDefine(
131                Padre::Wx::MarkError(),
132                Wx::wxSTC_MARK_SMALLRECT,
133                $red,
134                $red,
135        );
136        $self->MarkerDefine(
137                Padre::Wx::MarkWarn(),
138                Wx::wxSTC_MARK_SMALLRECT,
139                $orange,
140                $orange,
141        );
142        $self->MarkerDefine(
143                Padre::Wx::MarkLocation(),
144                Wx::wxSTC_MARK_SMALLRECT,
145                $green,
146                $green,
147        );
148        $self->MarkerDefine(
149                Padre::Wx::MarkBreakpoint(),
150                Wx::wxSTC_MARK_SMALLRECT,
151                $blue,
152                $blue,
153        );
154
155        return $self;
156}
157
158sub main {
159        $_[0]->GetGrandParent;
160}
161
162# convenience accessor method (and to ensure consistency)
163# return the Padre::Config instance
164sub get_config {
165        return shift->main->ide->config;
166}
167
168# convenience methods
169# return the character at a given position as a perl string
170sub get_character_at {
171        my ( $self, $pos ) = @_;
172        return chr( $self->GetCharAt($pos) );
173}
174
175
176
177
178sub data {
179        my $name    = shift;
180        my $private = shift;
181
182        return $data if not defined $name;
183        return $data if defined $data and $name eq $data_name;
184
185        my $file =
186                $private
187                ? File::Spec->catfile(
188                Padre::Constant::CONFIG_DIR,
189                'styles', "$name.yml"
190                )
191                : Padre::Util::sharefile( 'styles', "$name.yml" );
192        my $tdata;
193        eval { $tdata = YAML::Tiny::LoadFile($file); };
194        if ($@) {
195                warn $@;
196        } else {
197                $data_name    = $name;
198                $data_private = $private;
199                $data         = $tdata;
200        }
201        return $data;
202}
203
204# Error Message
205sub error {
206        my $self = shift;
207        my $text = shift;
208        Wx::MessageBox(
209                $text,
210                Wx::gettext("Error"),
211                Wx::wxOK,
212                $self->main
213        );
214}
215
216# Most of this should be read from some external files
217# but for now we use this if statement
218sub padre_setup {
219        my $self = shift;
220
221        TRACE("before setting the lexer") if DEBUG;
222        $self->SetLexer( $self->{Document}->lexer );
223
224        # the next line will change the ESC key to cut the current selection
225        # See: http://www.yellowbrain.com/stc/keymap.html
226        #$self->CmdKeyAssign(Wx::wxSTC_KEY_ESCAPE, 0, Wx::wxSTC_CMD_CUT);
227
228        # This is supposed to be Wx::wxSTC_CP_UTF8
229        # and Wx::wxUNICODE or wxUSE_UNICODE should be on
230        $self->SetCodePage(65001);
231
232        my $mimetype = $self->{Document}->mimetype || 'text/plain';
233        if ( $MIME_STYLE{$mimetype} ) {
234                $self->padre_setup_style( $MIME_STYLE{$mimetype} );
235
236        } elsif ( $mimetype eq 'text/plain' ) {
237                $self->padre_setup_plain;
238                my $filename = $self->{Document}->filename || q{};
239                if ( $filename and $filename =~ /\.([^.]+)$/ ) {
240                        my $ext = lc $1;
241
242                        # re-setup if file extension is .conf
243                        $self->padre_setup_style('conf') if $ext eq 'conf';
244                }
245
246        } elsif ($mimetype) {
247
248                # setup some default coloring
249                # for the time being it is the same as for Perl
250                $self->padre_setup_style('padre');
251        } else {
252
253                # if mimetype is not known, then no coloring for now
254                # but mimimal configuration should apply here too
255                $self->padre_setup_plain;
256        }
257
258        return;
259}
260
261# Called a key is released in the editor
262sub on_key_up {
263        my ( $self, $event ) = @_;
264
265        # The new behavior for a non-destructive CTRL-L
266        if ( $event->GetKeyCode == ord('L') and $event->ControlDown ) {
267                my $line = $self->GetLine( $self->GetCurrentLine() );
268                if ( $line !~ /^\s*$/ ) {
269
270                        # Only cut on non-black lines
271                        $self->CmdKeyExecute(Wx::wxSTC_CMD_LINECUT);
272                } else {
273
274                        # Otherwise delete the line
275                        $self->CmdKeyExecute(Wx::wxSTC_CMD_LINEDELETE);
276                }
277                $event->Skip(0); # done processing this nothing more to do
278                return;
279        }
280
281        # Apply smart highlighting when the shift key is down
282        if ( $self->main->ide->config->editor_smart_highlight_enable && $event->ShiftDown ) {
283                $self->on_smart_highlight_begin($event);
284        }
285
286        $event->Skip(1);     # we need to keep processing this event
287
288}
289
290sub padre_setup_plain {
291        my $self   = shift;
292        my $config = $self->main->ide->config;
293        $self->set_font;
294        $self->StyleClearAll;
295
296        if ( defined $data->{plain}->{current_line_foreground} ) {
297                $self->SetCaretForeground( _color( $data->{plain}->{current_line_foreground} ) );
298        }
299        if ( defined $data->{plain}->{currentline} ) {
300                if ( defined $config->editor_currentline_color ) {
301                        if ( $data->{plain}->{currentline} ne $config->editor_currentline_color ) {
302                                $data->{plain}->{currentline} = $config->editor_currentline_color;
303                        }
304                }
305                $self->SetCaretLineBackground( _color( $data->{plain}->{currentline} ) );
306        } elsif ( defined $config->editor_currentline_color ) {
307                $self->SetCaretLineBackground( _color( $config->editor_currentline_color ) );
308        }
309
310        foreach my $k ( keys %{ $data->{plain}->{foregrounds} } ) {
311                $self->StyleSetForeground( $k, _color( $data->{plain}->{foregrounds}->{$k} ) );
312        }
313
314        # Apply tag style for selected lexer (blue)
315        #$self->StyleSetSpec( Wx::wxSTC_H_TAG, "fore:#0000ff" );
316
317        if ( $self->can('SetLayoutDirection') ) {
318                $self->SetLayoutDirection(Wx::wxLayout_LeftToRight);
319        }
320
321        $self->SetEdgeColumn( $config->editor_right_margin_column );
322        $self->SetEdgeMode( $config->editor_right_margin_enable ? Wx::wxSTC_EDGE_LINE : Wx::wxSTC_EDGE_NONE );
323
324        $self->setup_style_from_config('plain');
325
326        return;
327}
328
329sub padre_setup_style {
330        my $self   = shift;
331        my $name   = shift;
332        my $config = $self->main->ide->config;
333
334        $self->padre_setup_plain;
335        for ( 0 .. Wx::wxSTC_STYLE_DEFAULT ) {
336                $self->StyleSetBackground( $_, _color( $data->{$name}->{background} ) );
337        }
338        $self->setup_style_from_config($name);
339
340        # if mimetype is known, then it might
341        # be Perl with in-line POD
342        if ( $config->editor_folding and $config->editor_fold_pod ) {
343                $self->fold_pod;
344        }
345
346        return;
347}
348
349sub setup_style_from_config {
350        my $self   = shift;
351        my $name   = shift;
352        my $style  = $data->{$name};
353        my $colors = $style->{colors};
354
355        # The selection background (if applicable)
356        # (The Scintilla official selection background colour is cc0000)
357        if ( $style->{selection_background} ) {
358                $self->SetSelBackground( 1, _color( $style->{selection_background} ) );
359        }
360        if ( $style->{selection_foreground} ) {
361                $self->SetSelForeground( 1, _color( $style->{selection_foreground} ) );
362        }
363
364        # Set the styles
365        foreach my $k ( keys %$colors ) {
366                my $v;
367
368                # allow for plain numbers
369                if ( $k =~ /^\d+$/ ) {
370                        $v = $k;
371                }
372
373                # but normally, we have Wx:: or PADRE_ constants
374                else {
375                        my $f = 'Wx::' . $k;
376                        if ( $k =~ /^PADRE_/ ) {
377                                $f = 'Padre::Constant::' . $k;
378                        }
379                        no strict "refs";
380                        $v = eval { $f->() };
381                        if ($@) {
382                                warn "invalid key '$k'\n";
383                                next;
384                        }
385                }
386
387                my $color = $data->{$name}->{colors}->{$k};
388                if ( exists $color->{foreground} ) {
389                        $self->StyleSetForeground( $v, _color( $color->{foreground} ) );
390                }
391                if ( exists $color->{background} ) {
392                        $self->StyleSetBackground( $v, _color( $color->{background} ) );
393                }
394                if ( exists $color->{bold} ) {
395                        $self->StyleSetBold( $v, $color->{bold} );
396                }
397                if ( exists $color->{italics} ) {
398                        $self->StyleSetItalic( $v, $color->{italic} );
399                }
400                if ( exists $color->{eolfilled} ) {
401                        $self->StyleSetEOLFilled( $v, $color->{eolfilled} );
402                }
403                if ( exists $color->{underlined} ) {
404                        $self->StyleSetUnderline( $v, $color->{underline} );
405                }
406        }
407}
408
409sub _color {
410        my $rgb = shift;
411        my @c = ( 0xFF, 0xFF, 0xFF );
412        if ( not defined $rgb ) {
413
414                #Carp::cluck("undefined color");
415        } elsif ( $rgb =~ /^(..)(..)(..)$/ ) {
416                @c = map { hex($_) } ( $1, $2, $3 );
417        } else {
418
419                #Carp::cluck("invalid color '$rgb'");
420        }
421        return Wx::Colour->new(@c);
422}
423
424
425
426
427
428=head2 get_brace_info
429
430Look at a given position in the editor if there is a brace (according to the
431setting editor_braces) before or after, and return the information about the context
432It always look first at the character after the position.
433
434        Params:
435                pos - the cursor position in the editor [defaults to cursor position) : int
436               
437        Return:
438                undef if no brace, otherwise [brace, actual_pos, is_after, is_opening]
439                where:
440                        brace - the brace char at actual_pos
441                        actual_pos - the actual position where the brace has been found
442                        is_after - true iff the brace is after the cursor : boolean
443                        is_opening - true iff only the brace is an opening one
444                       
445        Examples:
446
447                |{} => should find the { : [0,{,1,1]
448                {|} => should find the } : [1,},1,0]
449                {| } => should find the { : [0,{,0,1]
450
451=cut
452
453
454sub get_brace_info {
455        my ( $self, $pos ) = @_;
456        $pos = $self->GetCurrentPos unless defined $pos;
457
458        # try the after position first (default one for BraceMatch)
459        my $is_after = 1;
460        my $brace    = $self->get_character_at($pos);
461        my $is_brace = $self->get_brace_type($brace);
462        if ( !$is_brace && $pos > 0 ) { # try the before position
463                $brace    = $self->get_character_at( --$pos );
464                $is_brace = $self->get_brace_type($brace) or return undef;
465                $is_after = 0;
466        }
467        my $is_opening = $is_brace % 2; # odd values are opening
468        return [ $pos, $brace, $is_after, $is_opening ];
469}
470
471
472=head2 get_brace_type
473
474Tell if a character is a brace, and if it is an opening or a closing one
475
476        Params:
477                char - a character : string
478               
479        Return:
480                int : 0 if this is not a brace, an odd value if it is an opening brace and an even
481                one for a closing brace
482
483=cut
484
485my %_cached_braces;
486
487sub get_brace_type {
488        my ( $self, $char ) = @_;
489        unless (%_cached_braces) {
490                my $i = 1; # start from one so that all values are true
491                $_cached_braces{$_} = $i++ foreach ( split //, $BRACES );
492        }
493        my $v = $_cached_braces{$char} or return 0;
494        return $v;
495}
496
497
498
499# some uncorrect behaviour (| is the cursor)
500# {} : never highlighted
501# { } : always correct
502#
503#
504
505sub apply_style {
506        my ( $self, $style_info ) = @_;
507        my %previous_style = %$style_info;
508        $previous_style{style} = $self->GetStyleAt( $style_info->{start} );
509
510        $self->StartStyling( $style_info->{start}, 0xFF );
511        $self->SetStyling( $style_info->{len}, $style_info->{style} );
512
513        return \%previous_style;
514}
515
516
517my $previous_expr_hiliting_style;
518
519sub highlight_braces {
520        my ($self) = @_;
521
522        my $expression_highlighting = $self->get_config->editor_brace_expression_highlighting;
523
524        # remove current highlighting if any
525        $self->BraceHighlight( $STC_INVALID_POSITION, $STC_INVALID_POSITION );
526        if ($previous_expr_hiliting_style) {
527                $self->apply_style($previous_expr_hiliting_style);
528                $previous_expr_hiliting_style = undef;
529        }
530
531        my $pos1          = $self->GetCurrentPos;
532        my $info1         = $self->get_brace_info($pos1) or return;
533        my ($actual_pos1) = @$info1;
534
535        my $actual_pos2 = $self->BraceMatch($actual_pos1);
536
537        #       return if abs( $pos1 - $pos2 ) < 2;
538
539        return if $actual_pos2 == $STC_INVALID_POSITION; #Wx::wxSTC_INVALID_POSITION  #????
540
541        $self->BraceHighlight( $actual_pos1, $actual_pos2 );
542
543        if ($expression_highlighting) {
544                my $pos2 = $self->find_matching_brace($pos1) or return;
545                my %style = (
546                        start => $pos1 < $pos2 ? $pos1 : $pos2,
547                        len => abs( $pos1 - $pos2 ), style => Wx::wxSTC_STYLE_DEFAULT
548                );
549                $previous_expr_hiliting_style = $self->apply_style( \%style );
550        }
551
552
553        return;
554}
555
556
557=head2 find_matching_brace
558
559Find the position of to the matching brace if any. If the cursor is inside the braces the destination
560will be inside too, same it is outside.
561
562        Params:
563                pos - the cursor position in the editor [defaults to cursor position) : int
564               
565        Return:
566                matching_pos - the matching position, or undef if none
567
568=cut
569
570sub find_matching_brace {
571        my ( $self, $pos ) = @_;
572        $pos = $self->GetCurrentPos unless defined $pos;
573        my $info1 = $self->get_brace_info($pos) or return;
574        my ( $actual_pos1, $brace, $is_after, $is_opening ) = @$info1;
575
576        my $actual_pos2 = $self->BraceMatch($actual_pos1);
577        return if $actual_pos2 == $STC_INVALID_POSITION;
578        $actual_pos2++ if $is_after; # ensure is stays inside if origin is inside, same four outside
579        return $actual_pos2;
580}
581
582
583=head2 goto_matching_brace
584
585Move the cursor to the matching brace if any. If the cursor is inside the braces the destination
586will be inside too, same it is outside.
587
588        Params:
589                pos - the cursor position in the editor [defaults to cursor position) : int
590               
591
592=cut
593
594sub goto_matching_brace {
595        my ( $self, $pos ) = @_;
596        my $pos2 = $self->find_matching_brace($pos) or return;
597        $self->GotoPos($pos2);
598}
599
600=head2 select_to_matching_brace
601
602Select to the matching opening or closing brace. If the cursor is inside the braces the destination
603will be inside too, same it is outside.
604
605        Params:
606                pos - the cursor position in the editor [defaults to cursor position) : int
607               
608
609
610=cut
611
612sub select_to_matching_brace {
613        my ( $self, $pos ) = @_;
614        $pos = $self->GetCurrentPos unless defined $pos;
615        my $pos2 = $self->find_matching_brace($pos) or return;
616        my $start = ( $pos < $pos2 ) ? $self->GetSelectionStart() : $self->GetSelectionEnd();
617        $self->SetSelection( $start, $pos2 );
618
619}
620
621# currently if there are 9 lines we set the margin to 1 width and then
622# if another line is added it is not seen well.
623# actually I added some improvement allowing a 50% growth in the file
624# and requireing a min of 2 width
625sub show_line_numbers {
626        my ( $self, $on ) = @_;
627
628        # premature optimization, caching the with that was on the 3rd place at load time
629        # as timed my Deve::NYTProf
630        $width ||= $self->TextWidth( Wx::wxSTC_STYLE_LINENUMBER, "m" ); # width of a single character
631        if ($on) {
632                my $n = 1 + List::Util::max( 2, length( $self->GetLineCount * 2 ) );
633                my $width = $n * $width;
634                $self->SetMarginWidth( 0, $width );
635                $self->SetMarginType( 0, Wx::wxSTC_MARGIN_NUMBER );
636        } else {
637                $self->SetMarginWidth( 0, 0 );
638                $self->SetMarginType( 0, Wx::wxSTC_MARGIN_NUMBER );
639        }
640
641        return;
642}
643
644# Just a placeholder
645sub show_symbols {
646        my ( $self, $on ) = @_;
647
648        #       $self->SetMarginWidth(1, 0);
649
650        # $self->SetMarginWidth(1, 16);   #margin 1 for symbols, 16 px wide
651        # $self->SetMarginType(1, Wx::wxSTC_MARGIN_SYMBOL);
652
653        return;
654}
655
656sub show_folding {
657        my ( $self, $on ) = @_;
658
659        if ($on) {
660
661                # Setup a margin to hold fold markers
662                $self->SetMarginType( 2, Wx::wxSTC_MARGIN_SYMBOL ); # margin number 2 for symbols
663                $self->SetMarginMask( 2, Wx::wxSTC_MASK_FOLDERS );  # set up mask for folding symbols
664                $self->SetMarginSensitive( 2, 1 );                  # this one needs to be mouse-aware
665                $self->SetMarginWidth( 2, 16 );                     # set margin 2 16 px wide
666
667                # define folding markers
668                my $w = Wx::Colour->new("white");
669                my $b = Wx::Colour->new("black");
670                $self->MarkerDefine( Wx::wxSTC_MARKNUM_FOLDEREND,     Wx::wxSTC_MARK_BOXPLUSCONNECTED,  $w, $b );
671                $self->MarkerDefine( Wx::wxSTC_MARKNUM_FOLDEROPENMID, Wx::wxSTC_MARK_BOXMINUSCONNECTED, $w, $b );
672                $self->MarkerDefine( Wx::wxSTC_MARKNUM_FOLDERMIDTAIL, Wx::wxSTC_MARK_TCORNER,           $w, $b );
673                $self->MarkerDefine( Wx::wxSTC_MARKNUM_FOLDERTAIL,    Wx::wxSTC_MARK_LCORNER,           $w, $b );
674                $self->MarkerDefine( Wx::wxSTC_MARKNUM_FOLDERSUB,     Wx::wxSTC_MARK_VLINE,             $w, $b );
675                $self->MarkerDefine( Wx::wxSTC_MARKNUM_FOLDER,        Wx::wxSTC_MARK_BOXPLUS,           $w, $b );
676                $self->MarkerDefine( Wx::wxSTC_MARKNUM_FOLDEROPEN,    Wx::wxSTC_MARK_BOXMINUS,          $w, $b );
677
678                # This would be nice but the color used for drawing the lines is
679                # Wx::wxSTC_STYLE_DEFAULT, i.e. usually black and therefore quite
680                # obtrusive...
681                # $self->SetFoldFlags( Wx::wxSTC_FOLDFLAG_LINEBEFORE_CONTRACTED | Wx::wxSTC_FOLDFLAG_LINEAFTER_CONTRACTED );
682
683                # activate
684                $self->SetProperty( 'fold' => 1 );
685
686                Wx::Event::EVT_STC_MARGINCLICK(
687                        $self, -1,
688                        sub {
689                                my ( $editor, $event ) = @_;
690                                if ( $event->GetMargin() == 2 ) {
691                                        my $line_clicked  = $editor->LineFromPosition( $event->GetPosition() );
692                                        my $level_clicked = $editor->GetFoldLevel($line_clicked);
693
694                                        # TO DO check this (cf. ~/contrib/samples/stc/edit.cpp from wxWidgets)
695                                        #if ( $level_clicked && wxSTC_FOLDLEVELHEADERFLAG) > 0) {
696                                        $editor->ToggleFold($line_clicked);
697
698                                        #}
699                                }
700                        }
701                );
702        } else {
703                $self->SetMarginSensitive( 2, 0 );
704                $self->SetMarginWidth( 2, 0 );
705
706                # deactivate
707                $self->SetProperty( 'fold' => 1 );
708                $self->unfold_all;
709        }
710
711        return;
712}
713
714sub set_font {
715        my $self   = shift;
716        my $config = $self->main->ide->config;
717        my $font   = Wx::Font->new( 10, Wx::wxTELETYPE, Wx::wxNORMAL, Wx::wxNORMAL );
718        if ( defined $config->editor_font && length $config->editor_font > 0 ) { # empty default...
719                $font->SetNativeFontInfoUserDesc( $config->editor_font );
720        }
721        $self->SetFont($font);
722        $self->StyleSetFont( Wx::wxSTC_STYLE_DEFAULT, $font );
723        return;
724}
725
726sub set_preferences {
727        my $self   = shift;
728        my $config = $self->main->ide->config;
729
730        $self->show_line_numbers( $config->editor_linenumbers );
731        $self->show_folding( $config->editor_folding );
732        $self->SetIndentationGuides( $config->editor_indentationguides );
733        $self->SetViewEOL( $config->editor_eol );
734        $self->SetViewWhiteSpace( $config->editor_whitespace );
735        $self->SetCaretLineVisible( $config->editor_currentline );
736
737        $self->padre_setup;
738
739        $self->{Document}->set_indentation_style;
740
741        return;
742}
743
744sub show_calltip {
745        my $self   = shift;
746        my $config = $self->main->ide->config;
747        return unless $config->editor_calltips;
748
749        my $pos    = $self->GetCurrentPos;
750        my $line   = $self->LineFromPosition($pos);
751        my $first  = $self->PositionFromLine($line);
752        my $prefix = $self->GetTextRange( $first, $pos ); # line from beginning to current position
753        if ( $self->CallTipActive ) {
754                $self->CallTipCancel;
755        }
756
757        my $doc      = Padre::Current->document or return;
758        my $keywords = $doc->keywords;
759        my $regex    = join '|', sort { length $a <=> length $b } keys %$keywords;
760
761        my $tip;
762        if ( $prefix =~ /(?:^|[^\w\$\@\%\&])($regex)[ (]?$/ ) {
763                my $z = $keywords->{$1};
764                return if not $z or not ref($z) or ref($z) ne 'HASH';
765                $tip = "$z->{cmd}\n$z->{exp}";
766        }
767        if ($tip) {
768                $self->CallTipShow( $self->CallTipPosAtStart() + 1, $tip );
769        }
770        return;
771}
772
773# For auto-indentation (i.e. one more level), we do the following:
774# 1) get the white spaces of the previous line and add them here as well
775# 2) after a brace indent one level more than previous line
776# 3) while doing all this, respect the current (sadly global) indentation settings
777# For auto-de-indentation (i.e. closing brace), we remove one level of indentation
778# instead.
779# FIX ME/TO DO: needs some refactoring
780sub autoindent {
781        my ( $self, $mode ) = @_;
782
783        my $config = $self->main->ide->config;
784        return unless $config->editor_autoindent;
785        return if $config->editor_autoindent eq 'no';
786
787        if ( $mode eq 'deindent' ) {
788                $self->_auto_deindent($config);
789        } else {
790
791                # default to "indent"
792                $self->_auto_indent($config);
793        }
794
795        return;
796}
797
798sub _auto_indent {
799        my ( $self, $config ) = @_;
800
801        my $pos       = $self->GetCurrentPos;
802        my $prev_line = $self->LineFromPosition($pos) - 1;
803        return if $prev_line < 0;
804
805        my $indent_style = $self->{Document}->get_indentation_style;
806
807        my $content = $self->_get_line_by_number($prev_line);
808        my $indent = ( $content =~ /^(\s+)/ ? $1 : '' );
809
810        if ( $config->editor_autoindent eq 'deep' and $content =~ /\{\s*$/ ) {
811                my $indent_width = $indent_style->{indentwidth};
812                my $tab_width    = $indent_style->{tabwidth};
813                if ( $indent_style->{use_tabs} and $indent_width != $tab_width ) {
814
815                        # do tab compression if necessary
816                        # - First, convert all to spaces (aka columns)
817                        # - Then, add an indentation level
818                        # - Then, convert to tabs as necessary
819                        my $tab_equivalent = " " x $tab_width;
820                        $indent =~ s/\t/$tab_equivalent/g;
821                        $indent .= $tab_equivalent;
822                        $indent =~ s/$tab_equivalent/\t/g;
823                } elsif ( $indent_style->{use_tabs} ) {
824
825                        # use tabs only
826                        $indent .= "\t";
827                } else {
828                        $indent .= " " x $indent_width;
829                }
830        }
831        if ( $indent ne '' ) {
832                $self->InsertText( $pos, $indent );
833                $self->GotoPos( $pos + length($indent) );
834        }
835
836        return;
837}
838
839sub _auto_deindent {
840        my ( $self, $config ) = @_;
841
842        my $pos  = $self->GetCurrentPos;
843        my $line = $self->LineFromPosition($pos);
844
845        my $indent_style = $self->{Document}->get_indentation_style;
846
847        my $content = $self->_get_line_by_number($line);
848        my $indent = ( $content =~ /^(\s+)/ ? $1 : '' );
849
850        # This is for } on a new line:
851        if ( $config->editor_autoindent eq 'deep' and $content =~ /^\s*\}\s*$/ ) {
852                my $prev_line    = $line - 1;
853                my $prev_content = ( $prev_line < 0 ? '' : $self->_get_line_by_number($prev_line) );
854                my $prev_indent  = ( $prev_content =~ /^(\s+)/ ? $1 : '' );
855
856                # de-indent only in these cases:
857                # - same indentation level as prev. line and not a brace on prev line
858                # - higher indentation than pr. l. and a brace on pr. line
859                if ( $prev_indent eq $indent && $prev_content !~ /^\s*{/
860                        or length($prev_indent) < length($indent) && $prev_content =~ /\{\s*$/ )
861                {
862                        my $indent_width = $indent_style->{indentwidth};
863                        my $tab_width    = $indent_style->{tabwidth};
864                        if ( $indent_style->{use_tabs} and $indent_width != $tab_width ) {
865
866                                # do tab compression if necessary
867                                # - First, convert all to spaces (aka columns)
868                                # - Then, add an indentation level
869                                # - Then, convert to tabs as necessary
870                                my $tab_equivalent = " " x $tab_width;
871                                $indent =~ s/\t/$tab_equivalent/g;
872                                $indent =~ s/$tab_equivalent$//;
873                                $indent =~ s/$tab_equivalent/\t/g;
874                        } elsif ( $indent_style->{use_tabs} ) {
875
876                                # use tabs only
877                                $indent =~ s/\t$//;
878                        } else {
879                                my $indentation_level = " " x $indent_width;
880                                $indent =~ s/$indentation_level$//;
881                        }
882                }
883
884                # replace indentation of the current line
885                $self->GotoPos( $pos - 1 );
886                $self->DelLineLeft();
887                $pos = $self->GetCurrentPos();
888                $self->InsertText( $pos, $indent );
889                $self->GotoPos( $self->GetLineEndPosition($line) );
890        }
891
892        # this is if the line matches "blahblahSomeText}".
893        elsif ( $config->editor_autoindent eq 'deep' and $content =~ /\}\s*$/ ) {
894
895                # TO DO: What should happen in this case?
896        }
897
898        return;
899}
900
901# given a line number, returns the contents
902sub _get_line_by_number {
903        my $self    = shift;
904        my $line_no = shift;
905
906        my $start = $self->PositionFromLine($line_no);
907        my $end   = $self->GetLineEndPosition($line_no);
908        return $self->GetTextRange( $start, $end );
909}
910
911sub fold_all {
912        my ($self) = @_;
913
914        my $lineCount   = $self->GetLineCount;
915        my $currentLine = $lineCount;
916
917        while ( $currentLine >= 0 ) {
918                if ( ( my $parentLine = $self->GetFoldParent($currentLine) ) > 0 ) {
919                        if ( $self->GetFoldExpanded($parentLine) ) {
920                                $self->ToggleFold($parentLine);
921                                $currentLine = $parentLine;
922                        } else {
923                                $currentLine--;
924                        }
925                } else {
926                        $currentLine--;
927                }
928        }
929
930        return;
931}
932
933sub unfold_all {
934        my ($self) = @_;
935
936        my $lineCount   = $self->GetLineCount;
937        my $currentLine = 0;
938
939        while ( $currentLine <= $lineCount ) {
940                if ( !$self->GetFoldExpanded($currentLine) ) {
941                        $self->ToggleFold($currentLine);
942                }
943                $currentLine++;
944        }
945
946        return;
947}
948
949# When the focus is received by the editor
950sub on_focus {
951        my $self     = shift;
952        my $event    = shift;
953        my $main     = $self->main;
954        my $document = $main->current->document;
955        TRACE( "Focus received file: " . ( $document->filename || '' ) ) if DEBUG;
956
957        # NOTE: The editor focus event fires a LOT, even for trivial things
958        # like changing focus to another application and immediately back again,
959        # or switching between tools in Padre.
960        # Don't do any refreshing here, it is an excessive waste of resources.
961        # Instead, put them in the events that ACTUALLY change application state.
962        my $lock = $main->lock('UPDATE');
963
964        # TO DO
965        # This is called even if the mouse is moved away from padre and back again
966        # we should restrict some of the updates to cases when we switch from one file to
967        # another
968        if ( $self->needs_manual_colorize ) {
969                TRACE("needs_manual_colorize") if DEBUG;
970                my $lexer = $self->GetLexer;
971                if ( $lexer == Wx::wxSTC_LEX_CONTAINER ) {
972                        $document->colorize;
973                } else {
974                        $document->remove_color;
975                        $self->Colourise( 0, $self->GetLength );
976                }
977                $self->needs_manual_colorize(0);
978        } else {
979                TRACE("no need to colorize") if DEBUG;
980        }
981
982        # NIOTE: This is so the cursor will show up
983        $event->Skip(1);
984
985        return;
986}
987
988sub on_char {
989        my ( $self, $event ) = @_;
990
991        my $doc = $self->{Document};
992        if ( $doc->can('event_on_char') ) {
993                $doc->event_on_char( $self, $event );
994        }
995
996        if ( $self->main->ide->{has_Time_HiRes} ) {
997                $doc->{last_char_time} = Time::HiRes::time();
998        } else {
999                $doc->{last_char_time} = time;
1000        }
1001
1002        $event->Skip;
1003        return;
1004}
1005
1006sub clear_smart_highlight {
1007        my $self = shift;
1008
1009        my @styles = @{ $self->{styles} };
1010        if ( scalar @styles ) {
1011                foreach my $style (@styles) {
1012                        $self->StartStyling( $style->{start}, 0xFF );
1013                        $self->SetStyling( $style->{len}, $style->{style} );
1014                }
1015                $#{ $self->{styles} } = -1;
1016        }
1017}
1018
1019sub on_smart_highlight_begin {
1020        my ( $self, $event ) = @_;
1021
1022        my $selection        = $self->GetSelectedText;
1023        my $selection_length = length $selection;
1024        return if $selection_length == 0;
1025
1026        my $selection_re = quotemeta $selection;
1027        my $line_count   = $self->GetLineCount;
1028        my $line_num     = $self->GetCurrentLine;
1029
1030        # Limits search to C+N..C-N from current line respecting limits ofcourse
1031        # to optimize CPU usage
1032        my $NUM_LINES = 400;
1033        my $from      = ( $line_num - $NUM_LINES <= 0 ) ? 0 : $line_num - $NUM_LINES;
1034        my $to        = ( $line_count <= $line_num + $NUM_LINES ) ? $line_count : $line_num + $NUM_LINES;
1035
1036        # Clear previous smart highlights
1037        $self->clear_smart_highlight;
1038
1039        # find matching occurrences
1040        foreach my $i ( $from .. $to ) {
1041                my $line_start = $self->PositionFromLine($i);
1042                my $line       = $self->GetLine($i);
1043                while ( $line =~ /$selection_re/g ) {
1044                        my $start = $line_start + pos($line) - $selection_length;
1045
1046                        push @{ $self->{styles} },
1047                                {
1048                                start => $start,
1049                                len   => $selection_length,
1050                                style => $self->GetStyleAt($start)
1051                                };
1052                }
1053        }
1054
1055        # smart highlight if there are more than one occurrence...
1056        if ( scalar @{ $self->{styles} } > 1 ) {
1057                foreach my $style ( @{ $self->{styles} } ) {
1058                        $self->StartStyling( $style->{start}, 0xFF );
1059                        $self->SetStyling( $style->{len}, Wx::wxSTC_STYLE_DEFAULT );
1060                }
1061        }
1062
1063}
1064
1065sub on_smart_highlight_end {
1066        my ( $self, $event ) = @_;
1067
1068        $self->clear_smart_highlight;
1069        $event->Skip;
1070}
1071
1072sub on_left_up {
1073        my ( $self, $event ) = @_;
1074
1075        my $config = $self->main->ide->config;
1076
1077        my $text = $self->GetSelectedText;
1078        if ( Padre::Constant::WXGTK and defined $text and $text ne '' ) {
1079
1080                # Only on X11 based platforms
1081                #               Wx::wxTheClipboard->UsePrimarySelection(1);
1082                if ( $config->mid_button_paste ) {
1083                        $self->put_text_to_clipboard( $text, 1 );
1084                } else {
1085                        $self->put_text_to_clipboard($text);
1086                }
1087
1088                #               Wx::wxTheClipboard->UsePrimarySelection(0);
1089        }
1090
1091        my $doc = $self->{Document};
1092        if ( $doc->can('event_on_left_up') ) {
1093                $doc->event_on_left_up( $self, $event );
1094        }
1095
1096        $event->Skip;
1097        return;
1098}
1099
1100sub on_middle_up {
1101        my ( $self, $event ) = @_;
1102
1103        my $config = $self->main->ide->config;
1104
1105        # TO DO: Sometimes there are unexpected effects when using the middle button.
1106        # It seems that another event is doing something but not within this module.
1107        # Please look at ticket #390 for details!
1108
1109        Wx::wxTheClipboard->UsePrimarySelection(1)
1110                if $config->mid_button_paste;
1111
1112        if ( Padre::Constant::WIN32 or ( !$config->mid_button_paste ) ) {
1113                Padre::Current->editor->Paste;
1114        }
1115
1116        my $doc = $self->{Document};
1117        if ( $doc->can('event_on_middle_up') ) {
1118                $doc->event_on_middle_up( $self, $event );
1119        }
1120
1121        Wx::wxTheClipboard->UsePrimarySelection(0)
1122                if $config->mid_button_paste;
1123
1124        if ( $config->mid_button_paste ) {
1125                $event->Skip;
1126        } else {
1127                $event->Skip(0);
1128        }
1129        return;
1130}
1131
1132
1133sub on_right_down {
1134        my $self  = shift;
1135        my $event = shift;
1136        my $main  = $self->main;
1137        my $pos   = $self->GetCurrentPos;
1138
1139        #my $line  = $self->LineFromPosition($pos);
1140        #print "right down: $pos\n"; # this is the position of the cursor and not that of the mouse!
1141        #my $p = $event->GetLogicalPosition;
1142        #print "x: ", $p->x, "\n";
1143
1144        require Padre::Wx::Menu::RightClick;
1145        my $menu = Padre::Wx::Menu::RightClick->new( $main, $self, $event );
1146
1147        if ( $event->isa('Wx::MouseEvent') ) {
1148                $self->PopupMenu( $menu->wx, $event->GetX, $event->GetY );
1149        } else { #Wx::CommandEvent
1150                $self->PopupMenu( $menu->wx, 50, 50 ); # TO DO better location
1151        }
1152}
1153
1154sub on_mouse_motion {
1155        my $self   = shift;
1156        my $event  = shift;
1157        my $config = $self->main->ide->config;
1158
1159        $event->Skip;
1160        return unless $config->main_syntaxcheck;
1161
1162        my $mousePos         = $event->GetPosition;
1163        my $line             = $self->LineFromPosition( $self->PositionFromPoint($mousePos) );
1164        my $firstPointInLine = $self->PointFromPosition( $self->PositionFromLine($line) );
1165
1166        my ( $offset1, $offset2 ) = ( 0, 18 );
1167        if ( $config->editor_folding ) {
1168                $offset1 += 18;
1169                $offset2 += 18;
1170        }
1171
1172        if (    $mousePos->x < ( $firstPointInLine->x - $offset1 )
1173                and $mousePos->x > ( $firstPointInLine->x - $offset2 ) )
1174        {
1175                unless ( $self->MarkerGet($line) ) {
1176                        $self->CallTipCancel;
1177                        return;
1178                }
1179                $self->CallTipShow(
1180                        $self->PositionFromLine($line),
1181                        $self->{synchk_calltips}->{$line}
1182                );
1183        } else {
1184                $self->CallTipCancel;
1185        }
1186
1187        return;
1188}
1189
1190sub text_select_all {
1191        my ( $main, $event ) = @_;
1192
1193        my $id = $main->notebook->GetSelection;
1194        return if $id == -1;
1195        $main->notebook->GetPage($id)->SelectAll;
1196        return;
1197}
1198
1199sub text_selection_mark_start {
1200        my ($self) = @_;
1201
1202        # find positions
1203        $self->{selection_mark_start} = $self->GetCurrentPos;
1204
1205        # change selection if start and end are defined
1206        $self->SetSelection(
1207                $self->{selection_mark_start},
1208                $self->{selection_mark_end}
1209        ) if defined $self->{selection_mark_end};
1210}
1211
1212sub text_selection_mark_end {
1213        my ($self) = @_;
1214
1215        $self->{selection_mark_end} = $self->GetCurrentPos;
1216
1217        # change selection if start and end are defined
1218        $self->SetSelection(
1219                $self->{selection_mark_start},
1220                $self->{selection_mark_end}
1221        ) if defined $self->{selection_mark_start};
1222}
1223
1224sub text_selection_clear_marks {
1225        my $editor = $_[0]->current->editor;
1226        undef $editor->{selection_mark_start};
1227        undef $editor->{selection_mark_end};
1228}
1229
1230#
1231# my ($begin, $end) = $self->current_paragraph;
1232#
1233# return $begin and $end position of current paragraph.
1234#
1235sub current_paragraph {
1236        my ($editor) = @_;
1237
1238        my $curpos = $editor->GetCurrentPos;
1239        my $lineno = $editor->LineFromPosition($curpos);
1240
1241        # check if we're in between paragraphs
1242        return ( $curpos, $curpos ) if $editor->GetLine($lineno) =~ /^\s*$/;
1243
1244        # find the start of paragraph by searching backwards till we find a
1245        # line with only whitespace in it.
1246        my $para1 = $lineno;
1247        while ( $para1 > 0 ) {
1248                my $line = $editor->GetLine($para1);
1249                last if $line =~ /^\s*$/;
1250                $para1--;
1251        }
1252
1253        # now, find the end of paragraph by searching forwards until we find
1254        # only white space
1255        my $lastline = $editor->GetLineCount;
1256        my $para2    = $lineno;
1257        while ( $para2 < $lastline ) {
1258                $para2++;
1259                my $line = $editor->GetLine($para2);
1260                last if $line =~ /^\s*$/;
1261        }
1262
1263        # return the position
1264        my $begin = $editor->PositionFromLine( $para1 + 1 );
1265        my $end   = $editor->PositionFromLine($para2);
1266        return ( $begin, $end );
1267}
1268
1269sub Paste {
1270        my $self = shift;
1271
1272        # Workaround for Copy/Paste bug ticket #390
1273        my $text = $self->get_text_from_clipboard;
1274
1275        if ($text) {
1276
1277                # Conversion of pasted text is really needed since it usually comes
1278                # with the platform's line endings
1279                #
1280                # Please see ticket:589, "Pasting in a UNIX document in win32
1281                # corrupts it to MIXEd"
1282                $self->ReplaceSelection( $self->_convert_paste_eols($text) );
1283        }
1284
1285        return 1;
1286}
1287
1288#
1289# This method converts line ending based on current document EOL mode
1290# and the newline type for the current text
1291#
1292sub _convert_paste_eols {
1293        my ( $self, $paste ) = @_;
1294
1295        my $newline_type = Padre::Util::newline_type($paste);
1296        my $eol_mode     = $self->GetEOLMode();
1297
1298        # Handle the 'None' one-liner case
1299        if ( $newline_type eq 'None' ) {
1300                $newline_type = $self->main->config->default_line_ending;
1301        }
1302
1303        #line endings
1304        my $CR   = "\015";
1305        my $LF   = "\012";
1306        my $CRLF = "$CR$LF";
1307        my ( $from, $to );
1308
1309        # From what to convert from?
1310        if ( $newline_type eq 'WIN' ) {
1311                $from = $CRLF;
1312        } elsif ( $newline_type eq 'UNIX' ) {
1313                $from = $LF;
1314        } elsif ( $newline_type eq 'MAC' ) {
1315                $from = $CR;
1316        }
1317
1318        # To what to convert to?
1319        if ( $eol_mode eq Wx::wxSTC_EOL_CRLF ) {
1320                $to = $CRLF;
1321        } elsif ( $eol_mode eq Wx::wxSTC_EOL_LF ) {
1322                $to = $LF;
1323        } else {
1324
1325                #must be Wx::wxSTC_EOL_CR
1326                $to = $CR;
1327        }
1328
1329        # Convert only when it is needed
1330        if ( $from ne $to ) {
1331                $paste =~ s/$from/$to/g;
1332        }
1333
1334        return $paste;
1335}
1336
1337sub put_text_to_clipboard {
1338        my ( $self, $text, $clipboard ) = @_;
1339        @_ = (); # Feeble attempt to kill Scalars Leaked
1340
1341        return if $text eq '';
1342
1343        my $config = $self->main->ide->config;
1344
1345        $clipboard ||= 0;
1346
1347        # Backup last clipboard value:
1348        $self->{Clipboard_Old} = $self->get_text_from_clipboard;
1349
1350        #         if $self->{Clipboard_Old} ne $self->get_text_from_clipboard;
1351
1352        Wx::wxTheClipboard->Open;
1353        Wx::wxTheClipboard->UsePrimarySelection($clipboard)
1354                if $config->mid_button_paste;
1355        Wx::wxTheClipboard->SetData( Wx::TextDataObject->new($text) );
1356        Wx::wxTheClipboard->Close;
1357
1358        return;
1359}
1360
1361sub get_text_from_clipboard {
1362
1363        my $self = shift;
1364
1365        my $text = '';
1366        Wx::wxTheClipboard->Open;
1367        if ( Wx::wxTheClipboard->IsSupported(Wx::wxDF_TEXT) ) {
1368                my $data = Wx::TextDataObject->new;
1369                if ( Wx::wxTheClipboard->GetData($data) ) {
1370                        $text = $data->GetText if defined($data);
1371                }
1372        }
1373        if ( $text eq $self->GetSelectedText ) {
1374                $text = $self->{Clipboard_Old};
1375        }
1376
1377        Wx::wxTheClipboard->Close;
1378        return $text;
1379}
1380
1381# Comment or uncomment text depending on the first selected line.
1382# This is the most coherent way to handle mixed blocks (commented and
1383# uncommented lines).
1384sub comment_toggle_lines {
1385        my ( $self, $begin, $end, $str ) = @_;
1386        if ( _get_line_by_number( $self, $begin ) =~ /\s*$str/ ) {
1387                uncomment_lines(@_);
1388        } else {
1389                comment_lines(@_);
1390        }
1391}
1392
1393# $editor->comment_lines($begin, $end, $str);
1394# $str is either # for perl or // for Javascript, etc.
1395# $str might be ['<--', '-->] for html
1396sub comment_lines {
1397        my ( $self, $begin, $end, $str ) = @_;
1398
1399        $self->BeginUndoAction;
1400        if ( ref $str eq 'ARRAY' ) {
1401                my $pos = $self->PositionFromLine($begin);
1402                $self->InsertText( $pos, $str->[0] );
1403                $pos = $self->GetLineEndPosition($end);
1404                $self->InsertText( $pos, $str->[1] );
1405        } else {
1406                my $is_first_column = $self->GetColumn( $self->GetCurrentPos ) == 0;
1407                if ( $is_first_column && $end > $begin ) {
1408                        $end--;
1409                }
1410                foreach my $line ( $begin .. $end ) {
1411
1412                        # insert $str (# or //)
1413                        my $pos = $self->PositionFromLine($line);
1414                        $self->InsertText( $pos, $str );
1415                }
1416        }
1417        $self->EndUndoAction;
1418        return;
1419}
1420
1421#
1422# $editor->uncomment_lines($begin, $end, $str);
1423#
1424# uncomment lines $begin..$end
1425#
1426sub uncomment_lines {
1427        my ( $self, $begin, $end, $str ) = @_;
1428
1429        $self->BeginUndoAction;
1430        if ( ref $str eq 'ARRAY' ) {
1431                my $first = $self->PositionFromLine($begin);
1432                my $last  = $first + length( $str->[0] );
1433                my $text  = $self->GetTextRange( $first, $last );
1434                if ( $text eq $str->[0] ) {
1435                        $self->SetSelection( $first, $last );
1436                        $self->ReplaceSelection('');
1437                }
1438                $last  = $self->GetLineEndPosition($end);
1439                $first = $last - length( $str->[1] );
1440                $text  = $self->GetTextRange( $first, $last );
1441                if ( $text eq $str->[1] ) {
1442                        $self->SetSelection( $first, $last );
1443                        $self->ReplaceSelection('');
1444                }
1445        } else {
1446                my $length          = length $str;
1447                my $is_first_column = $self->GetColumn( $self->GetCurrentPos ) == 0;
1448                if ( $is_first_column && $end > $begin ) {
1449                        $end--;
1450                }
1451                foreach my $line ( $begin .. $end ) {
1452                        my $first = $self->PositionFromLine($line);
1453                        my $last  = $first + $length;
1454                        my $text  = $self->GetTextRange( $first, $last );
1455                        if ( $text eq $str ) {
1456                                $self->SetSelection( $first, $last );
1457                                $self->ReplaceSelection('');
1458                        }
1459                }
1460        }
1461        $self->EndUndoAction;
1462
1463        return;
1464}
1465
1466sub fold_pod {
1467        my ($self) = @_;
1468
1469        my $currentLine = 0;
1470        my $lastLine    = $self->GetLineCount;
1471
1472        while ( $currentLine <= $lastLine ) {
1473                if ( $self->_get_line_by_number($currentLine) =~ /^=(pod|head)/ ) {
1474                        if ( $self->GetFoldExpanded($currentLine) ) {
1475                                $self->ToggleFold($currentLine);
1476                                my $foldLevel = $self->GetFoldLevel($currentLine);
1477                                $currentLine = $self->GetLastChild( $currentLine, $foldLevel );
1478                        }
1479                        $currentLine++;
1480                } else {
1481                        $currentLine++;
1482                }
1483        }
1484
1485        return;
1486}
1487
1488sub configure_editor {
1489        my ( $self, $doc ) = @_;
1490
1491        my $newline_type = $doc->newline_type;
1492
1493        $self->SetEOLMode( $mode{$newline_type} or $mode{ $self->main->config->default_line_ending } );
1494
1495        if ( defined $doc->{original_content} ) {
1496                $self->SetText( $doc->{original_content} );
1497        }
1498        $self->EmptyUndoBuffer;
1499
1500        $doc->{newline_type} = $newline_type;
1501
1502        return;
1503}
1504
1505sub goto_line_centerize {
1506        $_[0]->goto_pos_centerize( $_[0]->PositionFromLine( $_[1] ) );
1507}
1508
1509# borrowed from Kephra
1510sub goto_pos_centerize {
1511        my ( $self, $pos ) = @_;
1512
1513        my $max = $self->GetLength;
1514        $pos = 0 unless $pos or $pos < 0;
1515        $pos = $max if $pos > $max;
1516
1517        $self->SetCurrentPos($pos);
1518        $self->SearchAnchor;
1519
1520        my $line = $self->GetCurrentLine;
1521        $self->ScrollToLine( $line - ( $self->LinesOnScreen / 2 ) );
1522        $self->EnsureVisible($line);
1523        $self->EnsureCaretVisible;
1524        $self->SetSelection( $pos, $pos );
1525        $self->SetFocus;
1526}
1527
1528sub insert_text {
1529        my ( $self, $text ) = @_;
1530
1531        my $data = Wx::TextDataObject->new;
1532        $data->SetText($text);
1533        my $length = $data->GetTextLength;
1534
1535        $self->ReplaceSelection('');
1536        my $pos = $self->GetCurrentPos;
1537        $self->InsertText( $pos, $text );
1538        $self->GotoPos( $pos + $length - 1 );
1539
1540        return;
1541}
1542
1543sub insert_from_file {
1544        my ( $self, $file ) = @_;
1545
1546        my $text;
1547        if ( open( my $fh, '<', $file ) ) {
1548                binmode($fh);
1549                local $/ = undef;
1550                $text = <$fh>;
1551                close $fh;
1552        } else {
1553                return;
1554        }
1555
1556        $self->insert_text($text);
1557
1558        return $file;
1559}
1560
1561sub vertically_align {
1562        my $editor = shift;
1563
1564        # Get the selected lines
1565        my $begin = $editor->LineFromPosition( $editor->GetSelectionStart );
1566        my $end   = $editor->LineFromPosition( $editor->GetSelectionEnd );
1567        if ( $begin == $end ) {
1568                $editor->error( Wx::gettext("You must select a range of lines") );
1569                return;
1570        }
1571        my @line = ( $begin .. $end );
1572        my @text = ();
1573        foreach (@line) {
1574                my $x = $editor->PositionFromLine($_);
1575                my $y = $editor->GetLineEndPosition($_);
1576                push @text, $editor->GetTextRange( $x, $y );
1577        }
1578
1579        # Get the align character from the selection start
1580        # (which must be a non-whitespace non-word character)
1581        my $start = $editor->GetSelectionStart;
1582        my $c = $editor->GetTextRange( $start, $start + 1 );
1583        unless ( defined $c and $c =~ /^[^\s\w]$/ ) {
1584                $editor->error( Wx::gettext("First character of selection must be a non-word character to align") );
1585        }
1586
1587        # Locate the position of the align character,
1588        # and the position of the earliest whitespace before it.
1589        my $qc       = quotemeta $c;
1590        my @position = ();
1591        foreach (@text) {
1592                if (/^(.+?)(\s*)$qc/) {
1593                        push @position, [ length("$1"), length("$2") ];
1594                } else {
1595
1596                        # This line is not a member of the align set
1597                        push @position, undef;
1598                }
1599        }
1600
1601        # Find the latest position of the starting whitespace.
1602        my $longest = List::Util::max map { $_->[0] } grep {$_} @position;
1603
1604        # Now lets line them up
1605        $editor->BeginUndoAction;
1606        foreach ( 0 .. $#line ) {
1607                next unless $position[$_];
1608                my $spaces = $longest - $position[$_]->[0] - $position[$_]->[1] + 1;
1609                if ( $_ == 0 ) {
1610                        $start = $start + $spaces;
1611                }
1612                my $insert = $editor->PositionFromLine( $line[$_] ) + $position[$_]->[0];
1613                if ( $spaces > 0 ) {
1614                        $editor->InsertText( $insert, ' ' x $spaces );
1615                } elsif ( $spaces < 0 ) {
1616                        $editor->SetSelection( $insert, $insert - $spaces );
1617                        $editor->ReplaceSelection('');
1618                }
1619        }
1620        $editor->EndUndoAction;
1621
1622        # Move the selection to the new position
1623        $editor->SetSelection( $start, $start );
1624
1625        return;
1626}
1627
1628sub needs_manual_colorize {
1629        if ( defined $_[1] ) {
1630                $_[0]->{needs_manual_colorize} = $_[1];
1631        }
1632        return $_[0]->{needs_manual_colorize};
1633}
1634
16351;
1636
1637# Copyright 2008-2010 The Padre development team as listed in Padre.pm.
1638# LICENSE
1639# This program is free software; you can redistribute it and/or
1640# modify it under the same terms as Perl 5 itself.
Note: See TracBrowser for help on using the browser.