org.codehaus.groovy.tools.shell.completion.GroovySyntaxCompletor.groovy Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of driver-cql-shaded Show documentation
Show all versions of driver-cql-shaded Show documentation
A Shaded CQL ActivityType driver for http://nosqlbench.io/
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.codehaus.groovy.tools.shell.completion
import antlr.TokenStreamException
import groovy.transform.TupleConstructor
import jline.console.completer.Completer
import jline.internal.Configuration
import org.codehaus.groovy.antlr.GroovySourceToken
import org.codehaus.groovy.antlr.SourceBuffer
import org.codehaus.groovy.antlr.UnicodeEscapingReader
import org.codehaus.groovy.antlr.parser.GroovyLexer
import org.codehaus.groovy.tools.shell.CommandRegistry
import org.codehaus.groovy.tools.shell.Groovysh
import org.codehaus.groovy.tools.shell.util.Logger
import static org.codehaus.groovy.antlr.parser.GroovyTokenTypes.DOT
import static org.codehaus.groovy.antlr.parser.GroovyTokenTypes.EOF
import static org.codehaus.groovy.antlr.parser.GroovyTokenTypes.IDENT
import static org.codehaus.groovy.antlr.parser.GroovyTokenTypes.LITERAL_as
import static org.codehaus.groovy.antlr.parser.GroovyTokenTypes.LITERAL_boolean
import static org.codehaus.groovy.antlr.parser.GroovyTokenTypes.LITERAL_byte
import static org.codehaus.groovy.antlr.parser.GroovyTokenTypes.LITERAL_catch
import static org.codehaus.groovy.antlr.parser.GroovyTokenTypes.LITERAL_char
import static org.codehaus.groovy.antlr.parser.GroovyTokenTypes.LITERAL_class
import static org.codehaus.groovy.antlr.parser.GroovyTokenTypes.LITERAL_def
import static org.codehaus.groovy.antlr.parser.GroovyTokenTypes.LITERAL_double
import static org.codehaus.groovy.antlr.parser.GroovyTokenTypes.LITERAL_enum
import static org.codehaus.groovy.antlr.parser.GroovyTokenTypes.LITERAL_false
import static org.codehaus.groovy.antlr.parser.GroovyTokenTypes.LITERAL_finally
import static org.codehaus.groovy.antlr.parser.GroovyTokenTypes.LITERAL_float
import static org.codehaus.groovy.antlr.parser.GroovyTokenTypes.LITERAL_import
import static org.codehaus.groovy.antlr.parser.GroovyTokenTypes.LITERAL_instanceof
import static org.codehaus.groovy.antlr.parser.GroovyTokenTypes.LITERAL_int
import static org.codehaus.groovy.antlr.parser.GroovyTokenTypes.LITERAL_interface
import static org.codehaus.groovy.antlr.parser.GroovyTokenTypes.LITERAL_long
import static org.codehaus.groovy.antlr.parser.GroovyTokenTypes.LITERAL_package
import static org.codehaus.groovy.antlr.parser.GroovyTokenTypes.LITERAL_short
import static org.codehaus.groovy.antlr.parser.GroovyTokenTypes.LITERAL_this
import static org.codehaus.groovy.antlr.parser.GroovyTokenTypes.LITERAL_true
import static org.codehaus.groovy.antlr.parser.GroovyTokenTypes.LITERAL_try
import static org.codehaus.groovy.antlr.parser.GroovyTokenTypes.LITERAL_void
import static org.codehaus.groovy.antlr.parser.GroovyTokenTypes.OPTIONAL_DOT
import static org.codehaus.groovy.antlr.parser.GroovyTokenTypes.SPREAD_DOT
/**
* Implements the Completor interface to provide competions for
* GroovyShell by tokenizing the buffer and invoking other classes depending on the tokens found.
*
* @author Marty Saxton
*/
class GroovySyntaxCompletor implements Completer {
protected final static Logger LOG = Logger.create(GroovySyntaxCompletor)
private final Groovysh shell
private final List identifierCompletors
private final IdentifierCompletor classnameCompletor
private final ReflectionCompletor reflectionCompletor
private final InfixKeywordSyntaxCompletor infixCompletor
private final Completer defaultFilenameCompletor
private final Completer windowsFilenameCompletor
private final Completer instringFilenameCompletor
private final Completer backslashCompletor
private static final boolean isWin = Configuration.isWindows()
private final GroovyShell gs = new GroovyShell()
static enum CompletionCase {
SECOND_IDENT,
NO_COMPLETION,
DOT_LAST,
SPREAD_DOT_LAST,
PREFIX_AFTER_DOT,
PREFIX_AFTER_SPREAD_DOT,
NO_DOT_PREFIX,
INSTANCEOF
}
GroovySyntaxCompletor(final Groovysh shell,
final ReflectionCompletor reflectionCompletor,
IdentifierCompletor classnameCompletor,
final List identifierCompletors,
final Completer filenameCompletor) {
this.shell = shell
this.classnameCompletor = classnameCompletor
this.identifierCompletors = identifierCompletors
infixCompletor = new InfixKeywordSyntaxCompletor()
backslashCompletor = new BackslashEscapeCompleter()
this.reflectionCompletor = reflectionCompletor
defaultFilenameCompletor = filenameCompletor
windowsFilenameCompletor = new FileNameCompleter(false, true, false)
instringFilenameCompletor = new FileNameCompleter(false, false, false)
}
@Override
int complete(final String bufferLine, final int cursor, final List candidates) {
if (!bufferLine) {
return -1
}
if (isCommand(bufferLine, shell.registry)) {
return -1
}
// complete given the context of the whole buffer, not just last line
// Build a single string for the lexer
List tokens = []
try {
if (!tokenizeBuffer(bufferLine.substring(0, cursor), shell.buffers.current(), tokens)) {
return -1
}
} catch (InStringException ise) {
int completionStart = ise.column + ise.openDelim.size()
def remainder = bufferLine.substring(completionStart)
def completer = instringFilenameCompletor
if (['"', "'", '"""', "'''"].contains(ise.openDelim)) {
if (isWin) {
completer = windowsFilenameCompletor
}
// perhaps a backslash
if (remainder.contains("\\")) {
try {
gs.evaluate("'$remainder'")
} catch (Exception ex1) {
try {
gs.evaluate("'${remainder.substring(0, remainder.size() - 1)}'")
// only get here if there is an unescaped backslash at the end of the buffer
// ignore the result since it is only informational
return backslashCompletor.complete(remainder, cursor, candidates)
} catch (Exception ex2) {
}
}
}
}
int completionResult = completer.complete(remainder, cursor - completionStart, candidates)
if (completionResult >= 0) {
return completionStart + completionResult
}
return completionResult
}
CompletionCase completionCase = getCompletionCase(tokens)
if (completionCase == CompletionCase.NO_COMPLETION) {
return -1
}
if (completionCase == CompletionCase.SECOND_IDENT) {
if (infixCompletor.complete(tokens, candidates)) {
return tokens.last().column - 1
}
return -1
}
if (completionCase == CompletionCase.INSTANCEOF) {
if (classnameCompletor.complete(tokens, candidates)) {
return tokens.last().column - 1
}
return -1
}
int result
switch (completionCase) {
case CompletionCase.NO_DOT_PREFIX:
result = completeIdentifier(tokens, candidates)
break
case CompletionCase.DOT_LAST:
case CompletionCase.PREFIX_AFTER_DOT:
case CompletionCase.SPREAD_DOT_LAST:
case CompletionCase.PREFIX_AFTER_SPREAD_DOT:
result = reflectionCompletor.complete(tokens, candidates)
break
default:
// bug
throw new RuntimeException("Unknown Completion case: $completionCase")
}
return result
}
static CompletionCase getCompletionCase(final List tokens) {
GroovySourceToken currentToken = tokens[-1]
// now look at last 2 tokens to decide whether we are in a completion situation at all
if (currentToken.type == IDENT) {
// cursor is on identifier, use it as prefix and check whether it follows a dot
if (tokens.size() == 1) {
return CompletionCase.NO_DOT_PREFIX
}
GroovySourceToken previousToken = tokens[-2]
if (previousToken.type == DOT || previousToken.type == OPTIONAL_DOT) {
// we have a dot, so need to evaluate the statement up to the dot for completion
if (tokens.size() < 3) {
return CompletionCase.NO_COMPLETION
}
return CompletionCase.PREFIX_AFTER_DOT
} else if (previousToken.type == SPREAD_DOT) {
// we have a dot, so need to evaluate the statement up to the dot for completion
if (tokens.size() < 3) {
return CompletionCase.NO_COMPLETION
}
return CompletionCase.PREFIX_AFTER_SPREAD_DOT
} else {
// no dot, so we complete a varname, classname, or similar
switch (previousToken.type) {
// if any of these is before, no useful completion possible in this completor
case LITERAL_import:
case LITERAL_class:
case LITERAL_interface:
case LITERAL_enum:
case LITERAL_def:
case LITERAL_void:
case LITERAL_boolean:
case LITERAL_byte:
case LITERAL_char:
case LITERAL_short:
case LITERAL_int:
case LITERAL_float:
case LITERAL_long:
case LITERAL_double:
case LITERAL_package:
case LITERAL_true:
case LITERAL_false:
case LITERAL_as:
case LITERAL_this:
case LITERAL_try:
case LITERAL_finally:
case LITERAL_catch:
return CompletionCase.NO_COMPLETION
case IDENT:
// identifiers following each other could mean Declaration (no completion) or closure invocation
// closure invocation too complex for now to complete
return CompletionCase.SECOND_IDENT
default:
return CompletionCase.NO_DOT_PREFIX
}
}
} else if (currentToken.type == DOT || currentToken.type == OPTIONAL_DOT) {
// cursor is on dot, so need to evaluate the statement up to the dot for completion
if (tokens.size() == 1) {
return CompletionCase.NO_COMPLETION
}
return CompletionCase.DOT_LAST
} else if (currentToken.type == SPREAD_DOT) {
// cursor is on spread-dot, so need to evaluate the statement up to the dot for completion
if (tokens.size() == 1) {
return CompletionCase.NO_COMPLETION
}
return CompletionCase.SPREAD_DOT_LAST
} else if (currentToken.type == LITERAL_instanceof) {
return CompletionCase.INSTANCEOF
} else {
LOG.debug('Untreated toke type: ' + currentToken.type)
}
return CompletionCase.NO_COMPLETION
}
int completeIdentifier(final List tokens, final List candidates) {
boolean foundMatches = false
for (IdentifierCompletor completor : identifierCompletors) {
foundMatches |= completor.complete(tokens, candidates)
}
if (foundMatches) {
return tokens.last().column - 1
}
return -1
}
static boolean isCommand(final String bufferLine, final CommandRegistry registry) {
// for shell commands, don't complete
int commandEnd = bufferLine.indexOf(' ')
if (commandEnd != -1) {
String commandTokenText = bufferLine.substring(0, commandEnd)
for (command in registry.commands()) {
if (commandTokenText == command.name || commandTokenText in command.aliases) {
return true
}
}
}
return false
}
static GroovyLexer createGroovyLexer(final String src) {
Reader unicodeReader = new UnicodeEscapingReader(new StringReader(src), new SourceBuffer())
GroovyLexer lexer = new GroovyLexer(unicodeReader)
unicodeReader.setLexer(lexer)
return lexer
}
@TupleConstructor
static class InStringException extends Exception {
int column
String openDelim
@Override
String toString() {
super.toString() + "[column=$column, openDelim=$openDelim]"
}
}
private static final STRING_STARTERS = [/"""/, /'''/, /"/, /'/, '$/', '/']
/**
* Adds to result the identified tokens for the bufferLines
* @param bufferLine
* @param previousLines
* @param result
* @return true if lexing was successful
*/
static boolean tokenizeBuffer(final String bufferLine,
final List previousLines,
final List result) {
GroovyLexer groovyLexer
if (previousLines.size() > 0) {
StringBuilder src = new StringBuilder()
for (String line : previousLines) {
src.append(line).append('\n')
}
src.append(bufferLine)
groovyLexer = createGroovyLexer(src.toString())
} else {
groovyLexer = createGroovyLexer(bufferLine)
}
// Build a list of tokens using a GroovyLexer
GroovySourceToken nextToken
GroovySourceToken lastToken
while (true) {
try {
nextToken = groovyLexer.nextToken() as GroovySourceToken
if (nextToken.type == EOF) {
if (!result.isEmpty() && nextToken.line > result.last().line) {
// no completion if EOF line has no tokens
return false
}
break
}
result << nextToken
lastToken = nextToken
} catch (TokenStreamException e) {
// getting the next token failed, possibly due to unclosed quotes; investigate rest of the line to confirm
if (lastToken != null) {
String restline = bufferLine.substring(lastToken.columnLast - 1)
int leadingBlanks = restline.find('^[ ]*').length()
if (restline) {
String remainder = restline.substring(leadingBlanks)
//System.err.println "|" + remainder + "|"
// Exception with following quote either means we're in String or at end of GString.
String openDelim = STRING_STARTERS.find { remainder.startsWith(it) }
if (openDelim && previousLines.size() + 1 == lastToken.line) {
throw new InStringException(lastToken.columnLast + leadingBlanks - 1, openDelim)
}
}
}
return false
} catch (NullPointerException e) {
// this can happen when e.g. a string as not closed
return false
}
}
return !result.empty
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy