pmacs3/mode/scala.py

377 lines
13 KiB
Python
Raw Normal View History

from tab import StackTabber2
from mode import Fundamental
from lex import Grammar, PatternRule, RegionRule, PatternMatchRule
2010-09-30 18:18:57 -04:00
from mode.sh import ShGrammar
from mode.xml import XMLGrammar
from mode.pipe import Pipe
from method.shell import Interact
from method import Method, arg
import default
import urllib2
import os
import re
chr1 = '[a-zA-Z_]'
chr2 = '[a-zA-Z_0-9]'
word = chr1 + chr2 + '*'
class NestedCommentGrammar(Grammar): pass
NestedCommentGrammar.rules = [
RegionRule('comment', r'/\*', NestedCommentGrammar, r'\*/'),
PatternRule('data', r'(?:[^\*]|\*(?!/))+'),
]
class StringGrammar(Grammar):
rules = [
PatternRule('escaped', r"\\u[0-9A-Fa-f]{4}|\\[0-7]{1,3}|\\[btnfr\"'\\]"),
PatternRule('data', r'[^\\"]+'),
]
class SubTypeGrammar(Grammar): pass
SubTypeGrammar.rules = [
RegionRule('sub', r'\[', SubTypeGrammar, r'\]'),
PatternRule('scala.type', '[a-zA-Z0-9_]+'),
PatternRule('spaces', ' +'),
PatternRule('scala.annotation', '@[a-zA-Z_][a-zA-Z0-9_]*'),
]
class ScalaGrammar(Grammar):
rules = [
PatternRule('scala.comment', '//.*$'),
RegionRule('scala.comment', r'/\*', NestedCommentGrammar, r'\*/'),
2010-09-30 18:18:57 -04:00
RegionRule('scala.script', r'#!.+$', ShGrammar, r'!#'),
PatternMatchRule('x', r'(?<!:)(:)([a-zA-Z0-9_]+)',
'delimiter', 'scala.type'),
PatternMatchRule('x', r'(?<!:)(:)( +)([a-zA-Z0-9_]+)',
'delimiter', 'spaces', 'scala.type'),
#PatternMatchRule('x', r'(?<=[a-zA-Z0-9_ ])(:)([a-zA-Z0-9_]+)',
# 'delimiter', 'scala.type'),
#PatternMatchRule('x', r'(?<=[a-zA-Z0-9_ ])(:)( +)([a-zA-Z0-9_]+)',
# 'delimiter', 'spaces', 'scala.type'),
PatternMatchRule('x', r'(extends)( +)([a-zA-Z0-9_]+)',
'scala.reserved', 'spaces', 'scala.type'),
PatternMatchRule('x', r'(with)( +)([a-zA-Z0-9_]+)',
'scala.reserved', 'spaces', 'scala.type'),
#PatternRule('delimiter', r'(?:;|{|}|\[|\]|\(|\)|,|\.|<(?![a-zA-Z_])|>|:|/|\+|-|\*|=)'),
RegionRule('sub', r'(?<=:)\(', SubTypeGrammar, r'\)'),
PatternRule('delimiter', r'(?:;|{|}|\(|\)|,|\.|<(?![a-zA-Z_])|>|:|/|\+|-|\*|=)'),
RegionRule('sub', r'\[', SubTypeGrammar, r'\]'),
RegionRule('scala.inline', r'(?:^| )(?=<[a-zA-Z_])', XMLGrammar, '^[ \t]*$'),
PatternRule('spaces', r'(?:\t| )+'),
PatternRule('eol', r'\n'),
PatternRule('scala.def', '(?<=(?<![a-zA-Z0-9_])def )[a-zA-Z_][a-zA-Z0-9_]*'),
PatternRule('scala.class', '(?<=(?<![a-zA-Z0-9_])class )[a-zA-Z_][a-zA-Z0-9_]*'),
PatternRule('scala.object', '(?<=(?<![a-zA-Z0-9_])object )[a-zA-Z_][a-zA-Z0-9_]*'),
PatternRule('scala.trait', '(?<=(?<![a-zA-Z0-9_])trait )[a-zA-Z_][a-zA-Z0-9_]*'),
PatternRule('scala.pseudo', '(?:true|null|false)(?!%s)' % word),
PatternRule('scala.reserved', '(?:yield|with|while|var|val|until|type|true|try|trait|throw|to|this|super|sealed|return|protected|private|package|override|object|null|new|match|lazy|import|implicit|if|forSome|for|finally|final|false|extends|else|do|def|class|catch|case|abstract)(?!%s)' % word),
PatternRule('scala.integer', '-?(?:0|[1-9])[0-9]*[Ll]?'),
PatternRule('scala.integer', '-?0x[0-9A-Fa-f]+[Ll]?'),
PatternRule('scala.integer', '-?0[0-7]+[Ll]?'),
PatternRule('scala.float', r'-?[0-9]+\.[0-9]*'), # FIXME
PatternRule('scala.char', r"'(?:[^'\\]|\\u[0-9A-Fa-f]{4}|\\[0-7]{1,3}|\\[btnfr\"'\\])'"),
2010-09-30 18:18:57 -04:00
RegionRule('scala.string', '"""', Grammar, '"""'),
RegionRule('scala.string', '"', StringGrammar, '"'),
PatternRule('scala.symbol', "'[a-zA-Z_][a-zA-Z0-9_]*"),
PatternRule('scala.annotation', '@[a-zA-Z_][a-zA-Z0-9_]*'),
PatternRule('scala.bareword', '[a-zA-Z_][a-zA-Z0-9_]*'),
]
class ScalaTabber(StackTabber2):
#open_tokens = {'delimiter': {'{': '}', '(': ')', '[': ']'}}
#close_tokens = {'delimiter': {'}': '{', ')': '(', ']': '['}}
open_tokens = {'delimiter': {'{': '}', '(': ')'},
'sub.start': {'[': ']'}}
close_tokens = {'delimiter': {'}': '{', ')': '('},
'sub.end': {']': '['}}
control_tokens = {'scala.reserved': set(('if', 'else', 'while', 'do', 'for'))}
end_at_eof = True
start_free_tokens = {'string.start': 'string.end'}
end_free_tokens = {'string.end': 'string.start'}
is_ignored_tokens = set(('spaces', 'eol', 'comment', 'comment.start',
'comment.data', 'comment.null', 'comment.end'))
is_indent_tokens = set(('spaces',))
def _is_base(self, y):
# the first line is always safe
if y == 0: return True
# if there are no tokens we don't really have any info
tokens = self._get_tokens(y)
if not tokens: return False
# if it looks like a top-level class, object or function, then say ok
t = tokens[0]
if t.fqmatchs('scala.reserved', ('class', 'object', 'def')):
return True
# the default is to assume no
return False
class ScalaStart(Interact):
args = []
modename = 'scalapipe'
reuse = True
def _execute(self, w, **vargs):
Interact._execute(self, w, bname='*Scala*', cmd='scala')
class ScalaDocBrowse(Method):
def _execute(self, w, **vargs):
a = w.application
url = a.config['scala.api']
Interact().execute(w, bname='*Scala-Doc*', cmd='links "%s"' % url)
class ScalaDocLookup(Method):
args = [arg('name', t='string', p='Name: ', dv=default.current_word,
ld=True, h='The Scala name to get help on')]
def _get_path(self, w, name):
return w.application.getpath('cache', 'scala', name)
def _get_url(self, w, name, url):
path = self._get_path(w, name)
w.application.mkdirs('cache', 'scala')
if not os.path.exists(path):
open(path, 'w').write(urllib2.urlopen(url).read())
html = open(path, 'r').read()
return html
def _execute(self, w, **vargs):
try:
from BeautifulSoup import BeautifulSoup
except ImportError:
w.set_error('BeautifulSoup is not installed...')
return
a = w.application
name = vargs.get('name')
html = self._get_url(w, 'api.html', a.config['scala.api'])
soup = BeautifulSoup(html)
tags = soup.findAll('li')
for li in tags:
if li['title'].endswith(name):
frag = li.contents[0]['href']
url2 = a.config['scala.api-base'] + '/' + frag
a.run_external('links', url2)
return
w.set_error('error looking up %s...' % name)
class ScalaXRayBase(Method):
_is_method = False
pkg_re = re.compile('^package (.+)$')
def find_pkg(self, w):
for line in w.buffer.lines:
m = self.pkg_re.match(line)
if m:
return m.group(1)
raise Exception("no package found")
def get_sxr_dir(self, w):
# TODO: fixme fixme
path = 'target/scala_2.8.0/classes.sxr'
if os.path.exists(path):
return path
else:
raise Exception("classes.sxr not found")
2010-10-28 17:02:21 -04:00
def get_pkg_tags(self, w, pkg, base):
return os.path.join(base, pkg.replace('.', '/') + '.scala.txt')
def get_public_tags(self, w, base):
return os.path.join(base, 'public-tags')
2010-10-28 17:02:21 -04:00
def term_def_lookup(self, w):
pkg = self.find_pkg(w)
base = self.get_sxr_dir(w)
tags = self.get_public_tags
def type_def_lookup(self, t, w):
base = self.get_sxr_dir(w)
tags = self.get_public_tags(w, base)
f = open(tags, 'r')
for line in f:
toks = line.split()
if toks[0] != 'type': continue
if toks[1] != t: continue
path = toks[2]
n = int(toks[4])
return (path, n)
return (None, None)
def type_lookup(self, n, w):
pkg = self.find_pkg(w)
base = self.get_sxr_dir(w)
2010-10-28 17:02:21 -04:00
path = w.buffer.path
r = re.compile('^.*src/main/scala/(.+)$')
m = r.match(path)
tags = os.path.join(base, m.group(1) + '.txt')
f = open(tags, 'r')
for line in f:
toks = line.split('\t')
i = int(toks[0])
j = int(toks[1])
if i <= n and n <= j:
return toks[2]
return None
class ScalaGetType(ScalaXRayBase):
type_re = re.compile('(?:[^ \[\]{}()\.,;:]+\.)*([^ \[\]{}()\.,;:]+)')
ret_re = re.compile('\)(?=[a-zA-Z0-9_])')
def _execute(self, w, **vargs):
word = w.get_token().string
if word is None or word.strip() == "":
w.set_error('no word selected')
return
n = w.cursor_byte_offset()
t = self.type_lookup(n, w)
2010-10-28 17:02:21 -04:00
if not t:
w.set_error('%s has unknown type' % word)
2010-10-28 17:02:21 -04:00
return
if w.application.config['scala.type-abbrev']:
t = self.type_re.sub(lambda m: m.group(1), t)
#t = t.replace(')', ') => ')
t = self.ret_re.sub(') => ', t)
t = t.replace(': ', ':')
t = t.replace(',', ', ')
w.set_error(t)
class ScalaGotoDefinition(ScalaXRayBase):
2010-10-28 17:02:21 -04:00
generic_re = re.compile('\[.+\]')
def _execute(self, w, **vargs):
2010-10-28 17:02:21 -04:00
word = w.get_token().string
if word is None or word.strip() == "":
w.set_error('no word selected')
return
n = w.cursor_byte_offset()
t = self.type_lookup(n, w)
if not t:
w.set_error('%s has unknown type' % word)
return
# remove generics
t = self.generic_re.sub('', t)
path, n = self.type_def_lookup(t, w)
if not path:
w.set_error('%s not found' % t)
return
b = w.buffer
a = w.application
b2 = a.open_path(path)
a.switch_buffer(b2)
if b == b2:
a.methods['goto-char'].execute(w, charno=n - 1)
a.methods['center-view'].execute(w)
else:
a.methods['goto-char'].execute(b2.windows[0], charno=n - 1)
a.methods['center-view'].execute(b2.windows[0])
w.set_error('opening %s...' % path)
# white is for delimiters, operators, numbers
default = ('default', 'default')
# magenta is for keywords/builtins
lo_magenta = ('magenta202', 'default')
hi_magenta = ('magenta414', 'default')
# red is for comments
lo_red = ('red300', 'default')
hi_red = ('red511', 'default')
# orange is for macro definitions, headers and constants
hi_orange = ('yellow531', 'default')
lo_orange = ('yellow520', 'default')
# yellow is for parts of macros
hi_yellow = ('yellow551', 'default')
lo_yellow = ('yellow330', 'default')
# green is for strings and characters
lo_green = ('green030', 'default')
hi_green = ('green050', 'default')
# cyan is for types
lo_cyan = ('cyan033', 'default')
hi_cyan = ('cyan155', 'default')
# blue is definitions, functions and some macros
lo_blue = ('blue113', 'default')
hi_blue = ('blue225', 'default')
class Scala(Fundamental):
name = 'Scala'
extensions = ['.scala']
tabwidth = 2
tabbercls = ScalaTabber
grammar = ScalaGrammar
commentc = '//'
2010-10-28 17:02:21 -04:00
actions = [ScalaStart, ScalaDocBrowse, ScalaDocLookup, ScalaGetType, ScalaGotoDefinition]
opentokens = ('delimiter', 'sub.start', 'sub.sub.start', 'sub.sub.sub.start')
opentags = {'(': ')', '[': ']', '{': '}'}
closetokens = ('delimiter', 'sub.end', 'sub.sub.end', 'sub.sub.sub.end')
closetags = {')': '(', ']': '[', '}': '{'}
config = {
'scala.api': 'http://www.scala-lang.org/api/current/allclasses.html',
'scala.api-base': 'http://www.scala-lang.org/api/current',
'scala.type-abbrev': True,
}
colors = {
2010-09-30 18:18:57 -04:00
'scala.script.start': hi_red,
'scala.script.end': hi_red,
'scala.annotation': lo_orange,
'scala.pseudo': hi_magenta,
'scala.reserved': hi_cyan,
'scala.integer': default,
'scala.float': default,
'scala.bareword': default,
'scala.symbol': hi_orange,
'scala.class': hi_yellow,
'scala.object': hi_yellow,
'scala.trait': hi_yellow,
'scala.type': hi_magenta,
'scala.def': hi_blue,
'scala.def': hi_blue,
}
_bindings = {
'scala-get-type': ('M-,',),
2010-10-28 17:02:21 -04:00
'scala-goto-definition': ('C-c ,',),
'close-paren': (')',),
'close-brace': ('}',),
'close-bracket': (']',),
}
class ScalaPipe(Pipe):
name = 'scalapipe'
grammar = ScalaGrammar
def install(*args):
Scala.install(*args)
ScalaPipe.install(*args)