7 from notebook
import Notebook
8 from statement
import Statement
, ExecutionError
9 from worksheet
import Worksheet
10 from custom_result
import CustomResult
15 def __init__(self
, start
=-1, end
=-1):
22 self
.error_message
= None
23 self
.error_line
= None
24 self
.error_offset
= None
27 return "StatementChunk(%d,%d,%s,%s,'%s')" % (self
.start
, self
.end
, self
.needs_compile
, self
.needs_execute
, self
.text
)
29 def set_text(self
, text
):
33 except AttributeError:
37 self
.needs_compile
= text
!= None
38 self
.needs_execute
= False
42 def mark_for_execute(self
):
43 if self
.statement
== None:
46 self
.needs_execute
= True
48 def compile(self
, worksheet
):
49 if self
.statement
!= None:
52 self
.needs_compile
= False
56 self
.error_message
= None
57 self
.error_line
= None
58 self
.error_offset
= None
61 self
.statement
= Statement(self
.text
, worksheet
)
62 self
.needs_execute
= True
63 except SyntaxError, e
:
64 self
.error_message
= e
.msg
65 self
.error_line
= e
.lineno
66 self
.error_offset
= e
.offset
68 def execute(self
, parent
):
69 assert(self
.statement
!= None)
71 self
.needs_compile
= False
72 self
.needs_execute
= False
74 self
.error_message
= None
75 self
.error_line
= None
76 self
.error_offset
= None
79 self
.statement
.set_parent(parent
)
80 self
.statement
.execute()
81 self
.results
= self
.statement
.results
82 except ExecutionError
, e
:
83 self
.error_message
= "\n".join(traceback
.format_tb(e
.traceback
)[2:]) + "\n" + str(e
.cause
)
84 self
.error_line
= e
.traceback
.tb_frame
.f_lineno
85 self
.error_offset
= None
88 def __init__(self
, start
=-1, end
=-1):
93 return "BlankChunk(%d,%d)" % (self
.start
, self
.end
)
96 def __init__(self
, start
=-1, end
=-1):
101 return "CommentChunk(%d,%d)" % (self
.start
, self
.end
)
104 def __init__(self
, start
=-1, end
=-1):
109 return "ResultChunk(%d,%d)" % (self
.start
, self
.end
)
111 BLANK
= re
.compile(r
'^\s*$')
112 COMMENT
= re
.compile(r
'^\s*#')
113 CONTINUATION
= re
.compile(r
'^\s+')
115 class ResultChunkFixupState
:
118 class ShellBuffer(gtk
.TextBuffer
, Worksheet
):
120 'begin-user-action': 'override',
121 'end-user-action': 'override',
122 'insert-text': 'override',
123 'delete-range': 'override',
124 'chunk-status-changed': (gobject
.SIGNAL_RUN_LAST
, gobject
.TYPE_NONE
, (gobject
.TYPE_PYOBJECT
,)),
125 'add-custom-result': (gobject
.SIGNAL_RUN_LAST
, gobject
.TYPE_NONE
, (gobject
.TYPE_PYOBJECT
, gobject
.TYPE_PYOBJECT
)),
127 # It would be more GObject to make these properties, but we'll wait on that until
128 # decent property support lands:
130 # http://blogs.gnome.org/johan/2007/04/30/simplified-gobject-properties/
132 'filename-changed': (gobject
.SIGNAL_RUN_LAST
, gobject
.TYPE_NONE
, ()),
133 # Clumsy naming is because GtkTextBuffer already has a modified flag, but that would
134 # include changes to the results
135 'code-modified-changed': (gobject
.SIGNAL_RUN_LAST
, gobject
.TYPE_NONE
, ()),
138 def __init__(self
, notebook
):
139 gtk
.TextBuffer
.__init
__(self
)
140 Worksheet
.__init
__(self
, notebook
)
142 self
.__red
_tag
= self
.create_tag(foreground
="red")
143 self
.__result
_tag
= self
.create_tag(family
="monospace", style
="italic", wrap_mode
=gtk
.WRAP_WORD
, editable
=False)
144 # Order here is significant ... we want the recompute tag to have higher priority, so
146 self
.__error
_tag
= self
.create_tag(foreground
="#aa0000")
147 self
.__recompute
_tag
= self
.create_tag(foreground
="#888888")
148 self
.__comment
_tag
= self
.create_tag(foreground
="#00aa00")
150 self
.__chunks
= [BlankChunk(0,0)]
151 self
.__modifying
_results
= False
152 self
.__user
_action
_count
= 0
155 self
.code_modified
= False
157 def __assign_lines(self
, chunk_start
, lines
, statement_end
):
160 if statement_end
>= chunk_start
:
161 def notnull(l
): return l
!= None
162 text
= "\n".join(filter(notnull
, lines
[0:statement_end
+ 1 - chunk_start
]))
165 for i
in xrange(chunk_start
, statement_end
+ 1):
166 if isinstance(self
.__chunks
[i
], StatementChunk
):
167 old_statement
= self
.__chunks
[i
]
170 if old_statement
!= None:
171 # An old statement can only be turned into *one* new statement; this
172 # prevents us getting fooled if we split a statement
173 for i
in xrange(old_statement
.start
, old_statement
.end
+ 1):
174 self
.__chunks
[i
] = None
176 changed
= not old_statement
.needs_compile
and text
!= old_statement
.text
177 chunk
= old_statement
180 chunk
= StatementChunk()
183 changed_chunks
.append(chunk
)
185 chunk
.start
= chunk_start
186 chunk
.end
= statement_end
189 for i
in xrange(chunk_start
, statement_end
+ 1):
190 self
.__chunks
[i
] = chunk
191 self
.__lines
[i
] = lines
[i
- chunk_start
]
193 for i
in xrange(statement_end
+ 1, chunk_start
+ len(lines
)):
194 line
= lines
[i
- chunk_start
]
197 chunk
= self
.__chunks
[i
- 1]
202 # a ResultChunk Must be in the before-start portion, nothing needs doing
204 elif BLANK
.match(line
):
205 if not isinstance(chunk
, BlankChunk
):
209 self
.__chunks
[i
] = chunk
210 self
.__lines
[i
] = lines
[i
- chunk_start
]
211 elif COMMENT
.match(line
):
212 if not isinstance(chunk
, CommentChunk
):
213 chunk
= CommentChunk()
216 self
.__chunks
[i
] = chunk
217 self
.__lines
[i
] = lines
[i
- chunk_start
]
218 # This is O(n^2) inefficient
219 self
.__apply
_tag
_to
_chunk
(self
.__comment
_tag
, chunk
)
221 return changed_chunks
223 def __rescan(self
, start_line
, end_line
):
224 rescan_start
= start_line
225 while rescan_start
> 0:
226 if rescan_start
< start_line
:
227 new_text
= old_text
= self
.__lines
[rescan_start
]
229 old_text
= self
.__lines
[rescan_start
]
230 i
= self
.get_iter_at_line(rescan_start
)
232 if not i_end
.ends_line():
233 i_end
.forward_to_line_end()
234 new_text
= self
.get_slice(i
, i_end
)
236 if old_text
== None or BLANK
.match(old_text
) or COMMENT
.match(old_text
) or CONTINUATION
.match(old_text
) or \
237 new_text
== None or BLANK
.match(new_text
) or COMMENT
.match(new_text
) or CONTINUATION
.match(new_text
):
242 # If previous contents of the modified range ended within a statement, then we need to rescan all of it;
243 # since we may have already deleted all of the statement lines within the modified range, we detect
244 # this case by seeing if the line *after* our range is a continuation line.
245 rescan_end
= end_line
246 while rescan_end
+ 1 < len(self
.__chunks
):
247 if isinstance(self
.__chunks
[rescan_end
+ 1], StatementChunk
) and self
.__chunks
[rescan_end
+ 1].start
!= rescan_end
+ 1:
252 chunk_start
= rescan_start
253 statement_end
= rescan_start
- 1
257 i
= self
.get_iter_at_line(rescan_start
)
261 for line
in xrange(rescan_start
, rescan_end
+ 1):
262 if line
< start_line
:
263 line_text
= self
.__lines
[line
]
266 if not i_end
.ends_line():
267 i_end
.forward_to_line_end()
268 line_text
= self
.get_slice(i
, i_end
)
270 if line_text
== None:
271 chunk_lines
.append(line_text
)
272 elif BLANK
.match(line_text
):
273 chunk_lines
.append(line_text
)
274 elif COMMENT
.match(line_text
):
275 chunk_lines
.append(line_text
)
276 elif CONTINUATION
.match(line_text
):
277 chunk_lines
.append(line_text
)
280 changed_chunks
.extend(self
.__assign
_lines
(chunk_start
, chunk_lines
, statement_end
))
283 chunk_lines
= [line_text
]
287 changed_chunks
.extend(self
.__assign
_lines
(chunk_start
, chunk_lines
, statement_end
))
288 if len(changed_chunks
) > 0:
289 # The the chunks in changed_chunks are already marked as needing recompilation; we
290 # need to emit signals and also mark those chunks and all subsequent chunks as
291 # needing reexecution
292 first_changed_line
= changed_chunks
[0].start
293 for chunk
in changed_chunks
:
294 if chunk
.start
< first_changed_line
:
295 first_changed_line
= chunk
.start
297 for chunk
in self
.iterate_chunks(first_changed_line
):
298 if isinstance(chunk
, StatementChunk
):
299 chunk
.mark_for_execute()
301 result
= self
.__find
_result
(chunk
)
303 self
.__apply
_tag
_to
_chunk
(self
.__recompute
_tag
, result
)
305 self
.emit("chunk-status-changed", chunk
)
307 self
.emit("chunk-status-changed", result
)
310 def iterate_chunks(self
, start_line
=0, end_line
=None):
311 if end_line
== None or end_line
>= len(self
.__chunks
):
312 end_line
= len(self
.__chunks
) - 1
313 if start_line
>= len(self
.__chunks
) or end_line
< start_line
:
316 chunk
= self
.__chunks
[start_line
]
317 while chunk
== None and start_line
< end_line
:
319 chunk
= self
.__chunks
[start_line
]
324 last_chunk
= self
.__chunks
[end_line
]
325 while last_chunk
== None:
327 last_chunk
= self
.__chunks
[end_line
]
331 if chunk
== last_chunk
:
333 chunk
= self
.__chunks
[chunk
.end
+ 1]
336 chunk
= self
.__chunks
[line
]
338 def do_begin_user_action(self
):
339 self
.__user
_action
_count
+= 1
341 def do_end_user_action(self
):
342 self
.__user
_action
_count
-= 1
344 def do_insert_text(self
, location
, text
, text_len
):
345 start_line
= location
.get_line()
346 if self
.__user
_action
_count
> 0:
347 if isinstance(self
.__chunks
[start_line
], ResultChunk
):
351 if not self
.__modifying
_results
:
352 print "Inserting '%s' at %s" % (text
, (location
.get_line(), location
.get_line_offset()))
354 gtk
.TextBuffer
.do_insert_text(self
, location
, text
, text_len
)
355 end_line
= location
.get_line()
357 if self
.__modifying
_results
:
360 if self
.__user
_action
_count
> 0:
361 self
.__set
_modified
(True)
363 result_fixup_state
= self
.__get
_result
_fixup
_state
(start_line
, start_line
)
365 self
.__chunks
[start_line
+ 1:start_line
+ 1] = [None for i
in xrange(start_line
, end_line
)]
366 self
.__lines
[start_line
+ 1:start_line
+ 1] = [None for i
in xrange(start_line
, end_line
)]
368 for chunk
in self
.iterate_chunks(start_line
):
369 if chunk
.start
> start_line
:
370 chunk
.start
+= (end_line
- start_line
)
371 if chunk
.end
> start_line
:
372 chunk
.end
+= (end_line
- start_line
)
374 self
.__rescan
(start_line
, end_line
)
376 self
.__fixup
_results
(result_fixup_state
, [location
])
379 print "After insert, chunks are", self
.__chunks
381 def __delete_chunk(self
, chunk
, revalidate_iter1
=None, revalidate_iter2
=None):
382 # revalidate_iter1 and revalidate_iter2 get moved to point to the location
383 # of the deleted chunk and revalidated. This is useful only as part of the
384 # workaround-hack in __fixup_results
385 self
.__modifying
_results
= True
387 if revalidate_iter1
!= None:
388 i_start
= revalidate_iter1
389 i_start
.set_line(chunk
.start
)
391 i_start
= self
.get_iter_at_line(chunk
.start
)
392 if revalidate_iter2
!= None:
393 i_end
= revalidate_iter2
394 i_end
.set_line(chunk
.end
)
396 i_end
= self
.get_iter_at_line(chunk
.end
)
398 if i_end
.get_line() == chunk
.end
: # Last line of buffer
399 i_end
.forward_to_line_end()
400 self
.delete(i_start
, i_end
)
402 self
.__chunks
[chunk
.start
:chunk
.end
+ 1] = []
403 self
.__lines
[chunk
.start
:chunk
.end
+ 1] = []
405 n_deleted
= chunk
.end
+ 1 - chunk
.start
407 # Overlapping chunks can occur temporarily when inserting
408 # or deleting text merges two adjacent statements with a ResultChunk in between, so iterate
409 # all chunks, not just the ones after the deleted chunk
410 for c
in self
.iterate_chunks():
411 if c
.end
>= chunk
.end
:
413 elif c
.end
>= chunk
.start
:
414 c
.end
= chunk
.start
- 1
416 if c
.start
>= chunk
.end
:
419 self
.__modifying
_results
= False
421 def __find_result(self
, statement
):
422 for chunk
in self
.iterate_chunks(statement
.end
+ 1):
423 if isinstance(chunk
, ResultChunk
):
425 elif isinstance(chunk
, StatementChunk
):
428 def __get_result_fixup_state(self
, first_modified_line
, last_modified_line
):
429 state
= ResultChunkFixupState()
431 state
.statement_before
= None
432 state
.result_before
= None
433 for i
in xrange(first_modified_line
- 1, -1, -1):
434 if isinstance(self
.__chunks
[i
], ResultChunk
):
435 state
.result_before
= self
.__chunks
[i
]
436 elif isinstance(self
.__chunks
[i
], StatementChunk
):
437 if state
.result_before
!= None:
438 state
.statement_before
= self
.__chunks
[i
]
441 state
.statement_after
= None
442 state
.result_after
= None
444 for i
in xrange(last_modified_line
+ 1, len(self
.__chunks
)):
445 if isinstance(self
.__chunks
[i
], ResultChunk
):
446 state
.result_after
= self
.__chunks
[i
]
447 for j
in xrange(i
- 1, -1, -1):
448 if isinstance(self
.__chunks
[j
], StatementChunk
):
449 state
.statement_after
= self
.__chunks
[j
]
450 assert state
.statement_after
.results
!= None
452 elif isinstance(self
.__chunks
[i
], StatementChunk
) and self
.__chunks
[i
].start
== i
:
457 def __fixup_results(self
, state
, revalidate_iters
):
462 if state
.result_before
!= None:
463 # If lines were added into the StatementChunk that produced the ResultChunk above the edited segment,
464 # then the ResultChunk needs to be moved after the newly inserted lines
465 if state
.statement_before
.end
> state
.result_before
.start
:
468 if state
.result_after
!= None:
469 # If the StatementChunk that produced the ResultChunk after the edited segment was deleted, then the
470 # ResultChunk needs to be deleted as well
471 if self
.__chunks
[state
.statement_after
.start
] != state
.statement_after
:
474 # If another StatementChunk was inserted between the StatementChunk and the ResultChunk, then we
475 # need to move the ResultChunk above that statement
476 for i
in xrange(state
.statement_after
.end
+ 1, state
.result_after
.start
):
477 if self
.__chunks
[i
] != state
.statement_after
and isinstance(self
.__chunks
[i
], StatementChunk
):
480 if not (move_before
or delete_after
or move_after
):
484 print "Result fixups: move_before=%s, delete_after=%s, move_after=%s" % (move_before
, delete_after
, move_after
)
486 revalidate
= map(lambda iter: (iter, self
.create_mark(None, iter, True)), revalidate_iters
)
488 # This hack is a workaround for being unable to assign iters by value in PyGtk, see
489 # http://bugzilla.gnome.org/show_bug.cgi?id=481715
490 if len(revalidate_iters
) > 0:
491 revalidate_iter
= revalidate_iters
[0]
493 revalidate_iter
= None
495 if len(revalidate_iters
) > 1:
496 raise Exception("I don't know how to keep more than one iter valid")
499 self
.__delete
_chunk
(state
.result_before
, revalidate_iter1
=revalidate_iter
)
500 self
.insert_result(state
.statement_before
, revalidate_iter
=revalidate_iter
)
502 if delete_after
or move_after
:
503 self
.__delete
_chunk
(state
.result_after
, revalidate_iter1
=revalidate_iter
)
505 self
.insert_result(state
.statement_after
, revalidate_iter
=revalidate_iter
)
507 for iter, mark
in revalidate
:
508 new
= self
.get_iter_at_mark(mark
)
509 iter.set_line(new
.get_line())
510 iter.set_line_index(new
.get_line_index())
511 self
.delete_mark(mark
)
513 def do_delete_range(self
, start
, end
):
514 start_line
= start
.get_line()
515 end_line
= end
.get_line()
517 # Prevent the user from doing deletes that would merge a ResultChunk chunk into a statement
518 if self
.__user
_action
_count
> 0 and not self
.__modifying
_results
:
519 if start
.ends_line() and isinstance(self
.__chunks
[start_line
], ResultChunk
):
522 if end
.starts_line() and not start
.starts_line() and isinstance(self
.__chunks
[end_line
], ResultChunk
):
524 end
.forward_to_line_end()
527 if start
.compare(end
) == 0:
530 if start
.starts_line() and end
.starts_line():
531 (first_deleted_line
, last_deleted_line
) = (start_line
, end_line
- 1)
532 (new_start
, new_end
) = (start_line
, start_line
- 1) # empty
533 last_modified_line
= end_line
- 1
534 elif start
.starts_line():
535 if start_line
== end_line
:
536 (first_deleted_line
, last_deleted_line
) = (start_line
, start_line
- 1) # empty
537 (new_start
, new_end
) = (start_line
, start_line
)
538 last_modified_line
= start_line
540 (first_deleted_line
, last_deleted_line
) = (start_line
, end_line
- 1)
541 (new_start
, new_end
) = (start_line
, start_line
)
542 last_modified_line
= end_line
544 (first_deleted_line
, last_deleted_line
) = (start_line
+ 1, end_line
)
545 (new_start
, new_end
) = (start_line
, start_line
)
546 last_modified_line
= end_line
549 if not self
.__modifying
_results
:
550 print "Deleting range %s" % (((start
.get_line(), start
.get_line_offset()), (end
.get_line(), end
.get_line_offset())),)
551 print "first_deleted_line=%d, last_deleted_line=%d, new_start=%d, new_end=%d, last_modified_line=%d" % (first_deleted_line
, last_deleted_line
, new_start
, new_end
, last_modified_line
)
553 gtk
.TextBuffer
.do_delete_range(self
, start
, end
)
555 if self
.__modifying
_results
:
558 if self
.__user
_action
_count
> 0:
559 self
.__set
_modified
(True)
561 result_fixup_state
= self
.__get
_result
_fixup
_state
(new_start
, last_modified_line
)
563 self
.__chunks
[first_deleted_line
:last_deleted_line
+ 1] = []
564 self
.__lines
[first_deleted_line
:last_deleted_line
+ 1] = []
565 n_deleted
= 1 + last_deleted_line
- first_deleted_line
567 for chunk
in self
.iterate_chunks():
568 if chunk
.end
>= last_deleted_line
:
569 chunk
.end
-= n_deleted
;
570 elif chunk
.end
>= first_deleted_line
:
571 chunk
.end
= first_deleted_line
- 1
573 if chunk
.start
>= last_deleted_line
:
574 chunk
.start
-= n_deleted
576 self
.__rescan
(new_start
, new_end
)
578 # We can only revalidate one iter due to PyGTK limitations; see comment in __fixup_results
579 # It turns out it works to cheat and only revalidate the end iter
580 # self.__fixup_results(result_fixup_state, [start, end])
581 self
.__fixup
_results
(result_fixup_state
, [end
])
584 print "After delete, chunks are", self
.__chunks
589 for chunk
in self
.iterate_chunks():
590 if isinstance(chunk
, StatementChunk
):
592 if chunk
.needs_compile
or (chunk
.needs_execute
and not have_error
):
593 old_result
= self
.__find
_result
(chunk
)
595 self
.__delete
_chunk
(old_result
)
597 if chunk
.needs_compile
:
600 if chunk
.error_message
!= None:
601 self
.insert_result(chunk
)
603 if chunk
.needs_execute
and not have_error
:
605 chunk
.execute(parent
)
606 if chunk
.error_message
!= None:
607 self
.insert_result(chunk
)
608 elif len(chunk
.results
) > 0:
609 self
.insert_result(chunk
)
611 if chunk
.error_message
!= None:
615 self
.emit("chunk-status-changed", chunk
)
617 parent
= chunk
.statement
620 print "After calculate, chunks are", self
.__chunks
622 def get_chunk(self
, line_index
):
623 return self
.__chunks
[line_index
]
625 def __apply_tag_to_chunk(self
, tag
, chunk
):
626 start
= self
.get_iter_at_line(chunk
.start
)
627 end
= self
.get_iter_at_line(chunk
.end
)
628 end
.forward_to_line_end()
629 self
.apply_tag(tag
, start
,end
)
631 def insert_result(self
, chunk
, revalidate_iter
=None):
632 # revalidate_iter gets move to point to the end of the inserted result and revalidated.
633 # This is useful only as part of the workaround-hack in __fixup_results
634 self
.__modifying
_results
= True
635 if revalidate_iter
!= None:
636 location
= revalidate_iter
637 location
.set_line(chunk
.end
)
639 location
= self
.get_iter_at_line(chunk
.end
)
640 location
.forward_to_line_end()
642 if chunk
.error_message
:
643 results
= [ chunk
.error_message
]
645 results
= chunk
.results
647 for result
in results
:
648 if isinstance(result
, basestring
):
649 self
.insert(location
, "\n" + result
)
650 elif isinstance(result
, CustomResult
):
651 self
.insert(location
, "\n")
652 anchor
= self
.create_child_anchor(location
)
653 self
.emit("add-custom-result", result
, anchor
)
655 self
.__modifying
_results
= False
656 n_inserted
= location
.get_line() - chunk
.end
658 result_chunk
= ResultChunk(chunk
.end
+ 1, chunk
.end
+ n_inserted
)
659 self
.__chunks
[chunk
.end
+ 1:chunk
.end
+ 1] = [result_chunk
for i
in xrange(0, n_inserted
)]
660 self
.__lines
[chunk
.end
+ 1:chunk
.end
+ 1] = [None for i
in xrange(0, n_inserted
)]
662 self
.__apply
_tag
_to
_chunk
(self
.__result
_tag
, result_chunk
)
664 if chunk
.error_message
:
665 self
.__apply
_tag
_to
_chunk
(self
.__error
_tag
, result_chunk
)
667 for chunk
in self
.iterate_chunks(result_chunk
.end
+ 1):
668 chunk
.start
+= n_inserted
669 chunk
.end
+= n_inserted
671 def __set_filename_and_modified(self
, filename
, modified
):
672 filename_changed
= filename
!= self
.filename
673 modified_changed
= modified
!= self
.code_modified
675 if not (filename_changed
or modified_changed
):
678 self
.filename
= filename
679 self
.code_modified
= modified
682 self
.emit('filename-changed')
685 self
.emit('code-modified-changed')
687 def __set_modified(self
, modified
):
688 if modified
== self
.code_modified
:
691 self
.code_modified
= modified
692 self
.emit('code-modified-changed')
694 def __do_clear(self
):
695 # This is actually working pretty much coincidentally, since the Delete
696 # code wasn't really written with non-interactive deletes in mind, and
697 # when there are ResultChunk present, a non-interactive delete will
698 # use ranges including them. But the logic happens to work out.
700 self
.delete(self
.get_start_iter(), self
.get_end_iter())
704 self
.__set
_filename
_and
_modified
(None, False)
706 def load(self
, filename
):
712 self
.__set
_filename
_and
_modified
(filename
, False)
713 self
.insert(self
.get_start_iter(), text
)
715 def save(self
, filename
=None):
717 if self
.filename
== None:
718 raise ValueError("No current or specified filename")
720 filename
= self
.filename
722 # TODO: The atomic-save implementation here is Unix-specific and won't work on Windows
723 tmpname
= filename
+ ".tmp"
725 # We use binary mode, since we don't want to munge line endings to the system default
726 # on a load-save cycle
727 f
= open(tmpname
, "wb")
731 iter = self
.get_start_iter()
732 for chunk
in self
.iterate_chunks():
734 while next
.get_line() <= chunk
.end
:
735 if not next
.forward_line(): # at end of buffer
738 if not isinstance(chunk
, ResultChunk
):
739 chunk_text
= self
.get_slice(iter, next
)
745 os
.rename(tmpname
, filename
)
748 self
.__set
_filename
_and
_modified
(filename
, False)
755 if __name__
== '__main__':
762 if len(l1
) != len(l2
):
765 for i
in xrange(0, len(l1
)):
769 if type(e1
) != type(e2
) or e1
.start
!= e2
.start
or e1
.end
!= e2
.end
:
774 buffer = ShellBuffer(Notebook())
776 def expect(expected
):
777 chunks
= [ x
for x
in buffer.iterate_chunks() ]
778 if not compare(chunks
, expected
):
779 raise AssertionError("Got:\n %s\nExpected:\n %s" % (chunks
, expected
))
781 def insert(line
, offset
, text
):
782 i
= buffer.get_iter_at_line(line
)
783 i
.set_line_offset(offset
)
784 buffer.insert(i
, text
)
786 def delete(start_line
, start_offset
, end_line
, end_offset
):
787 i
= buffer.get_iter_at_line(start_line
)
788 i
.set_line_offset(start_offset
)
789 j
= buffer.get_iter_at_line(end_line
)
790 j
.set_line_offset(end_offset
)
791 buffer.delete_interactive(i
, j
, True)
794 insert(0, 0, "1\n\n#2\ndef a():\n 3")
795 expect([S(0,0), B(1,1), C(2,2), S(3,4)])
800 # Turning a statement into a continuation line
801 insert(0, 0, "1 \\\n+ 2\n")
803 expect([S(0,1), B(2,2)])
805 # Calculation resulting in result chunks
808 expect([S(0,1), R(2,2), S(3,3), R(4,4), B(5,5)])
810 # Check that splitting a statement with a delete results in the
811 # result chunk being moved to the last line of the first half
813 expect([S(0,0), R(1,1), S(2,2), S(3,3), R(4,4), B(5,5)])
815 # Editing a continuation line, while leaving it a continuation
818 insert(0, 0, "1\\\n + 2\\\n + 3")
822 # Deleting an entire continuation line
825 insert(0, 0, "for i in (1,2):\n print i\n print i + 1\n")
826 expect([S(0,2), B(3,3)])
828 expect([S(0,1), B(2,2)])
831 # Try writing to a file, and reading it back
844 insert(0, 0, SAVE_TEST
)
847 handle
, fname
= tempfile
.mkstemp(".txt", "shell_buffer")
856 if saved
!= SAVE_TEST
:
857 raise AssertionError("Got '%s', expected '%s'", saved
, SAVE_TEST
)
862 expect([S(0,0), S(1,1), R(2,2), C(3,3), B(4,4), S(5,5)])