The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
#!/usr/bin/perl
# A simple syntax highlighting editor for perl.
# This example demonstrates using Perl FLTK to make a fancy text editor.
# It syntax highlights Perl code (poorly), showing how to use the 
# Fl_Text_Buffer and Fl_Text_Editor widget

# Import FLTK and export the necessary tag group constants.
use FLTK qw( :Keytypes :Colors :Fonts :Utils);

# Make sure we got an argument.
$in = shift;

# Set a style table. A style table is an array of array references, declared 
# here as a series of anonymous array refs (as indicated by brackets). Each 
# array ref contains a color, a font name, and a size. When added to the 
# Fl_Text_Editor widget these entries will be referenced in alphabetical 
# order starting with 'A'. So, the first entry in the style table will be 
# refered to as 'A', the second as 'B', etc, etc.
@styles = ([FL_BLACK, FL_COURIER, 12],
           [FL_RED, FL_COURIER, 12],
           [FL_BLUE, FL_COURIER, 12],
           [fl_rgb(34, 139, 34), FL_COURIER_BOLD, 12],
           [fl_rgb(0, 130, 255), FL_COURIER_ITALIC, 12]);

# Create the text and style buffers.
$sb = new Fl_Text_Buffer();
$tb = new Fl_Text_Buffer();

# The load_file() member function is a pure perl addition to this class. It
# takes a filename, slurps the file's contents into a string and passes that
# to Fl_Text_Buffer::text().
if($in) {
  $tb->load_file($in);
} else {
  $tb->text("#!/usr/bin/perl\n");
}
$tb->tab_distance(2);

# Grab the highlight data, we'll pad the buffer with 'A', our default, to 
# start.
$hdata = $tb->text();
$hdata =~ s/(.)/A/gs;
$sb->text($hdata);

# This is the highlighting routine
do_highlight();

# Set a modify callback on our main text buffer.
$tb->add_modify_callback(\&my_mcb);

# Start the GUI
$window = new Fl_Window(400,480);
$edit = new Fl_Text_Editor(0,5,400,440);
# Add the highlighting data to the Editor widget. Pass it the style 
# Fl_Text_Buffer, the style table (NOTE: It's passed by reference.), a 
# 'unstyled character, and a unstyled callback. The last two arguments are 
# not used, but must be here, they are currently disabled in FLTK 2, but that
# is probably a temporary situation. FLTK hackers will note the absence of 
# the style table size, this is not needed because the size is calculated 
# when translating the style array into the interal structure representation.
$edit->highlight_data($sb, \@styles, 'x', sub { print "no op\n";});
$edit->buffer($tb);

# Add a key binding to the editor. Doesn't really do anything in this example
# except to show how to make key bindings.
$edit->add_key_binding(FL_Enter, 0, \&kb_sub);
$edit->move_down();
$edit->end();
$btn = new Fl_Button(350, 450, 45, 25, "Quit");
$btn->callback(sub { exit;});
$window->resizable($edit);
$window->end();
$window->show();
Fl::run();

# An example Fl_Text_Editor key binding. Key bindings are passed the integer
# position of the cursor in the Text Buffer and an object reference to the 
# editor widget that called the function. Key Bindings work like FLTK's 
# widget handler() routine, returning 1 if the function handles the keypress
# or 0 if not. This example just calls the default key binding.
sub kb_sub {
  my ($i, $e) = @_;
  return $e->kf_enter($i, $e);
}

# A modify callback routine for Fl_Text_Buffer. It recieves the cursor 
# position in the buffer, a flag if the change is an insertion, deletion, or
# a restyled change. The final argument is the text deleted in a delete 
# modification is signalled.
sub my_mcb {
  my ($pos, $nInserted, $nDeleted, $nRestyled, $text) = @_;
  if($nInserted) {
    $sb->insert($pos, 'A');
    do_highlight();
    $edit->redraw();
  } elsif($nDeleted) {
    $sb->remove($pos, ($pos + 1));
    do_highlight();
    $edit->redraw();
  }
}

