/usr/share/gps/plug-ins/text_utils.py is in gnat-gps-common 5.3dfsg-1ubuntu1.
This file is owned by root:root, with mode 0o644.
The actual contents of the file can be viewed below.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 | """Defines editor-specific functions
YOU SHOULD ALMOST ALWAYS LOAD THIS FILE
This script defines a number of python functions and GPS actions that can
be used inside GPS editors. These can be used to move the cursor around or
edit the text.
They are often programmed so that they emulate the Emacs editor, but they
are independent of the Emacs mode and do not override any key shortcut. As
a result you can define your own shortcuts for the actions defined in this
package.
See also emacs.xml
"""
############################################################################
## No user customization below this line
############################################################################
import GPS
import string, traceback
import navigation_utils
import gtk
from gps_utils import *
should_extend_selection = False
# Whether the selection should be extended when moving the cursor
GPS.Preference ("Plugins/emacs/transient_mark").create (
"Transient Mark", "boolean",
"""If unset, the selected region is never unselected when the clipboard is modified by a Cut/Copy/Paste operation. This is broadly similar to the Emacs mode with the same name""", False)
GPS.Preference("Plugins/emacs/bgcolor").create(
"Background color", "color",
"""Background color for popup windows (zap-to-char,...)""",
"yellow")
def replace (frm, to, text):
"""Replace a part of the buffer by the given text"""
frm.buffer ().delete (frm, to)
frm.buffer ().insert (frm, text)
def goto_subprogram_start (cursor):
"""Return an EditorLocation corresponding to the subprogram in which
cursor is"""
blocks = {"CAT_PROCEDURE":1, "CAT_FUNCTION":1, "CAT_ENTRY":1,
"CAT_PROTECTED":1, "CAT_TASK":1, "CAT_PACKAGE":1}
if cursor.block_type() == "CAT_UNKNOWN":
return None
min = cursor.buffer().beginning_of_buffer()
while not blocks.has_key (cursor.block_type()) and cursor > min:
cursor = cursor.block_start() - 1
if cursor > min: return cursor
else: return None
def get_local_vars (subprogram):
"""Return a list of GPS.Entity that are variables local to the
subprogram. It might not work accurately with nested subprograms"""
result = []
if subprogram:
locFile = subprogram.body().file()
locFrom = subprogram.body().line()
locTo = subprogram.end_of_scope().line()
for e in locFile.entities (local=True):
decl = e.declaration()
if not e.is_type() \
and decl.file() == locFile \
and decl.line() >= locFrom \
and decl.line() <= locTo:
result.append (e)
return result
def delete_until_char(char, buffer=None):
"""Delete all characters forward from the current cursor position,
until CHAR is seen. CHAR itself is also deleted.
If the current character is CHAR, it is skipped and the next
occurrences of CHAR is searched.
"""
if not buffer:
buffer = GPS.EditorBuffer.get()
start = buffer.current_view().cursor()
end = start + 1
while end.get_char() != char:
end = end + 1
buffer.delete (start, end)
@interactive("Editor", name="zap to char")
class Zap_To_Char(CommandWindow):
"""Deletes all characters from the cursor position up to and including
the next occurrence of a character. The character is queried
interactively
"""
def __init__(self):
CommandWindow.__init__(
self,
prompt="Zap to char:",
on_changed=self.on_changed)
self.set_background(GPS.Preference("Plugins/emacs/bgcolor").get())
@with_save_excursion
def on_changed(self, input, cursor_pos):
delete_until_char(char=input)
self.destroy()
@interactive ("Editor", name="toggle wrapping")
def toggle_editor_wrapping():
"""Toggle word wrapping in the current editor"""
buffer = GPS.EditorBuffer.get ()
v = buffer.current_view()
from pygps import get_widgets_by_type
text_view = get_widgets_by_type(gtk.TextView, v.pywidget())[0]
if text_view.get_wrap_mode() == gtk.WRAP_NONE:
text_view.set_wrap_mode(gtk.WRAP_WORD)
else:
text_view.set_wrap_mode(gtk.WRAP_NONE)
@interactive ("Editor", in_ada_file, name="subprogram box")
@with_save_excursion
def add_subprogram_box():
"""Search backward for the first subprogram or package declaration. Before the start of this declaration, insert a comment box containing the name of the subprogram. This provides helpful separations between subprograms, and is similar to the style used in the GNAT compiler or GPS themselves"""
buffer = GPS.EditorBuffer.get ()
loc = goto_subprogram_start (buffer.current_view().cursor())
if loc:
name = loc.block_name()
loc = loc.block_start().beginning_of_line();
dashes = '-' * (len (name) + 6)
box = dashes + "\n" + "-- " + name + " --\n" + dashes + "\n\n"
buffer.insert (loc, box)
buffer.indent (loc, loc.forward_line (3))
@interactive ("Editor", "Source editor", name="select line")
def select_line():
"""Select the current line in the current editor, including trailing newline
This moves the cursor to the end of the line"""
buffer = GPS.EditorBuffer.get ()
loc = buffer.current_view ().cursor ()
buffer.select (loc.beginning_of_line(), loc.end_of_line() + 1)
def get_selection_or_buffer (buffer=None):
"""If a selection exists, returns its beginning and end. Otherwise
return the beginning and end of buffer.
The buffer is returned as the first field of the tuple"""
if not buffer:
buffer = GPS.EditorBuffer.get ()
start = buffer.selection_start ()
end = buffer.selection_end ()
if start == end:
return (buffer, buffer.beginning_of_buffer (), buffer.end_of_buffer ())
else:
return (buffer, start, end)
def get_selection_or_word (buffer=None):
"""If a selection exists, returns its beginning and end. Otherwise
return the beginning and end of the current word..
The buffer is returned as the first field of the tuple"""
if not buffer:
buffer = GPS.EditorBuffer.get ()
start = buffer.selection_start ()
end = buffer.selection_end ()
if start == end:
loc = buffer.current_view().cursor ()
return (buffer, goto_word_start (loc), goto_word_end (loc))
else:
return (buffer, start, end)
def get_selection_or_line (buffer, location):
"""If a selection exists, returns its beginning and end. Otherwise
return the beginning and end of line.
The buffer is returned as the first field of the tuple"""
if isinstance (location, FileLocation):
location = GPS.EditorLocation(buffer, location.line(), location.column())
buffer = location.buffer ()
start = buffer.selection_start ()
end = buffer.selection_end ()
if start == end:
return (buffer, location.beginning_of_line (), location.end_of_line ())
else:
return (buffer, start, end)
@interactive ("Editor", "Source editor", name="Move block right",
menu="/Edit/Selection/Move right", key="control-alt-greater")
def move_block (chars=1):
"""Move the current selection chars characters to the right. If chars
is negative, moves to the left. If there is no selection, indent
the current line."""
buffer = GPS.EditorBuffer.get ()
tab_width = 8
# Determine extents of the selection
start_line = buffer.selection_start().line()
end_line = buffer.selection_end().line()
beg_loc = buffer.selection_start().beginning_of_line()
end_loc = buffer.selection_end().end_of_line()
had_selection = not (buffer.selection_start() == buffer.selection_end())
if not had_selection:
cursor_loc = buffer.current_view ().cursor()
cursor_line = cursor_loc.line()
cursor_col = cursor_loc.column()
end_loc = end_loc.forward_char(-1)
text = buffer.get_chars(beg_loc, end_loc)
newtext = []
for line in text.split('\n'):
if chars > 0:
# Insert x chars at the beginning of the line
newtext += [" " * chars + line]
else:
# ... remove x blanks from the beginning of the text ...
for c in range (-chars):
if line == "":
break
if line[0] == '\t':
line = " " * (tab_width - 1) + line[1:]
elif line[0] == ' ':
line = line[1:]
else:
break
newtext += [line]
buffer.start_undo_group ()
buffer.delete (beg_loc, end_loc)
buffer.insert (EditorLocation (buffer, start_line, 1), "\n".join(newtext))
buffer.finish_undo_group ()
if had_selection:
# Reselect the range of lines
start_loc = EditorLocation (buffer, start_line, 1)
end_loc = EditorLocation (buffer, end_line, 1).end_of_line()
buffer.select (start_loc, end_loc)
else:
# Replace the cursor
buffer.current_view().goto (
EditorLocation (buffer,
cursor_line,
max (0, cursor_col + chars)))
make_interactive (lambda:move_block(-1),
category="Editor", filter="Source editor",
menu="/Edit/Selection/Move left", key="control-alt-less",
name="Move block left")
@interactive("Editor", "Source editor", menu="/Edit/Selection/Untabify")
@with_save_excursion
def untabify ():
"""Replace tab characters in the current selection (or the whole buffer)
with the correct amount of spaces. The tab stops are every n columns
where n is specified by a preference in the Preferences dialog"""
tab_width = 8
buffer, start, end = get_selection_or_buffer ()
while start < end:
start = start.search ("\t", dialog_on_failure=False)
if not start:
break
size = tab_width - ((start [0].column() - 1) % tab_width)
replace (start [0], start [1] - 1, " " * size)
start = start [1]
def lines_with_digit (buffer, loc, max=None):
"""Return an EditorLocation pointing to the last line adjacent to
loc that contains a digit in the same column as loc. See description
of serialize () for an example. The search can be limited to a
specific max location"""
if max:
max = max.end_of_line ()
else:
max = buffer.end_of_buffer ()
col = loc.column () - 1
loc2 = loc.end_of_line () + 1 # to beginning of next line
while loc2 < max:
eol = loc2.end_of_line ()
check = loc2 + col
if check > eol or not buffer.get_chars (check, check).isdigit():
return loc2 - 1
loc2 = eol + 1 # to beginning of next line
return max
@interactive ("Editor", "Source editor", "/Edit/Rectangle/Serialize")
@with_save_excursion
def serialize (increment=1):
"""Increment a set of numbers found on adjacent lines.
The exact behavior depends on whether there is a current selection
or not.
If there is no selection, then the set of lines considered is from
the current line on and includes all adjacent lines that have at
least one digit in the original columns. In the following example,
| marks the place where the cursor is at the beginning:
AAA |10 AAA
CCC 34567 CCC
DDD DDD
then only the first two lines will be modified, and will become
AAA 10 AAA
CCC 11 CCC
DDD DDD
If there is a selection, all the lines in the selection are
modified. For each line, the columns that had digits in the first
line are modified, no matter what they actually contain. In the
example above, if you select all three lines, the replacement becomes
AAA 10 AAA
CCC 11567 CCC
DDD 12D
ie only the fifth and sixth columns are modified since only those
columns contained digits in the first line. This feature assumes that
you are selecting a relevant set of lines. But it allows you to
transform blank lines more easily. For instance, if you have
AAA 1
BBB
CCC
this is transformed into
AAA 1
BBB 2
CCC 3
"""
buffer = GPS.EditorBuffer.get ()
start = buffer.selection_start ()
end = buffer.selection_end ()
if start == end:
has_sel = False
start = buffer.current_view ().cursor ()
end = lines_with_digit (buffer, start)
else:
has_sel = True
loc = start
# From start .. end, all lines are equal now
end = end.end_of_line ()
# Find the range of text to replace on each line
repl = loc
while buffer.get_chars (repl, repl).isdigit():
repl = repl + 1
frm_col = loc.column () - 1 # columns start at 0 on a line
end_col = (repl - 1).column () - 1
try:
value = int (buffer.get_chars (loc, repl - 1)) + increment
except:
GPS.Console().write ("Cursor must be before a number")
return
format = "%0" + str (end_col - frm_col + 1) + "d"
# And now do the replacement
repl = loc.end_of_line () + 1 # to beginning of next line
while repl < end:
if has_sel:
# We had a selection: make sure the column range exists on the
# line, and fill it with the value
eol = repl.end_of_line ()
if repl + frm_col > eol:
buffer.insert (eol,
" " * ((eol - repl) - frm_col + 2)
+ format % value)
else:
replace (repl + frm_col, min (repl + end_col, eol),
format % value)
else:
# We had no selection: replace the digit, no matter how many cols
to = repl + frm_col
while buffer.get_chars (to, to).isdigit():
to = to + 1
replace (repl + frm_col, to - 1, format % value)
repl = repl.end_of_line () + 1
value = value + increment
@interactive ("Editor", "Source editor", name="kill forward")
def delete_forward():
"""Delete the character just after the cursor in the current editor"""
buffer = GPS.EditorBuffer.get()
cursor = buffer.current_view().cursor()
buffer.delete (cursor, cursor)
@interactive ("Editor", "Source editor", name="Delete Line")
def delete_line ():
"""Delete the current line and place the cursor on the beginning of the
next line.
"""
buffer = GPS.EditorBuffer.get() # get the current buffer
view = buffer.current_view() # get the current view of this buffer
location = view.cursor() # get the location of the cursor
# Get the bounds to delete
start = location.beginning_of_line()
end = location.end_of_line()
# Do the deletion
buffer.delete (start, end)
def kill_line (location = None, count=1):
""" Kills the end of the line on which LOCATION is.
If LOCATION is unspecified, the current cursor location in the current
editor is used.
If the line is empty or contains only white spaces, the whole line is
deleted.
This is a better emulation of Emacs's behavior than the one provided by
default by gtk+, which doesn't handle whitespaces correctly.
When called several times from the same line, entries are appended in
the clipboard.
Count is the number of lines to delete. If greater than 1, then the
whole lines are deleted, including newline characters."""
if not location:
location = GPS.EditorBuffer.get ().current_view ().cursor ()
buffer = location.buffer ()
start = location
append = GPS.last_command() == "kill-line"
GPS.set_last_command ("kill-line")
# In case the current location points to a line terminator we just cut it
if count == 1 and start.get_char() == "\n":
buffer.cut (start, start, append)
else:
bol = start
for line in range (1, count + 1):
end = bol.end_of_line ()
str = buffer.get_chars (start, end)
strip_str = str.rstrip ()
if count == 1 \
and len (str) > 0 \
and str [len (str) - 1] == '\n' and strip_str != "":
end = end.forward_char (-1)
bol = end+1
buffer.cut (start, end, append)
################################################
## Moving the cursor
################################################
@interactive ("Editor", "Source editor", name="goto beginning of buffer")
def beginning_of_buffer():
"""Move the cursor to the beginning of the buffer"""
buffer = GPS.EditorBuffer.get()
buffer.current_view().goto (buffer.beginning_of_buffer(),
should_extend_selection)
@interactive ("Editor", "Source editor", name="goto end of buffer")
def end_of_buffer():
"""Move the cursor to the end of the buffer"""
buffer = GPS.EditorBuffer.get()
buffer.current_view().goto (buffer.end_of_buffer(), should_extend_selection)
@interactive ("Editor", "Source editor", name="goto beginning of line")
def goto_beginning_of_line():
"""Goto the beginning of line"""
view = GPS.EditorBuffer.get().current_view()
view.goto (view.cursor().beginning_of_line(), should_extend_selection)
def end_of_line(file, line):
"""Goto to the end of the line in file"""
buffer = GPS.EditorBuffer.get (GPS.File (file))
loc = GPS.EditorLocation (buffer, line, 1)
buffer.current_view().goto (loc.end_of_line() - 1)
@interactive ("Editor", "Source editor", name="goto end of line")
def goto_end_of_line():
"""Goto the end of line"""
view = GPS.EditorBuffer.get().current_view()
view.goto (view.cursor().end_of_line(), should_extend_selection)
def is_space (char):
return char == ' ' or char == '\t'
def goto_word_start (iter, underscore_is_word=True):
"""Move to the beginning of the current word (or leave the cursor
where it is). This properly handles '_'
"""
if underscore_is_word:
while not iter.starts_word ():
iter = iter.forward_word (-1)
return iter
else:
while not iter.starts_word ():
prev = iter
iter = iter.forward_char (-1)
c = iter.get_char ()
if c == '_':
return prev
return iter
def goto_word_end (iter, underscore_is_word=True):
if underscore_is_word:
while True:
iter = iter.forward_word ()
try:
if iter.get_char () != '_':
return iter.forward_char (-1)
except:
return iter.buffer().end_of_buffer ()
else:
while not iter.ends_word ():
prev = iter
iter = iter.forward_char (1)
try:
if iter.get_char () == '_':
return prev
except:
# Probably an invalid position.
return iter.buffer().end_of_buffer ()
return iter
def delete_spaces(backward=True, forward=True, leave_one=False):
"""Delete all spaces around cursor, possibly leaving one"""
buffer = GPS.EditorBuffer.get()
start = buffer.current_view().cursor()
end = start
if forward:
max = end.end_of_line()
while is_space (end.get_char()) and end < max:
end = end + 1
end = end - 1
if backward:
max = start.beginning_of_line()
start = start - 1
while is_space (start.get_char()) and start >= max:
start = start - 1
start = start + 1
if start <= end:
buffer.delete(start, end)
if leave_one:
buffer.insert(start, " ")
@interactive ("Editor", "Source editor", name="delete horizontal space")
@with_save_excursion
def delete_horizontal_space(backward=1, forward=1):
"""Delete all spaces and tabs around the cursor in the current editor.
The two parameters can be used to control in what directions white spaces are
searched for"""
delete_spaces(leave_one=False)
@interactive("Editor", "Source editor", name="just one space")
@with_save_excursion
def just_one_space():
"""Delete all spaces and tabs around the cursor, leaving one space.
If there are no spaces around, a new space is inserted
"""
delete_spaces(leave_one=True)
@interactive ("Editor", "Source editor", name="transpose chars")
def transpose_chars():
"""Transpose characters around cursor, moving forward one character."""
buffer = GPS.EditorBuffer.get()
cursor = buffer.current_view().cursor()
if cursor > buffer.beginning_of_buffer():
c = cursor.get_char ()
buffer.start_undo_group ()
buffer.delete (cursor, cursor)
buffer.insert (cursor - 1, c)
buffer.current_view().goto (cursor + 1)
buffer.finish_undo_group ()
@interactive ("Editor", "Source editor", name="Transpose lines")
def transpose_lines (location = None):
"""Transpose the line at LOCATION (or current line) and the previous one,
leaving the cursor after both"""
if not location:
location = GPS.EditorBuffer.get().current_view ().cursor ()
buffer = location.buffer ()
if location.line () < buffer.lines_count ():
buffer.start_undo_group ()
start = location.beginning_of_line ()
end = location.end_of_line ()
text = buffer.get_chars (start, end)
buffer.delete (start, end)
buffer.insert (start.forward_line (-1), text)
buffer.current_view ().goto (start.end_of_line () + 1)
buffer.finish_undo_group ()
@interactive ("Editor", "Source editor", name="open line")
@with_save_excursion
def open_line():
"""Insert a newline and leave cursor at its current place."""
buffer = GPS.EditorBuffer.get()
buffer.insert (buffer.current_view().cursor(), "\n")
@interactive ("Editor", "Source editor", name="Join line")
def join_line ():
"""Join the current line and the following one, separated by a single
space, and leaves the cursor on the space"""
buffer = GPS.EditorBuffer.get()
eol = buffer.current_view().cursor().end_of_line()
buffer.start_undo_group ()
buffer.current_view().goto (eol)
buffer.delete (eol, eol) ## Newline character
delete_spaces(backward=False, forward=True, leave_one=False)
if not is_space (eol.forward_char (-1).get_char ()):
buffer.insert (eol, " ")
buffer.finish_undo_group ()
def apply_func_to_word (func, location=None):
"""Apply a function to the current word (starting at the current character).
FUNC takes one argument, the text it replaces, and should return the
replacement text"""
if not location:
location = GPS.EditorBuffer.get ().current_view().cursor()
buffer = location.buffer()
buffer.start_undo_group ()
end = location.forward_word()
text = func (buffer.get_chars (location, end))
replace (location, end, text)
buffer.finish_undo_group ()
@interactive ("Editor", "Source editor", name="Upper case word")
def upper_case_word (location=None):
"""Upper case the current word (starting at the current character)"""
apply_func_to_word (str.upper, location)
@interactive ("Editor", "Source editor", name="Lower case word")
def lower_case_word (location=None):
"""Lower case the current word (starting at the current character)"""
apply_func_to_word (str.lower, location)
@interactive ("Editor", "Source editor", name="Capitalize word")
def capitalize_case_word (location=None):
"""Capitalize the current word (starting at the current character)"""
apply_func_to_word (str.capitalize, location)
@interactive ("Editor", "Source editor", name="Center line")
def center_line ():
"""Center the current line on the screen. If a comment line then the
text inside the comment is centered, the comment markers remain
unchanged.
"""
buffer = GPS.EditorBuffer.get()
location = buffer.current_view ().cursor ()
initial = location.create_mark()
buffer.start_undo_group ()
start = location.beginning_of_line ()
end = location.end_of_line ()
text = buffer.get_chars (start, end)
if text[0:2] == "--" or text[0:2] == "//" or text[0:2] == "##":
start = start + 2
if text[-3:] == "--\n" or text[-3:] == "//\n" or text[-3:] == "##\n":
# Use right comment characters to center the text
end = end - 3
text = buffer.get_chars (start, end).strip()
spaces = end.column() - start.column() + 1 - len(text)
before = spaces / 2
after = spaces / 2
if before + after != spaces:
after = after + 1
buffer.delete (start, end)
buffer.insert (start, ' ' * before + text + ' ' * after)
else:
# No right comment characters, use the highlight column to center the text
col = GPS.Preference ("Src-Editor-Highlight-Column").get()
text = buffer.get_chars (start, end).strip()
spaces = int(col) - start.column() - len(text)
before = spaces / 2
buffer.delete (start, end - 1)
buffer.insert (start, ' ' * before + text)
# Move to next line
buffer.current_view().goto (GPS.EditorLocation \
(buffer,
line=initial.location().forward_line(1).line(),
column=location.column()))
buffer.finish_undo_group ()
class BlockIterator:
"""An iterator for the various sections of an editor.
Each step in the iteration returns a tuple (start, end) of EditorLocation
instances for the section.
The constructor parameter overlay_name can be one of:
- "": The whole buffer is returned
- "selection": The current selection in the buffer is returned
- "word": The current word in the buffer is returned
- overlay name: All sections for which this overlay applies are
returned. The name could be one of "comment",
"keywords", "string" or "character"
Example of use:
buffer = EditorBuffer.get()
for start, end in BlockIterator (buffer, "comments"):
...
"""
def __init__ (self, buffer, overlay_name):
self.mark = buffer.beginning_of_buffer ().create_mark()
if overlay_name != "" \
and overlay_name != "selection" \
and overlay_name != "word":
self.overlay = buffer.create_overlay (overlay_name)
self.in_comment = \
buffer.beginning_of_buffer().has_overlay (self.overlay)
else:
self.overlay = None
self.overlay_name = overlay_name
def __iter__ (self):
return self
def next (self):
loc = self.mark.location ()
if not self.overlay:
if loc < loc.buffer().end_of_buffer():
self.mark.move (loc.buffer().end_of_buffer())
if self.overlay_name == "selection":
return (loc.buffer().selection_start(),
loc.buffer().selection_end())
elif self.overlay_name == "word":
cursor = loc.buffer().current_view().cursor()
start = cursor
while not start.starts_word(): start = start - 1
while not cursor.ends_word() : cursor = cursor + 1
return (start, cursor)
else:
return (loc.buffer().beginning_of_buffer(),
loc.buffer().end_of_buffer())
raise StopIteration
else:
# Find beginning of next section
if not loc.has_overlay (self.overlay):
loc = loc.forward_overlay (self.overlay)
if loc >= loc.buffer().end_of_buffer():
raise StopIteration
loc2 = loc.forward_overlay (self.overlay)
self.mark.move (loc2 + 1)
return (loc, loc2 - 1)
class WordIterator:
"""An iterator for all words in a block. Each iteration returns a
tuple (start, end) of EditorLocation instances.
Example of use:
buffer = EditorBuffer.get()
for blockStart, blockEnd in BlockIterator (buffer, "comments"):
for wordStart, wordEnd in WordIterator (blockStart, blockEnd):
...
"""
def __init__ (self, start, end):
self.mark = start.create_mark()
self.end = end
def __iter__ (self):
return self
def starts_at (self, loc):
self.mark.move (loc)
def next (self):
loc = self.mark.location ()
while loc < self.end:
loc2 = loc.forward_word ()
if loc.get_char().isalpha():
# Use a mark, in case the buffer is modified
self.mark.move (loc2 + 1)
return (loc, loc2 - 1)
else:
loc = loc + 1
raise StopIteration
class LineIterator:
"""An iterator for all lines in a block. Each iteration returns a
tuple (start, end) of EditorLocation instances."""
def __init__ (self, start, end):
self.mark = start.create_mark()
self.end = end.create_mark()
def __iter__ (self):
return self
def next (self):
loc = self.mark.location()
if loc >= self.end.location():
raise StopIteration
loc2 = loc.end_of_line()
if loc2 >= self.end.location():
self.mark.move (self.end.location() + 1)
return (loc, self.end.location())
else:
self.mark.move (loc2 + 1)
return (loc, loc2)
### Emulating Emacs selection:
### In Emacs, one sets the mark first, then when the cursor is moved the
### selection is extended appropriately. This is rather tricky to emulate
### in gtk+.
### There are two implementations: when pygtk is available, we simply
### temporarily override the key bindings so that the selection is always
### extended. This avoids all flickering, has no run-time cost, and is
### certainly the nicest. Not quite perfect though, since other functions
### that move the cursor will not extend the selection, only the basic
### key bindings defined for a gkt.TextView do.
### However, if pygtk is not available, we emulate it by monitoring all
### location changes. The slow down is almost invisible, but since the
### selection is first cancelled by gtk+ when the cursor is moved, and we
### then reselect it, there is some flickering
try:
import gtk, gobject
has_pygtk = 1
HOME = 65360
LEFT = 65361
UP = 65362
RIGHT = 65363
DOWN = 65364
PAGE_UP = 65365
PAGE_DOWN = 65366
END = 65367
KP_HOME = 65429
KP_LEFT = 65430
KP_UP = 65431
KP_RIGHT = 65432
KP_DOWN = 65433
KP_PAGE_UP = 65434
KP_PAGE_DOWN = 65435
KP_END = 65436
def override (key, modifier, movement, step, select):
gtk.binding_entry_remove (gtk.TextView, key, modifier)
gtk.binding_entry_add_signal (gtk.TextView, key, modifier,
"move_cursor",
gobject.TYPE_ENUM, movement,
gobject.TYPE_INT, step,
gobject.TYPE_BOOLEAN, select)
def override_key_bindings (select):
"""Override the default TextView keybinding to either always force
the extension the selection, or not"""
global should_extend_selection
should_extend_selection = select
override (RIGHT, 0, gtk.MOVEMENT_VISUAL_POSITIONS, 1, select)
override (KP_RIGHT, 0, gtk.MOVEMENT_VISUAL_POSITIONS, 1, select)
override (LEFT, 0, gtk.MOVEMENT_VISUAL_POSITIONS, -1, select)
override (KP_LEFT, 0, gtk.MOVEMENT_VISUAL_POSITIONS, -1, select)
override (RIGHT, gtk.gdk.CONTROL_MASK, gtk.MOVEMENT_WORDS, 1, select)
override (KP_RIGHT, gtk.gdk.CONTROL_MASK, gtk.MOVEMENT_WORDS, 1, select)
override (LEFT, gtk.gdk.CONTROL_MASK, gtk.MOVEMENT_WORDS, -1, select)
override (KP_LEFT, gtk.gdk.CONTROL_MASK, gtk.MOVEMENT_WORDS, -1, select)
override (UP, 0, gtk.MOVEMENT_DISPLAY_LINES, -1, select)
override (KP_UP, 0, gtk.MOVEMENT_DISPLAY_LINES, -1, select)
override (DOWN, 0, gtk.MOVEMENT_DISPLAY_LINES, 1, select)
override (KP_DOWN, 0, gtk.MOVEMENT_DISPLAY_LINES, 1, select)
override (UP, gtk.gdk.CONTROL_MASK, gtk.MOVEMENT_PARAGRAPHS, -1, select)
override (KP_UP, gtk.gdk.CONTROL_MASK, gtk.MOVEMENT_PARAGRAPHS, -1, select)
override (DOWN, gtk.gdk.CONTROL_MASK, gtk.MOVEMENT_PARAGRAPHS, 1, select)
override (KP_DOWN, gtk.gdk.CONTROL_MASK, gtk.MOVEMENT_PARAGRAPHS, 1, select)
override (HOME, 0, gtk.MOVEMENT_DISPLAY_LINE_ENDS, -1, select)
override (KP_HOME, 0, gtk.MOVEMENT_DISPLAY_LINE_ENDS, -1, select)
override (END, 0, gtk.MOVEMENT_DISPLAY_LINE_ENDS, 1, select)
override (KP_END, 0, gtk.MOVEMENT_DISPLAY_LINE_ENDS, 1, select)
override (HOME, gtk.gdk.CONTROL_MASK, gtk.MOVEMENT_BUFFER_ENDS, -1, select)
override (KP_HOME, gtk.gdk.CONTROL_MASK, gtk.MOVEMENT_BUFFER_ENDS, -1, select)
override (END, gtk.gdk.CONTROL_MASK, gtk.MOVEMENT_BUFFER_ENDS, 1, select)
override (KP_END, gtk.gdk.CONTROL_MASK, gtk.MOVEMENT_BUFFER_ENDS, 1, select)
override (PAGE_UP, 0, gtk.MOVEMENT_PAGES, -1, select)
override (KP_PAGE_UP, 0, gtk.MOVEMENT_PAGES, -1, select)
override (PAGE_DOWN, 0, gtk.MOVEMENT_PAGES, 1, select)
override (KP_PAGE_DOWN, 0, gtk.MOVEMENT_PAGES, 1, select)
override (PAGE_UP, gtk.gdk.CONTROL_MASK, gtk.MOVEMENT_HORIZONTAL_PAGES, -1, select)
override (KP_PAGE_UP, gtk.gdk.CONTROL_MASK, gtk.MOVEMENT_HORIZONTAL_PAGES, -1, select)
override (PAGE_DOWN, gtk.gdk.CONTROL_MASK, gtk.MOVEMENT_HORIZONTAL_PAGES, 1, select)
override (KP_PAGE_DOWN, gtk.gdk.CONTROL_MASK, gtk.MOVEMENT_HORIZONTAL_PAGES, 1, select)
except ImportError:
has_pygtk = 0
def on_location_changed (hook, file, line, column):
try:
buffer = GPS.EditorBuffer.get (file)
mark = buffer.get_mark ("emacs_selection_bound")
buffer.select (mark.location(), buffer.current_view().cursor())
except:
pass ## no such mark
@interactive ("Editor", "Source editor", name="set mark command")
def set_mark_command (location = None):
"""Set mark at LOCATION (or current cursor if LOCATION is unspecified)
This is similar to Emacs's behavior: a mark is put at the current cursor position. You can then move the cursor elsewhere, and delete the text between this mark and the new cursor position. See also the action 'Cancel mark command'"""
if not location:
location = GPS.EditorBuffer.get ().current_view ().cursor ()
if has_pygtk:
location.create_mark ("selection_bound")
override_key_bindings (select = True)
else:
location.create_mark ("emacs_selection_bound")
GPS.Hook ("location_changed").add (on_location_changed)
@interactive ("Editor", "Source editor", name="Cancel mark command")
def cancel_mark_command (buffer = None):
"""Cancel the mark in BUFFER
Remove the emacs-emulation mark in the current editor. See also the action 'Set mark command'"""
if not buffer:
buffer = GPS.EditorBuffer.get ()
try:
buffer.unselect ()
if has_pygtk:
override_key_bindings (select = False)
else:
buffer.get_mark ("emacs_selection_bound").delete ()
GPS.Hook ("location_changed").remove (on_location_changed)
except:
pass ## No such mark
def on_clipboard_changed (hook):
"""Called when the contents of the clipboard has changed"""
if GPS.Preference ("Plugins/emacs/transient_mark").get():
cancel_mark_command ()
GPS.Hook ("clipboard_changed").add (on_clipboard_changed)
GPS.parse_xml ("""
<action name="kill line" output="none" category="Editor">
<description>This is similar to Emacs' kill-line function. It deletes the end of the line after the cursor's current column. If the cursor is at the end of the line, it deletes the newline character and therefore joins the current line and the next.
The text that is deleted is copied to the clipboard. If you call this action multiple times from the same location, all deleted text is merged into a single clipboard, so that a single Paste will put it all back.
When this command is executed after a repeat_next command, the whole line is deleted to provide a more intuitive behavior.</description>
<filter id="Source editor" />
<shell lang="python">if $repeat == 1: text_utils.kill_line (None, $remaining+1)</shell>
</action>
<action name="New View Horizontal" output="none" category="MDI">
<description>When on an editor, splits the current notebook into two side-by-side windows, so that the two windows show two views of the same file. If another window already exists to the side, a new view is created inside that existing notebook, rather than create a new one</description>
<shell lang="python">GPS.MDI.current().split (vertically=False,new_view=True,reuse=True)</shell>
</action>
<action name="New View Vertical" output="none" category="MDI">
<description>When on an editor, splits the current notebook into two windows vertically, so that the two windows show two views of the same file. If another window already exists above or below, a new view is created inside that existing notebook, rather than create a new one</description>
<shell lang="python">GPS.MDI.current().split (vertically=True,new_view=True,reuse=True)</shell>
</action>
""")
|