File Coverage

blib/lib/Text/Editor/Vip/Buffer.pm
Criterion Covered Total %
statement 7 9 77.7
branch n/a
condition n/a
subroutine 3 3 100.0
pod n/a
total 10 12 83.3


line stmt bran cond sub pod time code
1              
2             package Text::Editor::Vip::Buffer;
3              
4 5     5   236564 use strict;
  5         13  
  5         207  
5 5     5   25 use warnings ;
  5         9  
  5         225  
6 5     5   15146 use Data::TreeDumper ;
  0            
  0            
7              
8             BEGIN
9             {
10             use Exporter ();
11              
12             use vars qw ($VERSION @ISA @EXPORT @EXPORT_OK %EXPORT_TAGS);
13             $VERSION = 0.01;
14             @ISA = qw (Exporter);
15             @EXPORT = qw ();
16             @EXPORT_OK = qw ();
17             %EXPORT_TAGS = ();
18             }
19              
20             #-------------------------------------------------------------------------------
21              
22             use Time::HiRes ;
23             use Carp qw(carp confess cluck);
24             use List::Util qw(min) ;
25              
26             use Text::Editor::Vip::Buffer::List ;
27             use Text::Editor::Vip::Buffer::Constants ;
28             use Text::Editor::Vip::Selection ;
29             use Text::Editor::Vip::CommandBlock ;
30              
31             #-------------------------------------------------------------------------------
32              
33             =head1 NAME
34              
35             Text::Editor::Vip::Buffer - Editing engine
36              
37             =head1 SYNOPSIS
38              
39             use Text::Editor::Vip::Buffer ;
40             my $buffer = new Text::Editor::Vip::Buffer() ;
41            
42             =head1 DESCRIPTION
43              
44             This module implements the core functionality for an editing engine. It knows about
45             selection, undo and plugins.
46              
47             =head1 MEMBER FUNCTIONS
48              
49             =cut
50              
51             my $uid = 0 ; #well, hmm, good enough
52              
53             sub new
54             {
55              
56             =head2 new
57              
58             Create a Text::Editor::Vip::Buffer .
59              
60             my $buffer = new Text::Editor::Vip::Buffer() ;
61              
62             =cut
63              
64             my $invocant = shift ;
65              
66             my $class = ref($invocant) || $invocant ;
67             my $buffer = {} ;
68              
69             my ($package, $file_name, $line) = caller() ;
70             $file_name =~ s/[^0-9a-zA-Z_]/_/g ;
71              
72             # push this object in a 'unique' class
73             # this lets us expand a single object functionality without expanding
74             # all objects
75             $class .= "::${file_name}_${line}_$uid" ;
76             $uid++ ;
77            
78             my $buffer_package = __PACKAGE__;
79             eval "unshift \@${class}::ISA, '$buffer_package' ;" ;
80              
81             bless $buffer, $class ;
82              
83             $buffer->Setup(@_) ;
84             $buffer->LoadAndExpandWith('Text::Editor::Vip::Buffer::DoUndoRedo') ;
85             $buffer->LoadAndExpandWith('Text::Editor::Vip::Buffer::Indenter') ;
86             $buffer->LoadAndExpandWith('Text::Editor::Vip::Buffer::Selection') ;
87              
88             return($buffer) ;
89             }
90              
91             #-------------------------------------------------------------------------------
92              
93             sub Setup
94             {
95              
96             =head2 Setup
97              
98             Helper sub called by new. This is considerer private.
99              
100             =cut
101              
102             my $buffer = shift ;
103             my $expansions = $buffer->{EXPANSIONS} || [] ;
104              
105             %$buffer =
106             (
107             NODES => new Text::Editor::Vip::Buffer::List()
108             , EXPANSIONS => $expansions
109            
110             , MARKED_AS_EDITED => 0
111            
112             , DO_PREFIX => ''
113             , DO_STACK => []
114             , UNDO_PREFIX => ''
115             , UNDO_STACK => []
116             , REDO_STACK => []
117            
118             , CLIPBOARDS => {}
119             , MODIFICATION_LINE => 0
120             , MODIFICATION_CHARACTER => 0
121             , SELECTION => new Text::Editor::Vip::Selection()
122             , @_
123             ) ;
124              
125              
126             $buffer->{NODES}->Push({TEXT => ''}) ;
127             }
128              
129             #-------------------------------------------------------------------------------
130              
131             =head2 Reset
132              
133             Empties the buffer from it's contents as if it was newly created. L are still plugged into the buffer. This
134             is very practical when writting tests.
135              
136             $buffer->Reset() ;
137              
138             =cut
139              
140             *Reset = \&Setup ;
141              
142             #-------------------------------------------------------------------------------
143              
144             sub ExpandedWithOrLoad
145             {
146              
147             =head2 ExpandedWithOrLoad
148              
149             See L.
150              
151             If the name passed as first argument doesn't match a sub within the object, the module, passed as second argument,
152             is loaded.
153              
154             # newly created buffer that is missing a functionality
155             $buffer->SomeSub(); # perl dies
156            
157             # load plugin first
158             $buffer->LoadAndExpandWith('Text::Editor::Vip::Buffer::Plugins::SomePlugin') ;
159             $buffer->SomeSub(); # ok
160            
161             # load the plugin only if the sub is available. This doesn't guarantee that 'SomeSub' has been
162             # loaded from the passed Plugin.
163            
164             $buffer->ExpandedWithOrLoad('SomeSub', 'Text::Editor::Vip::Buffer::Plugins::SomePlugin') ;
165             $buffer->SomeSub(); #ok
166              
167             Returns 1 if the sub existed and 0 if it didn't and the module was loaded or the error type B generated.
168              
169             =cut
170              
171             my $buffer = shift ;
172             my $sub_name = shift ;
173             my $module = shift ;
174              
175             my $class = ref($buffer) ;
176              
177             my $sub ;
178             eval "\$sub = ${class}->can('${sub_name}') ;" ;
179              
180             if(defined $sub)
181             {
182             push @{$buffer->{EXPANSIONS}}, {CALLER => [caller()], LOOKING_FOR => $sub_name, FOUND => 1} ;
183             return(1) ;
184             }
185             else
186             {
187             $buffer->LoadAndExpandWith($module, 1) ;
188            
189             push @{$buffer->{EXPANSIONS}}, {CALLER => [caller()], LOOKING_FOR => $sub_name, LOADING_MODULE => $module} ;
190             return(0) ;
191             }
192             }
193              
194             #-------------------------------------------------------------------------------
195              
196             sub LoadAndExpandWith
197             {
198              
199             =head2 LoadAndExpandWith
200              
201             See L.
202              
203             Loads a perl module (plugin) and adds all it functionality to the buffer
204              
205             $buffer->LoadAndExpandWith('Text::Editor::Vip::Plugins::Buffer::File') ;
206            
207             # we can now read files
208             $buffer->InsertFile(__FILE__) ;
209              
210             =cut
211              
212             # look at Export::Cluster, Export::Dispatch
213              
214             my $buffer = shift ;
215             my $module = shift ;
216             my $no_history = shift ;
217              
218             eval "use $module ;" ;
219             die __PACKAGE__ . " couldn't load '$module':\n$@\n" if $@ ;
220              
221             my $class = ref($buffer) ;
222             eval "push \@${class}::ISA, '$module' ;" ;
223              
224             $buffer->PushUndoStep
225             (
226             "\$buffer->LoadAndExpandWith('$module') ;"
227             , "# undo for \$buffer->LoadAndExpandWith('$module') ;"
228             ) ;
229              
230             unless($no_history)
231             {
232             push @{$buffer->{EXPANSIONS}}, {CALLER => [caller()], LOADING_MODULE => $module} ;
233             }
234              
235             #alternative way to expend the object
236              
237             # expands the current's package ISA not the objects isa
238             #~ push @ISA, $module ;
239              
240             #~ my $class = ref($buffer) ;
241              
242             #~ my $symbole_tabel = "main::${module}::" ;
243              
244             #~ no strict ;
245             #~ if($symbole_tabel->{EXTEND_VIP_BUFFER})
246             #~ {
247             #~ for(sort @{*{$symbole_tabel->{EXTEND_VIP_BUFFER}}{ARRAY}})
248             #~ {
249             #~ if(*{$symbole_tabel->{$_}}{CODE})
250             #~ {
251             #~ print "code => $_\n" ;
252             #~ $buffer->ExpandWith($_, *{$symbole_tabel->{$_}}{CODE})
253             #~ }
254             #~ }
255             #~ }
256             }
257              
258             #-------------------------------------------------------------------------------
259              
260             sub ExpandWith
261             {
262              
263             =head2 ExpandWith
264              
265             See L.
266              
267             Adds a sub to a buffer instance.
268              
269             $buffer->ExpandWith
270             (
271             'GotoBufferStart' # member function name
272             , \&some_sub # implementaton for GotoBufferStart
273             ) ;
274            
275             # we can now go to the buffers start
276             $buffer->GotoBufferStart() ;
277              
278             The second argument is optional, if it is not given, Text::Editor::Vip::Buffer will take the sub from the caller namespace
279              
280             sub GotoBufferStart
281             {
282             my $buffer = shift ; # remember we are a plugin to an object oriented module
283             $buffer->SetModificationPosition,(0, 0) ;
284             }
285            
286             $buffer->ExpandWith( 'GotoBufferStart') ;
287             $buffer->GotoBufferStart() ;
288              
289             DEV. WARNING!
290             This is going to give us troubles when using it for macros that are saved to disk!
291             we must find a way to replug when loading the macro back
292              
293             =cut
294              
295             my $buffer = shift ;
296             my $sub_name = shift ;
297             my $sub = shift ;
298              
299             my $class = ref($buffer) ;
300              
301             my $warning = '' ;
302             local $SIG{'__WARN__'} = sub {$warning = $_[0] ;} ;
303              
304             #~ $DB::single = 1 ;
305              
306             my ($package, $file_name, $line) = caller() ;
307             $package ||= '' ;
308              
309             my $location = "$file_name:$line" ;
310              
311             if($sub)
312             {
313             die __PACKAGE__ . " not a sub reference '$sub_name' at $location:\n" unless 'CODE' eq ref $sub ;
314            
315             eval "*${class}::${sub_name} = \$sub;" ;
316             push @{$buffer->{EXPANSIONS}}, {CALLER => [caller()], SUB_REF => $sub_name} ;
317             }
318             else
319             {
320             # load the named sub from the caller package
321            
322             die __PACKAGE__ . " error exapnding with undefined named sub at $location\n" unless defined $sub_name and $sub_name ne '' ;
323              
324             my $found_sub ;
325             eval "\$found_sub = ${package}->can('${sub_name}') ;" ;
326             die __PACKAGE__ . " error exapnding with named sub '$sub_name' at $location.\n" unless defined $found_sub ;
327              
328             eval "*${class}::${sub_name} = \\\&$package\::${sub_name};" ;
329             die __PACKAGE__ . " error exapnding with named sub ''$sub_name' at $location:\n$@\n" if $@ ;
330              
331             push @{$buffer->{EXPANSIONS}}, {CALLER => [$package, $file_name, $line], SUB_REF_IN_CALLER_SPACE => $sub_name} ;
332             }
333             }
334              
335             #-------------------------------------------------------------------------------
336              
337             sub PrintExpansionHistory
338             {
339              
340             =head2 PrintExpansionHistory
341              
342             Displays the expansion done to the buffer
343              
344             =cut
345              
346             my $buffer = shift ;
347             my $message = shift || '' ;
348              
349             my ($package, $file_name, $line) = caller() ;
350             $message .= " @ '$file_name:$line'" ;
351              
352             print DumpTree($buffer->{EXPANSIONS}, $message) ;
353             }
354              
355             #-------------------------------------------------------------------------------
356              
357             sub Do
358             {
359              
360             =head2 Do
361              
362             Let you run any perl code on the buffer. The variable $buffer is made available in your perl code.
363              
364             ($result, $message) = $buffer->Do("# comment\n\$buffer->Insert('bar') ;") ;
365             is($buffer->GetText(), "bar", 'buffer contains \'bar\'' ) ;
366              
367             Returns (1) on success and (0, "error message") on failure.
368              
369             =cut
370              
371             my $buffer = shift ;
372             my $perl_script = shift || '' ;
373              
374             our $buffer = $buffer ;
375             eval $perl_script ;
376              
377             if($@)
378             {
379             $buffer->PrintError("\n* Failed evaluating buffer command *\n$perl_script\n$@\n") ;
380             return(0, $@) ;
381             }
382             else
383             {
384             return(1) ;
385             }
386             }
387              
388             #-------------------------------------------------------------------------------
389              
390             sub PrintError
391             {
392              
393             =head2 PrintError
394              
395             This sub is called when an error occures. It should be overriden by the buffer user. We use this
396             sub to abstract error handling and allow different handling dependind on the buffer user.
397              
398             If the user is a plain perl script, the error might just be logged while a dialogue might be displayed
399             if the user is a full UI.
400              
401             =cut
402              
403             my $buffer = shift ;
404             my $message = shift ;
405              
406             my ($package, $file_name, $line) = caller() ;
407              
408             confess "\n\n Using default PrintError wich dies !!\n\n$message" ;
409             }
410              
411             #-------------------------------------------------------------------------------
412              
413             sub GetText
414             {
415              
416             =head2 GetText
417              
418             Returns the buffer contents joined with "\n".
419              
420             See L.
421              
422             =cut
423              
424             my $buffer = shift ;
425              
426             my $text = '' ;
427              
428             for(0 .. ($buffer->GetNumberOfLines() - 2))
429             {
430             $text .= $buffer->GetLine($_)->{TEXT} . "\n" ;
431             }
432              
433             $text .= $buffer->GetLine(($buffer->GetNumberOfLines() - 1))->{TEXT} ;
434              
435             return($text) ;
436             }
437              
438             #-------------------------------------------------------------------------------
439              
440             sub GetTextAsArrayRef
441             {
442              
443             =head2 GetTextAsArrayRef
444              
445             Returns a copy of the buffers content as an array reference.
446              
447             See L.
448              
449             =cut
450              
451             my $buffer = shift ;
452              
453             my @text ;
454              
455             for(0 .. ($buffer->GetNumberOfLines() - 1))
456             {
457             push @text, $buffer->GetLine($_)->{TEXT} ;
458             }
459              
460             return(\@text) ;
461             }
462              
463             #-------------------------------------------------------------------------------
464              
465             sub SetLineAttribute
466             {
467              
468             =head2 SetLineAttribute
469              
470             Attaches a named attribute to a line.
471              
472             $buffer->SetLineAttribute(0, 'TEST', $some_data) ;
473             $retrieved_data = $buffer->GetLineAttribute(0'TEST', $some_data) ;
474              
475             =cut
476              
477             my ($buffer, $line, $attribute_name, $attribute) = @_ ;
478             $line = $buffer->GetModificationLine() unless defined $line ;
479              
480             $buffer->GetLine($line)->{$attribute_name} = $attribute ;
481             }
482              
483             #-------------------------------------------------------------------------------
484              
485             sub GetLineAttribute
486             {
487              
488             =head2 SetLineAttribute
489              
490             Retrieves a named attribute from a line.
491              
492             $buffer->SetLineAttribute(0, 'TEST', $some_data) ;
493             $retrieved_data = $buffer->GetLineAttribute(0, 'TEST') ;
494              
495             =cut
496              
497             my ($buffer, $line, $attribute_name, $attribute) = @_ ;
498              
499             $line = $buffer->GetModificationLine() unless defined $line ;
500              
501             return($buffer->GetLine($line)->{$attribute_name}) ;
502             }
503              
504             #-------------------------------------------------------------------------------
505              
506             =head2 MarkedBufferAsEdited
507              
508             Used to mak the buffer as edited after a modification. You should not need to use this function
509             if you access the buffer through it's interface. Which you should always do.
510              
511             =head2 MarkedBufferAsUndited
512              
513             Used to mak the buffer as unedited You should not need to use this function.
514              
515             =head2 IsBufferMarkedAsEdited
516              
517             Used to query the buffer about its state. Returns (1) if the buffer was edit. (0) otherwise.
518              
519             =head2 GetLastEditionTImestamp
520              
521             Returns the time of the last edition.
522              
523             =cut
524              
525             sub IsBufferMarkedAsEdited {return($_[0]->{MARKED_AS_EDITED}) ;}
526             sub MarkBufferAsEdited { $_[0]->{MARKED_AS_EDITED} = 1 ; $_[0]->{EDITED_AT_TIME} = Time::HiRes::time() ;}
527             sub MarkBufferAsUnedited {$_[0]->{MARKED_AS_EDITED} = 0 ;}
528              
529             sub GetLastEditionTImestamp {$_[0]->{EDITED_AT_TIME};}
530              
531             #-------------------------------------------------------------------------------
532              
533             sub GetNumberOfLines
534             {
535              
536             =head2 GetNumberOfLines
537              
538             Returns the number of lines in the buffer.
539              
540             =cut
541              
542             return($_[0]->{NODES}->GetNumberOfNodes()) ;
543             }
544              
545             #------------------------------------------------------------------------------
546              
547             sub GetLastLineIndex
548             {
549              
550             =head2 GetLastfLineIndex
551              
552             Returns theindex of the last line. the buffer always contains at least one line thus the last line index is always 0 or more.
553              
554             =cut
555              
556             return($_[0]->{NODES}->GetNumberOfNodes() - 1) ;
557             }
558              
559             #------------------------------------------------------------------------------
560              
561             sub GetModificationPosition
562             {
563              
564             =head2 GetModificationPosition
565              
566             Returns the position, line and character, where the next modification will occure.
567              
568             =cut
569              
570             return($_[0]->{MODIFICATION_LINE}, $_[0]->{MODIFICATION_CHARACTER}) ;
571             }
572              
573             #-------------------------------------------------------------------------------
574              
575             sub SetModificationPosition
576             {
577              
578             =head2 SetModificationPosition
579              
580             Sets the position, line and character, where the next modification will occure.
581              
582             $buffer->SetModificationPosition(0, 15) ;
583              
584             =cut
585              
586             my ($buffer, $line, $character) = @_ ;
587              
588             my $undo_block = new Text::Editor::Vip::CommandBlock($buffer, "\$buffer->SetModificationPosition($line, $character) ;", ' #', "# undo for \$buffer->SetModificationPosition($line, $character) ;", ' ') ;
589              
590             $buffer->SetModificationLine($line) ;
591             $buffer->SetModificationCharacter($character) ;
592             }
593              
594             #-------------------------------------------------------------------------------
595              
596             sub OffsetModificationPosition
597             {
598              
599             =head2 OffsetModificationPosition
600              
601             Offset the position, line and character, where the next modification will occure. an exception is thrown if position is not valid
602              
603             $buffer->OffsetModificationPosition(0, 15) ;
604              
605             =cut
606              
607             my ($buffer, $line_offset, $character_offset) = @_ ;
608              
609             my $undo_block = new Text::Editor::Vip::CommandBlock
610             (
611             $buffer
612             , "\$buffer->OffsetModificationPosition($line_offset, $character_offset) ;"
613             , ' #'
614             , "\$buffer->OffsetModificationPosition(-($line_offset), -($character_offset)) ;"
615             , ' '
616             ) ;
617              
618             $buffer->SetModificationLine($buffer->GetModificationLine() + $line_offset) ;
619             $buffer->SetModificationCharacter($buffer->GetModificationCharacter() + $character_offset) ;
620             }
621              
622             #-------------------------------------------------------------------------------
623              
624             sub OffsetModificationPositionGuarded
625             {
626              
627             =head2 OffsetModificationPositionGuarded
628              
629             Offsets the position, line and character, where the next modification will occure. Nothing happends if the new position is invalid
630              
631             $buffer->OffsetModificationPositionGuarded(0, 15) ;
632              
633             =cut
634              
635             my ($buffer, $line_offset, $character_offset) = @_ ;
636              
637             my $new_line = $buffer->GetModificationLine() + $line_offset ;
638             my $new_character = $buffer->GetModificationCharacter() + $character_offset ;
639              
640             if
641             (
642             $new_line < $buffer->GetNumberOfLines()
643             && 0 <= $new_line
644             && 0 <= $new_character
645             )
646             {
647             $buffer->OffsetModificationPosition($line_offset, $character_offset) ;
648             return(1) ;
649             }
650             else
651             {
652             return(0) ;
653             }
654             }
655              
656             #-------------------------------------------------------------------------------
657              
658             sub GetModificationLine
659             {
660              
661             =head2 GetModificationLine
662              
663             Returns the line where the next modification will occure.
664              
665             =cut
666              
667             return($_[0]->{MODIFICATION_LINE}) ;
668             }
669              
670             #-------------------------------------------------------------------------------
671              
672             sub SetModificationLine
673             {
674              
675             =head2 SetModificationLine
676              
677             Set the line where the next modification will occure.
678              
679             =cut
680              
681             my $buffer = shift ;
682             my $a_new_modification_line = shift ;
683              
684             my $current_line = $buffer->GetModificationLine() ;
685              
686             if
687             (
688             $a_new_modification_line < $buffer->GetNumberOfLines()
689             && 0 <= $a_new_modification_line
690             )
691             {
692             if($a_new_modification_line != $current_line)
693             {
694             PushUndoStep
695             (
696             $buffer
697             , "\$buffer->SetModificationLine($a_new_modification_line) ;"
698             , "\$buffer->SetModificationLine($current_line) ;"
699             ) ;
700            
701             $buffer->{MODIFICATION_LINE} = $a_new_modification_line ;
702             }
703             }
704             else
705             {
706             $buffer->PrintError("Invalid line index: $a_new_modification_line. Number of lines: " . $buffer->GetNumberOfLines(). "\n") ;
707             }
708             }
709              
710             #-------------------------------------------------------------------------------
711              
712             sub GetModificationCharacter
713             {
714              
715             =head2 GetModificationLine
716              
717             Returns the character where the next modification will occure.
718              
719             =cut
720              
721             my $buffer = shift ;
722             return($buffer->{MODIFICATION_CHARACTER}) ;
723             }
724              
725             #-------------------------------------------------------------------------------
726              
727             sub SetModificationCharacter
728             {
729              
730             =head2 GetModificationLine
731              
732             Sets the character where the next modification will occure.
733              
734             =cut
735              
736             my $buffer = shift ;
737             my $a_new_modification_character = shift ;
738              
739             my $current_character = $buffer->GetModificationCharacter() ;
740              
741             if(0 <= $a_new_modification_character)
742             {
743             if($a_new_modification_character != $current_character)
744             {
745             PushUndoStep
746             (
747             $buffer
748             , "\$buffer->SetModificationCharacter($a_new_modification_character) ;"
749             , "\$buffer->SetModificationCharacter($current_character) ;"
750             ) ;
751            
752             $buffer->{MODIFICATION_CHARACTER} = $a_new_modification_character ;
753             }
754             }
755             else
756             {
757             $buffer->PrintError("Invalid character index: $a_new_modification_character\n") ;
758             }
759             }
760              
761             #-------------------------------------------------------------------------------
762              
763             sub GetLine
764             {
765              
766             =head2 GetLine
767              
768             Returns the Line object used by the buffer. This is a private sub and should not be used directly.
769              
770             See L.
771              
772             =cut
773              
774             my $buffer = shift ;
775             my $a_line_index = shift ;
776              
777             return( $buffer->{NODES}->GetNodeData($a_line_index) ) ;
778             }
779              
780             #-------------------------------------------------------------------------------
781              
782             sub GetLineText
783             {
784              
785             =head2 GetLineText
786              
787             Returns the text of the line passes as argument or the current modification line if no argument is passed.
788              
789             my $line_12_text = $buffer->GetLineText(12) ;
790             my $current_line_text = $buffer->GetLineText() ;
791              
792             =cut
793              
794             my $buffer = shift ;
795             my $a_line_index = shift ;
796              
797             $a_line_index = $buffer->GetModificationLine() unless defined $a_line_index ;
798              
799             if(0 <= $a_line_index && $a_line_index < $buffer->GetNumberOfLines())
800             {
801             return($buffer->GetLine($a_line_index)->{TEXT}) ;
802             }
803             else
804             {
805             $buffer->PrintError("GetLineText: Invalid line index: $a_line_index. Number of lines: " . $buffer->GetNumberOfLines(). "\n") ;
806             return('') ;
807             }
808             }
809              
810             #-------------------------------------------------------------------------------
811              
812             sub GetLineTextWithNewline
813             {
814              
815             =head2 GetLineTextWithNewline
816              
817             Returns the text of the line passes as argument or the current modification line if no argument is passed. A "\n" is
818             appended if the line is not the last line in the buffer.
819              
820             my $line_12_text = $buffer->GetLineTextWithNewline(12) ;
821             my $current_line_text = $buffer->GetLineTextWithNewline() ;
822              
823             =cut
824              
825             my $buffer = shift ;
826             my $a_line_index = shift ;
827              
828             $a_line_index = $buffer->GetModificationLine() unless defined $a_line_index ;
829              
830             if(0 <= $a_line_index && $a_line_index < $buffer->GetNumberOfLines())
831             {
832             if($a_line_index == $buffer->GetLastLineIndex())
833             {
834             return($buffer->GetLine($a_line_index)->{TEXT}) ;
835             }
836             else
837             {
838             return($buffer->GetLine($a_line_index)->{TEXT} . "\n") ;
839             }
840             }
841             else
842             {
843             $buffer->PrintError("GetLineText: Invalid line index: $a_line_index. Number of lines: " . $buffer->GetNumberOfLines(). "\n") ;
844             return('') ;
845             }
846             }
847              
848             #-------------------------------------------------------------------------------
849              
850             sub GetLineLength
851             {
852              
853             =head2 GetLineLength
854              
855             Returns the length of the text of the line passes as argument or the current modification line if no argument is passed.
856              
857             my $line_12_text = $buffer->GetLineText(12) ;
858             my $current_line_text = $buffer->GetLineText() ;
859              
860             =cut
861              
862             my $buffer = shift ;
863             my $a_line_index = shift ;
864              
865             $a_line_index = $buffer->GetModificationLine() unless defined $a_line_index ;
866              
867             return(length($buffer->GetLineText($a_line_index))) ;
868             }
869              
870             #-------------------------------------------------------------------------------
871              
872             sub Backspace
873             {
874              
875             =head2 Backspace
876              
877             Deletes characters backwards. The number of characters to delete is passed as an argument.
878             Doing a Backspace while at the begining of a line warps to the previous line.
879              
880             =cut
881              
882             my $buffer = shift ;
883             my $number_of_character_to_delete = shift || 0 ;
884              
885             return if 0 >= $number_of_character_to_delete ;
886              
887             my $undo_block = new Text::Editor::Vip::CommandBlock($buffer, "\$buffer->Backspace($number_of_character_to_delete) ;", ' #', "# undo for \$buffer->Backspace($number_of_character_to_delete)", ' ') ;
888              
889             if($buffer->{SELECTION}->IsEmpty())
890             {
891             for (1 .. $number_of_character_to_delete)
892             {
893            
894             my $current_line = $buffer->GetModificationLine() ;
895             my $current_position = $buffer->GetModificationCharacter() ;
896              
897             if($current_position != 0)
898             {
899             $buffer->SetModificationCharacter($current_position - 1) ;
900            
901             if($current_position <= $buffer->GetLineLength($current_line))
902             {
903             $buffer->Delete(1) ;
904             }
905             #else
906             #after end of line, already modified position
907             }
908             else
909             {
910             if($current_line != 0)
911             {
912             $buffer->SetModificationLine($current_line -1) ;
913            
914             #Move to end of line
915             $buffer->SetModificationCharacter
916             (
917             $buffer->GetLineLength
918             (
919             $buffer->GetModificationLine()
920             )
921             ) ;
922            
923             $buffer->Delete(1) ;
924             }
925             #else
926             # at first line
927             }
928             }
929             }
930             else
931             {
932             $buffer->DeleteSelection() ;
933             $buffer->Backspace($number_of_character_to_delete - 1) ;
934             }
935             }
936              
937             #-------------------------------------------------------------------------------
938              
939             sub ClearLine
940             {
941              
942             =head2 ClearLine
943              
944             Removes all text from the passed line index or the current modification line if no argument is given.
945             The line itself is not deleted and the modification position is not modified.
946              
947             $buffer->ClearLine(0) ;
948              
949             =cut
950              
951             my $buffer = shift ;
952             my $line_index = shift ;
953              
954             $line_index = $buffer->GetModificationLine() unless defined $line_index ;
955              
956             my $modification_line = $buffer->GetModificationLine() ;
957             my $modification_character = $buffer->GetModificationCharacter() ;
958              
959             if(0 <= $line_index && $line_index < $buffer->GetNumberOfLines())
960             {
961             my $line = $buffer->GetLine($line_index) ;
962             my $text = $line->{TEXT} ;
963             $line->{TEXT} = '' ;
964            
965             $buffer->MarkBufferAsEdited() ;
966            
967             PushUndoStep
968             (
969             $buffer
970             , "\$buffer->ClearLine($line_index) ;"
971             , [
972             "\$buffer->SetModificationPosition($line_index, 0) ;"
973             , '$buffer->Insert("' . Stringify($text) .'") ;'
974             , "\$buffer->SetModificationPosition($modification_line, $modification_character) ;"
975             ]
976            
977             ) ;
978             }
979             else
980             {
981             $buffer->PrintError("GetLineText: Invalid line index: $line_index. Number of lines: " . $buffer->GetNumberOfLines(). "\n") ;
982             }
983             }
984              
985             #-------------------------------------------------------------------------------
986              
987             sub Delete
988             {
989              
990             =head2 Delete
991              
992             Deleted, from the modification position, the number of characters passed as argument.
993              
994             Deletes the selection if it exists; the deleted selection decrements the number of character to delete argument
995              
996             =cut
997              
998             my $buffer = shift ;
999             my $a_number_of_character_to_delete = shift || 0 ;
1000              
1001             return if 0 >= $a_number_of_character_to_delete ;
1002              
1003             my $undo_block = new Text::Editor::Vip::CommandBlock($buffer, "\$buffer->Delete($a_number_of_character_to_delete) ;", ' #', "# undo for \$buffer->Delete($a_number_of_character_to_delete)", ' ') ;
1004              
1005             unless($buffer->{SELECTION}->IsEmpty())
1006             {
1007             $buffer->DeleteSelection() ;
1008             $a_number_of_character_to_delete-- ;
1009             }
1010              
1011             return if 0 >= $a_number_of_character_to_delete ;
1012              
1013             my ($modification_line, $modification_character) = $buffer->GetModificationPosition() ;
1014             my $line_length = $buffer->GetLineLength() ;
1015              
1016             if($modification_character < $line_length)
1017             {
1018             my $line_ref = \($buffer->GetLine($modification_line)->{TEXT}) ;
1019            
1020             my $character_to_delete_on_this_line = min
1021             (
1022             $line_length - $modification_character
1023             , $a_number_of_character_to_delete
1024             ) ;
1025             my $deleted_text = substr
1026             (
1027             $$line_ref
1028             , $modification_character
1029             , $character_to_delete_on_this_line
1030             , ''
1031             ) ;
1032            
1033             PushUndoStep
1034             (
1035             $buffer
1036             , "# deleting in current line"
1037             , [
1038             '$buffer->Insert("' . Stringify($deleted_text) . '") ;'
1039             , "\$buffer->SetModificationPosition($modification_line, $modification_character) ;"
1040             ]
1041             ) ;
1042            
1043             $a_number_of_character_to_delete -= $character_to_delete_on_this_line ;
1044             }
1045             else
1046             {
1047             # at end of line, copy next line to this line
1048            
1049             return if $modification_line == ($buffer->GetNumberOfLines() - 1) ;
1050            
1051             $buffer->Insert($buffer->GetLine($modification_line + 1)->{TEXT}) ;
1052             $buffer->DeleteLine($modification_line + 1) ;
1053             $buffer->SetModificationPosition($modification_line, $modification_character) ;
1054            
1055             $a_number_of_character_to_delete-- ; # delete '\n'
1056             }
1057            
1058             if($a_number_of_character_to_delete)
1059             {
1060             $buffer->Delete($a_number_of_character_to_delete) ;
1061             }
1062              
1063             $buffer->MarkBufferAsEdited() ;
1064             }
1065              
1066             #-------------------------------------------------------------------------------
1067              
1068             sub DeleteLine
1069             {
1070              
1071             =head2 DeleteLine
1072              
1073             Deleted, the line passed as argument. if no argument is passed, the current line is deleted.
1074             The selection and modification position are not modified.
1075              
1076             =cut
1077              
1078             my $buffer = shift ;
1079             my $a_line_to_delete_index = shift ;
1080              
1081             $a_line_to_delete_index = $buffer->GetModificationLine() unless defined $a_line_to_delete_index ;
1082              
1083             return if $buffer->GetNumberOfLines() == 1 ; # buffer always has at least one line
1084              
1085             my ($modification_line, $modification_character) = $buffer->GetModificationPosition() ;
1086              
1087             my $text = Stringify($buffer->GetLineText($a_line_to_delete_index)) ;
1088              
1089             my $undo_block = new Text::Editor::Vip::CommandBlock($buffer, "# DeleteLine", ' ', '# undo for DeleteLine', ' ') ;
1090              
1091             if($a_line_to_delete_index != ($buffer->GetNumberOfLines() - 1))
1092             {
1093             PushUndoStep
1094             (
1095             $buffer
1096             , "\$buffer->DeleteLine($a_line_to_delete_index) ;"
1097             , [
1098             "\$buffer->SetModificationPosition($a_line_to_delete_index, 0) ;"
1099             , "\$buffer->Insert(\"$text\\n\") ;"
1100             , "\$buffer->SetModificationPosition($modification_line, $modification_character) ;"
1101             ]
1102             ) ;
1103             }
1104             else
1105             {
1106             #deleting last line
1107             my $previous_line = $a_line_to_delete_index - 1 ;
1108             my $end_of_previous_line = $buffer->GetLineLength($previous_line) ;
1109            
1110             PushUndoStep
1111             (
1112             $buffer
1113             , "\$buffer->DeleteLine($a_line_to_delete_index) ;"
1114             , [
1115             "\$buffer->SetModificationPosition($previous_line, $end_of_previous_line) ;"
1116             , "\$buffer->Insert(\"\\n$text\") ;"
1117             , "\$buffer->SetModificationPosition($modification_line, $modification_character) ;"
1118             ]
1119             ) ;
1120             }
1121            
1122             $buffer->{NODES}->DeleteNode($a_line_to_delete_index) if $buffer->GetNumberOfLines() > 1 ;
1123             $buffer->MarkBufferAsEdited() ;
1124             }
1125              
1126             #-------------------------------------------------------------------------------
1127              
1128             sub InsertNewLine
1129             {
1130              
1131             =head2 InsertNewLine
1132              
1133             Inserts a new line at the modification position. If the modification position is after the end of the
1134             current line, spaces are used to pad the current line.
1135              
1136             InsertNewLine takes one parameter that can be set to SMART_INDENTATION or NO_SMART_INDENTATION.
1137             If SMART_INDENTATION is used (default) , B is called. B does nothing by default.
1138             This lets you define your own indentation strategy. See B.
1139              
1140             $buffer->Insert("hi\nThere\nWhats\nYour\nName\n") ;
1141              
1142             =cut
1143              
1144             my $buffer = shift ;
1145             my $use_smart_indentation = shift || SMART_INDENTATION ;
1146              
1147             my $undo_block = new Text::Editor::Vip::CommandBlock($buffer, "InsertNewLine(\$buffer, $use_smart_indentation) ;", ' #', '# undo for InsertNewLine($use_smart_indentation)', ' ') ;
1148              
1149             my ($modification_line, $modification_character) = $buffer->GetModificationPosition() ;
1150              
1151             my $buffer_line = $buffer->GetLine($modification_line) ;
1152             my $buffer_line_text = $buffer_line->{TEXT} ;
1153              
1154             my $next_line_text = '' ;
1155              
1156             if($modification_character < length $buffer_line_text)
1157             {
1158             $next_line_text = substr($buffer_line_text, $modification_character) ;
1159             }
1160              
1161             $buffer_line_text = substr($buffer_line_text, 0, $modification_character) ;
1162            
1163             $buffer_line->{TEXT} = $buffer_line_text ;
1164             $buffer->{NODES}->InsertAfter($modification_line, {TEXT => $next_line_text} ) ;
1165              
1166             $buffer->SetModificationPosition($modification_line + 1, 0) ;
1167              
1168             PushUndoStep
1169             (
1170             $buffer
1171             , "\$buffer->InsertNewLine($use_smart_indentation) ;"
1172             , '$buffer->Backspace(1) ;'
1173             ) ;
1174              
1175             $buffer->IndentNewLine($modification_line + 1) if $use_smart_indentation ;
1176              
1177             $buffer->MarkBufferAsEdited() ;
1178             }
1179              
1180             #-------------------------------------------------------------------------------
1181              
1182             sub Stringify
1183             {
1184              
1185             =head2 Stringify
1186              
1187             Quotes a string or an array of string so it can be serialized in perl code
1188              
1189             =cut
1190              
1191             my $text_to_stringify = shift ;
1192             $text_to_stringify = '' unless defined $text_to_stringify ;
1193              
1194             my $stringified_text = '' ;
1195              
1196             my @text_to_stringify = ref($text_to_stringify) eq 'ARRAY' ? @$text_to_stringify: ($text_to_stringify) ;
1197              
1198             for(@text_to_stringify)
1199             {
1200             s/\\/\\\\/g ;
1201            
1202             s/\$/\\\$/g ;
1203             s/\@/\\\@/g ;
1204             s/"/\\"/g ;
1205            
1206             s/\n/\\n/g ;
1207             s/\t/\\t/g ;
1208             s/\r/\\r/g ;
1209              
1210             $stringified_text .= $_ ;
1211             }
1212            
1213             return($stringified_text) ;
1214             }
1215              
1216             #-------------------------------------------------------------------------------
1217              
1218             sub Insert
1219             {
1220              
1221             =head2 Insert
1222              
1223             Inserts a string or a list of strings, passed as an array reference, into the buffer.
1224              
1225             $buffer->Insert("bar") ;
1226            
1227             my @text = ("Someone\n", "wants me\nNow") ;
1228             $buffer->Insert(\@text);
1229            
1230             $buffer->Insert("\t something \n new") ;
1231              
1232             Only "\n" is considered special and forces the addition of a new line in the buffer.
1233              
1234             B takes a second argument . When set to SMART_INDENTATION (the default),
1235             B is called to indent the newly inserted line. The default B
1236             does nothing but you can override it to implement any indentation you please. If you want to
1237             insert raw text, pass NO_SMART_INDENTATION as a second argument.
1238              
1239             NO_SMART_INDENTATION is defined in Text::Editor::Vip::Buffer::Constants.
1240              
1241             =cut
1242              
1243             my $buffer = shift ;
1244             my $text_to_insert = shift ;
1245             my $use_smart_indentation = shift || SMART_INDENTATION ;
1246              
1247             $text_to_insert ='' unless defined $text_to_insert ;
1248             my @text_to_insert ;
1249              
1250             if(ref($text_to_insert) eq 'ARRAY')
1251             {
1252             @text_to_insert = @$text_to_insert ;
1253             }
1254             else
1255             {
1256             @text_to_insert = ($text_to_insert) ;
1257             }
1258              
1259             my $stringified_text_to_insert = Stringify($text_to_insert);
1260              
1261             my $undo_block = new Text::Editor::Vip::CommandBlock
1262             (
1263             $buffer
1264             , "\$buffer->Insert(\"$stringified_text_to_insert\", $use_smart_indentation) ;", ' #'
1265             , "# undo for \$buffer->Insert(\"$stringified_text_to_insert\", $use_smart_indentation)", ' '
1266             ) ;
1267              
1268             $buffer->DeleteSelection() ;
1269              
1270             for(@text_to_insert)
1271             {
1272             for(split /(\n)/) # transform a\nb\nccc into 3 lines
1273             {
1274             if("\n" eq $_)
1275             {
1276             $buffer->InsertNewLine($use_smart_indentation) ;
1277             }
1278             else
1279             {
1280             my $line_ref = \($buffer->GetLine($buffer->GetModificationLine())->{TEXT}) ;
1281             my $modification_character = $buffer->GetModificationCharacter() ;
1282             my $line_length = length($$line_ref) ;
1283            
1284             #do we need padding
1285             if($modification_character - $line_length > 0)
1286             {
1287             $buffer->SetModificationCharacter($line_length) ;
1288             $buffer->Insert(' ' x ($modification_character - $line_length)) ;
1289             }
1290            
1291             # insert characters
1292             substr($$line_ref, $modification_character, 0, $_) ;
1293            
1294             my $text_to_insert_length = length($_) ;
1295             $stringified_text_to_insert = Stringify($_);
1296            
1297             PushUndoStep
1298             (
1299             $buffer
1300             , "\$buffer->Insert(\"$stringified_text_to_insert\", $use_smart_indentation) ;"
1301             , "\$buffer->Delete($text_to_insert_length) ;"
1302             ) ;
1303            
1304             $buffer->SetModificationCharacter($modification_character + length()) ;
1305             }
1306             }
1307            
1308             $buffer->MarkBufferAsEdited() ;
1309             }
1310             }
1311              
1312             #-------------------------------------------------------------------------------
1313              
1314             1 ;
1315              
1316             =head1 PLUGINS
1317              
1318             Vip::Buffer has a very simple plugin system. You can add a function to the buffer with
1319             L, L and L. The functions
1320             added through plugins are made available to the instance, calling the plugin sub, only.
1321              
1322             Think of it as a late inheritence that does the job it needs to do.
1323              
1324             Perl is full of wonders.
1325              
1326             =head1 BUGS
1327              
1328             =head1 AUTHOR
1329              
1330             Khemir Nadim ibn Hamouda
1331             CPAN ID: NKH
1332             mailto:nadim@khemir.net
1333             http:// no web site
1334              
1335             =head1 COPYRIGHT
1336              
1337             This program is free software; you can redistribute
1338             it and/or modify it under the same terms as Perl itself.
1339              
1340             The full text of the license can be found in the
1341             LICENSE file included with this module.
1342              
1343             =cut