pmacs3/mode/scala.py

318 lines
11 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_public_tags(self, w, base):
return os.path.join(base, 'public-tags')
def type_lookup(self, n, w):
pkg = self.find_pkg(w)
base = self.get_sxr_dir(w)
tags = os.path.join(base, pkg.replace('.', '/') + '.scala.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 t:
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)
else:
w.set_error('%s has unknown type' % word)
class ScalaGotoDefinition(ScalaXRayBase):
def _execute(self, w, **vargs):
w.set_error('borken')
# 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]
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-,',),
'close-paren': (')',),
'close-brace': ('}',),
'close-bracket': (']',),
}
class ScalaPipe(Pipe):
name = 'scalapipe'
grammar = ScalaGrammar
def install(*args):
Scala.install(*args)
ScalaPipe.install(*args)