Skip to content

Commit

Permalink
handle multiple selections
Browse files Browse the repository at this point in the history
  • Loading branch information
mreq committed Dec 26, 2016
1 parent 4bafa0b commit b1f65ae
Show file tree
Hide file tree
Showing 9 changed files with 146 additions and 137 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@
All notable changes to this project will be documented in this file.
This project adheres to [Semantic Versioning](http://semver.org/). Changelog muster from [olivierlacan/keep-a-changelog](https://github.com/olivierlacan/keep-a-changelog).

## [0.1.0] - 2016-12-26
### Added
- handle multiple selections

## [0.0.5] - 2016-12-26
### Added
- `sublime_command` spell
Expand Down
49 changes: 49 additions & 0 deletions context/context.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import re


def check_scope(current_scope, target_scopes):
valid = True
for target_scope in target_scopes:
if not re.search(target_scope, current_scope):
valid = False
break
return valid


def check_line_matches(line_text, target_parts):
valid = True
for target_part in target_parts:
if not re.search(target_part, line_text):
valid = False
break
return valid


def check(view, spell):
valid = True

for sel in view.sel():
line = view.line(sel.a)
line_text = view.substr(line)
view_scope = view.scope_name(sel.a)

context = spell.get('context', {'scope': [], 'line_matches': []})
spell_scope = context.get('scope', [])
line_matches = context.get('line_matches', [])
values = spell.get('args', {'values': None}).get('values', None)

if values:
escaped_values = [re.escape(val) for val in values]
line_matches.append('(' + '|'.join(escaped_values) + ')')

valid = check_scope(view_scope, spell_scope)

if not valid:
break

valid = check_line_matches(line_text, line_matches)

if not valid:
break

return valid
41 changes: 0 additions & 41 deletions context/context_checker.py

This file was deleted.

13 changes: 7 additions & 6 deletions spells/perform_line_regex.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,15 @@ class PerformLineRegexSpell(MagicSpell):

def cast(self):
pattern = re.compile(self.spell.get('args').get('pattern'))
replacement = self.spell.get('args').get('replacement')
sel = self.view.sel()[0]
line = self.view.line(sel.a)

replacement = self.spell.get('args').get('replacement')
if replacement == '$clipboard':
replacement = sublime.get_clipboard()

line_text = self.view.substr(line)
new_line_text = re.sub(pattern, "%s" % replacement, line_text)
for sel in self.view.sel():
line = self.view.line(sel.a)

line_text = self.view.substr(line)
new_line_text = re.sub(pattern, "%s" % replacement, line_text)

return self.view.replace(self.edit, line, new_line_text)
self.view.replace(self.edit, line, new_line_text)
71 changes: 25 additions & 46 deletions spells/replace_text.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,68 +2,47 @@
import sublime

from .magic_spell import MagicSpell
from ..utils import utils


class ReplaceTextSpell(MagicSpell):
required_args = ['where', 'delimiter', 'replacement']

def cast(self):
where = self.spell.get('args').get('where')
self.delimiter_length = len(self.spell.get('args').get('delimiter'))
self.delimiter = re.compile(self.spell.get('args').get('delimiter'))
self.replacement = self.spell.get('args').get('replacement')
self.sel = self.view.sel()[0]
self.line = self.view.line(self.sel.a)
delimiter = self.spell.get('args').get('delimiter')
delimiter_length = len(delimiter)
re_delimiter = re.compile(delimiter)

self.replacement = self.spell.get('args').get('replacement')
if self.replacement == '$clipboard':
self.replacement = sublime.get_clipboard()

if where == 'inside':
start = self.find_previous_delimiter(self.sel.a)
if start:
end = self.find_next_delimiter(self.sel.b)
if end:
return self.replace(start, end)
elif where == 'after':
start = self.find_first_delimiter_in_line()
if start:
start = start + self.delimiter_length
end = self.end_of_line()
if end:
return self.replace(start, end)
else:
raise AttributeError('Unknown value for "where": ' + where)

def find_previous_delimiter(self, start):
found = None
for sel in self.view.sel():
line = self.view.line(sel.a)

while start > self.line.a and start > 0:
region = sublime.Region(start - 1, start)
if self.delimiter.match(self.view.substr(region)):
found = start
break
start -= 1
if where == 'inside':
start = utils.find_previous_delimiter(
self.view, line, re_delimiter, sel.a)

return found
if start:
end = utils.find_next_delimiter(
self.view, line, re_delimiter, delimiter_length, sel.b)
if end:
self.replace(start, end)

def find_next_delimiter(self, start):
found = None
elif where == 'after':
start = utils.find_next_delimiter(
self.view, line, re_delimiter, delimiter_length, line.a)
if start:
start = start + delimiter_length
end = line.b
if end:
return self.replace(start, end)

while start < self.line.b and start < 999999:
region = sublime.Region(start, start + self.delimiter_length)
if self.delimiter.match(self.view.substr(region)):
found = start
break
start += 1

return found

def find_first_delimiter_in_line(self):
return self.find_next_delimiter(self.line.a)
else:
raise AttributeError('Unknown value for "where": ' + where)

def replace(self, start, end):
region = sublime.Region(start, end)
self.view.replace(self.edit, region, self.replacement)

def end_of_line(self):
return self.line.b
11 changes: 5 additions & 6 deletions spells/toggle_values.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,11 @@ def cast(self):
escaped_values = [re.escape(val) for val in self.values]
pattern = '(' + '|'.join(escaped_values) + ')'

sel = self.view.sel()[0]
line = self.view.line(sel.a)
line_text = self.view.substr(line)
new_line_text = re.sub(pattern, self.replace, line_text)

return self.view.replace(self.edit, line, new_line_text)
for sel in self.view.sel():
line = self.view.line(sel.a)
line_text = self.view.substr(line)
new_line_text = re.sub(pattern, self.replace, line_text)
self.view.replace(self.edit, line, new_line_text)

def replace(self, matchobj):
index = self.values.index(matchobj.group(0)) + 1
Expand Down
32 changes: 15 additions & 17 deletions sublime_magic.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,24 @@
import sublime
import sublime_plugin
from .messenger import messenger
from .context import context_checker
from .spells import *
from .context import context


class SublimeMagic(sublime_plugin.TextCommand):

def run(self, edit):
self.edit = edit
self.get_known_spells()
self.known_spells = [
'replace_text',
'perform_line_regex',
'toggle_values',
'sublime_command'
]

self.get_user_spells()
self.find_first_matching_spell()
self.find_first_matching_user_spell()

if self.spell is None:
self.message('No spell found.')
else:
Expand All @@ -21,24 +28,15 @@ def run(self, edit):
except NotImplementedError as e:
self.message(str(e))

def get_known_spells(self):
# TODO: can we do this better?
self.known_spells = []
for name, val in globals().items():
if hasattr(val, '__name__'):
if 'SublimeMagic.spells' in val.__name__:
self.known_spells.append(name)

def get_user_spells(self):
self.magic_settings = sublime.load_settings(
'SublimeMagic.sublime-settings')
self.user_spells = self.magic_settings.get('spells')

def find_first_matching_spell(self):
def find_first_matching_user_spell(self):
self.spell = None
checker = context_checker.ContextChecker(self.view)
for spell in self.user_spells:
if checker.check(spell):
if context.check(self.view, spell):
self.spell = spell
break

Expand All @@ -55,13 +53,13 @@ def cast_spell(self):
if not spell_name in self.known_spells:
raise NotImplementedError('Unknown spell: ' + spell_name)
try:
spell_fn = eval(
spell_class = eval(
spell_name +
'.' +
self.spell_to_class(spell_name) +
'Spell')
spell_fn = spell_fn(self.edit, self.view, self.spell)
spell_fn.cast()
spell_class = spell_class(self.edit, self.view, self.spell)
spell_class.cast()
name = self.spell.get('name', None)
if not name:
name = spell_name
Expand Down
35 changes: 14 additions & 21 deletions tests/test_context_checker.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,36 +2,29 @@
import sys

from base import SublimeMagicTestCase
from SublimeMagic.context import context

context_checker_module = sys.modules['SublimeMagic.context.context_checker']

def c(a):
return {
'context': a
}

class TestContextChecker(SublimeMagicTestCase):

def test_scope_name(self):
checker = context_checker_module.ContextChecker(self.view)
self.assertEqual(checker.scope_name, 'text.plain ')

def test_line_text(self):
self.setText('foo bar')
checker = context_checker_module.ContextChecker(self.view)
self.assertEqual(checker.line_text, 'foo bar')
class TestContextChecker(SublimeMagicTestCase):

def test_check_scope(self):
checker = context_checker_module.ContextChecker(self.view)

target_scopes = ['plain']
self.assertTrue(checker.check_scope(target_scopes))
test_context = c({'scope': ['plain']})
self.assertTrue(context.check(self.view, test_context))

target_scopes = ['foobar']
self.assertFalse(checker.check_scope(target_scopes))
test_context = c({'scope': ['foobar']})
self.assertFalse(context.check(self.view, test_context))

def test_check_line_matches(self):
self.setText('foo bar')
checker = context_checker_module.ContextChecker(self.view)

target_parts = ['foo', 'bar', 'foo bar']
self.assertTrue(checker.check_line_matches(target_parts))
test_context = c({'line_matches': ['foo', 'bar', 'foo bar']})
self.assertTrue(context.check(self.view, test_context))

target_parts = ['foobar']
self.assertFalse(checker.check_line_matches(target_parts))
test_context = c({'line_matches': ['foobar']})
self.assertFalse(context.check(self.view, test_context))
27 changes: 27 additions & 0 deletions utils/utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import sublime


def find_previous_delimiter(view, line, re_delimiter, start):
found = None

while start > line.a and start > 0:
region = sublime.Region(start - 1, start)
if re_delimiter.match(view.substr(region)):
found = start
break
start -= 1

return found


def find_next_delimiter(view, line, re_delimiter, delimiter_length, start):
found = None

while start < line.b and start < 999999:
region = sublime.Region(start, start + delimiter_length)
if re_delimiter.match(view.substr(region)):
found = start
break
start += 1

return found

0 comments on commit b1f65ae

Please sign in to comment.