# Cheap, inefficient Perl syntax highlighter. Viewing anything but perl code
# with this editor will look pretty crappy.
sub do_highlight {
  # Some perl keywords
  my @keywds = qw( if shift while do for return new print my sub open foreach use package elsif else );
  my $pos = 0;
  my $newpos = 0;
  my $wend;
  my $newbuf;
  my $nextchar;

  # Search for all instances of each keyword in the list and highlight them 
  # with the 4th item in our style table.
  foreach my $wrd (@keywds) {
    $pos = 0;
    while($tb->search_forward($pos, $wrd, $newpos, 1)) {
      $wend = $tb->word_end($newpos);
      $newbuf = $tb->text_range($newpos, $wend);
      # The next bit of code makes sure this is the actual word and isn't
      # a larger word with the keyword in it (e.g. sub, not subtract).
      while($newbuf ne $wrd) {
        $wend--;
        $newbuf = $tb->text_range($newpos, $wend);
      }
      $nextchar = $tb->character(($wend));
      if($nextchar eq " " || $nextchar eq "(" || $nextchar eq "{" || $nextchar eq ";") {
        $newbuf =~ s/(.)/D/g;
        $sb->replace($newpos, $wend, $newbuf);
      }
      $pos = $newpos + 1;
    }
  }
  $pos = 0;
  # Highlight all quoted strings with item 2 of the style table
  while($tb->search_forward($pos, '"', $newpos)) {
    if($tb->search_forward(($newpos+1), '"', $wend)) {
      $newbuf = $tb->text_range($newpos, $wend);
      $newbuf =~ s/(.)/B/g;
      $sb->replace($newpos, $wend, $newbuf);
      $pos = $wend + 1;
    } else {
      $pos++;;
    }
  }
  $pos = 0;
  # Highlight all qw(...) lists like strings.
  while($tb->search_forward($pos, 'qw(', $newpos)) {
    if($tb->search_forward(($newpos+1), ')', $wend)) {
      $newbuf = $tb->text_range($newpos, ($wend + 1));
      $newbuf =~ s/(.)/B/g;
      $sb->replace($newpos, ($wend + 1), $newbuf);
      $pos = $wend + 1;
    } else {
      $pos++;
    }
  }

  # Highlight the variables, arrays, hashes, and function references with the
  # third style item.
  $pos = 0;
  while($tb->search_forward($pos, '$', $newpos)) {
    $wend = $tb->word_end(($newpos+1));
    if($tb->character(($wend)) =~ m/([\!\@\/\_])/) {
      $wend++;
    }
    $newbuf = $tb->text_range($newpos, $wend);
    $newbuf =~ s/(.)/C/g;
    $sb->replace($newpos, $wend, $newbuf);
    $pos = $wend + 1;
  }
  $pos = 0;
  while($tb->search_forward($pos, '@', $newpos)) {
    $wend = $tb->word_end(($newpos+1));
    if($tb->character(($wend)) =~ m/([\!\@\/\_])/) {
      $wend++;
    }
    $newbuf = $tb->text_range($newpos, $wend);
    $newbuf =~ s/(.)/C/g;
    $sb->replace($newpos, $wend, $newbuf);
    $pos = $wend + 1;
  }
  $pos = 0;
  while($tb->search_forward($pos, '%', $newpos)) {
    $wend = $tb->word_end(($newpos+1));
    if($tb->character(($wend)) =~ m/([\!\@\/\_])/) {
      $wend++;
    }
    $newbuf = $tb->text_range($newpos, $wend);
    $newbuf =~ s/(.)/C/g;
    $sb->replace($newpos, $wend, $newbuf);
    $pos = $wend + 1;
  }
  $pos = 0;
  while($tb->search_forward($pos, '&', $newpos)) {
    $wend = $tb->word_end(($newpos+1));
    if($tb->character(($wend)) =~ m/([\!\@\/\_])/) {
      $wend++; 
    }
    $newbuf = $tb->text_range($newpos, $wend);
    $newbuf =~ s/(.)/C/g;
    $sb->replace($newpos, $wend, $newbuf);
    $pos = $wend + 1;
  } 

  # Highlight comments
  $pos = 0;
  while($tb->search_forward($pos, '#', $newpos)) {
    if($tb->character(($newpos - 1)) ne '$') {
      $wend = $tb->line_end($newpos);
      $newbuf = $tb->text_range($newpos, $wend);
      $newbuf =~ s/(.)/E/g;
      $sb->replace($newpos, $wend, $newbuf);
      $pos = $wend;
    } else {
      $pos = $newpos + 1;
    }
  }
}