Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Newer
Older
100644 761 lines (636 sloc) 22.123 kb
1b5aa30c »
2010-05-11 Initial commit
1 #!/usr/bin/env python
2 # -*- coding: utf-8 -*-
3
4 """
5 Middleware layer that communicates between editor and Zen Coding.
6 This layer describes all available Zen Coding actions, like
7 "Expand Abbreviation".
8 @author Sergey Chikuyonok (serge.che@gmail.com)
9 @link http://chikuyonok.ru
10 """
11 from zencoding import zen_core as zen_coding
01adefcb »
2010-05-13 Update from sergeche; explain changes in comments
12 from zencoding import html_matcher, zen_file
d39fb5ba »
2010-05-30 add edit points detection for css
13 from zen_core import char_at, ZenError
01adefcb »
2010-05-13 Update from sergeche; explain changes in comments
14 import re
15 import base64
16
17 mime_types = {
18 'gif': 'image/gif',
19 'png': 'image/png',
20 'jpg': 'image/jpeg',
21 'jpeg': 'image/jpeg',
22 'svg': 'image/svg+xml',
23 'html': 'text/html',
24 'htm': 'text/html'
25 }
1b5aa30c »
2010-05-11 Initial commit
26
27 def find_abbreviation(editor):
28 """
29 Search for abbreviation in editor from current caret position
30 @param editor: Editor instance
31 @type editor: ZenEditor
32 @return: str
33 """
34 start, end = editor.get_selection_range()
35 if start != end:
36 # abbreviation is selected by user
37 return editor.get_content()[start:end]
38
39 # search for new abbreviation from current caret position
40 cur_line_start, cur_line_end = editor.get_current_line_range()
41 return zen_coding.extract_abbreviation(editor.get_content()[cur_line_start:start])
42
43 def expand_abbreviation(editor, syntax=None, profile_name=None):
44 """
45 Find from current caret position and expand abbreviation in editor
46 @param editor: Editor instance
47 @type editor: ZenEditor
48 @param syntax: Syntax type (html, css, etc.)
49 @type syntax: str
50 @param profile_name: Output profile name (html, xml, xhtml)
51 @type profile_name: str
52 @return: True if abbreviation was expanded successfully
53 """
54 if syntax is None: syntax = editor.get_syntax()
55 if profile_name is None: profile_name = editor.get_profile_name()
56
57 range_start, caret_pos = editor.get_selection_range()
58 abbr = find_abbreviation(editor)
59 content = ''
60
61 if abbr:
62 content = zen_coding.expand_abbreviation(abbr, syntax, profile_name)
63 if content:
64 editor.replace_content(content, caret_pos - len(abbr), caret_pos)
65 return True
66
67 return False
68
69 def expand_abbreviation_with_tab(editor, syntax, profile_name='xhtml'):
70 """
71 A special version of <code>expandAbbreviation</code> function: if it can't
72 find abbreviation, it will place Tab character at caret position
73 @param editor: Editor instance
74 @type editor: ZenEditor
75 @param syntax: Syntax type (html, css, etc.)
76 @type syntax: str
77 @param profile_name: Output profile name (html, xml, xhtml)
78 @type profile_name: str
79 """
80 if not expand_abbreviation(editor, syntax, profile_name):
81 editor.replace_content(zen_coding.get_variable('indentation'), editor.get_caret_pos())
82
83 return True
84
85 def match_pair(editor, direction='out', syntax=None):
86 """
87 Find and select HTML tag pair
88 @param editor: Editor instance
89 @type editor: ZenEditor
90 @param direction: Direction of pair matching: 'in' or 'out'.
91 @type direction: str
92 """
93 direction = direction.lower()
94 if syntax is None: syntax = editor.get_profile_name()
95
96 range_start, range_end = editor.get_selection_range()
97 cursor = range_end
98 content = editor.get_content()
99 rng = None
100
101 old_open_tag = html_matcher.last_match['opening_tag']
102 old_close_tag = html_matcher.last_match['closing_tag']
103
104 if direction == 'in' and old_open_tag and range_start != range_end:
105 # user has previously selected tag and wants to move inward
106 if not old_close_tag:
107 # unary tag was selected, can't move inward
108 return False
109 elif old_open_tag.start == range_start:
110 if content[old_open_tag.end] == '<':
111 # test if the first inward tag matches the entire parent tag's content
112 _r = html_matcher.find(content, old_open_tag.end + 1, syntax)
113 if _r[0] == old_open_tag.end and _r[1] == old_close_tag.start:
114 rng = html_matcher.match(content, old_open_tag.end + 1, syntax)
115 else:
116 rng = (old_open_tag.end, old_close_tag.start)
117 else:
118 rng = (old_open_tag.end, old_close_tag.start)
119 else:
120 new_cursor = content[0:old_close_tag.start].find('<', old_open_tag.end)
121 search_pos = new_cursor + 1 if new_cursor != -1 else old_open_tag.end
122 rng = html_matcher.match(content, search_pos, syntax)
123 else:
124 rng = html_matcher.match(content, cursor, syntax)
125
126 if rng and rng[0] is not None:
127 editor.create_selection(rng[0], rng[1])
128 return True
129 else:
130 return False
131
132 def match_pair_inward(editor):
133 return match_pair(editor, 'in')
134
135 def match_pair_outward(editor):
136 return match_pair(editor, 'out')
137
138 def narrow_to_non_space(text, start, end):
139 """
140 Narrow down text indexes, adjusting selection to non-space characters
141 @type text: str
142 @type start: int
143 @type end: int
144 @return: list
145 """
146 # narrow down selection until first non-space character
147 while start < end:
148 if not text[start].isspace():
149 break
150
151 start += 1
152
153 while end > start:
154 end -= 1
155 if not text[end].isspace():
156 end += 1
157 break
158
159 return start, end
160
161 def wrap_with_abbreviation(editor, abbr, syntax=None, profile_name=None):
162 """
163 Wraps content with abbreviation
164 @param editor: Editor instance
165 @type editor: ZenEditor
166 @param syntax: Syntax type (html, css, etc.)
167 @type syntax: str
168 @param profile_name: Output profile name (html, xml, xhtml)
169 @type profile_name: str
170 """
171 if not abbr: return None
172
173 if syntax is None: syntax = editor.get_syntax()
174 if profile_name is None: profile_name = editor.get_profile_name()
175
176 start_offset, end_offset = editor.get_selection_range()
177 content = editor.get_content()
178
179 if start_offset == end_offset:
180 # no selection, find tag pair
181 rng = html_matcher.match(content, start_offset, profile_name)
182
183 if rng[0] is None: # nothing to wrap
184 return None
185 else:
186 start_offset, end_offset = rng
187
188 start_offset, end_offset = narrow_to_non_space(content, start_offset, end_offset)
189 line_bounds = get_line_bounds(content, start_offset)
190 padding = get_line_padding(content[line_bounds[0]:line_bounds[1]])
191
192 new_content = content[start_offset:end_offset]
193 result = zen_coding.wrap_with_abbreviation(abbr, unindent_text(new_content, padding), syntax, profile_name)
194
195 if result:
196 editor.replace_content(result, start_offset, end_offset)
197 return True
198
199 return False
200
201 def unindent(editor, text):
202 """
203 Unindent content, thus preparing text for tag wrapping
204 @param editor: Editor instance
205 @type editor: ZenEditor
206 @param text: str
207 @return str
208 """
209 return unindent_text(text, get_current_line_padding(editor))
210
211 def unindent_text(text, pad):
212 """
213 Removes padding at the beginning of each text's line
214 @type text: str
215 @type pad: str
216 """
217 lines = zen_coding.split_by_lines(text)
218
219 for i,line in enumerate(lines):
220 if line.startswith(pad):
221 lines[i] = line[len(pad):]
222
223 return zen_coding.get_newline().join(lines)
224
225 def get_current_line_padding(editor):
226 """
227 Returns padding of current editor's line
228 @return str
229 """
230 return get_line_padding(editor.get_current_line())
231
232 def get_line_padding(line):
233 """
234 Returns padding of current editor's line
235 @return str
236 """
237 m = re.match(r'^(\s+)', line)
238 return m and m.group(0) or ''
239
240 def find_new_edit_point(editor, inc=1, offset=0):
241 """
242 Search for new caret insertion point
243 @param editor: Editor instance
244 @type editor: ZenEditor
245 @param inc: Search increment: -1 — search left, 1 — search right
246 @param offset: Initial offset relative to current caret position
247 @return: -1 if insertion point wasn't found
248 """
249 cur_point = editor.get_caret_pos() + offset
250 content = editor.get_content()
7687b9ca »
2010-06-05 update comments and readme
251 syntax = editor.get_syntax() # (FM) needed to differentiate CSS
1b5aa30c »
2010-05-11 Initial commit
252 max_len = len(content)
253 next_point = -1
254 re_empty_line = r'^\s+$'
255
256 def get_line(ix):
257 start = ix
258 while start >= 0:
259 c = content[start]
260 if c == '\n' or c == '\r': break
261 start -= 1
262
263 return content[start:ix]
264
265 while cur_point < max_len and cur_point > 0:
266 cur_point += inc
267 cur_char = char_at(content, cur_point)
268 next_char = char_at(content, cur_point + 1)
269 prev_char = char_at(content, cur_point - 1)
270
69b79360 »
2010-06-02 update comments
271 if cur_char in '"\'' and syntax != 'css': # (FM) only for xml-like syntax
1b5aa30c »
2010-05-11 Initial commit
272 if next_char == cur_char and prev_char == '=':
273 # empty attribute
274 next_point = cur_point + 1
69b79360 »
2010-06-02 update comments
275 elif cur_char == '>' and next_char == '<' and syntax != 'css': # (FM) only for xml-like syntax
1b5aa30c »
2010-05-11 Initial commit
276 # between tags
277 next_point = cur_point + 1
69b79360 »
2010-06-02 update comments
278 # (FM) for css syntax
d39fb5ba »
2010-05-30 add edit points detection for css
279 elif cur_char == ':' and next_char == ';' and syntax == 'css':
280 next_point = cur_point + 1
281 elif cur_char == '(' and next_char == ')' and syntax == 'css':
282 next_point = cur_point + 1
69b79360 »
2010-06-02 update comments
283 # (FM) /for css syntax
1b5aa30c »
2010-05-11 Initial commit
284 elif cur_char in '\r\n':
285 # empty line
286 if re.search(re_empty_line, get_line(cur_point - 1)):
287 next_point = cur_point
288
289 if next_point != -1: break
290
291 return next_point
292
293 def prev_edit_point(editor):
294 """
295 Move caret to previous edit point
296 @param editor: Editor instance
297 @type editor: ZenEditor
298 """
299 cur_pos = editor.get_caret_pos()
300 new_point = find_new_edit_point(editor, -1)
301
302 if new_point == cur_pos:
303 # we're still in the same point, try searching from the other place
304 new_point = find_new_edit_point(editor, -1, -2)
305
306 if new_point != -1:
307 editor.set_caret_pos(new_point)
308 return True
309
310 return False
311
312 def next_edit_point(editor):
313 """
314 Move caret to next edit point
315 @param editor: Editor instance
316 @type editor: ZenEditor
317 """
318 new_point = find_new_edit_point(editor, 1)
319 if new_point != -1:
320 editor.set_caret_pos(new_point)
321 return True
322
323 return False
324
325 def insert_formatted_newline(editor, mode='html'):
326 """
327 Inserts newline character with proper indentation
328 @param editor: Editor instance
329 @type editor: ZenEditor
330 @param mode: Syntax mode (only 'html' is implemented)
331 @type mode: str
332 """
333 caret_pos = editor.get_caret_pos()
334 nl = zen_coding.get_newline()
335 pad = zen_coding.get_variable('indentation')
336
337 if mode == 'html':
338 # let's see if we're breaking newly created tag
339 pair = html_matcher.get_tags(editor.get_content(), editor.get_caret_pos(), editor.get_profile_name())
340
341 if pair[0] and pair[1] and pair[0]['type'] == 'tag' and pair[0]['end'] == caret_pos and pair[1]['start'] == caret_pos:
342 editor.replace_content(nl + pad + zen_coding.get_caret_placeholder() + nl, caret_pos)
343 else:
344 editor.replace_content(nl, caret_pos)
345 else:
346 editor.replace_content(nl, caret_pos)
347
348 return True
349
350 def select_line(editor):
351 """
352 Select line under cursor
353 @param editor: Editor instance
354 @type editor: ZenEditor
355 """
5bc97fc6 »
2010-05-12 Introduce 'Toggle between image url and data'
356 start, end = editor.get_current_line_range()
1b5aa30c »
2010-05-11 Initial commit
357 editor.create_selection(start, end)
358 return True
359
360 def go_to_matching_pair(editor):
361 """
362 Moves caret to matching opening or closing tag
363 @param editor: Editor instance
364 @type editor: ZenEditor
365 """
366 content = editor.get_content()
367 caret_pos = editor.get_caret_pos()
368
369 if content[caret_pos] == '<':
370 # looks like caret is outside of tag pair
371 caret_pos += 1
372
373 tags = html_matcher.get_tags(content, caret_pos, editor.get_profile_name())
374
375 if tags and tags[0]:
376 # match found
377 open_tag, close_tag = tags
378
379 if close_tag: # exclude unary tags
380 if open_tag['start'] <= caret_pos and open_tag['end'] >= caret_pos:
381 editor.set_caret_pos(close_tag['start'])
382 elif close_tag['start'] <= caret_pos and close_tag['end'] >= caret_pos:
383 editor.set_caret_pos(open_tag['start'])
384
385 return True
386
387 return False
388
389
390 def merge_lines(editor):
391 """
392 Merge lines spanned by user selection. If there's no selection, tries to find
393 matching tags and use them as selection
394 @param editor: Editor instance
395 @type editor: ZenEditor
396 """
397 start, end = editor.get_selection_range()
398 if start == end:
399 # find matching tag
400 pair = html_matcher.match(editor.get_content(), editor.get_caret_pos(), editor.get_profile_name())
401 if pair and pair[0] is not None:
402 start, end = pair
403
404 if start != end:
405 # got range, merge lines
406 text = editor.get_content()[start:end]
407 lines = map(lambda s: re.sub(r'^\s+', '', s), zen_coding.split_by_lines(text))
408 text = re.sub(r'\s{2,}', ' ', ''.join(lines))
409 editor.replace_content(text, start, end)
410 editor.create_selection(start, start + len(text))
411 return True
412
413 return False
414
415 def toggle_comment(editor):
416 """
417 Toggle comment on current editor's selection or HTML tag/CSS rule
418 @type editor: ZenEditor
419 """
420 syntax = editor.get_syntax()
421 if syntax == 'css':
422 return toggle_css_comment(editor)
423 else:
424 return toggle_html_comment(editor)
425
426 def toggle_html_comment(editor):
427 """
428 Toggle HTML comment on current selection or tag
429 @type editor: ZenEditor
430 @return: True if comment was toggled
431 """
432 start, end = editor.get_selection_range()
433 content = editor.get_content()
434
435 if start == end:
436 # no selection, find matching tag
437 pair = html_matcher.get_tags(content, editor.get_caret_pos(), editor.get_profile_name())
438 if pair and pair[0]: # found pair
439 start = pair[0].start
440 end = pair[1] and pair[1].end or pair[0].end
441
442 return generic_comment_toggle(editor, '<!--', '-->', start, end)
443
444 def toggle_css_comment(editor):
445 """
446 Simple CSS commenting
447 @type editor: ZenEditor
448 @return: True if comment was toggled
449 """
450 start, end = editor.get_selection_range()
451
452 if start == end:
453 # no selection, get current line
454 start, end = editor.get_current_line_range()
455
456 # adjust start index till first non-space character
457 start, end = narrow_to_non_space(editor.get_content(), start, end)
458
459 return generic_comment_toggle(editor, '/*', '*/', start, end)
460
461 def search_comment(text, pos, start_token, end_token):
462 """
463 Search for nearest comment in <code>str</code>, starting from index <code>from</code>
464 @param text: Where to search
465 @type text: str
466 @param pos: Search start index
467 @type pos: int
468 @param start_token: Comment start string
469 @type start_token: str
470 @param end_token: Comment end string
471 @type end_token: str
472 @return: None if comment wasn't found, list otherwise
473 """
474 start_ch = start_token[0]
475 end_ch = end_token[0]
476 comment_start = -1
477 comment_end = -1
478
479 def has_match(tx, start):
480 return text[start:start + len(tx)] == tx
481
482
483 # search for comment start
484 while pos:
485 pos -= 1
486 if text[pos] == start_ch and has_match(start_token, pos):
487 comment_start = pos
488 break
489
490 if comment_start != -1:
491 # search for comment end
492 pos = comment_start
493 content_len = len(text)
494 while content_len >= pos:
495 pos += 1
496 if text[pos] == end_ch and has_match(end_token, pos):
497 comment_end = pos + len(end_token)
498 break
499
500 if comment_start != -1 and comment_end != -1:
501 return comment_start, comment_end
502 else:
503 return None
504
505 def generic_comment_toggle(editor, comment_start, comment_end, range_start, range_end):
506 """
507 Generic comment toggling routine
508 @type editor: ZenEditor
509 @param comment_start: Comment start token
510 @type comment_start: str
511 @param comment_end: Comment end token
512 @type comment_end: str
513 @param range_start: Start selection range
514 @type range_start: int
515 @param range_end: End selection range
516 @type range_end: int
517 @return: bool
518 """
519 content = editor.get_content()
520 caret_pos = [editor.get_caret_pos()]
521 new_content = None
522
523 def adjust_caret_pos(m):
524 caret_pos[0] -= len(m.group(0))
525 return ''
526
527 def remove_comment(text):
528 """
529 Remove comment markers from string
530 @param {Sting} str
531 @return {String}
532 """
533 text = re.sub(r'^' + re.escape(comment_start) + r'\s*', adjust_caret_pos, text)
534 return re.sub(r'\s*' + re.escape(comment_end) + '$', '', text)
535
536 def has_match(tx, start):
537 return content[start:start + len(tx)] == tx
538
539 # first, we need to make sure that this substring is not inside comment
540 comment_range = search_comment(content, caret_pos[0], comment_start, comment_end)
541
542 if comment_range and comment_range[0] <= range_start and comment_range[1] >= range_end:
543 # we're inside comment, remove it
544 range_start, range_end = comment_range
545 new_content = remove_comment(content[range_start:range_end])
546 else:
547 # should add comment
548 # make sure that there's no comment inside selection
549 new_content = '%s %s %s' % (comment_start, re.sub(re.escape(comment_start) + r'\s*|\s*' + re.escape(comment_end), '', content[range_start:range_end]), comment_end)
550
551 # adjust caret position
552 caret_pos[0] += len(comment_start) + 1
553
554 # replace editor content
555 if new_content is not None:
556 d = caret_pos[0] - range_start
557 new_content = new_content[0:d] + zen_coding.get_caret_placeholder() + new_content[d:]
558 editor.replace_content(unindent(editor, new_content), range_start, range_end)
559 return True
560
561 return False
562
563 def split_join_tag(editor, profile_name=None):
564 """
565 Splits or joins tag, e.g. transforms it into a short notation and vice versa:
566 <div></div> → <div /> : join
567 <div /> → <div></div> : split
568 @param editor: Editor instance
569 @type editor: ZenEditor
570 @param profile_name: Profile name
571 @type profile_name: str
572 """
573 caret_pos = editor.get_caret_pos()
574 profile = zen_coding.get_profile(profile_name or editor.get_profile_name())
575 caret = zen_coding.get_caret_placeholder()
576
577 # find tag at current position
578 pair = html_matcher.get_tags(editor.get_content(), caret_pos, profile_name or editor.get_profile_name())
579 if pair and pair[0]:
580 new_content = pair[0].full_tag
581
582 if pair[1]: # join tag
583 closing_slash = ''
584 if profile['self_closing_tag'] is True:
585 closing_slash = '/'
586 elif profile['self_closing_tag'] == 'xhtml':
587 closing_slash = ' /'
588
589 new_content = re.sub(r'\s*>$', closing_slash + '>', new_content)
590
591 # add caret placeholder
592 if len(new_content) + pair[0].start < caret_pos:
593 new_content += caret
594 else:
595 d = caret_pos - pair[0].start
596 new_content = new_content[0:d] + caret + new_content[d:]
597
598 editor.replace_content(new_content, pair[0].start, pair[1].end)
599 else: # split tag
600 nl = zen_coding.get_newline()
601 pad = zen_coding.get_variable('indentation')
602
603 # define tag content depending on profile
604 tag_content = profile['tag_nl'] is True and nl + pad + caret + nl or caret
605
606 new_content = '%s%s</%s>' % (re.sub(r'\s*\/>$', '>', new_content), tag_content, pair[0].name)
607 editor.replace_content(new_content, pair[0].start, pair[0].end)
608
609 return True
610 else:
611 return False
612
613
614 def get_line_bounds(text, pos):
615 """
616 Returns line bounds for specific character position
617 @type text: str
618 @param pos: Where to start searching
619 @type pos: int
620 @return: list
621 """
622 start = 0
623 end = len(text) - 1
624
625 # search left
626 for i in range(pos - 1, 0, -1):
627 if text[i] in '\n\r':
628 start = i + 1
629 break
630
631 # search right
632 for i in range(pos, len(text)):
633 if text[i] in '\n\r':
634 end = i
635 break
636
637 return start, end
638
639 def remove_tag(editor):
640 """
641 Gracefully removes tag under cursor
642 @type editor: ZenEditor
643 """
644 caret_pos = editor.get_caret_pos()
645 content = editor.get_content()
646
647 # search for tag
648 pair = html_matcher.get_tags(content, caret_pos, editor.get_profile_name())
649 if pair and pair[0]:
650 if not pair[1]:
651 # simply remove unary tag
652 editor.replace_content(zen_coding.get_caret_placeholder(), pair[0].start, pair[0].end)
653 else:
654 tag_content_range = narrow_to_non_space(content, pair[0].end, pair[1].start)
655 start_line_bounds = get_line_bounds(content, tag_content_range[0])
656 start_line_pad = get_line_padding(content[start_line_bounds[0]:start_line_bounds[1]])
657 tag_content = content[tag_content_range[0]:tag_content_range[1]]
658
659 tag_content = unindent_text(tag_content, start_line_pad)
660 editor.replace_content(zen_coding.get_caret_placeholder() + tag_content, pair[0].start, pair[1].end)
661
662 return True
663 else:
664 return False
5bc97fc6 »
2010-05-12 Introduce 'Toggle between image url and data'
665
666 def encode_decode_base64(editor):
667 """
668 Encodes/decodes image under cursor to/from base64
01adefcb »
2010-05-13 Update from sergeche; explain changes in comments
669 @type editor: ZenEditor
670 @since: 0.65
5bc97fc6 »
2010-05-12 Introduce 'Toggle between image url and data'
671 """
672 data = editor.get_selection()
01adefcb »
2010-05-13 Update from sergeche; explain changes in comments
673 caret_pos, not_used = editor.get_selection_range() # (FM) caret_pos must point to the start of the selection
5bc97fc6 »
2010-05-12 Introduce 'Toggle between image url and data'
674
675 if not data:
01adefcb »
2010-05-13 Update from sergeche; explain changes in comments
676 # no selection, try to find image bounds from current caret position
5bc97fc6 »
2010-05-12 Introduce 'Toggle between image url and data'
677 text = editor.get_content()
01adefcb »
2010-05-13 Update from sergeche; explain changes in comments
678
5bc97fc6 »
2010-05-12 Introduce 'Toggle between image url and data'
679 while caret_pos >= 0:
01adefcb »
2010-05-13 Update from sergeche; explain changes in comments
680 if text.startswith('src=', caret_pos): # found <img src="">, (FM) startswith already exists
5bc97fc6 »
2010-05-12 Introduce 'Toggle between image url and data'
681 m = re.match(r'^(src=(["\'])?)([^\'"<>\s]+)\1?', text[caret_pos:])
682 if m:
683 data = m.group(3)
684 caret_pos += len(m.group(1))
685 break
01adefcb »
2010-05-13 Update from sergeche; explain changes in comments
686 elif text.startswith('url(', caret_pos): # found CSS url() pattern, (FM) startswith already exists
5bc97fc6 »
2010-05-12 Introduce 'Toggle between image url and data'
687 m = re.match(r'^(url\(([\'"])?)([^\'"\)\s]+)\1?/', text[caret_pos:])
688 if m:
689 data = m.group(3)
690 caret_pos += len(m.group(1))
691 break
01adefcb »
2010-05-13 Update from sergeche; explain changes in comments
692
5bc97fc6 »
2010-05-12 Introduce 'Toggle between image url and data'
693 caret_pos -= 1
694
695 if data:
01adefcb »
2010-05-13 Update from sergeche; explain changes in comments
696 if data.startswith('data:'): # (FM) startswith already exists
5bc97fc6 »
2010-05-12 Introduce 'Toggle between image url and data'
697 return decode_from_base64(editor, data, caret_pos)
698 else:
699 return encode_to_base64(editor, data, caret_pos)
700 else:
701 return False
702
703 def encode_to_base64(editor, img_path, pos):
704 """
705 Encodes image to base64
01adefcb »
2010-05-13 Update from sergeche; explain changes in comments
706 @requires: zen_file
707
708 @type editor: ZenEditor
709 @param img_path: Path to image
710 @type img_path: str
711 @param pos: Caret position where image is located in the editor
712 @type pos: int
713 @return: bool
5bc97fc6 »
2010-05-12 Introduce 'Toggle between image url and data'
714 """
715 editor_file = editor.get_file_path()
716 default_mime_type = 'application/octet-stream'
717
01adefcb »
2010-05-13 Update from sergeche; explain changes in comments
718 if editor_file is None:
719 raise ZenError("You should save your file before using this action")
720
5bc97fc6 »
2010-05-12 Introduce 'Toggle between image url and data'
721
722 # locate real image path
723 real_img_path = zen_file.locate_file(editor_file, img_path)
01adefcb »
2010-05-13 Update from sergeche; explain changes in comments
724 if real_img_path is None:
725 raise ZenError("Can't find %s file" % img_path)
5bc97fc6 »
2010-05-12 Introduce 'Toggle between image url and data'
726
727 b64 = base64.b64encode(zen_file.read(real_img_path))
728 if not b64:
01adefcb »
2010-05-13 Update from sergeche; explain changes in comments
729 raise ZenError("Can't encode file content to base64")
730
731
732 b64 = 'data:' + (mime_types[zen_file.get_ext(real_img_path)] or default_mime_type) + ';base64,' + b64
733
69b79360 »
2010-06-02 update comments
734 editor.replace_content(b64, pos, pos + len(img_path)) # (FM) don't use snippets so don't need $0
5bc97fc6 »
2010-05-12 Introduce 'Toggle between image url and data'
735 return True
736
737 def decode_from_base64(editor, data, pos):
738 """
739 Decodes base64 string back to file.
01adefcb »
2010-05-13 Update from sergeche; explain changes in comments
740 @requires: zen_editor.prompt
741 @requires: zen_file
742
743 @type editor: ZenEditor
744 @param data: Base64-encoded file content
745 @type data: str
746 @param pos: Caret position where image is located in the editor
747 @type pos: int
5bc97fc6 »
2010-05-12 Introduce 'Toggle between image url and data'
748 """
749 # ask user to enter path to file
750 file_path = editor.prompt('Enter path to file (absolute or relative)')
751 if not file_path:
752 return False
753
754 abs_path = zen_file.create_path(editor.get_file_path(), file_path)
755 if not abs_path:
01adefcb »
2010-05-13 Update from sergeche; explain changes in comments
756 raise ZenError("Can't save file")
5bc97fc6 »
2010-05-12 Introduce 'Toggle between image url and data'
757
01adefcb »
2010-05-13 Update from sergeche; explain changes in comments
758
759 zen_file.save(abs_path, base64.b64decode( re.sub(r'^data\:.+?;.+?,', '', data) ))
69b79360 »
2010-06-02 update comments
760 editor.replace_content(file_path, pos, pos + len(data)) # (FM) don't use snippets so don't need $0
01adefcb »
2010-05-13 Update from sergeche; explain changes in comments
761 return True
Something went wrong with that request. Please try again.