pmacs3/mode/scala.py

376 lines
13 KiB
Python

from tab import StackTabber2
from mode import Fundamental
from lex import Grammar, PatternRule, RegionRule, PatternMatchRule
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', ' +'),
]
class ScalaGrammar(Grammar):
rules = [
PatternRule('scala.comment', '//.*$'),
RegionRule('scala.comment', r'/\*', NestedCommentGrammar, r'\*/'),
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)'),
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\"'\\])'"),
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")
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')
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)
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)
if not t:
w.set_error('%s has unknown type' % word)
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):
generic_re = re.compile('\[.+\]')
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)
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 = '//'
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 = {
'scala.script.start': hi_red,
'scala.script.end': hi_red,
'scala.annotation': lo_green,
'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-,',),
'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)