PSI based on Grammar-Kit created for command line language in order to implement PY-11855

* GrammarKit instead of custom parsing
 * ToolWindow instead of popup
 * Inspection and help added
This commit is contained in:
Ilya.Kazakevich
2015-03-13 15:31:46 +03:00
parent 116d9c3ab8
commit 89eb617831
101 changed files with 3236 additions and 3323 deletions

View File

@@ -0,0 +1,36 @@
// This is a generated file. Not intended for manual editing.
package com.jetbrains.commandInterface.gnuCommandLine;
import com.intellij.psi.tree.IElementType;
import com.intellij.psi.PsiElement;
import com.intellij.lang.ASTNode;
import com.jetbrains.commandInterface.gnuCommandLine.psi.impl.*;
public interface CommandLineElementTypes {
IElementType ARGUMENT = new CommandLineElementType("ARGUMENT");
IElementType COMMAND = new CommandLineElementType("COMMAND");
IElementType OPTION = new CommandLineElementType("OPTION");
IElementType EQ = new IElementType("=", null);
IElementType LITERAL_STARTS_FROM_DIGIT = new IElementType("LITERAL_STARTS_FROM_DIGIT", null);
IElementType LITERAL_STARTS_FROM_LETTER = new IElementType("LITERAL_STARTS_FROM_LETTER", null);
IElementType LONG_OPTION_NAME_TOKEN = new IElementType("LONG_OPTION_NAME_TOKEN", null);
IElementType SHORT_OPTION_NAME_TOKEN = new IElementType("SHORT_OPTION_NAME_TOKEN", null);
class Factory {
public static PsiElement createElement(ASTNode node) {
IElementType type = node.getElementType();
if (type == ARGUMENT) {
return new CommandLineArgumentImpl(node);
}
else if (type == COMMAND) {
return new CommandLineCommandImpl(node);
}
else if (type == OPTION) {
return new CommandLineOptionImpl(node);
}
throw new AssertionError("Unknown element type: " + type);
}
}
}

View File

@@ -0,0 +1,167 @@
// This is a generated file. Not intended for manual editing.
package com.jetbrains.commandInterface.gnuCommandLine;
import com.intellij.lang.PsiBuilder;
import com.intellij.lang.PsiBuilder.Marker;
import static com.jetbrains.commandInterface.gnuCommandLine.CommandLineElementTypes.*;
import static com.jetbrains.commandInterface.gnuCommandLine.CommandLineParserUtil.*;
import com.intellij.psi.tree.IElementType;
import com.intellij.lang.ASTNode;
import com.intellij.psi.tree.TokenSet;
import com.intellij.lang.PsiParser;
@SuppressWarnings({"SimplifiableIfStatement", "UnusedAssignment"})
public class CommandLineParser implements PsiParser {
public ASTNode parse(IElementType t, PsiBuilder b) {
parseLight(t, b);
return b.getTreeBuilt();
}
public void parseLight(IElementType t, PsiBuilder b) {
boolean r;
b = adapt_builder_(t, b, this, null);
Marker m = enter_section_(b, 0, _COLLAPSE_, null);
if (t == ARGUMENT) {
r = argument(b, 0);
}
else if (t == COMMAND) {
r = command(b, 0);
}
else if (t == OPTION) {
r = option(b, 0);
}
else {
r = parse_root_(t, b, 0);
}
exit_section_(b, 0, m, t, r, true, TRUE_CONDITION);
}
protected boolean parse_root_(IElementType t, PsiBuilder b, int l) {
return root(b, l + 1);
}
/* ********************************************************** */
// LITERAL_STARTS_FROM_LETTER | LITERAL_STARTS_FROM_DIGIT
public static boolean argument(PsiBuilder b, int l) {
if (!recursion_guard_(b, l, "argument")) return false;
if (!nextTokenIs(b, "<argument>", LITERAL_STARTS_FROM_DIGIT, LITERAL_STARTS_FROM_LETTER)) return false;
boolean r;
Marker m = enter_section_(b, l, _NONE_, "<argument>");
r = consumeToken(b, LITERAL_STARTS_FROM_LETTER);
if (!r) r = consumeToken(b, LITERAL_STARTS_FROM_DIGIT);
exit_section_(b, l, m, ARGUMENT, r, false, null);
return r;
}
/* ********************************************************** */
// LITERAL_STARTS_FROM_LETTER
public static boolean command(PsiBuilder b, int l) {
if (!recursion_guard_(b, l, "command")) return false;
if (!nextTokenIs(b, LITERAL_STARTS_FROM_LETTER)) return false;
boolean r;
Marker m = enter_section_(b);
r = consumeToken(b, LITERAL_STARTS_FROM_LETTER);
exit_section_(b, m, COMMAND, r);
return r;
}
/* ********************************************************** */
// LONG_OPTION_NAME_TOKEN
static boolean long_option_name(PsiBuilder b, int l) {
return consumeToken(b, LONG_OPTION_NAME_TOKEN);
}
/* ********************************************************** */
// short_option_name <<bound_argument>> ? | long_option_name <<bound_argument>> ?
public static boolean option(PsiBuilder b, int l) {
if (!recursion_guard_(b, l, "option")) return false;
if (!nextTokenIs(b, "<option>", LONG_OPTION_NAME_TOKEN, SHORT_OPTION_NAME_TOKEN)) return false;
boolean r;
Marker m = enter_section_(b, l, _NONE_, "<option>");
r = option_0(b, l + 1);
if (!r) r = option_1(b, l + 1);
exit_section_(b, l, m, OPTION, r, false, null);
return r;
}
// short_option_name <<bound_argument>> ?
private static boolean option_0(PsiBuilder b, int l) {
if (!recursion_guard_(b, l, "option_0")) return false;
boolean r;
Marker m = enter_section_(b);
r = short_option_name(b, l + 1);
r = r && option_0_1(b, l + 1);
exit_section_(b, m, null, r);
return r;
}
// <<bound_argument>> ?
private static boolean option_0_1(PsiBuilder b, int l) {
if (!recursion_guard_(b, l, "option_0_1")) return false;
bound_argument(b, l + 1);
return true;
}
// long_option_name <<bound_argument>> ?
private static boolean option_1(PsiBuilder b, int l) {
if (!recursion_guard_(b, l, "option_1")) return false;
boolean r;
Marker m = enter_section_(b);
r = long_option_name(b, l + 1);
r = r && option_1_1(b, l + 1);
exit_section_(b, m, null, r);
return r;
}
// <<bound_argument>> ?
private static boolean option_1_1(PsiBuilder b, int l) {
if (!recursion_guard_(b, l, "option_1_1")) return false;
bound_argument(b, l + 1);
return true;
}
/* ********************************************************** */
// command (argument | option ) * <<eof>>
static boolean root(PsiBuilder b, int l) {
if (!recursion_guard_(b, l, "root")) return false;
if (!nextTokenIs(b, LITERAL_STARTS_FROM_LETTER)) return false;
boolean r;
Marker m = enter_section_(b);
r = command(b, l + 1);
r = r && root_1(b, l + 1);
r = r && eof(b, l + 1);
exit_section_(b, m, null, r);
return r;
}
// (argument | option ) *
private static boolean root_1(PsiBuilder b, int l) {
if (!recursion_guard_(b, l, "root_1")) return false;
int c = current_position_(b);
while (true) {
if (!root_1_0(b, l + 1)) break;
if (!empty_element_parsed_guard_(b, "root_1", c)) break;
c = current_position_(b);
}
return true;
}
// argument | option
private static boolean root_1_0(PsiBuilder b, int l) {
if (!recursion_guard_(b, l, "root_1_0")) return false;
boolean r;
Marker m = enter_section_(b);
r = argument(b, l + 1);
if (!r) r = option(b, l + 1);
exit_section_(b, m, null, r);
return r;
}
/* ********************************************************** */
// SHORT_OPTION_NAME_TOKEN
static boolean short_option_name(PsiBuilder b, int l) {
return consumeToken(b, SHORT_OPTION_NAME_TOKEN);
}
}

View File

@@ -0,0 +1,42 @@
package com.jetbrains.commandInterface.gnuCommandLine;
import com.intellij.lexer.*;
import com.intellij.psi.tree.IElementType;
import static com.jetbrains.commandInterface.gnuCommandLine.CommandLineElementTypes.*;
%%
%{
public _CommandLineLexer() {
this((java.io.Reader)null);
}
%}
%public
%class _CommandLineLexer
%implements FlexLexer
%function advance
%type IElementType
%unicode
EOL="\r"|"\n"|"\r\n"
LINE_WS=[\ \t\f]
WHITE_SPACE=({LINE_WS}|{EOL})+
LITERAL_STARTS_FROM_LETTER=[:letter:]([a-zA-Z_0-9]|:|\\|"/"|\.)*
LITERAL_STARTS_FROM_DIGIT=[:digit:]([a-zA-Z_0-9]|:|\\\|"/"|\.)*
SHORT_OPTION_NAME_TOKEN=-[:letter:]
LONG_OPTION_NAME_TOKEN=--[:letter:](-|[a-zA-Z_0-9])*
%%
<YYINITIAL> {
{WHITE_SPACE} { return com.intellij.psi.TokenType.WHITE_SPACE; }
"=" { return EQ; }
{LITERAL_STARTS_FROM_LETTER} { return LITERAL_STARTS_FROM_LETTER; }
{LITERAL_STARTS_FROM_DIGIT} { return LITERAL_STARTS_FROM_DIGIT; }
{SHORT_OPTION_NAME_TOKEN} { return SHORT_OPTION_NAME_TOKEN; }
{LONG_OPTION_NAME_TOKEN} { return LONG_OPTION_NAME_TOKEN; }
[^] { return com.intellij.psi.TokenType.BAD_CHARACTER; }
}

View File

@@ -0,0 +1,554 @@
/* The following code was generated by JFlex 1.4.3 on 12.03.15 21:27 */
package com.jetbrains.commandInterface.gnuCommandLine;
import com.intellij.lexer.*;
import com.intellij.psi.tree.IElementType;
import static com.jetbrains.commandInterface.gnuCommandLine.CommandLineElementTypes.*;
/**
* This class is a scanner generated by
* <a href="http://www.jflex.de/">JFlex</a> 1.4.3
* on 12.03.15 21:27 from the specification file
* <tt>C:/work/ultimate/community/python/gen/com/jetbrains/commandInterface/gnuCommandLine/_CommandLineLexer.flex</tt>
*/
public class _CommandLineLexer implements FlexLexer {
/** initial size of the lookahead buffer */
private static final int ZZ_BUFFERSIZE = 16384;
/** lexical states */
public static final int YYINITIAL = 0;
/**
* ZZ_LEXSTATE[l] is the state in the DFA for the lexical state l
* ZZ_LEXSTATE[l+1] is the state in the DFA for the lexical state l
* at the beginning of a line
* l is of the form l = 2*k, k a non negative integer
*/
private static final int ZZ_LEXSTATE[] = {
0, 0
};
/**
* Translates characters to character classes
*/
private static final String ZZ_CMAP_PACKED =
"\11\0\1\1\1\1\1\0\1\1\1\1\22\0\1\1\14\0\1\13"+
"\1\5\1\7\12\11\1\5\2\0\1\14\3\0\32\4\1\0\1\6"+
"\2\0\1\3\1\0\32\4\1\0\1\12\55\0\1\2\12\0\1\2"+
"\4\0\1\2\5\0\27\2\1\0\37\2\1\0\u013f\2\31\0\162\2"+
"\4\0\14\2\16\0\5\2\11\0\1\2\213\0\1\2\13\0\1\2"+
"\1\0\3\2\1\0\1\2\1\0\24\2\1\0\54\2\1\0\46\2"+
"\1\0\5\2\4\0\202\2\10\0\105\2\1\0\46\2\2\0\2\2"+
"\6\0\20\2\41\0\46\2\2\0\1\2\7\0\47\2\110\0\33\2"+
"\5\0\3\2\56\0\32\2\5\0\13\2\25\0\12\10\4\0\2\2"+
"\1\0\143\2\1\0\1\2\17\0\2\2\7\0\2\2\12\10\3\2"+
"\2\0\1\2\20\0\1\2\1\0\36\2\35\0\3\2\60\0\46\2"+
"\13\0\1\2\u0152\0\66\2\3\0\1\2\22\0\1\2\7\0\12\2"+
"\4\0\12\10\25\0\10\2\2\0\2\2\2\0\26\2\1\0\7\2"+
"\1\0\1\2\3\0\4\2\3\0\1\2\36\0\2\2\1\0\3\2"+
"\4\0\12\10\2\2\23\0\6\2\4\0\2\2\2\0\26\2\1\0"+
"\7\2\1\0\2\2\1\0\2\2\1\0\2\2\37\0\4\2\1\0"+
"\1\2\7\0\12\10\2\0\3\2\20\0\11\2\1\0\3\2\1\0"+
"\26\2\1\0\7\2\1\0\2\2\1\0\5\2\3\0\1\2\22\0"+
"\1\2\17\0\2\2\4\0\12\10\25\0\10\2\2\0\2\2\2\0"+
"\26\2\1\0\7\2\1\0\2\2\1\0\5\2\3\0\1\2\36\0"+
"\2\2\1\0\3\2\4\0\12\10\1\0\1\2\21\0\1\2\1\0"+
"\6\2\3\0\3\2\1\0\4\2\3\0\2\2\1\0\1\2\1\0"+
"\2\2\3\0\2\2\3\0\3\2\3\0\10\2\1\0\3\2\55\0"+
"\11\10\25\0\10\2\1\0\3\2\1\0\27\2\1\0\12\2\1\0"+
"\5\2\46\0\2\2\4\0\12\10\25\0\10\2\1\0\3\2\1\0"+
"\27\2\1\0\12\2\1\0\5\2\3\0\1\2\40\0\1\2\1\0"+
"\2\2\4\0\12\10\25\0\10\2\1\0\3\2\1\0\27\2\1\0"+
"\20\2\46\0\2\2\4\0\12\10\25\0\22\2\3\0\30\2\1\0"+
"\11\2\1\0\1\2\2\0\7\2\72\0\60\2\1\0\2\2\14\0"+
"\7\2\11\0\12\10\47\0\2\2\1\0\1\2\2\0\2\2\1\0"+
"\1\2\2\0\1\2\6\0\4\2\1\0\7\2\1\0\3\2\1\0"+
"\1\2\1\0\1\2\2\0\2\2\1\0\4\2\1\0\2\2\11\0"+
"\1\2\2\0\5\2\1\0\1\2\11\0\12\10\2\0\2\2\42\0"+
"\1\2\37\0\12\10\26\0\10\2\1\0\42\2\35\0\4\2\164\0"+
"\42\2\1\0\5\2\1\0\2\2\25\0\12\10\6\0\6\2\112\0"+
"\46\2\12\0\51\2\7\0\132\2\5\0\104\2\5\0\122\2\6\0"+
"\7\2\1\0\77\2\1\0\1\2\1\0\4\2\2\0\7\2\1\0"+
"\1\2\1\0\4\2\2\0\47\2\1\0\1\2\1\0\4\2\2\0"+
"\37\2\1\0\1\2\1\0\4\2\2\0\7\2\1\0\1\2\1\0"+
"\4\2\2\0\7\2\1\0\7\2\1\0\27\2\1\0\37\2\1\0"+
"\1\2\1\0\4\2\2\0\7\2\1\0\47\2\1\0\23\2\16\0"+
"\11\10\56\0\125\2\14\0\u026c\2\2\0\10\2\12\0\32\2\5\0"+
"\113\2\25\0\15\2\1\0\4\2\16\0\22\2\16\0\22\2\16\0"+
"\15\2\1\0\3\2\17\0\64\2\43\0\1\2\4\0\1\2\3\0"+
"\12\10\46\0\12\10\6\0\130\2\10\0\51\2\127\0\35\2\51\0"+
"\12\10\36\2\2\0\5\2\u038b\0\154\2\224\0\234\2\4\0\132\2"+
"\6\0\26\2\2\0\6\2\2\0\46\2\2\0\6\2\2\0\10\2"+
"\1\0\1\2\1\0\1\2\1\0\1\2\1\0\37\2\2\0\65\2"+
"\1\0\7\2\1\0\1\2\3\0\3\2\1\0\7\2\3\0\4\2"+
"\2\0\6\2\4\0\15\2\5\0\3\2\1\0\7\2\164\0\1\2"+
"\15\0\1\2\202\0\1\2\4\0\1\2\2\0\12\2\1\0\1\2"+
"\3\0\5\2\6\0\1\2\1\0\1\2\1\0\1\2\1\0\4\2"+
"\1\0\3\2\1\0\7\2\3\0\3\2\5\0\5\2\u0ebb\0\2\2"+
"\52\0\5\2\5\0\2\2\4\0\126\2\6\0\3\2\1\0\132\2"+
"\1\0\4\2\5\0\50\2\4\0\136\2\21\0\30\2\70\0\20\2"+
"\u0200\0\u19b6\2\112\0\u51a6\2\132\0\u048d\2\u0773\0\u2ba4\2\u215c\0\u012e\2"+
"\2\0\73\2\225\0\7\2\14\0\5\2\5\0\1\2\1\0\12\2"+
"\1\0\15\2\1\0\5\2\1\0\1\2\1\0\2\2\1\0\2\2"+
"\1\0\154\2\41\0\u016b\2\22\0\100\2\2\0\66\2\50\0\14\2"+
"\164\0\5\2\1\0\207\2\23\0\12\10\7\0\32\2\6\0\32\2"+
"\13\0\131\2\3\0\6\2\2\0\6\2\2\0\6\2\2\0\3\2"+
"\43\0";
/**
* Translates characters to character classes
*/
private static final char [] ZZ_CMAP = zzUnpackCMap(ZZ_CMAP_PACKED);
/**
* Translates DFA states to action switch labels.
*/
private static final int [] ZZ_ACTION = zzUnpackAction();
private static final String ZZ_ACTION_PACKED_0 =
"\1\0\1\1\1\2\1\3\1\4\1\1\1\5\1\0"+
"\1\6\2\0\1\7";
private static int [] zzUnpackAction() {
int [] result = new int[12];
int offset = 0;
offset = zzUnpackAction(ZZ_ACTION_PACKED_0, offset, result);
return result;
}
private static int zzUnpackAction(String packed, int offset, int [] result) {
int i = 0; /* index in packed string */
int j = offset; /* index in unpacked array */
int l = packed.length();
while (i < l) {
int count = packed.charAt(i++);
int value = packed.charAt(i++);
do result[j++] = value; while (--count > 0);
}
return j;
}
/**
* Translates a state to a row index in the transition table
*/
private static final int [] ZZ_ROWMAP = zzUnpackRowMap();
private static final String ZZ_ROWMAP_PACKED_0 =
"\0\0\0\15\0\32\0\47\0\64\0\101\0\15\0\116"+
"\0\15\0\133\0\150\0\165";
private static int [] zzUnpackRowMap() {
int [] result = new int[12];
int offset = 0;
offset = zzUnpackRowMap(ZZ_ROWMAP_PACKED_0, offset, result);
return result;
}
private static int zzUnpackRowMap(String packed, int offset, int [] result) {
int i = 0; /* index in packed string */
int j = offset; /* index in unpacked array */
int l = packed.length();
while (i < l) {
int high = packed.charAt(i++) << 16;
result[j++] = high | packed.charAt(i++);
}
return j;
}
/**
* The transition table of the DFA
*/
private static final int [] ZZ_TRANS = zzUnpackTrans();
private static final String ZZ_TRANS_PACKED_0 =
"\1\2\1\3\1\4\1\2\1\4\3\2\2\5\1\2"+
"\1\6\1\7\16\0\1\3\16\0\5\4\1\0\1\4"+
"\6\0\3\5\1\10\2\0\1\5\5\0\1\11\1\0"+
"\1\11\6\0\1\12\13\0\1\13\4\0\1\14\1\0"+
"\1\14\17\0\1\5\10\0\2\14\4\0\1\14\1\0"+
"\1\14\1\0";
private static int [] zzUnpackTrans() {
int [] result = new int[130];
int offset = 0;
offset = zzUnpackTrans(ZZ_TRANS_PACKED_0, offset, result);
return result;
}
private static int zzUnpackTrans(String packed, int offset, int [] result) {
int i = 0; /* index in packed string */
int j = offset; /* index in unpacked array */
int l = packed.length();
while (i < l) {
int count = packed.charAt(i++);
int value = packed.charAt(i++);
value--;
do result[j++] = value; while (--count > 0);
}
return j;
}
/* error codes */
private static final int ZZ_UNKNOWN_ERROR = 0;
private static final int ZZ_NO_MATCH = 1;
private static final int ZZ_PUSHBACK_2BIG = 2;
private static final char[] EMPTY_BUFFER = new char[0];
private static final int YYEOF = -1;
private static java.io.Reader zzReader = null; // Fake
/* error messages for the codes above */
private static final String ZZ_ERROR_MSG[] = {
"Unkown internal scanner error",
"Error: could not match input",
"Error: pushback value was too large"
};
/**
* ZZ_ATTRIBUTE[aState] contains the attributes of state <code>aState</code>
*/
private static final int [] ZZ_ATTRIBUTE = zzUnpackAttribute();
private static final String ZZ_ATTRIBUTE_PACKED_0 =
"\1\0\1\11\4\1\1\11\1\0\1\11\2\0\1\1";
private static int [] zzUnpackAttribute() {
int [] result = new int[12];
int offset = 0;
offset = zzUnpackAttribute(ZZ_ATTRIBUTE_PACKED_0, offset, result);
return result;
}
private static int zzUnpackAttribute(String packed, int offset, int [] result) {
int i = 0; /* index in packed string */
int j = offset; /* index in unpacked array */
int l = packed.length();
while (i < l) {
int count = packed.charAt(i++);
int value = packed.charAt(i++);
do result[j++] = value; while (--count > 0);
}
return j;
}
/** the current state of the DFA */
private int zzState;
/** the current lexical state */
private int zzLexicalState = YYINITIAL;
/** this buffer contains the current text to be matched and is
the source of the yytext() string */
private CharSequence zzBuffer = "";
/** this buffer may contains the current text array to be matched when it is cheap to acquire it */
private char[] zzBufferArray;
/** the textposition at the last accepting state */
private int zzMarkedPos;
/** the textposition at the last state to be included in yytext */
private int zzPushbackPos;
/** the current text position in the buffer */
private int zzCurrentPos;
/** startRead marks the beginning of the yytext() string in the buffer */
private int zzStartRead;
/** endRead marks the last character in the buffer, that has been read
from input */
private int zzEndRead;
/**
* zzAtBOL == true <=> the scanner is currently at the beginning of a line
*/
private boolean zzAtBOL = true;
/** zzAtEOF == true <=> the scanner is at the EOF */
private boolean zzAtEOF;
/* user code: */
public _CommandLineLexer() {
this((java.io.Reader)null);
}
public _CommandLineLexer(java.io.Reader in) {
this.zzReader = in;
}
/**
* Creates a new scanner.
* There is also java.io.Reader version of this constructor.
*
* @param in the java.io.Inputstream to read input from.
*/
public _CommandLineLexer(java.io.InputStream in) {
this(new java.io.InputStreamReader(in));
}
/**
* Unpacks the compressed character translation table.
*
* @param packed the packed character translation table
* @return the unpacked character translation table
*/
private static char [] zzUnpackCMap(String packed) {
char [] map = new char[0x10000];
int i = 0; /* index in packed string */
int j = 0; /* index in unpacked array */
while (i < 1222) {
int count = packed.charAt(i++);
char value = packed.charAt(i++);
do map[j++] = value; while (--count > 0);
}
return map;
}
public final int getTokenStart(){
return zzStartRead;
}
public final int getTokenEnd(){
return getTokenStart() + yylength();
}
public void reset(CharSequence buffer, int start, int end,int initialState){
zzBuffer = buffer;
zzBufferArray = com.intellij.util.text.CharArrayUtil.fromSequenceWithoutCopying(buffer);
zzCurrentPos = zzMarkedPos = zzStartRead = start;
zzPushbackPos = 0;
zzAtEOF = false;
zzAtBOL = true;
zzEndRead = end;
yybegin(initialState);
}
/**
* Refills the input buffer.
*
* @return <code>false</code>, iff there was new input.
*
* @exception java.io.IOException if any I/O-Error occurs
*/
private boolean zzRefill() throws java.io.IOException {
return true;
}
/**
* Returns the current lexical state.
*/
public final int yystate() {
return zzLexicalState;
}
/**
* Enters a new lexical state
*
* @param newState the new lexical state
*/
public final void yybegin(int newState) {
zzLexicalState = newState;
}
/**
* Returns the text matched by the current regular expression.
*/
public final CharSequence yytext() {
return zzBuffer.subSequence(zzStartRead, zzMarkedPos);
}
/**
* Returns the character at position <tt>pos</tt> from the
* matched text.
*
* It is equivalent to yytext().charAt(pos), but faster
*
* @param pos the position of the character to fetch.
* A value from 0 to yylength()-1.
*
* @return the character at position pos
*/
public final char yycharat(int pos) {
return zzBufferArray != null ? zzBufferArray[zzStartRead+pos]:zzBuffer.charAt(zzStartRead+pos);
}
/**
* Returns the length of the matched text region.
*/
public final int yylength() {
return zzMarkedPos-zzStartRead;
}
/**
* Reports an error that occured while scanning.
*
* In a wellformed scanner (no or only correct usage of
* yypushback(int) and a match-all fallback rule) this method
* will only be called with things that "Can't Possibly Happen".
* If this method is called, something is seriously wrong
* (e.g. a JFlex bug producing a faulty scanner etc.).
*
* Usual syntax/scanner level error handling should be done
* in error fallback rules.
*
* @param errorCode the code of the errormessage to display
*/
private void zzScanError(int errorCode) {
String message;
try {
message = ZZ_ERROR_MSG[errorCode];
}
catch (ArrayIndexOutOfBoundsException e) {
message = ZZ_ERROR_MSG[ZZ_UNKNOWN_ERROR];
}
throw new Error(message);
}
/**
* Pushes the specified amount of characters back into the input stream.
*
* They will be read again by then next call of the scanning method
*
* @param number the number of characters to be read again.
* This number must not be greater than yylength()!
*/
public void yypushback(int number) {
if ( number > yylength() )
zzScanError(ZZ_PUSHBACK_2BIG);
zzMarkedPos -= number;
}
/**
* Resumes scanning until the next regular expression is matched,
* the end of input is encountered or an I/O-Error occurs.
*
* @return the next token
* @exception java.io.IOException if any I/O-Error occurs
*/
public IElementType advance() throws java.io.IOException {
int zzInput;
int zzAction;
// cached fields:
int zzCurrentPosL;
int zzMarkedPosL;
int zzEndReadL = zzEndRead;
CharSequence zzBufferL = zzBuffer;
char[] zzBufferArrayL = zzBufferArray;
char [] zzCMapL = ZZ_CMAP;
int [] zzTransL = ZZ_TRANS;
int [] zzRowMapL = ZZ_ROWMAP;
int [] zzAttrL = ZZ_ATTRIBUTE;
while (true) {
zzMarkedPosL = zzMarkedPos;
zzAction = -1;
zzCurrentPosL = zzCurrentPos = zzStartRead = zzMarkedPosL;
zzState = ZZ_LEXSTATE[zzLexicalState];
zzForAction: {
while (true) {
if (zzCurrentPosL < zzEndReadL)
zzInput = (zzBufferArrayL != null ? zzBufferArrayL[zzCurrentPosL++] : zzBufferL.charAt(zzCurrentPosL++));
else if (zzAtEOF) {
zzInput = YYEOF;
break zzForAction;
}
else {
// store back cached positions
zzCurrentPos = zzCurrentPosL;
zzMarkedPos = zzMarkedPosL;
boolean eof = zzRefill();
// get translated positions and possibly new buffer
zzCurrentPosL = zzCurrentPos;
zzMarkedPosL = zzMarkedPos;
zzBufferL = zzBuffer;
zzEndReadL = zzEndRead;
if (eof) {
zzInput = YYEOF;
break zzForAction;
}
else {
zzInput = (zzBufferArrayL != null ? zzBufferArrayL[zzCurrentPosL++] : zzBufferL.charAt(zzCurrentPosL++));
}
}
int zzNext = zzTransL[ zzRowMapL[zzState] + zzCMapL[zzInput] ];
if (zzNext == -1) break zzForAction;
zzState = zzNext;
int zzAttributes = zzAttrL[zzState];
if ( (zzAttributes & 1) == 1 ) {
zzAction = zzState;
zzMarkedPosL = zzCurrentPosL;
if ( (zzAttributes & 8) == 8 ) break zzForAction;
}
}
}
// store back cached position
zzMarkedPos = zzMarkedPosL;
switch (zzAction < 0 ? zzAction : ZZ_ACTION[zzAction]) {
case 4:
{ return LITERAL_STARTS_FROM_DIGIT;
}
case 8: break;
case 1:
{ return com.intellij.psi.TokenType.BAD_CHARACTER;
}
case 9: break;
case 3:
{ return LITERAL_STARTS_FROM_LETTER;
}
case 10: break;
case 7:
{ return LONG_OPTION_NAME_TOKEN;
}
case 11: break;
case 5:
{ return EQ;
}
case 12: break;
case 6:
{ return SHORT_OPTION_NAME_TOKEN;
}
case 13: break;
case 2:
{ return com.intellij.psi.TokenType.WHITE_SPACE;
}
case 14: break;
default:
if (zzInput == YYEOF && zzStartRead == zzCurrentPos) {
zzAtEOF = true;
return null;
}
else {
zzScanError(ZZ_NO_MATCH);
}
}
}
}
}

View File

@@ -0,0 +1,16 @@
// This is a generated file. Not intended for manual editing.
package com.jetbrains.commandInterface.gnuCommandLine.psi;
import java.util.List;
import org.jetbrains.annotations.*;
import com.intellij.psi.PsiElement;
public interface CommandLineArgument extends PsiElement {
@Nullable
PsiElement getLiteralStartsFromDigit();
@Nullable
PsiElement getLiteralStartsFromLetter();
}

View File

@@ -0,0 +1,13 @@
// This is a generated file. Not intended for manual editing.
package com.jetbrains.commandInterface.gnuCommandLine.psi;
import java.util.List;
import org.jetbrains.annotations.*;
import com.intellij.psi.PsiElement;
public interface CommandLineCommand extends PsiElement {
@NotNull
PsiElement getLiteralStartsFromLetter();
}

View File

@@ -0,0 +1,22 @@
// This is a generated file. Not intended for manual editing.
package com.jetbrains.commandInterface.gnuCommandLine.psi;
import java.util.List;
import org.jetbrains.annotations.*;
import com.intellij.psi.PsiElement;
public interface CommandLineOption extends PsiElement {
@Nullable
PsiElement getLongOptionNameToken();
@Nullable
PsiElement getShortOptionNameToken();
@Nullable
@NonNls
String getOptionName();
boolean isLong();
}

View File

@@ -0,0 +1,26 @@
// This is a generated file. Not intended for manual editing.
package com.jetbrains.commandInterface.gnuCommandLine.psi;
import org.jetbrains.annotations.*;
import com.intellij.psi.PsiElementVisitor;
import com.intellij.psi.PsiElement;
public class CommandLineVisitor extends PsiElementVisitor {
public void visitArgument(@NotNull CommandLineArgument o) {
visitPsiElement(o);
}
public void visitCommand(@NotNull CommandLineCommand o) {
visitPsiElement(o);
}
public void visitOption(@NotNull CommandLineOption o) {
visitPsiElement(o);
}
public void visitPsiElement(@NotNull PsiElement o) {
visitElement(o);
}
}

View File

@@ -0,0 +1,37 @@
// This is a generated file. Not intended for manual editing.
package com.jetbrains.commandInterface.gnuCommandLine.psi.impl;
import java.util.List;
import org.jetbrains.annotations.*;
import com.intellij.lang.ASTNode;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiElementVisitor;
import com.intellij.psi.util.PsiTreeUtil;
import static com.jetbrains.commandInterface.gnuCommandLine.CommandLineElementTypes.*;
import com.jetbrains.commandInterface.gnuCommandLine.CommandLineElement;
import com.jetbrains.commandInterface.gnuCommandLine.psi.*;
public class CommandLineArgumentImpl extends CommandLineElement implements CommandLineArgument {
public CommandLineArgumentImpl(ASTNode node) {
super(node);
}
public void accept(@NotNull PsiElementVisitor visitor) {
if (visitor instanceof CommandLineVisitor) ((CommandLineVisitor)visitor).visitArgument(this);
else super.accept(visitor);
}
@Override
@Nullable
public PsiElement getLiteralStartsFromDigit() {
return findChildByType(LITERAL_STARTS_FROM_DIGIT);
}
@Override
@Nullable
public PsiElement getLiteralStartsFromLetter() {
return findChildByType(LITERAL_STARTS_FROM_LETTER);
}
}

View File

@@ -0,0 +1,31 @@
// This is a generated file. Not intended for manual editing.
package com.jetbrains.commandInterface.gnuCommandLine.psi.impl;
import java.util.List;
import org.jetbrains.annotations.*;
import com.intellij.lang.ASTNode;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiElementVisitor;
import com.intellij.psi.util.PsiTreeUtil;
import static com.jetbrains.commandInterface.gnuCommandLine.CommandLineElementTypes.*;
import com.jetbrains.commandInterface.gnuCommandLine.CommandLineElement;
import com.jetbrains.commandInterface.gnuCommandLine.psi.*;
public class CommandLineCommandImpl extends CommandLineElement implements CommandLineCommand {
public CommandLineCommandImpl(ASTNode node) {
super(node);
}
public void accept(@NotNull PsiElementVisitor visitor) {
if (visitor instanceof CommandLineVisitor) ((CommandLineVisitor)visitor).visitCommand(this);
else super.accept(visitor);
}
@Override
@NotNull
public PsiElement getLiteralStartsFromLetter() {
return findNotNullChildByType(LITERAL_STARTS_FROM_LETTER);
}
}

View File

@@ -0,0 +1,47 @@
// This is a generated file. Not intended for manual editing.
package com.jetbrains.commandInterface.gnuCommandLine.psi.impl;
import java.util.List;
import org.jetbrains.annotations.*;
import com.intellij.lang.ASTNode;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiElementVisitor;
import com.intellij.psi.util.PsiTreeUtil;
import static com.jetbrains.commandInterface.gnuCommandLine.CommandLineElementTypes.*;
import com.jetbrains.commandInterface.gnuCommandLine.CommandLineElement;
import com.jetbrains.commandInterface.gnuCommandLine.psi.*;
public class CommandLineOptionImpl extends CommandLineElement implements CommandLineOption {
public CommandLineOptionImpl(ASTNode node) {
super(node);
}
public void accept(@NotNull PsiElementVisitor visitor) {
if (visitor instanceof CommandLineVisitor) ((CommandLineVisitor)visitor).visitOption(this);
else super.accept(visitor);
}
@Override
@Nullable
public PsiElement getLongOptionNameToken() {
return findChildByType(LONG_OPTION_NAME_TOKEN);
}
@Override
@Nullable
public PsiElement getShortOptionNameToken() {
return findChildByType(SHORT_OPTION_NAME_TOKEN);
}
@Nullable
@NonNls
public String getOptionName() {
return CommandLinePsiImplUtils.getOptionName(this);
}
public boolean isLong() {
return CommandLinePsiImplUtils.isLong(this);
}
}

View File

@@ -18,7 +18,7 @@ __author__ = 'Ilya.Kazakevich'
version = LooseVersion(django.get_version())
assert version < LooseVersion('1.8a'), "Only Django <1.8 is supported now"
# Some django versions require setup
if django.setup:
if hasattr(django, 'setup'):
django.setup()
dumper = _xml.XmlDumper()
_optparse.report_data(dumper)

View File

@@ -0,0 +1,9 @@
<html>
<body>
<strong>Command line (commands, arguments and options) inspection.</strong>
<!-- tooltip end -->
<p>This inspection checks command you type in command console or command file. It helps you to make sure arguments are on their
places, option names are correct as well as arguments, provided for options.</p>
<p>Do not disable it if you are going to use command-line interfaces like manage.py in Django</p>
</body>
</html>

View File

@@ -0,0 +1,26 @@
<idea-plugin version="2">
<!--
Extension points to support gnu command line language.
See {@link com.jetbrains.commandInterface.gnuCommandLine} package
-->
<extensions defaultExtensionNs="com.intellij">
<fileTypeFactory implementation="com.jetbrains.commandInterface.gnuCommandLine.CommandLineFileTypeFactory"/>
<lang.parserDefinition language="CommandLine"
implementationClass="com.jetbrains.commandInterface.gnuCommandLine.CommandLineParserDefinition"/>
<lang.syntaxHighlighterFactory key="CommandLine"
implementationClass="com.jetbrains.commandInterface.gnuCommandLine.CommandLineSyntaxHighlighterFactory"/>
<psi.referenceContributor implementation="com.jetbrains.commandInterface.gnuCommandLine.CommandLineReferenceContributor"/>
<lang.elementManipulator forClass="com.jetbrains.commandInterface.gnuCommandLine.CommandLineElement"
implementationClass="com.jetbrains.commandInterface.gnuCommandLine.CommandLineElementManipulator"/>
<localInspection language="CommandLine" shortName="CommandLineInspection" displayName="Command-line inspection"
enabledByDefault="true" level="WARNING"
groupKey="INSP.GROUP.python"
groupBundle="com.jetbrains.python.PyBundle"
implementationClass="com.jetbrains.commandInterface.gnuCommandLine.CommandLineInspection"/>
<lang.documentationProvider language="CommandLine"
implementationClass="com.jetbrains.commandInterface.gnuCommandLine.CommandLineDocumentationProvider"/>
</extensions>
</idea-plugin>

View File

@@ -1,4 +1,4 @@
<idea-plugin version="2">
<idea-plugin version="2" xmlns:xi="http://www.w3.org/2001/XInclude">
<!-- Components and extensions declared in this file work both in PyCharm and Python plugin.
Both Community and Professional editions. -->
@@ -6,6 +6,9 @@
<module value="com.intellij.modules.python"/>
<!-- TODO: Doc -->
<xi:include href="/META-INF/gnu-command-line.xml" xpointer="xpointer(/idea-plugin/*)"/>
<extensions defaultExtensionNs="com.intellij">
<library.type implementation="com.jetbrains.python.library.PythonLibraryType"/>
<renameHandler implementation="com.jetbrains.python.magicLiteral.PyMagicLiteralRenameHandler"/>

View File

@@ -0,0 +1,154 @@
/*
* Copyright 2000-2014 JetBrains s.r.o.
*
* Licensed 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 com.jetbrains.commandInterface.command;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
// TODO: Support regex validation as well
/**
* Command argument (positional or option argument)
* This class represents command argument, not its value.
*
* @author Ilya.Kazakevich
*/
public final class Argument {
/**
* Argument help user-readable text
*/
@NotNull
private final String myHelpText;
/**
* List of values argument may have. Null if any value is possible.
*/
@Nullable
private final List<String> myAvailableValues;
@Nullable
private final ArgumentType myType;
/**
* @param helpText Argument help user-readable text
*/
public Argument(@NotNull final String helpText) {
this(helpText, null, null);
}
/**
* @param type Argument value type. Null if any type is possible.
*/
public Argument(@NotNull final ArgumentType type) {
this("", type);
}
/**
* @param helpText Argument help user-readable text
* @param type Argument value type. Null if any type is possible.
*/
public Argument(@NotNull final String helpText,
@Nullable final ArgumentType type) {
this(helpText, null, type);
}
/**
* @param availableValues List of values argument may have. Null if any value is possible.
*/
public Argument(@Nullable final List<String> availableValues) {
this("", availableValues, null);
}
/**
* @param helpText Argument help user-readable text
* @param availableValues List of values argument may have. Null if any value is possible.
*/
public Argument(@NotNull final String helpText, @Nullable final List<String> availableValues) {
this(helpText, availableValues, null);
}
/**
* @param helpText Argument help user-readable text
* @param availableValues List of values argument may have. Null if any value is possible.
* @param type Argument value type. Null if any type is possible.
*/
public Argument(@NotNull final String helpText,
@Nullable final List<String> availableValues,
@Nullable final ArgumentType type) {
myHelpText = helpText;
myAvailableValues = (availableValues == null ? null : new ArrayList<String>(availableValues));
myType = type;
}
/**
* @return Argument help user-readable text
*/
@NotNull
public String getHelpText() {
return myHelpText;
}
/**
* @return List of values argument may have. Null if any value is possible.
*/
@Nullable
public List<String> getAvailableValues() {
return (myAvailableValues == null ? null : Collections.unmodifiableList(myAvailableValues));
}
/**
* Validates argument value. Argument tries its best to validate value based on information, provided by constructor.
*
* @param value value to check
* @return true if argument may have this value.
*/
public boolean isValid(@NotNull final String value) {
if (!isTypeValid(value)) {
return false;
}
if (myAvailableValues == null) {
return true;
}
return myAvailableValues.contains(value);
}
/**
* Ensures value conforms type (if known)
*
* @param value value to check
* @return false if type is known and it differs from value
*/
private boolean isTypeValid(@NotNull final String value) {
// We only check integer for now
if (myType == ArgumentType.INTEGER) {
try {
// We just getCommandLineInfo it to get exception
//noinspection ResultOfMethodCallIgnored
Integer.parseInt(value);
}
catch (final NumberFormatException ignored) {
return false;
}
}
return true;
}
}

View File

@@ -13,13 +13,13 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jetbrains.python.commandInterface.command;
package com.jetbrains.commandInterface.command;
/**
* Argument type to be used with {@link OptionTypedArgumentInfo}
* Argument type to be used with {@link Argument}
* @author Ilya.Kazakevich
*/
public enum OptionArgumentType {
public enum ArgumentType {
/**
* String (actually, anything)
*/

View File

@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jetbrains.python.commandInterface.command;
package com.jetbrains.commandInterface.command;
import com.intellij.openapi.util.Pair;
import org.jetbrains.annotations.Nullable;

View File

@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jetbrains.python.commandInterface.command;
package com.jetbrains.commandInterface.command;
import com.intellij.openapi.module.Module;
import org.jetbrains.annotations.NotNull;
@@ -36,10 +36,13 @@ public interface Command {
String getName();
/**
* @param tryCutOutArguments Try to remove information about arguments from help text (i.e. "[file] removes file" -> "removes file").
* Command may or may not support it.
* It should ignore argument if it does not know how to cut out argument info.
* @return Command readable help text
*/
@Nullable
String getHelp();
String getHelp(boolean tryCutOutArguments);
/**
@@ -57,7 +60,7 @@ public interface Command {
/**
* Execute command
*
* @param module module to execute command against
* @param module module to execute command against
* @param parameters command's arguments and options (just like entered by user but splitted by space)
*/
void execute(@NotNull final Module module, @NotNull final List<String> parameters);

View File

@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jetbrains.python.commandInterface.command;
package com.jetbrains.commandInterface.command;
import com.google.common.base.Preconditions;
import com.intellij.openapi.util.Pair;

View File

@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jetbrains.python.commandInterface.command;
package com.jetbrains.commandInterface.command;
import com.intellij.openapi.util.Pair;
import org.jetbrains.annotations.Nullable;

View File

@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jetbrains.python.commandInterface.command;
package com.jetbrains.commandInterface.command;
import com.google.common.base.Preconditions;
import com.intellij.openapi.util.Pair;
@@ -37,7 +37,7 @@ public final class Option {
@NotNull
private final List<String> myShortNames = new ArrayList<String>();
@Nullable
private final Pair<Integer, OptionArgumentInfo> myArgumentAndQuantity;
private final Pair<Integer, Argument> myArgumentAndQuantity;
@NotNull
private final String myHelp;
@@ -47,7 +47,7 @@ public final class Option {
* @param shortNames option short names
* @param longNames option long names
*/
public Option(@Nullable final Pair<Integer, OptionArgumentInfo> argumentAndQuantity,
public Option(@Nullable final Pair<Integer, Argument> argumentAndQuantity,
@NotNull final String help,
@NotNull final Collection<String> shortNames,
@NotNull final Collection<String> longNames) {
@@ -85,11 +85,12 @@ public final class Option {
return Collections.unmodifiableList(myShortNames);
}
// TODO: USe "known arguments info" to prevent copy/paste
/**
* @return if option accepts argument -- pair of [argument_quantity, its_type_info]. Null otherwise.
* @return if option accepts argument -- pair of [argument_quantity, argument]. Null otherwise.
*/
@Nullable
public Pair<Integer, OptionArgumentInfo> getArgumentAndQuantity() {
public Pair<Integer, Argument> getArgumentAndQuantity() {
return myArgumentAndQuantity;
}

View File

@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jetbrains.python.commandInterface.command;
package com.jetbrains.commandInterface.command;
import com.intellij.openapi.util.Pair;
import org.jetbrains.annotations.NotNull;

View File

@@ -13,24 +13,15 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jetbrains.python.commandInterface.rangeBasedPresenter;
import org.jetbrains.annotations.Nullable;
/**
* Engine to execute command line
* <h1>Command with arguments and options.</h1>
* <p>
* Each {@link com.jetbrains.commandInterface.command.Command} may have one or more positional {@link com.jetbrains.commandInterface.command.Argument arguments}
* and several {@link com.jetbrains.commandInterface.command.Option options} (with arguments as well).
* You need to implemenet {@link com.jetbrains.commandInterface.command.Command} first.
* </p>
*
* @author Ilya.Kazakevich
*/
public interface Executor {
/**
* @return information about what to execute (like command name)
*/
@Nullable
String getExecutionDescription();
/**
* Execute command!
*/
void execute();
}
package com.jetbrains.commandInterface.command;

View File

@@ -0,0 +1,70 @@
/*
* Copyright 2000-2015 JetBrains s.r.o.
*
* Licensed 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 com.jetbrains.commandInterface.console;
import com.intellij.openapi.module.Module;
import com.intellij.psi.PsiFileFactory;
import com.intellij.util.Consumer;
import com.jetbrains.commandInterface.command.Command;
import com.jetbrains.commandInterface.gnuCommandLine.CommandLineLanguage;
import com.jetbrains.commandInterface.gnuCommandLine.psi.CommandLineFile;
import com.jetbrains.python.psi.PyUtil;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.regex.Pattern;
/**
* Delegates console execution to command.
*
* @author Ilya.Kazakevich
*/
class CommandExecutor implements Consumer<String> {
@NotNull
private static final Pattern EMPTY_SPACE = Pattern.compile("\\s+");
@NotNull
private final Collection<Command> myCommands = new ArrayList<Command>();
@NotNull
private final Module myModule;
CommandExecutor(@NotNull final Collection<Command> commands, @NotNull final Module module) {
myCommands.addAll(commands);
myModule = module;
}
@Override
public final void consume(final String t) {
/**
* We need to: 1) parse input 2) fetch command 3) split its arguments.
*/
final PsiFileFactory fileFactory = PsiFileFactory.getInstance(myModule.getProject());
final CommandLineFile file = PyUtil.as(fileFactory.createFileFromText(CommandLineLanguage.INSTANCE, t), CommandLineFile.class);
if (file == null) {
return;
}
final String commandName = file.getCommand();
for (final Command command : myCommands) {
if (command.getName().equals(commandName)) {
final List<String> argument = Arrays.asList(EMPTY_SPACE.split(file.getText()));
// 1 because we need to command which is on the first place
command.execute(myModule, argument.subList(1, argument.size()));
}
}
}
}

View File

@@ -0,0 +1,118 @@
/*
* Copyright 2000-2015 JetBrains s.r.o.
*
* Licensed 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 com.jetbrains.commandInterface.console;
import com.intellij.execution.console.LanguageConsoleBuilder;
import com.intellij.execution.console.LanguageConsoleImpl;
import com.intellij.execution.console.LanguageConsoleView;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.command.CommandProcessor;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Condition;
import com.intellij.openapi.wm.ToolWindow;
import com.intellij.openapi.wm.ToolWindowAnchor;
import com.intellij.openapi.wm.ToolWindowManager;
import com.intellij.ui.content.Content;
import com.intellij.ui.content.ContentManager;
import com.intellij.ui.content.impl.ContentImpl;
import com.intellij.util.Consumer;
import com.jetbrains.commandInterface.command.Command;
import com.jetbrains.commandInterface.gnuCommandLine.CommandLineLanguage;
import com.jetbrains.commandInterface.gnuCommandLine.psi.CommandLineFile;
import com.jetbrains.python.psi.PyUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.List;
/**
* Displays command-line console for user.
*
* @author Ilya.Kazakevich
*/
public final class CommandLineConsole {
private CommandLineConsole() {
}
/**
* Creates and displays command-line console for user.
*
* @param module module to display console for.
* @param consoleName Console name (would be used in prompt, history etc)
* @param commandList list of commands available for this console. You may pass null here, but in this case no validation nor suggestion
* would work. Additionaly, no executor would be registered, so you will need to use
* {@link LanguageConsoleBuilder#registerExecuteAction(LanguageConsoleView, Consumer, String, String, Condition)}
* by yourself passing this method result as arg to enable execution, history etc.
* @return newly created console. You do not need to do anything with this value to display console: it will be displayed automatically
*/
@NotNull
public static LanguageConsoleView createConsole(
@NotNull final Module module,
@NotNull final String consoleName,
@Nullable final List<Command> commandList) {
final Project project = module.getProject();
final ToolWindowManager toolWindowManager = ToolWindowManager.getInstance(project);
ToolWindow window = toolWindowManager.getToolWindow(consoleName);
if (window == null) {
window = toolWindowManager.registerToolWindow(consoleName, true, ToolWindowAnchor.BOTTOM);
}
window.activate(null);
final ContentManager contentManager = window.getContentManager();
contentManager.removeAllContents(true);
final LanguageConsoleView console = new LanguageConsoleImpl(project, "", CommandLineLanguage.INSTANCE);
console.setPrompt(consoleName + " > ");
console.setEditable(true);
final CommandLineFile file = PyUtil.as(console.getFile(), CommandLineFile.class);
if (file != null && commandList != null) {
file.setCommands(commandList);
LanguageConsoleBuilder.registerExecuteAction(console, new CommandExecutor(commandList, module), consoleName, consoleName, null);
}
final Content content = new ContentImpl(console.getComponent(), "", true);
contentManager.addContent(content);
showHiddenCommandWorkAround(console);
return console;
}
/**
* TODO: Investigate why do we need this hack
* For some reason console is not displayed correctly unless we type something to it (i.e. space)
*
* @param console console to type space
*/
private static void showHiddenCommandWorkAround(@NotNull final LanguageConsoleView console) {
console.getComponent().setVisible(true);
CommandProcessor.getInstance().executeCommand(null, new Runnable() {
@Override
public void run() {
ApplicationManager.getApplication().runWriteAction(new Runnable() {
@Override
public void run() {
console.getEditorDocument().insertString(0, " ");
console.getComponent().grabFocus();
}
});
}
}, null, null);
}
}

View File

@@ -13,17 +13,12 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jetbrains.python.commandLineParser;
import org.jetbrains.annotations.NotNull;
/**
* This exception is thrown when command line can't be parsed
* Package to display command line console on the bottom of the screen. It supports history, execution, syntax highlighting and so on.
* Entry point is {@link com.jetbrains.commandInterface.console.CommandLineConsole}
*
* @author Ilya.Kazakevich
* @see com.jetbrains.commandInterface.console.CommandLineConsole
*/
public class MalformedCommandLineException extends Exception {
public MalformedCommandLineException(@NotNull final String message) {
super(message);
}
}
package com.jetbrains.commandInterface.console;

View File

@@ -0,0 +1,74 @@
/*
* Copyright 2000-2015 JetBrains s.r.o.
*
* Licensed 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 com.jetbrains.commandInterface.gnuCommandLine;
import com.intellij.codeInsight.completion.PrioritizedLookupElement;
import com.intellij.codeInsight.lookup.LookupElement;
import com.intellij.codeInsight.lookup.LookupElementBuilder;
import com.intellij.psi.PsiElement;
import com.intellij.util.ArrayUtil;
import com.jetbrains.commandInterface.gnuCommandLine.psi.*;
import com.jetbrains.commandInterface.command.Option;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.Collection;
/**
* Ref to be injected in command line argument
* @author Ilya.Kazakevich
*/
public final class CommandLineArgumentReference extends CommandLineElementReference<CommandLineArgument> {
CommandLineArgumentReference(@NotNull final CommandLineArgument element) {
super(element);
}
@Nullable
@Override
public PsiElement resolve() {
return null;
}
@NotNull
@Override
public Object[] getVariants() {
final ValidationResult validationResult = getValidationResult();
if (validationResult == null) {
return EMPTY_ARRAY;
}
final Collection<LookupElement> result = new ArrayList<LookupElement>();
final Collection<String> argumentValues = validationResult.getPossibleArgumentValues(getElement());
// priority is used to display args before options
if (argumentValues != null) {
for (final String value : argumentValues) {
result.add(PrioritizedLookupElement.withPriority(LookupElementBuilder.create(value).withBoldness(true), 1));
}
}
if (!validationResult.isOptionArgument(getElement())) {
for (final Option option : validationResult.getUnusedOptions()) {
for (final String value : option.getAllNames()) {
result.add(PrioritizedLookupElement.withPriority(LookupElementBuilder.create(value).withTailText(" :" + option.getHelp()), 0));
}
}
}
return ArrayUtil.toObjectArray(result);
}
}

View File

@@ -0,0 +1,76 @@
/*
* Copyright 2000-2015 JetBrains s.r.o.
*
* Licensed 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 com.jetbrains.commandInterface.gnuCommandLine;
import com.intellij.codeInsight.lookup.LookupElement;
import com.intellij.codeInsight.lookup.LookupElementBuilder;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.PsiElement;
import com.intellij.util.ArrayUtil;
import com.jetbrains.commandInterface.gnuCommandLine.psi.CommandLineCommand;
import com.jetbrains.commandInterface.command.Command;
import com.jetbrains.commandInterface.gnuCommandLine.psi.CommandLineFile;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
/**
* Ref to be injected in command itself
* @author Ilya.Kazakevich
*/
public final class CommandLineCommandReference extends CommandLineElementReference<CommandLineCommand> {
CommandLineCommandReference(@NotNull final CommandLineCommand element) {
super(element);
}
@Nullable
@Override
public PsiElement resolve() {
return null;
}
@NotNull
@Override
public Object[] getVariants() {
final CommandLineFile file = getCommandLineFile();
if (file == null) {
return EMPTY_ARRAY;
}
final List<Command> commands = file.getCommands();
if (commands == null) {
return EMPTY_ARRAY;
}
final Collection<LookupElement> commandNames = new ArrayList<LookupElement>();
for (final Command command : commands) {
LookupElementBuilder lookupElementBuilder = LookupElementBuilder.create(command.getName());
final String help = command.getHelp(true);
if (!StringUtil.isEmpty(help)) {
lookupElementBuilder = lookupElementBuilder.withTailText(" :" + help);
}
commandNames.add(lookupElementBuilder);
}
return ArrayUtil.toObjectArray(commandNames);
}
}

View File

@@ -0,0 +1,67 @@
/*
* Copyright 2000-2015 JetBrains s.r.o.
*
* Licensed 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 com.jetbrains.commandInterface.gnuCommandLine;
import com.intellij.lang.documentation.DocumentationProvider;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiManager;
import com.jetbrains.commandInterface.command.Command;
import com.jetbrains.commandInterface.gnuCommandLine.psi.CommandLineFile;
import org.jetbrains.annotations.Nullable;
import java.util.List;
/**
* Provides quick help for arguments
* @author Ilya.Kazakevich
*/
public final class CommandLineDocumentationProvider implements DocumentationProvider {
@Nullable
@Override
public String getQuickNavigateInfo(final PsiElement element, final PsiElement originalElement) {
return null;
}
@Nullable
@Override
public List<String> getUrlFor(final PsiElement element, final PsiElement originalElement) {
return null;
}
@Nullable
@Override
public String generateDoc(final PsiElement element, @Nullable final PsiElement originalElement) {
if (element instanceof CommandLineFile) {
final Command command = ((CommandLineFile)element).findRealCommand();
if (command != null) {
return command.getHelp(false); // We do not need arguments info in help text
}
}
return null;
}
@Nullable
@Override
public PsiElement getDocumentationElementForLookupItem(final PsiManager psiManager, final Object object, final PsiElement element) {
return null;
}
@Nullable
@Override
public PsiElement getDocumentationElementForLink(final PsiManager psiManager, final String link, final PsiElement context) {
return null;
}
}

View File

@@ -13,32 +13,28 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jetbrains.python.commandLineParser.optParse;
package com.jetbrains.commandInterface.gnuCommandLine;
import com.intellij.openapi.util.Pair;
import com.intellij.extapi.psi.ASTWrapperPsiElement;
import com.intellij.lang.ASTNode;
import com.intellij.psi.PsiReference;
import com.intellij.psi.impl.source.resolve.reference.ReferenceProvidersRegistry;
import org.jetbrains.annotations.NotNull;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Deals with short-style options like -b or -f
* Parent of all command line elements (enables reference injection, see {@link #getReferences()})
*
* @author Ilya.Kazakevich
*/
final class ShortOptionParser extends OptionParserRegexBased {
/**
* Short-style option regexp
*/
private static final Pattern SHORT_OPT_PATTERN = Pattern.compile("^(-[a-zA-Z0-9])([^ -])?");
ShortOptionParser() {
super(SHORT_OPT_PATTERN);
public class CommandLineElement extends ASTWrapperPsiElement {
protected CommandLineElement(@NotNull final ASTNode node) {
super(node);
}
@NotNull
@Override
protected Pair<String, String> getOptionTextAndNameFromMatcher(@NotNull final Matcher matcher) {
return Pair.create(matcher.group(1), matcher.group(1));
public final PsiReference[] getReferences() {
// We need it to enable reference injection
return ReferenceProvidersRegistry.getReferencesFromProviders(this);
}
}

View File

@@ -13,30 +13,23 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jetbrains.python.commandLineParser.optParse;
package com.jetbrains.commandInterface.gnuCommandLine;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.util.TextRange;
import com.intellij.psi.AbstractElementManipulator;
import org.jetbrains.annotations.NotNull;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Supports --long-option-style-with=value
* Manipulator to support reference injection. Will fail with out of it.
*
* @author Ilya.Kazakevich
*/
final class LongOptionParser extends OptionParserRegexBased {
@NotNull
private static final Pattern LONG_OPT_PATTERN = Pattern.compile("^((--[a-zA-Z0-9-]+)=?)");
public final class CommandLineElementManipulator extends AbstractElementManipulator<CommandLineElement> {
LongOptionParser() {
super(LONG_OPT_PATTERN);
}
@NotNull
@Override
protected Pair<String, String> getOptionTextAndNameFromMatcher(@NotNull final Matcher matcher) {
return Pair.create(matcher.group(1), matcher.group(2));
public CommandLineElement handleContentChange(@NotNull final CommandLineElement element,
@NotNull final TextRange range,
final String newContent) {
return null;
}
}

View File

@@ -0,0 +1,69 @@
/*
* Copyright 2000-2015 JetBrains s.r.o.
*
* Licensed 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 com.jetbrains.commandInterface.gnuCommandLine;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiReferenceBase;
import com.intellij.psi.util.PsiTreeUtil;
import com.jetbrains.commandInterface.command.Command;
import com.jetbrains.commandInterface.gnuCommandLine.psi.CommandLineFile;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.List;
/**
* Parent of all references injected to command line elements
*
* @author Ilya.Kazakevich
*/
abstract class CommandLineElementReference<T extends PsiElement> extends PsiReferenceBase<T> {
protected CommandLineElementReference(@NotNull final T element) {
super(element);
}
/**
* @return command line file this element sits in (if any)
*/
@Nullable
protected final CommandLineFile getCommandLineFile() {
return PsiTreeUtil.getParentOfType(getElement(), CommandLineFile.class);
}
/**
* @return command line validation result (if any)
*/
@Nullable
protected final ValidationResult getValidationResult() {
final CommandLineFile file = getCommandLineFile();
if (file == null) {
return null;
}
return file.getValidationResult();
}
/**
* @return real command used in parent file (if any)
*/
@Nullable
protected final Command getCommand() {
final CommandLineFile file = getCommandLineFile();
if (file == null) {
return null;
}
return file.findRealCommand();
}
}

View File

@@ -13,25 +13,19 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jetbrains.python.commandLineParser;
package com.jetbrains.commandInterface.gnuCommandLine;
import com.intellij.psi.tree.IElementType;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
/**
* Visitor to process options and arguments
* Command line element type to be used in parser
*
* @author Ilya.Kazakevich
* @see CommandLinePart#accept(CommandLinePartVisitor)
*/
public interface CommandLinePartVisitor {
/**
* @param option option to visit
*/
void visitOption(@NotNull CommandLineOption option);
/**
* @param argument argument to visit
*/
void visitArgument(@NotNull CommandLineArgument argument);
final class CommandLineElementType extends IElementType {
CommandLineElementType(@NotNull @NonNls final String debugName) {
super(debugName, CommandLineLanguage.INSTANCE);
}
}

View File

@@ -0,0 +1,63 @@
/*
* Copyright 2000-2015 JetBrains s.r.o.
*
* Licensed 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 com.jetbrains.commandInterface.gnuCommandLine;
import com.intellij.openapi.fileTypes.FileType;
import com.intellij.openapi.fileTypes.LanguageFileType;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
/**
* Command line file type
* @author Ilya.Kazakevich
*/
public final class CommandLineFileType extends LanguageFileType {
public static final FileType INSTANCE = new CommandLineFileType();
/**
* Command line extension
*/
static final String EXTENSION = "cmdline";
CommandLineFileType() {
super(CommandLineLanguage.INSTANCE);
}
@NotNull
@Override
public String getName() {
return CommandLineLanguage.INSTANCE.getID();
}
@NotNull
@Override
public String getDescription() {
return CommandLineLanguage.INSTANCE.getID();
}
@NotNull
@Override
public String getDefaultExtension() {
return '.' + EXTENSION;
}
@Nullable
@Override
public Icon getIcon() {
return null;
}
}

View File

@@ -13,24 +13,20 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jetbrains.python.commandLineParser;
package com.jetbrains.commandInterface.gnuCommandLine;
import com.jetbrains.python.WordWithPosition;
import com.intellij.openapi.fileTypes.FileTypeConsumer;
import com.intellij.openapi.fileTypes.FileTypeFactory;
import org.jetbrains.annotations.NotNull;
/**
* Positional or option argument.
* Factory to create command line files
*
* @author Ilya.Kazakevich
*/
public final class CommandLineArgument extends CommandLinePart {
public CommandLineArgument(@NotNull final WordWithPosition argumentValue) {
super(argumentValue);
}
public final class CommandLineFileTypeFactory extends FileTypeFactory {
@Override
public void accept(@NotNull final CommandLinePartVisitor visitor) {
visitor.visitArgument(this);
public void createFileTypes(@NotNull final FileTypeConsumer consumer) {
consumer.consume(CommandLineFileType.INSTANCE, CommandLineFileType.EXTENSION);
}
}

View File

@@ -0,0 +1,130 @@
/*
* Copyright 2000-2015 JetBrains s.r.o.
*
* Licensed 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 com.jetbrains.commandInterface.gnuCommandLine;
import com.intellij.codeInspection.*;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiElementVisitor;
import com.intellij.psi.util.PsiTreeUtil;
import com.jetbrains.commandInterface.gnuCommandLine.psi.*;
import com.jetbrains.commandInterface.command.Command;
import com.jetbrains.python.PyBundle;
import org.jetbrains.annotations.Nls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.List;
/**
* Inspection that ensures command line options and arguments are correct.
* It works only if list of available commands is provided to command file
*
* @author Ilya.Kazakevich
*/
public final class CommandLineInspection extends LocalInspectionTool {
@Nls
@NotNull
@Override
public String getDisplayName() {
return PyBundle.message("commandLine.inspection.name");
}
@NotNull
@Override
public String getShortName() {
return getClass().getSimpleName();
}
@Override
public boolean isEnabledByDefault() {
return true;
}
@NotNull
@Override
public PsiElementVisitor buildVisitor(@NotNull final ProblemsHolder holder,
final boolean isOnTheFly,
@NotNull final LocalInspectionToolSession session) {
return new MyVisitor(holder);
}
private static final class MyVisitor extends CommandLineVisitor {
@NotNull
private final ProblemsHolder myHolder;
private MyVisitor(@NotNull final ProblemsHolder holder) {
myHolder = holder;
}
@Nullable
private static CommandLineFile getFile(@NotNull final PsiElement element) {
return PsiTreeUtil.getParentOfType(element, CommandLineFile.class);
}
@Nullable
private static ValidationResult getValidationResult(@NotNull final PsiElement element) {
final CommandLineFile file = getFile(element);
if (file == null) {
return null;
}
return file.getValidationResult();
}
@Override
public void visitCommand(@NotNull final CommandLineCommand o) {
super.visitCommand(o);
final CommandLineFile file = getFile(o);
if (file == null) {
return;
}
final List<Command> commands = file.getCommands();
if (commands == null) {
return;
}
for (final Command command : commands) {
if (o.getText().equals(command.getName())) {
return;
}
}
myHolder.registerProblem(o, PyBundle.message("commandLine.inspection.badCommand"));
}
@Override
public void visitOption(@NotNull final CommandLineOption o) {
super.visitOption(o);
final ValidationResult validationResult = getValidationResult(o);
if (validationResult != null && validationResult.isBadValue(o)) {
myHolder.registerProblem(o, PyBundle.message("commandLine.inspection.badOption"));
}
}
@Override
public void visitArgument(@NotNull final CommandLineArgument o) {
super.visitArgument(o);
final ValidationResult validationResult = getValidationResult(o);
if (validationResult != null) {
if (validationResult.isBadValue(o)) {
myHolder.registerProblem(o, PyBundle.message("commandLine.inspection.badArgument"));
}
else if (validationResult.isExcessArgument(o)) {
myHolder.registerProblem(o, PyBundle.message("commandLine.inspection.excessArgument"));
}
}
}
}
}

View File

@@ -13,22 +13,19 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jetbrains.python.commandInterface.swingView;
package com.jetbrains.commandInterface.gnuCommandLine;
import java.awt.event.ActionEvent;
import com.intellij.lang.Language;
/**
* "Execute command" action
*
* Command line language itself
* <pre>my_command --option=value foo</pre>
* @author Ilya.Kazakevich
*/
final class ExecutionKeyStrokeAction extends KeyStrokeAction {
ExecutionKeyStrokeAction() {
super(KeyStrokeInfo.EXECUTION);
}
public final class CommandLineLanguage extends Language {
public static final CommandLineLanguage INSTANCE = new CommandLineLanguage();
@Override
public void actionPerformed(final ActionEvent e) {
myPresenter.executionRequested();
private CommandLineLanguage() {
super("CommandLine");
}
}

View File

@@ -0,0 +1,60 @@
/*
* Copyright 2000-2015 JetBrains s.r.o.
*
* Licensed 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 com.jetbrains.commandInterface.gnuCommandLine;
import com.intellij.psi.PsiElement;
import com.intellij.util.ArrayUtil;
import com.jetbrains.commandInterface.gnuCommandLine.psi.CommandLineOption;
import com.jetbrains.commandInterface.command.Option;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.Collection;
/**
* Ref to be injected into command line option
* @author Ilya.Kazakevich
*/
public final class CommandLineOptionReference extends CommandLineElementReference<CommandLineOption> {
CommandLineOptionReference(@NotNull final CommandLineOption element) {
super(element);
}
@Nullable
@Override
public PsiElement resolve() {
return null;
}
@NotNull
@Override
public Object[] getVariants() {
final ValidationResult validationResult = getValidationResult();
if (validationResult == null) {
return EMPTY_ARRAY;
}
final Collection<String> result = new ArrayList<String>();
for (final Option option : validationResult.getUnusedOptions()) {
// Suggest long options for -- and short for -
result.addAll((getElement().isLong() ? option.getLongNames() : option.getShortNames()));
}
return ArrayUtil.toStringArray(result);
}
}

View File

@@ -0,0 +1,90 @@
/*
* Copyright 2000-2015 JetBrains s.r.o.
*
* Licensed 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 com.jetbrains.commandInterface.gnuCommandLine;
import com.intellij.lang.ASTNode;
import com.intellij.lang.ParserDefinition;
import com.intellij.lang.PsiParser;
import com.intellij.lexer.FlexAdapter;
import com.intellij.lexer.Lexer;
import com.intellij.openapi.project.Project;
import com.intellij.psi.FileViewProvider;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.psi.TokenType;
import com.intellij.psi.tree.IFileElementType;
import com.intellij.psi.tree.TokenSet;
import com.jetbrains.commandInterface.gnuCommandLine.CommandLineElementTypes.Factory;
import com.jetbrains.commandInterface.gnuCommandLine.psi.CommandLineFile;
import org.jetbrains.annotations.NotNull;
/**
* Command line language parser definition
*
* @author Ilya.Kazakevich
*/
public final class CommandLineParserDefinition implements ParserDefinition {
@NotNull
@Override
public Lexer createLexer(final Project project) {
return new FlexAdapter(new _CommandLineLexer());
}
@Override
public PsiParser createParser(final Project project) {
return new CommandLineParser();
}
@Override
public IFileElementType getFileNodeType() {
return new IFileElementType(CommandLineLanguage.INSTANCE);
}
@NotNull
@Override
public TokenSet getWhitespaceTokens() {
return TokenSet.create(TokenType.WHITE_SPACE);
}
@NotNull
@Override
public TokenSet getCommentTokens() {
return TokenSet.EMPTY;
}
@NotNull
@Override
public TokenSet getStringLiteralElements() {
return TokenSet.EMPTY;
}
@NotNull
@Override
public PsiElement createElement(final ASTNode node) {
return Factory.createElement(node);
}
@Override
public PsiFile createFile(final FileViewProvider viewProvider) {
return new CommandLineFile(viewProvider);
}
@Override
public SpaceRequirements spaceExistanceTypeBetweenTokens(final ASTNode left, final ASTNode right) {
return SpaceRequirements.MAY;
}
}

View File

@@ -0,0 +1,54 @@
/*
* Copyright 2000-2015 JetBrains s.r.o.
*
* Licensed 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 com.jetbrains.commandInterface.gnuCommandLine;
import com.intellij.lang.PsiBuilder;
import com.intellij.lang.parser.GeneratedParserUtilBase;
import com.intellij.psi.TokenType;
import com.intellij.psi.tree.IElementType;
import org.jetbrains.annotations.NotNull;
/**
* Tool to be used in parser generation to handle "=" for long option
*
* @author Ilya.Kazakevich
*/
final class CommandLineParserUtil extends GeneratedParserUtilBase {
private CommandLineParserUtil() {
}
static void bound_argument(@NotNull final PsiBuilder b, final int i) {
final IElementType tokenType = b.getTokenType();
final IElementType leftElement = b.rawLookup(-1);
final IElementType rightElement = b.rawLookup(1);
if (leftElement == null || TokenType.WHITE_SPACE.equals(leftElement)) {
return;
}
/**
* At '=' position: if no whitespace to left and right, we move to argument.
* And we report error if whitespace to the left.
*/
if (tokenType == CommandLineElementTypes.EQ) {
if (leftElement.equals(CommandLineElementTypes.LONG_OPTION_NAME_TOKEN)) {
if (rightElement == null || TokenType.WHITE_SPACE.equals(rightElement)) {
b.error("Space between argument is its value is unexpected");
}
b.advanceLexer();
}
}
}
}

View File

@@ -0,0 +1,56 @@
/*
* Copyright 2000-2015 JetBrains s.r.o.
*
* Licensed 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 com.jetbrains.commandInterface.gnuCommandLine;
import com.intellij.patterns.PlatformPatterns;
import com.intellij.psi.*;
import com.intellij.util.ProcessingContext;
import com.jetbrains.commandInterface.gnuCommandLine.psi.*;
import org.jetbrains.annotations.NotNull;
/**
* Injects references to command-line parts
*
* @author Ilya.Kazakevich
*/
public final class CommandLineReferenceContributor extends PsiReferenceContributor {
private static final ReferenceProvider REFERENCE_PROVIDER = new ReferenceProvider();
@Override
public void registerReferenceProviders(@NotNull final PsiReferenceRegistrar registrar) {
registrar.registerReferenceProvider(PlatformPatterns.psiElement(CommandLineElement.class), REFERENCE_PROVIDER);
}
private static class ReferenceProvider extends PsiReferenceProvider {
@NotNull
@Override
public final PsiReference[] getReferencesByElement(@NotNull final PsiElement element,
@NotNull final ProcessingContext context) {
if (element instanceof CommandLineCommand) {
return new PsiReference[]{new CommandLineCommandReference((CommandLineCommand)element)};
}
if (element instanceof CommandLineArgument) {
return new PsiReference[]{new CommandLineArgumentReference((CommandLineArgument)element)};
}
if (element instanceof CommandLineOption) {
return new PsiReference[]{new CommandLineOptionReference((CommandLineOption)element)};
}
return PsiReference.EMPTY_ARRAY;
}
}
}

View File

@@ -0,0 +1,78 @@
/*
* Copyright 2000-2015 JetBrains s.r.o.
*
* Licensed 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 com.jetbrains.commandInterface.gnuCommandLine;
import com.intellij.lexer.FlexAdapter;
import com.intellij.lexer.Lexer;
import com.intellij.openapi.editor.DefaultLanguageHighlighterColors;
import com.intellij.openapi.editor.colors.TextAttributesKey;
import com.intellij.openapi.fileTypes.SyntaxHighlighter;
import com.intellij.openapi.fileTypes.SyntaxHighlighterFactory;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.tree.IElementType;
import com.intellij.util.containers.hash.HashMap;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Map;
/**
* Highlights tokens based on lexer
* @author Ilya.Kazakevich
*/
public final class CommandLineSyntaxHighlighterFactory extends SyntaxHighlighterFactory {
@NotNull
@Override
public SyntaxHighlighter getSyntaxHighlighter(@Nullable final Project project,
@Nullable final VirtualFile virtualFile) {
return new CommandLineSyntaxHighlighter();
}
private static class CommandLineSyntaxHighlighter implements SyntaxHighlighter {
private static final Map<IElementType, TextAttributesKey> ATTRIBUTES = new HashMap<IElementType, TextAttributesKey>();
public static final TextAttributesKey[] NO_ATTRS = new TextAttributesKey[0];
static {
ATTRIBUTES.put(CommandLineElementTypes.LITERAL_STARTS_FROM_LETTER,
TextAttributesKey.createTextAttributesKey("GNU.LETTER", DefaultLanguageHighlighterColors.LOCAL_VARIABLE)
);
ATTRIBUTES.put(CommandLineElementTypes.LITERAL_STARTS_FROM_DIGIT,
TextAttributesKey.createTextAttributesKey("GNU.NUMBER", DefaultLanguageHighlighterColors.NUMBER)
);
ATTRIBUTES.put(CommandLineElementTypes.SHORT_OPTION_NAME_TOKEN,
TextAttributesKey.createTextAttributesKey("GNU.SHORT_OPTION", DefaultLanguageHighlighterColors.INSTANCE_METHOD)
);
ATTRIBUTES.put(CommandLineElementTypes.LONG_OPTION_NAME_TOKEN,
TextAttributesKey.createTextAttributesKey("GNU.LONG_OPTION", DefaultLanguageHighlighterColors.INSTANCE_METHOD)
);
}
@NotNull
@Override
public Lexer getHighlightingLexer() {
return new FlexAdapter(new _CommandLineLexer());
}
@NotNull
@Override
public TextAttributesKey[] getTokenHighlights(final IElementType tokenType) {
final TextAttributesKey attributesKey = ATTRIBUTES.get(tokenType);
return (attributesKey == null ? NO_ATTRS : new TextAttributesKey[]{attributesKey});
}
}
}

View File

@@ -0,0 +1,62 @@
/*
* Copyright 2000-2015 JetBrains s.r.o.
*
* Licensed 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 com.jetbrains.commandInterface.gnuCommandLine;
import com.intellij.psi.PsiElement;
import com.jetbrains.commandInterface.command.Option;
import com.jetbrains.commandInterface.gnuCommandLine.psi.CommandLineArgument;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Collection;
/**
* Result of command line validation
*
* @author Ilya.Kazakevich
*/
public interface ValidationResult {
/**
* @param element element to check
* @return true if element is known to have bad/illegal value
*/
boolean isBadValue(@NotNull PsiElement element);
/**
* @param argument element to check
* @return true if argument is excess
*/
boolean isExcessArgument(@NotNull CommandLineArgument argument);
/**
* @return list of allowed options unused by user
*/
@NotNull
Collection<Option> getUnusedOptions();
/**
* @param argument argument to check
* @return true if argument is option argument (position argument otherwise)
*/
boolean isOptionArgument(@NotNull CommandLineArgument argument);
/**
* @param argument argument to check
* @return list of available argument values or null if unknown
*/
@Nullable
Collection<String> getPossibleArgumentValues(@NotNull CommandLineArgument argument);
}

View File

@@ -0,0 +1,44 @@
/*TODO:Add generation to ant*/
// This file is used to auto-generate lexer and parser for gnu command line language
// Please make sure .flex, and .java generated files are all in "gen" folder under this module.
// Generate flex first, then generate lexer by flex, and then generate PSI and parser
{
// All 3 elements should implement this class
mixin("command")="com.jetbrains.commandInterface.gnuCommandLine.CommandLineElement"
mixin("argument")="com.jetbrains.commandInterface.gnuCommandLine.CommandLineElement"
mixin("option")="com.jetbrains.commandInterface.gnuCommandLine.CommandLineElement"
parserClass = 'com.jetbrains.commandInterface.gnuCommandLine.CommandLineParser'
parserUtilClass="com.jetbrains.commandInterface.gnuCommandLine.CommandLineParserUtil"
psiImplUtilClass="com.jetbrains.commandInterface.gnuCommandLine.psi.impl.CommandLinePsiImplUtils"
psiPackage = 'com.jetbrains.commandInterface.gnuCommandLine.psi'
psiImplPackage = 'com.jetbrains.commandInterface.gnuCommandLine.psi.impl'
elementTypeHolderClass = 'com.jetbrains.commandInterface.gnuCommandLine.CommandLineElementTypes'
psiClassPrefix = "CommandLine"
psiVisitorName = "CommandLineVisitor"
elementTypeClass = 'com.jetbrains.commandInterface.gnuCommandLine.CommandLineElementType'
tokens=[
space='regexp:\s+' // WARNING: Comment out or remove this (space) before generating Flex file! It is here only for live preview.
EQ = '='
LITERAL_STARTS_FROM_LETTER='regexp:\p{Alpha}(\w|:|\\|/|\.)*'
LITERAL_STARTS_FROM_DIGIT='regexp:\p{Digit}(\w|:|\\\|/|\.)*'
SHORT_OPTION_NAME_TOKEN='regexp:-\p{Alpha}'
LONG_OPTION_NAME_TOKEN='regexp:--\p{Alpha}(-|\w)*'
]
}
root ::= command (argument | option ) * <<eof>>
command ::= LITERAL_STARTS_FROM_LETTER
option ::= (short_option_name <<bound_argument>> ? | long_option_name <<bound_argument>> ?) {
methods=[ getOptionName isLong ]
}
private short_option_name ::= SHORT_OPTION_NAME_TOKEN
private long_option_name ::= LONG_OPTION_NAME_TOKEN
argument ::= LITERAL_STARTS_FROM_LETTER | LITERAL_STARTS_FROM_DIGIT

View File

@@ -0,0 +1,40 @@
/*
* Copyright 2000-2015 JetBrains s.r.o.
*
* Licensed 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.
*/
/**
* <h1>GNU command line language PSI</h1>
* <h2>Command line language</h2>
* <p>
* <a href="http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap12.html#tag_12_02">GNU</a> command line language syntax
* based on <a href="http://www.gnu.org/software/libc/manual/html_node/Argument-Syntax.html">POSIX</a> syntax.
* <pre>
* my_command --option --option-2=argument positional_argument -s
* </pre>
* </p>
* <h2>PSI</h2>
* <p>Generation based on Grammar-Kit, see .bnf file, do not edit parser nor lexer manually.
* When parsed, {@link com.jetbrains.commandInterface.gnuCommandLine.psi.CommandLineFile} is root element for
* {@link com.jetbrains.commandInterface.gnuCommandLine.CommandLineLanguage}.
* <strong>Warning</strong>: always fill {@link com.jetbrains.commandInterface.gnuCommandLine.psi.CommandLineFile#setCommands(java.util.List)}
* if possible.
* </p>
* <h2>Extension points</h2>
* <p>This package has a a lot of extension points (language, inspection etc). Make sure all of them are registered</p>
*
*
* @author Ilya.Kazakevich
*/
package com.jetbrains.commandInterface.gnuCommandLine;

View File

@@ -0,0 +1,130 @@
/*
* Copyright 2000-2015 JetBrains s.r.o.
*
* Licensed 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 com.jetbrains.commandInterface.gnuCommandLine.psi;
import com.intellij.extapi.psi.PsiFileBase;
import com.intellij.openapi.fileTypes.FileType;
import com.intellij.openapi.util.Key;
import com.intellij.psi.FileViewProvider;
import com.intellij.psi.util.PsiTreeUtil;
import com.jetbrains.commandInterface.gnuCommandLine.CommandLineLanguage;
import com.jetbrains.commandInterface.command.Command;
import com.jetbrains.commandInterface.gnuCommandLine.ValidationResult;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Collection;
import java.util.List;
/**
* Gnu command line file (topmost element).
*
* @author Ilya.Kazakevich
*/
public final class CommandLineFile extends PsiFileBase {
private static final Key<List<Command>> COMMANDS = Key.create("COMMANDS");
public CommandLineFile(final FileViewProvider provider) {
super(provider, CommandLineLanguage.INSTANCE);
}
@NotNull
@Override
public FileType getFileType() {
return getViewProvider().getVirtualFile().getFileType();
}
/**
* @return list of commands, available for this file
* @see #setCommands(List)
*/
@Nullable
public List<Command> getCommands() {
return getCopyableUserData(COMMANDS);
}
/**
* @param commands list of commands, available for this file. Better to provide one, if you know
*/
public void setCommands(@NotNull final List<Command> commands) {
putCopyableUserData(COMMANDS, commands);
}
/**
* Tries to find real command used in this file.
* You need to first inject list of commands {@link #setCommands(List)}.
*
* @return Command if found and available, or null if command can't be parsed or bad command.
*/
@Nullable
public Command findRealCommand() {
final String command = getCommand();
final List<Command> realCommands = getCommands();
if (realCommands == null) {
return null;
}
for (final Command realCommand : realCommands) {
if (realCommand.getName().equals(command)) {
return realCommand;
}
}
return null;
}
/**
* Tries to validate file.
*
* @return file validation info or null if file is junk or list of commands is unknown (see {@link #setCommands(List)})
*/
@Nullable
public ValidationResult getValidationResult() {
return ValidationResultImpl.create(this);
}
/**
* @return command (text) typed by user. I.e "my_command" in "my_command --foo --bar"
*/
@Nullable
public String getCommand() {
final CommandLineCommand command = PsiTreeUtil.getChildOfType(this, CommandLineCommand.class);
if (command != null) {
return command.getText();
}
return null;
}
/**
* @return all arguments from file
*/
@NotNull
public Collection<CommandLineArgument> getArguments() {
return PsiTreeUtil.findChildrenOfType(this, CommandLineArgument.class);
}
/**
* @return all options from file
*/
@NotNull
public Collection<CommandLineOption> getOptions() {
return PsiTreeUtil.findChildrenOfType(this, CommandLineOption.class);
}
}

View File

@@ -0,0 +1,215 @@
/*
* Copyright 2000-2015 JetBrains s.r.o.
*
* Licensed 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 com.jetbrains.commandInterface.gnuCommandLine.psi;
import com.intellij.openapi.util.Pair;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiWhiteSpace;
import com.intellij.util.containers.hash.HashMap;
import com.jetbrains.commandInterface.gnuCommandLine.ValidationResult;
import com.jetbrains.commandInterface.command.Argument;
import com.jetbrains.commandInterface.command.Command;
import com.jetbrains.commandInterface.command.Option;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
/**
* Validation result provider and holder implemented as visitor.
* @author Ilya.Kazakevich
*/
final class ValidationResultImpl extends CommandLineVisitor implements ValidationResult {
/**
* List of options with names
*/
@NotNull
private final HashMap<String, Option> myOptions = new HashMap<String, Option>();
/**
* We always need command to validate args
*/
@NotNull
private final Command myCommand;
/**
* Number of next positional argument. I.e. will be 3 for "my_arg arg_1 arg_2"
*/
private int myCurrentPositionArgument;
/**
* If next arg is supposed to be option arg, then option and number of expected args stored here.
* Null stored otherwise.
*/
@Nullable
private Pair<Option, Integer> myCurrentOptionAndArgsLeft;
/**
* List of elements whose values are known to be bad
*/
@NotNull
private final Collection<PsiElement> myBadValues = new ArrayList<PsiElement>();
/**
* List of elements which is known to be excess
*/
@NotNull
private final Collection<CommandLineArgument> myExcessArguments = new ArrayList<CommandLineArgument>();
/**
* Possible values for argument
*/
@NotNull
private final Map<CommandLineArgument, List<String>> myPossibleValues = new HashMap<CommandLineArgument, List<String>>();
/**
* List of arguments known to be option arguments
*/
@NotNull
private final Collection<CommandLineArgument> myOptionArguments = new ArrayList<CommandLineArgument>();
private ValidationResultImpl(@NotNull final Command command) {
for (final Option option : command.getOptions()) {
for (final String optionName : option.getAllNames()) {
myOptions.put(optionName, option);
}
}
myCommand = command;
}
@Override
public boolean isBadValue(@NotNull final PsiElement element) {
return myBadValues.contains(element);
}
@Override
public boolean isExcessArgument(@NotNull final CommandLineArgument argument) {
return myExcessArguments.contains(argument);
}
@Override
@NotNull
public Collection<Option> getUnusedOptions() {
return myOptions.values();
}
@Override
public boolean isOptionArgument(@NotNull final CommandLineArgument argument) {
return myOptionArguments.contains(argument);
}
@Override
@Nullable
public Collection<String> getPossibleArgumentValues(@NotNull final CommandLineArgument argument) {
return myPossibleValues.get(argument);
}
/**
* Creates validation result by file
* @param file file to validate
* @return validation result or null if file has no command or command is unknown
*/
@Nullable
static ValidationResult create(final CommandLineFile file) {
final Command command = file.findRealCommand();
if (command == null) {
return null;
}
final ValidationResultImpl validationLayout = new ValidationResultImpl(command);
file.acceptChildren(validationLayout);
return validationLayout;
}
@Override
public void visitArgument(@NotNull final CommandLineArgument o) {
super.visitArgument(o);
if (myCurrentOptionAndArgsLeft != null) {
processOptionArgument(o);
return;
}
// Process as positional
processPositionalArgument(o);
}
private void processPositionalArgument(@NotNull final CommandLineArgument o) {
final Pair<Boolean, Argument> argumentPair = myCommand.getArgumentsInfo().getArgument(myCurrentPositionArgument++);
if (argumentPair == null) {
myExcessArguments.add(o);
}
else {
processArgument(o, argumentPair.second);
}
}
private void processOptionArgument(@NotNull final CommandLineArgument o) {
assert myCurrentOptionAndArgsLeft != null: "Method can't be called if no current option exist";
if (myCurrentOptionAndArgsLeft.second > 0) {
myCurrentOptionAndArgsLeft = Pair.create(myCurrentOptionAndArgsLeft.first, myCurrentOptionAndArgsLeft.second - 1);
final Pair<Integer, Argument> argumentAndQuantity = myCurrentOptionAndArgsLeft.first.getArgumentAndQuantity();
// TODO: Use class instead of pair to prevent such a stupid checks
assert argumentAndQuantity != null: "Option has arguments left but no argument info";
final Argument argumentInfo = argumentAndQuantity.getSecond();
processArgument(o, argumentInfo);
myOptionArguments.add(o);
}
else if (myCurrentOptionAndArgsLeft.second == 0) {
myCurrentOptionAndArgsLeft = null;
myExcessArguments.add(o);
}
}
private void processArgument(@NotNull final CommandLineArgument o, final Argument argumentInfo) {
final List<String> availableValues = argumentInfo.getAvailableValues();
if (availableValues != null) {
myPossibleValues.put(o, availableValues);
}
if (!argumentInfo.isValid(o.getText())) {
myBadValues.add(o);
}
}
@Override
public void visitWhiteSpace(final PsiWhiteSpace space) {
super.visitWhiteSpace(space);
// -aSHORT_OPT_ARGUMENT, but -a NEW_POSITION_ARGUMENT, so whitespace makes sense
if (myCurrentOptionAndArgsLeft != null && myCurrentOptionAndArgsLeft.second == 0) {
myCurrentOptionAndArgsLeft = null;
}
}
@Override
public void visitOption(@NotNull final CommandLineOption o) {
super.visitOption(o);
if (myOptions.containsKey(o.getOptionName())) {
// Remove from list of available options
final Option option = myOptions.remove(o.getOptionName());
for (final String optionName : option.getAllNames()) {
myOptions.remove(optionName);
}
final Pair<Integer, Argument> argumentAndQuantity = option.getArgumentAndQuantity();
if (argumentAndQuantity != null) {
myCurrentOptionAndArgsLeft = Pair.create(option, argumentAndQuantity.first);
}
else {
myCurrentOptionAndArgsLeft = new Pair<Option, Integer>(option, 0);
}
}
else {
myBadValues.add(o); //No such option available
}
}
}

View File

@@ -0,0 +1,64 @@
/*
* Copyright 2000-2015 JetBrains s.r.o.
*
* Licensed 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 com.jetbrains.commandInterface.gnuCommandLine.psi.impl;
import com.intellij.psi.PsiElement;
import com.jetbrains.commandInterface.gnuCommandLine.psi.CommandLineOption;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* Class to be used by autogenerated PSI elements to delegate logic
*
* @author Ilya.Kazakevich
*/
final class CommandLinePsiImplUtils {
private CommandLinePsiImplUtils() {
}
/**
* Checks if option is long (--long-option vs -s)
*
* @param o option
* @return true if long
*/
static boolean isLong(@NotNull final CommandLineOption o) {
return o.getLongOptionNameToken() != null;
}
/**
* Returns option name regardless it is long or short.
*
* @param o option
* @return name (if any)
*/
@Nullable
@NonNls
static String getOptionName(@NotNull final CommandLineOption o) {
final PsiElement longNameToken = o.getLongOptionNameToken();
if (longNameToken != null) {
return longNameToken.getText();
}
final PsiElement shortOptionNameToken = o.getShortOptionNameToken();
if (shortOptionNameToken != null) {
return shortOptionNameToken.getText();
}
return null;
}
}

View File

@@ -15,9 +15,10 @@
*/
/**
* Command with arguments and options.
* Each {@link com.jetbrains.python.commandInterface.command.Command} may have one or more positional {@link com.jetbrains.python.commandInterface.command.Argument arguments}
* and several {@link com.jetbrains.python.commandInterface.command.Option options}.
* Gnu command line language PSI implementation.
* Do not use this package directly, consider using interfaces from {@link com.jetbrains.commandInterface.gnuCommandLine.psi} instead.
* <strong>Warning</strong>: most files in this package is autogenerated. Do not touch them directly.
*
* @author Ilya.Kazakevich
*/
package com.jetbrains.python.commandInterface.command;
package com.jetbrains.commandInterface.gnuCommandLine.psi.impl;

View File

@@ -15,8 +15,9 @@
*/
/**
* {@link com.jetbrains.python.commandLineParser.CommandLineParser} based on <a href="https://docs.python.org/2/library/optparse.html">OptParse</a>
* See {@link com.jetbrains.python.commandLineParser.optParse.OptParseCommandLineParser}
* Gnu command line public PSI interfaces: Command, option, argument etc.
*
* <strong>Warning</strong>: most files in this package is autogenerated. Do not touch them directly.
* @author Ilya.Kazakevich
*/
package com.jetbrains.python.commandLineParser.optParse;
package com.jetbrains.commandInterface.gnuCommandLine.psi;

View File

@@ -0,0 +1,55 @@
/*
* Copyright 2000-2015 JetBrains s.r.o.
*
* Licensed 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.
*/
/**
* <h1>Command-line interface</h1>
* <h2>What is the purpose of this package?</h2>
* <p>
* This package is based on ideas of command-line with command, positional arguments, options and their arguments.
* Initial idea is taken from <a href="http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap12.html#tag_12_02">POSIX</a>
* and enhanced by <a href="http://www.gnu.org/software/libc/manual/html_node/Argument-Syntax.html">GNU</a>.
* It also supported by Python in <a href="https://docs.python.org/2/library/optparse.html">optparse</a> package.
* Command-line is something like
* <pre>my_command positional_argument -s --long-option --another-long-option=arg1 arg2 some_other_arg</pre>
* and this package helps you to parse command lines, highlight errors, display a console-like interface and execute commands.
* </p>
* <h2>What this package consists of?</h2>
* <p>
* This package has 3 subpackages:
* <ol>
* <li>{@link com.jetbrains.commandInterface.command} contains classes to describe commands, arguments and options.
* It is up to you where to obtain list of available commands, but you should implement {@link com.jetbrains.commandInterface.command.Command}
* first, and create list of it with arguments and options. See package info for more details</li>
* <li>{@link com.jetbrains.commandInterface.gnuCommandLine} is language based on
* <a href="http://www.gnu.org/software/libc/manual/html_node/Argument-Syntax.html">GNU</a> command line representation.
* It has PSI, so it parses command lines into tree of PsiElements. But this package <strong>is not only for parsing</strong>:
* If you provide list of
* {@link com.jetbrains.commandInterface.command.Command commands} to {@link com.jetbrains.commandInterface.gnuCommandLine.psi.CommandLineFile}
* (see {@link com.jetbrains.commandInterface.gnuCommandLine.psi.CommandLineFile#setCommands(java.util.List)}), it will inject references
* (to provide autocompletion) and activate inspection to validate options and arguments. </li>
* <li>{@link com.jetbrains.commandInterface.console} displays console-like interface at the bottom of the screen to give user
* ability to wotk with your command-line.</li>
* </ol>
* </p>
* <h2>How to use this package?</h2>
* <ol>
* <li>Implement {@link com.jetbrains.commandInterface.command.Command}</li>
* <li>Create list of {@link com.jetbrains.commandInterface.command.Command commands}</li>
* <li>Provide it to {@link com.jetbrains.commandInterface.console.CommandLineConsole#createConsole(com.intellij.openapi.module.Module, java.lang.String, java.util.List)}</li>
* </ol>
* @author Ilya.Kazakevich
*/
package com.jetbrains.commandInterface;

View File

@@ -864,16 +864,9 @@ remote.interpreter.configure.temp.files.path.label=PyCharm helpers path:
custom.type.mimic.name=Dynamic class based on {0}
# Values for command argument value validation
commandLine.validation.badCommand=Unknown command
commandLine.validation.argMissing=Required argument value is missing
commandLine.validation.optArgMissing=Option argument value is missing
commandLine.validation.argBadValue=Argument can't have this value
commandLine.validation.excessArg=Excess argument value
commandLine.validation.badOption=Bad option or option already set
commandLine.validation.noArgAllowed=No argument allowed for this option
# And for labels
commandLine.subText.key.complete=Use {0} to complete selected variant
commandLine.subText.key.suggestions=Use {0} to view available values
commandLine.subText.key.executeUnknown=Click {0} to execute
commandLine.subText.key.executeCommand=Click {0} to execute "{1}"
# CommandLine
commandLine.inspection.name=Command-line inspection
commandLine.inspection.badCommand=Bad or unknown command. make sure this command really exists.
commandLine.inspection.badOption=Bad or unknown option. make sure this option really exists.
commandLine.inspection.badArgument=Argument can't have this value. use autocompletion to check list of possible values.
commandLine.inspection.excessArgument=Excess argument or argument is not possible here

View File

@@ -1,158 +0,0 @@
/*
* Copyright 2000-2015 JetBrains s.r.o.
*
* Licensed 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 com.jetbrains.python;
import com.intellij.util.Range;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
/**
* Word with range position. To be used to mark some part of text or split it.
*
* @author Ilya.Kazakevich
*/
public final class WordWithPosition extends Range<Integer> {
@NotNull
private final String myWord;
/**
* Creates word with beam (it has start, but it is infinite)
*
* @param word word
* @param from start
*/
public WordWithPosition(@NotNull final String word, final int from) {
this(word, from, Integer.MAX_VALUE);
}
/**
* @param word text
* @param from from where
* @param to to where
*/
public WordWithPosition(@NotNull final String word, final int from, final int to) {
super(from, to);
myWord = word;
}
/**
* Creates instance with certain text and range (start/end)
* @param word text
* @param range range
*/
public WordWithPosition(@NotNull final String word, @NotNull final Range<Integer> range) {
this(word, range.getFrom(), range.getTo());
}
@NotNull
public String getText() {
return myWord;
}
/**
* Returns texts only from collection of instances of this class
*
* @param words collection of instances of this class
* @return collection of strings (text)
*/
@NotNull
public static List<String> fetchText(@NotNull final Collection<WordWithPosition> words) {
final List<String> result = new ArrayList<String>(words.size());
for (final WordWithPosition word : words) {
result.add(word.myWord);
}
return result;
}
/**
* Creates new instance with all fields copied but text
*
* @param newText new test to add
* @return new instance
*/
@NotNull
public WordWithPosition copyWithDifferentText(@NotNull final String newText) {
return new WordWithPosition(newText, getFrom(), getTo());
}
@Override
public String toString() {
return "WordWithPosition{" +
"myWord='" + myWord + '\'' +
", myFrom=" + getFrom() +
", myTo=" + getTo() +
'}';
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
WordWithPosition position = (WordWithPosition)o;
if (!getFrom().equals(position.getFrom())) return false;
if (!getTo().equals(position.getTo())) return false;
if (!myWord.equals(position.myWord)) return false;
return true;
}
@Override
public int hashCode() {
int result = myWord.hashCode();
result = 31 * result + getFrom();
result = 31 * result + getTo();
return result;
}
/**
* Tool that splits text into parts using one or more whitespace as delimiter.
* Each part contains text and boundaries (from and to)
*
* @param text text to split
* @return parse result
*/
@NotNull
public static List<WordWithPosition> splitText(@NotNull final String text) {
// TODO: Rewrite using regex or scanner?
int position = 0;
int wordStart = -1;
final List<WordWithPosition> parts = new ArrayList<WordWithPosition>();
for (final char c : text.toCharArray()) {
if (Character.isWhitespace(c) && wordStart != -1) {
// Close word
parts.add(new WordWithPosition(text.substring(wordStart, position), wordStart, position));
wordStart = -1;
}
else if (!Character.isWhitespace(c) && wordStart == -1) {
// Start word
wordStart = position;
}
position++;
}
if (wordStart != -1) {
// Adding last word
parts.add(new WordWithPosition(text.substring(wordStart), wordStart, position));
}
return parts;
}
}

View File

@@ -1,50 +0,0 @@
/*
* Copyright 2000-2014 JetBrains s.r.o.
*
* Licensed 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 com.jetbrains.python.commandInterface;
import com.jetbrains.python.vp.Presenter;
import org.jetbrains.annotations.Nullable;
/**
* Presenter for command-line interface to be paired with view.
*
* @author Ilya.Kazakevich
*/
public interface CommandInterfacePresenter extends Presenter {
/**
* Called by view when user types new text or text changed by some other reason
*
*/
void textChanged();
/**
* Called by view when user requests for completion (like tab)
*
* @param valueFromSuggestionList value selected from suggestion list (if any selected)
*/
void completionRequested(@Nullable String valueFromSuggestionList);
/**
* Called by view when user asks for suggestions (like CTRL+Space)
*/
void suggestionRequested();
/**
* Called by view when user wants to execute command (like enter)
*
*/
void executionRequested();
}

View File

@@ -1,38 +0,0 @@
/*
* Copyright 2000-2014 JetBrains s.r.o.
*
* Licensed 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 com.jetbrains.python.commandInterface;
import org.jetbrains.annotations.NotNull;
/**
* Adapter that holds view.
* @author Ilya.Kazakevich
*/
public abstract class CommandInterfacePresenterAdapter implements CommandInterfacePresenter {
@NotNull
protected final CommandInterfaceView myView;
protected CommandInterfacePresenterAdapter(@NotNull final CommandInterfaceView view) {
myView = view;
}
@Override
public void launch() {
myView.show();
}
}

View File

@@ -1,121 +0,0 @@
/*
* Copyright 2000-2014 JetBrains s.r.o.
*
* Licensed 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 com.jetbrains.python.commandInterface;
import com.intellij.util.Range;
import com.jetbrains.python.WordWithPosition;
import com.jetbrains.python.suggestionList.SuggestionsBuilder;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Collection;
import java.util.List;
/**
* View for command-line interface to be paired with view.
*
* @author Ilya.Kazakevich
*/
public interface CommandInterfaceView {
/**
* Special place in command line that represents "after the last character" place.
* To be used in methods like {@link #setInfoAndErrors(java.util.Collection, java.util.Collection)} to mark it.
*/
@NotNull
Range<Integer> AFTER_LAST_CHARACTER_RANGE = new Range<Integer>(Integer.MAX_VALUE, Integer.MAX_VALUE);
/**
* Launches view
*/
void show();
/**
* Suggests user some elements (for completion reason)
*
* @param suggestions what to suggest (see {@link com.jetbrains.python.suggestionList.SuggestionsBuilder})
* @param absolute display list in its main position, or directly near the caret
* @param toSelect word to select in list (if any)
*/
void displaySuggestions(@NotNull SuggestionsBuilder suggestions, boolean absolute, @Nullable String toSelect);
/**
* Each time caret meets certain place, view should check whether some subtext has to be displayed.
* There are 2 types of subtext to be displayed:
* <ol>
* <li>Suggestion Text: View says something like "click FOO to see list of suggestions". Only presenter knows exact places where
* suggestions are available, so it should provide them</li>
* <li>Default text: In all other cases view displays default text (if available).</li>
* </ol>
* <p/>
* Presenter provides view list of special places
*
* @param defaultSubText default text
* @param suggestionAvailablePlaces list of places where suggestions are available in format [from, to].
*/
void configureSubTexts(@Nullable String defaultSubText,
@NotNull List<Range<Integer>> suggestionAvailablePlaces);
/**
* Hide suggestion list
*/
void removeSuggestions();
/**
* @return text, entered by user
*/
@NotNull
String getText();
/**
* When caret meets certain place, view may display some info and some errors.
* Errors, how ever, may always be emphasized (with something like red line).
* This function configures view with pack of ranges and texts to display.
* Special place {@link #AFTER_LAST_CHARACTER_RANGE} may also be used.
* Each place is described as start-end position (in chars) where it should be enabled.
* Each call removes previously enabled information.
*
* @param errors places to be marked as errors with error text.
* @param infoBalloons places to display info balloon
* @see #AFTER_LAST_CHARACTER_RANGE
*/
void setInfoAndErrors(@NotNull final Collection<WordWithPosition> infoBalloons, @NotNull final Collection<WordWithPosition> errors);
/**
* Inserts text after caret moving next chars to the right
*
* @param text text to insert
*/
void insertTextAfterCaret(@NotNull String text);
/**
* Replaces current text with another one.
*
* @param from from
* @param to to
* @param newText text to replace
*/
void replaceText(final int from, final int to, @NotNull String newText);
/**
* @return position of caret (in chars)
*/
int getCaretPosition();
}

View File

@@ -1,78 +0,0 @@
/*
* Copyright 2000-2014 JetBrains s.r.o.
*
* Licensed 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 com.jetbrains.python.commandInterface.command;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
// TODO: Support regex validation as well
/**
* Command <strong>positional, not named</strong> argument (not option!).
* This class represents command argument, not its value.
*
*
* @author Ilya.Kazakevich
*/
public final class Argument {
/**
* Argument help user-readable text
*/
@NotNull
private final String myHelpText;
/**
* List of values argument may have. Null if any value is possible.
*/
@Nullable
private final List<String> myAvailableValues;
/**
* @param helpText Argument help user-readable text
*/
public Argument(@NotNull final String helpText) {
this(helpText, null);
}
/**
* @param helpText Argument help user-readable text
* @param availableValues List of values argument may have. Null if any value is possible.
*/
public Argument(@NotNull final String helpText, @Nullable final List<String> availableValues) {
myHelpText = helpText;
myAvailableValues = (availableValues == null ? null : new ArrayList<String>(availableValues));
}
/**
* @return Argument help user-readable text
*/
@NotNull
public String getHelpText() {
return myHelpText;
}
/**
* @return List of values argument may have. Null if any value is possible.
*/
@Nullable
public List<String> getAvailableValues() {
return (myAvailableValues == null ? null : Collections.unmodifiableList(myAvailableValues));
}
}

View File

@@ -1,42 +0,0 @@
/*
* Copyright 2000-2015 JetBrains s.r.o.
*
* Licensed 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 com.jetbrains.python.commandInterface.command;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.List;
/**
* Information about option argument
*
* @author Ilya.Kazakevich
*/
public interface OptionArgumentInfo {
/**
* Validates argument value
*
* @param value value to validate
* @return true if valid
*/
boolean isValid(@NotNull String value);
/**
* @return list of available values (if argument is based on list of choices), or null if any value is accepted (but should be validated)
*/
@Nullable
List<String> getAvailableValues();
}

View File

@@ -1,51 +0,0 @@
/*
* Copyright 2000-2015 JetBrains s.r.o.
*
* Licensed 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 com.jetbrains.python.commandInterface.command;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
/**
* For options, whose argument is based on list of choices
* @author Ilya.Kazakevich
*/
public final class OptionChoiceBasedArgumentInfo implements OptionArgumentInfo {
@NotNull
private final List<String> myChoices = new ArrayList<String>();
/**
* @param choices available choices
*/
public OptionChoiceBasedArgumentInfo(@NotNull final Collection<String> choices) {
myChoices.addAll(choices);
}
@Override
public boolean isValid(@NotNull final String value) {
return myChoices.contains(value);
}
@Nullable
@Override
public List<String> getAvailableValues() {
return Collections.unmodifiableList(myChoices);
}
}

View File

@@ -1,61 +0,0 @@
/*
* Copyright 2000-2015 JetBrains s.r.o.
*
* Licensed 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 com.jetbrains.python.commandInterface.command;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.List;
/**
* For options, whose argument is based on certain type.
*
* @author Ilya.Kazakevich
* @see OptionArgumentType
*/
public final class OptionTypedArgumentInfo implements OptionArgumentInfo {
@NotNull
private final OptionArgumentType myType;
/**
* @param type type argument(s) of this option may have
*/
public OptionTypedArgumentInfo(@NotNull final OptionArgumentType type) {
myType = type;
}
@Override
public boolean isValid(@NotNull final String value) {
// We only check integer for now
if (myType == OptionArgumentType.INTEGER) {
try {
// We just getCommandLineInfo it to get exception
//noinspection ResultOfMethodCallIgnored
Integer.parseInt(value);
}
catch (final NumberFormatException ignored) {
return false;
}
}
return true;
}
@Nullable
@Override
public List<String> getAvailableValues() {
return null;
}
}

View File

@@ -1,204 +0,0 @@
/*
* Copyright 2000-2015 JetBrains s.r.o.
*
* Licensed 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 com.jetbrains.python.commandInterface.commandBasedRangeDriver;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.util.Range;
import com.jetbrains.python.PyBundle;
import com.jetbrains.python.commandInterface.command.Argument;
import com.jetbrains.python.commandInterface.command.Command;
import com.jetbrains.python.commandInterface.command.Option;
import com.jetbrains.python.commandInterface.command.OptionArgumentInfo;
import com.jetbrains.python.commandInterface.rangeBasedPresenter.Executor;
import com.jetbrains.python.commandInterface.rangeBasedPresenter.RangeInfo;
import com.jetbrains.python.commandInterface.rangeBasedPresenter.RangeInfoDriver;
import com.jetbrains.python.commandInterface.rangeBasedPresenter.SuggestionInfo;
import com.jetbrains.python.commandLineParser.CommandLine;
import com.jetbrains.python.commandLineParser.CommandLineParser;
import com.jetbrains.python.commandLineParser.CommandLinePart;
import com.jetbrains.python.commandLineParser.MalformedCommandLineException;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.*;
/**
* Driver that returns pack of range infos for certain command line
*
* @author Ilya.Kazakevich
*/
public final class CommandBasedRangeInfoDriver implements RangeInfoDriver {
@NotNull
private final Map<String, Command> myCommands = new TreeMap<String, Command>(); // To sort commands by name
@NotNull
private final Module myModule;
@NotNull
private final CommandLineParser myCommandLineParser;
/**
*
* @param module module (to be used in execution)
* @param commandLineParser parser to parse command lines
* @param commands available commands
*/
public CommandBasedRangeInfoDriver(
@NotNull final Module module,
@NotNull final CommandLineParser commandLineParser,
@NotNull final Collection<? extends Command> commands) {
for (final Command command : commands) {
myCommands.put(command.getName(), command);
}
myModule = module;
myCommandLineParser = commandLineParser;
}
@NotNull
@Override
public Pair<Executor, List<RangeInfo>> getCommandLineInfo(@NotNull final String commandLineText) {
// TODO: Copty/paste with exceptiojn
if (StringUtil.isEmpty(commandLineText)) {
final RangeInfo info = new RangeInfo(null, "", new SuggestionInfo(
true, true, myCommands.keySet()
), RangeInfo.TERMINATION_RANGE, false
);
return new Pair<Executor, List<RangeInfo>>(null, Collections.singletonList(info));
}
final CommandLine commandLine;
try {
commandLine = myCommandLineParser.parse(commandLineText);
}
catch (final MalformedCommandLineException ignored) {
return new Pair<Executor, List<RangeInfo>>(null, Collections.singletonList(
new RangeInfo(null, PyBundle.message("commandLine.validation.badCommand"), new SuggestionInfo(
false, true, myCommands.keySet()
), new Range<Integer>(0, commandLineText.length()), false)
));
}
final Command command = getExistingCommand(commandLine);
final List<CommandLinePart> commandLineParts = commandLine.getParts();
if (command == null) {
// Bad command inserted
return createBadCommandInfo(commandLine);
}
final UnusedOptionsCollector unusedOptionsCollector = UnusedOptionsCollector.create(command, commandLineParts);
final RangeInfoCollector rangeInfoCollector = RangeInfoCollector.create(command, commandLineParts, unusedOptionsCollector);
final SuggestionInfo commandSuggestions = new SuggestionInfo(false, true, myCommands.keySet());
// Add command as first range info
final List<RangeInfo> rangeInfos =
new ArrayList<RangeInfo>(Collections.singletonList(new RangeInfo(null, null, commandSuggestions, commandLine.getCommand(), false)));
// Then add collected infos
rangeInfos.addAll(rangeInfoCollector.getRangeInfos());
/////// What about "after the caret" ?
final Pair<Boolean, Argument> unsatisfiedArgument = rangeInfoCollector.getUnsatisfiedPositionalArgument();
final OptionArgumentInfo unsatisfiedOptionArgument = rangeInfoCollector.getUnsatisfiedOptionArgument();
// TODO: Move to collector after test
if (unsatisfiedOptionArgument != null) {
final List<String> availableValues = unsatisfiedOptionArgument.getAvailableValues();
final SuggestionInfo suggestionInfo;
if (availableValues != null) {
suggestionInfo =
new SuggestionInfo(false, false, availableValues);
}
else {
suggestionInfo = null;
}
rangeInfos
.add(new RangeInfo(null, PyBundle.message("commandLine.validation.optArgMissing"), suggestionInfo, RangeInfo.TERMINATION_RANGE,
false));
}
else if (unsatisfiedArgument != null) {
final boolean required = unsatisfiedArgument.first;
final Argument argument = unsatisfiedArgument.second;
// Only add error if required
final String error = required ? PyBundle.message("commandLine.validation.argMissing") : null;
final List<String> availableValues = unusedOptionsCollector.addUnusedOptions(argument.getAvailableValues());
final RangeInfo lastArgInfo =
new RangeInfo(argument.getHelpText(), error,
(availableValues != null ? new SuggestionInfo(false, false, availableValues) : null), RangeInfo.TERMINATION_RANGE,
false);
rangeInfos.add(lastArgInfo);
}
else {
// Looks like all arguments are satisfied. Adding empty chunk to prevent completion etc.
// This is a hack, but with out of it last range info will always be used, even 200 chars after last place
rangeInfos.add(new RangeInfo(null, null, rangeInfoCollector.getCurrentSuggestions(false, null), RangeInfo.TERMINATION_RANGE, false));
}
assert rangeInfos.size() >= commandLineParts.size() : "Contract broken: not enough chunks";
return Pair.<Executor, List<RangeInfo>>create(new CommandExecutor(command, myModule, commandLine.getPartsAsText()), rangeInfos);
}
@Nullable
private Command getExistingCommand(@NotNull final CommandLine commandLine) {
return myCommands.get(commandLine.getCommand().getText());
}
/**
* Creates range info info signaling command is bad or junk
*
*
* @param commandLine command line passed by user
* @return info to return
*/
@NotNull
private Pair<Executor, List<RangeInfo>> createBadCommandInfo(final CommandLine commandLine) {
final List<RangeInfo> result = new ArrayList<RangeInfo>();
// We know that first chunk command line, but we can't say anything about outher chunks except they are bad.
// How ever, we must say something
result
.add(new RangeInfo(null, PyBundle.message("commandLine.validation.badCommand"), new SuggestionInfo(true, true, myCommands.keySet()),
commandLine.getCommand(), false));
// Command is unknown, all other parts are junk
for (final CommandLinePart part : commandLine.getParts()) {
result.add(new RangeInfo(null, "", false, part.getWord()));
}
return Pair.create(null, result);
}
/**
* Finds option by its name
* @param command current command
* @param optionName option name
* @return option or null if no option found
*/
@Nullable
static Option findOptionByName(@NotNull final Command command, @NotNull final String optionName) {
for (final Option option : command.getOptions()) {
for (final String name : option.getAllNames()) {
if (name.equals(optionName)) {
return option;
}
}
}
return null;
}
}

View File

@@ -1,62 +0,0 @@
/*
* Copyright 2000-2015 JetBrains s.r.o.
*
* Licensed 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 com.jetbrains.python.commandInterface.commandBasedRangeDriver;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.util.ArrayUtil;
import com.jetbrains.python.commandInterface.command.Command;
import com.jetbrains.python.commandInterface.rangeBasedPresenter.Executor;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Arrays;
/**
* Executes commands according to {@link Executor} contract
*
* @author Ilya.Kazakevich
*/
final class CommandExecutor implements Executor {
@NotNull
private final Command myCommand;
@NotNull
private final Module myModule;
@NotNull
private final String[] myArguments;
/**
* @param command command to execute
* @param module module to execute against
* @param argumentsLine all command arguments as testline
*/
CommandExecutor(@NotNull final Command command, @NotNull final Module module, @NotNull final String argumentsLine) {
myCommand = command;
myModule = module;
myArguments = (StringUtil.isEmpty(argumentsLine) ? ArrayUtil.EMPTY_STRING_ARRAY : argumentsLine.split(" "));
}
@Nullable
@Override
public String getExecutionDescription() {
return myCommand.getHelp();
}
@Override
public void execute() {
myCommand.execute(myModule, Arrays.asList(myArguments));
}
}

View File

@@ -1,250 +0,0 @@
/*
* Copyright 2000-2015 JetBrains s.r.o.
*
* Licensed 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 com.jetbrains.python.commandInterface.commandBasedRangeDriver;
import com.google.common.collect.Sets;
import com.intellij.openapi.util.Pair;
import com.intellij.util.containers.HashSet;
import com.jetbrains.python.PyBundle;
import com.jetbrains.python.WordWithPosition;
import com.jetbrains.python.commandInterface.command.Argument;
import com.jetbrains.python.commandInterface.command.Command;
import com.jetbrains.python.commandInterface.command.Option;
import com.jetbrains.python.commandInterface.command.OptionArgumentInfo;
import com.jetbrains.python.commandInterface.rangeBasedPresenter.RangeInfo;
import com.jetbrains.python.commandInterface.rangeBasedPresenter.SuggestionInfo;
import com.jetbrains.python.commandLineParser.CommandLineArgument;
import com.jetbrains.python.commandLineParser.CommandLineOption;
import com.jetbrains.python.commandLineParser.CommandLinePart;
import com.jetbrains.python.commandLineParser.CommandLinePartVisitor;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.*;
/**
* Visitor that collections ranges info by visiting options and arguments
*
* @author Ilya.Kazakevich
*/
final class RangeInfoCollector implements CommandLinePartVisitor {
/**
* Options already met in command line. It has nothing to do with {@link UnusedOptionsCollector}!
*/
@NotNull
private final Set<String> myUsedOptions = new HashSet<String>();
@NotNull
private final List<RangeInfo> myRangeInfos = new ArrayList<RangeInfo>();
@NotNull
private final Command myCommand;
private int myNumOfProcessedPositionalArguments = 0;
private boolean mySkipNextArgument;
private Pair<Integer, OptionArgumentInfo> myExpectedOptionArgument;
@NotNull
private final UnusedOptionsCollector myUnusedOptionsCollector;
/**
* @param command command
* @param unusedOptionsCollector instance of unused options collector
*/
private RangeInfoCollector(@NotNull final Command command,
@NotNull final UnusedOptionsCollector unusedOptionsCollector) {
myCommand = command;
myUnusedOptionsCollector = unusedOptionsCollector;
}
@NotNull
static RangeInfoCollector create(@NotNull final Command command,
@NotNull final Iterable<CommandLinePart> commandLineParts,
@NotNull final UnusedOptionsCollector unusedOptionsCollector) {
final RangeInfoCollector infoCollector = new RangeInfoCollector(command, unusedOptionsCollector);
for (final CommandLinePart part : commandLineParts) {
part.accept(infoCollector);
}
return infoCollector;
}
@Override
public void visitOption(@NotNull final CommandLineOption option) {
if (processOptionArgument(option.getWord())) {
return;
}
final Option commandOption = CommandBasedRangeInfoDriver.findOptionByName(myCommand, option.getOptionName());
final WordWithPosition attachedArgument = option.getAttachedArgument();
if (commandOption == null) {
// There is no such option
// To Mark attached arg so we skip it in {@link #visitArgument}
if (attachedArgument != null) {
mySkipNextArgument = true;
}
// No such option
myRangeInfos.add(new RangeInfo(null, PyBundle.message("commandLine.validation.badOption"), getCurrentSuggestions(false, null),
option.getWord(), false));
return;
}
final Pair<Integer, OptionArgumentInfo> argumentAndQuantity = commandOption.getArgumentAndQuantity();
// If option already used, then mark it
final String optionAlreadyUsed = Sets.intersection(Sets.newHashSet(commandOption.getAllNames()), myUsedOptions).isEmpty()
? null
: PyBundle.message("commandLine.validation.badOption");
myRangeInfos
.add(new RangeInfo(commandOption.getHelp(), optionAlreadyUsed, getCurrentSuggestions(false, null), option.getWord(),
argumentAndQuantity != null));
// remove from existing options
myUsedOptions.addAll(commandOption.getAllNames());
if (argumentAndQuantity == null) {
if (attachedArgument != null) {
myRangeInfos.add(new RangeInfo(null, PyBundle.message("commandLine.validation.noArgAllowed"), false, attachedArgument));
mySkipNextArgument = true;
}
}
else {
// Some option args required
myExpectedOptionArgument = argumentAndQuantity;
}
}
/**
* Process option argument (not to be confused with positional!)
*
* @param currentPart part with argument.
* @return false if there should not be any option argument so this method did nothing. True if there should be and it is processed.
*/
private boolean processOptionArgument(@NotNull final WordWithPosition currentPart) {
if (myExpectedOptionArgument == null) {
return false;
}
final OptionArgumentInfo argumentInfo = myExpectedOptionArgument.second;
int argsLeft = myExpectedOptionArgument.first;
final boolean valid = argumentInfo.isValid(currentPart.getText());
final List<String> availableValues = argumentInfo.getAvailableValues();
final SuggestionInfo suggestions;
if (availableValues != null) {
suggestions = new SuggestionInfo(false, false, availableValues);
}
else {
suggestions = null;
}
myRangeInfos.add(new RangeInfo(null, (valid ? null : PyBundle.message("commandLine.validation.argBadValue")),
suggestions, currentPart, false));
if (--argsLeft == 0) {
myExpectedOptionArgument = null;
}
else {
myExpectedOptionArgument = Pair.create(argsLeft, argumentInfo);
}
return true;
}
/**
* Returns suggestions available for current position.
*
* @param showAutomatically make suggestions displayed automatically
* @param argumentPair argument to use. If null, current argument according to internal counter would be used.
* @return suggestiom info or null if no suggestion available
*/
@Nullable
SuggestionInfo getCurrentSuggestions(final boolean showAutomatically, @Nullable Pair<Boolean, Argument> argumentPair) {
if (argumentPair == null) {
argumentPair = myCommand.getArgumentsInfo().getArgument(myNumOfProcessedPositionalArguments);
}
final List<String> suggestions = argumentPair != null ? myUnusedOptionsCollector.addUnusedOptions(
argumentPair.second.getAvailableValues()) : myUnusedOptionsCollector.addUnusedOptions(
null);
if (suggestions == null) {
return null;
}
return new SuggestionInfo(showAutomatically, false, suggestions);
}
@Override
public void visitArgument(@NotNull final CommandLineArgument argument) {
if (mySkipNextArgument) {
// Skip argument, clear flag and do nothing
mySkipNextArgument = false;
return;
}
if (processOptionArgument(argument.getWord())) {
return;
}
final Pair<Boolean, Argument> argumentPair = myCommand.getArgumentsInfo().getArgument(myNumOfProcessedPositionalArguments++);
if (argumentPair == null) {
//Exceed!
myRangeInfos.add(new RangeInfo(null, PyBundle.message("commandLine.validation.excessArg"), false, argument.getWord()));
return;
}
final Argument commandArgument = argumentPair.second;
final List<String> argumentAvailableValues = commandArgument.getAvailableValues();
final String argumentValue = argument.getWord().getText();
String errorMessage = null;
if (argumentAvailableValues != null && !argumentAvailableValues.contains(argumentValue)) {
// Bad value
errorMessage = PyBundle.message("commandLine.validation.argBadValue");
}
// Argument seems to be ok. We suggest values automatically only if value is bad
myRangeInfos.add(new RangeInfo(commandArgument.getHelpText(), errorMessage,
getCurrentSuggestions(errorMessage != null, argumentPair),
argument.getWord(), false));
}
/**
* @return calculates range infos for all command line parts (not the command itself!)
*/
@NotNull
Collection<RangeInfo> getRangeInfos() {
return Collections.unmodifiableList(myRangeInfos);
}
/**
* @return Unsatisfied (currently expected) positional argument ([required, arg]) or null if no arg expected
*/
@Nullable
Pair<Boolean, Argument> getUnsatisfiedPositionalArgument() {
return myCommand.getArgumentsInfo().getArgument(myNumOfProcessedPositionalArguments);
}
/**
* @return Unsatisfied (currently expected) option argument or null if no option argument expected
*/
public OptionArgumentInfo getUnsatisfiedOptionArgument() {
if (myExpectedOptionArgument != null) {
return myExpectedOptionArgument.second;
}
else {
return null;
}
}
}

View File

@@ -1,97 +0,0 @@
/*
* Copyright 2000-2015 JetBrains s.r.o.
*
* Licensed 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 com.jetbrains.python.commandInterface.commandBasedRangeDriver;
import com.intellij.util.containers.HashSet;
import com.jetbrains.python.commandInterface.command.Command;
import com.jetbrains.python.commandInterface.command.Option;
import com.jetbrains.python.commandLineParser.CommandLineArgument;
import com.jetbrains.python.commandLineParser.CommandLineOption;
import com.jetbrains.python.commandLineParser.CommandLinePart;
import com.jetbrains.python.commandLineParser.CommandLinePartVisitor;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Set;
/**
* Collections all used and unused options to report unused options then.
* It may be used to add unused options to suggestion list {@link #addUnusedOptions(Collection)}
*
* @author Ilya.Kazakevich
*/
final class UnusedOptionsCollector implements CommandLinePartVisitor {
@NotNull
private final Set<String> myUnusedOptions = new HashSet<String>();
@NotNull
private final Command myCommand;
/**
* @param command command to be used
* @param commandLineParts command line parts
* @return instance
*/
static UnusedOptionsCollector create(@NotNull final Command command, @NotNull final Iterable<CommandLinePart> commandLineParts) {
final UnusedOptionsCollector collector = new UnusedOptionsCollector(command);
for (final CommandLinePart part : commandLineParts) {
part.accept(collector);
}
return collector;
}
private UnusedOptionsCollector(@NotNull final Command command) {
for (final Option option : command.getOptions()) {
myUnusedOptions.addAll(option.getAllNames());
}
myCommand = command;
}
@Override
public void visitOption(@NotNull final CommandLineOption option) {
final Option commandOption = CommandBasedRangeInfoDriver.findOptionByName(myCommand, option.getOptionName());
if (commandOption != null) {
myUnusedOptions.removeAll(commandOption.getAllNames());
}
}
/**
* Merges list of unused options and other values provided as argument.
*
* @param mainValues values to merge options with. May be null.
* @return null of no options and no values provided or merged list of values and options. See method usages for more info
*/
@Nullable
List<String> addUnusedOptions(@Nullable final Collection<String> mainValues) {
if (mainValues == null && myUnusedOptions.isEmpty()) {
return null;
}
final List<String> result = new ArrayList<String>(myUnusedOptions);
if (mainValues != null) {
result.addAll(mainValues);
}
return (result.isEmpty() ? null : result);
}
@Override
public void visitArgument(@NotNull final CommandLineArgument argument) {
}
}

View File

@@ -1,29 +0,0 @@
/*
* Copyright 2000-2015 JetBrains s.r.o.
*
* Licensed 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.
*/
/**
* {@link com.jetbrains.python.commandInterface.rangeBasedPresenter.RangeInfoDriver} implementation based on idea of
* {@link com.jetbrains.python.commandInterface.command command, option and argument}.
*
* See {@link com.jetbrains.python.commandInterface.commandBasedRangeDriver.CommandBasedRangeInfoDriver} as entry point.
* It parses command line using {@link com.jetbrains.python.commandLineParser.CommandLineParser} and finds matching command and arguments
* provided by user
*
* @see com.jetbrains.python.commandInterface.command
* @see com.jetbrains.python.commandInterface.command.Command
* @author Ilya.Kazakevich
*/
package com.jetbrains.python.commandInterface.commandBasedRangeDriver;

View File

@@ -1,46 +0,0 @@
/*
* Copyright 2000-2014 JetBrains s.r.o.
*
* Licensed 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.
*/
/**
* View/Presenter pair that implements so-called "command line interface".
* It has several abilities, including (but not limited):
* <ol>
* <li>Suggestion box</li>
* <li>Error emphasising</li>
* <li>Popups</li>
* <li>AutoCompletion</li>
* </ol>
* <p>
* System consists of {@link com.jetbrains.python.commandInterface.CommandInterfacePresenter}
* and appropriate {@link com.jetbrains.python.commandInterface.CommandInterfaceView}.
* See {@link com.jetbrains.python.vp} for more info.
* </p>
*
* <p>
* There is also swing-based view implementation in {@link com.jetbrains.python.commandInterface.swingView}
* and presenter implementation based on idea of commands with arguments. See {@link com.jetbrains.python.commandInterface.rangeBasedPresenter}
* </p>
*
* <p>
* Presenter and View talk to each other only in term of text and chars. Presenter knows nothing about pixels and should never
* assume view have certain pixel size.
* </p>
*
*
*
* @author Ilya.Kazakevich
*/
package com.jetbrains.python.commandInterface;

View File

@@ -1,246 +0,0 @@
/*
* Copyright 2000-2015 JetBrains s.r.o.
*
* Licensed 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 com.jetbrains.python.commandInterface.rangeBasedPresenter;
import com.intellij.openapi.util.Pair;
import com.intellij.util.Range;
import com.jetbrains.python.WordWithPosition;
import com.jetbrains.python.commandInterface.CommandInterfacePresenterAdapter;
import com.jetbrains.python.commandInterface.CommandInterfaceView;
import com.jetbrains.python.suggestionList.SuggestionsBuilder;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.*;
/**
* Presenter that uses {@link RangeInfoDriver} to obtain {@link RangeInfo info}.
* Such info tells presenter whether this text has info, error, suggestions and so on.
* If caret situated far from text, then next neariest range should be found (see {@link #findNearestRangeInfo()}.
*
* @author Ilya.Kazakevich
* @see RangeInfo
*/
public final class RangeBasedPresenter extends CommandInterfacePresenterAdapter {
@NotNull
private final RangeInfoDriver myRangeInfoDriver;
@Nullable
private Executor myExecutor;
private final SortedSet<RangeInfo> myRangeInfos = new TreeSet<RangeInfo>();
public RangeBasedPresenter(@NotNull final CommandInterfaceView view,
@NotNull final RangeInfoDriver rangeInfoDriver) {
super(view);
myRangeInfoDriver = rangeInfoDriver;
}
@Override
public void launch() {
super.launch();
reparseText(true);
}
@Override
public void textChanged() {
reparseText(false);
}
private void reparseText(final boolean skipSuggestions) {
final Pair<Executor, List<RangeInfo>> rangeInfoStructure = myRangeInfoDriver.getCommandLineInfo(myView.getText());
myExecutor = rangeInfoStructure.first;
final List<RangeInfo> rangeInfos = rangeInfoStructure.second;
assert !rangeInfos.isEmpty() : "At least one chunk info should exist";
myRangeInfos.clear();
myRangeInfos.addAll(rangeInfos);
// configure Errors And Balloons
final Collection<WordWithPosition> infoBalloons = new ArrayList<WordWithPosition>();
final Collection<WordWithPosition> errorBalloons = new ArrayList<WordWithPosition>();
for (final RangeInfo rangeInfo : rangeInfos) {
final String error = rangeInfo.getError();
if (error != null) {
errorBalloons.add(new WordWithPosition(error, rangeInfo));
}
final String info = rangeInfo.getInfoBalloon();
if (info != null) {
infoBalloons.add(new WordWithPosition(info, rangeInfo));
}
}
myView.setInfoAndErrors(infoBalloons, errorBalloons);
if (!skipSuggestions) {
configureSuggestion(false);
}
// Configure subtexts
final List<Range<Integer>> placesWhereSuggestionAvailable = new ArrayList<Range<Integer>>();
for (final RangeInfo rangeInfo : rangeInfos) {
// If some place has suggestions, then add it
if (rangeInfo.getSuggestions() != null) {
placesWhereSuggestionAvailable.add(rangeInfo);
}
}
final String statusText = (myExecutor != null ? myExecutor.getExecutionDescription() : null);
myView.configureSubTexts(statusText, placesWhereSuggestionAvailable);
}
@Override
public void suggestionRequested() {
configureSuggestion(true); // Show or hide
}
/**
* Displays suggestions if needed.
*
* @param requestedExplicitly is suggesions where requested by user explicitly or not
*/
private void configureSuggestion(final boolean requestedExplicitly) {
myView.removeSuggestions();
final RangeInfo rangeInfo = findNearestRangeInfo();
final String text = getTextByRange(rangeInfo);
final SuggestionInfo suggestionInfo = rangeInfo.getSuggestions();
if (suggestionInfo == null || (!suggestionInfo.isShowSuggestionsAutomatically() && !requestedExplicitly)) {
return;
}
final List<String> suggestions = new ArrayList<String>(suggestionInfo.getSuggestions());
if (text != null && !requestedExplicitly) {
filterLeaveOnlyMatching(suggestions, text);
}
// TODO: Place to add history
if (!suggestions.isEmpty()) {
// No need to display empty suggestions
myView
.displaySuggestions(new SuggestionsBuilder(suggestions), suggestionInfo.isShowAbsolute(), text);
}
}
/**
* Filters collection of suggestions leaving only those starts with certain text.
*
* @param suggestions list to filter
* @param textToMatch leave only parts that start with this param
*/
private static void filterLeaveOnlyMatching(@NotNull final Iterable<String> suggestions, @NotNull final String textToMatch) {
// TODO: use guava instead?
final Iterator<String> iterator = suggestions.iterator();
while (iterator.hasNext()) {
if (!iterator.next().startsWith(textToMatch)) {
iterator.remove();
}
}
}
/**
* Searches for the nearest range to use.
*
* @return nearest range info
*/
@NotNull
private RangeInfo findNearestRangeInfo() {
final int caretPosition = myView.getCaretPosition();
for (final RangeInfo range : myRangeInfos) {
if (range.isWithin(caretPosition)) {
return range;
}
if (range.getFrom() > caretPosition) {
return range; // Ranges are sorted, so we are on the next range. Take it, if caret is not within range
}
}
return myRangeInfos.last();
}
@Override
public void completionRequested(@Nullable final String valueFromSuggestionList) {
final RangeInfo rangeInfo = findNearestRangeInfo();
final String text = getTextByRange(rangeInfo);
if (valueFromSuggestionList != null) {
// Just insert it
if (text != null) { // If caret is on the text itself
// TODO: Replace next range, if it has no space before it(--a=12 should be replaced wth arg)
myView.replaceText(rangeInfo.getFrom(), rangeInfo.getTo(), valueFromSuggestionList);
}
else {
myView.insertTextAfterCaret(valueFromSuggestionList);
}
return;
}
//User did not provide text no insert, do our best to find one
final SuggestionInfo suggestionInfo = rangeInfo.getSuggestions();
if (suggestionInfo == null) {
return; // No suggestion available for this chunk
}
final List<String> suggestions = new ArrayList<String>(suggestionInfo.getSuggestions());
if (text != null) {
filterLeaveOnlyMatching(suggestions, text);
}
if (suggestions.size() == 1) {
// Exclusive!
if (text != null) {
// TODO: Replace next range, if it has no space before it(--a=12 should be replaced wth arg)
myView.replaceText(rangeInfo.getFrom(), rangeInfo.getTo(), suggestions.get(0));
}
else {
myView.insertTextAfterCaret(suggestions.get(0));
}
}
}
/**
* Searches for text under the range
* @param rangeInfo range
* @return text or null if range does not contain any text
*/
@Nullable
private String getTextByRange(@NotNull final RangeInfo rangeInfo) {
if (rangeInfo.isTerminationRange()) {
return null;
}
else {
final String viewText = myView.getText();
return viewText.substring(rangeInfo.getFrom(), rangeInfo.getTo());
}
}
@Override
public void executionRequested() {
if (myExecutor == null) {
// TODO: Display error somehow
}
else {
myExecutor.execute();
}
}
}

View File

@@ -1,122 +0,0 @@
/*
* Copyright 2000-2015 JetBrains s.r.o.
*
* Licensed 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 com.jetbrains.python.commandInterface.rangeBasedPresenter;
import com.intellij.util.Range;
import com.jetbrains.python.commandInterface.CommandInterfaceView;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* Information about certain place in text, provided by driver.
*
* @author Ilya.Kazakevich
*/
public final class RangeInfo extends Range<Integer> implements Comparable<RangeInfo> {
/**
* Special "terminator": the last range which is after all other ranges.
*/
public static final Range<Integer> TERMINATION_RANGE = CommandInterfaceView.AFTER_LAST_CHARACTER_RANGE;
@Nullable
private final String myInfoBalloon;
@Nullable
private final String myError;
@Nullable
private final SuggestionInfo mySuggestions;
private final boolean myExclusiveBorders;
/**
* @param infoBalloon info to bind to this range
* @param error error to bind to this range
* @param exclusiveBorders true if range has exclusive borders and right border is not part of it. I.e. 3 is part of 1-3 range with out
* of exclusive borders, but not part of exclusive range
* @param range from and to
*/
public RangeInfo(@Nullable final String infoBalloon,
@Nullable final String error,
final boolean exclusiveBorders,
@NotNull final Range<Integer> range) {
this(infoBalloon, error, null, range, exclusiveBorders);
}
/**
* @param infoBalloon Info balloon to display when caret meets this place (null if display nothing)
* @param error Error balloon to display when caret meets this place and underline text as error (null if no error)
* @param suggestions list of suggestions available in this place (if any)
*/
public RangeInfo(@Nullable final String infoBalloon,
@Nullable final String error,
@Nullable final SuggestionInfo suggestions,
@NotNull final Range<Integer> range,
final boolean exclusiveBorders) {
super(range.getFrom(), range.getTo());
myInfoBalloon = infoBalloon;
myError = error;
mySuggestions = suggestions;
myExclusiveBorders = exclusiveBorders;
}
/**
* @return Info balloon to display when caret meets this place (null if display nothing)
*/
@Nullable
public String getInfoBalloon() {
return myInfoBalloon;
}
/**
* @return Error balloon to display when caret meets this place and underline text as error (null if no error)
*/
@Nullable
public String getError() {
return myError;
}
/**
* @return list of suggestions available in this place (if any)
*/
@Nullable
public SuggestionInfo getSuggestions() {
return mySuggestions;
}
@Override
public int compareTo(RangeInfo o) {
return getFrom().compareTo(o.getFrom());
}
@Override
public boolean isWithin(final Integer object) {
if (!super.isWithin(object)) {
return false;
}
if (myExclusiveBorders) {
return object < getTo();
}
return true;
}
public boolean isTerminationRange() {
// TODO: copy/paste with view
return TERMINATION_RANGE.getFrom().equals(getFrom()) && TERMINATION_RANGE.getTo().equals(getTo());
}
}

View File

@@ -1,39 +0,0 @@
/*
* Copyright 2000-2015 JetBrains s.r.o.
*
* Licensed 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 com.jetbrains.python.commandInterface.rangeBasedPresenter;
import com.intellij.openapi.util.Pair;
import org.jetbrains.annotations.NotNull;
import java.util.List;
/**
* Driver that knows how to getCommandLineInfo pack of chunks into chunk info.
*
* @author Ilya.Kazakevich
*/
public interface RangeInfoDriver {
/**
* Parses command line text into executor and pack of range infos.
* There <strong>always</strong> should be at least one range info and the last one is almost always {@link RangeInfo#TERMINATION_RANGE).
*
* @param commandLineText command line text to parse.
* @return pair or executor (the one that knows how to execute command line) and pack of range infos.
* <strong>Warning! </strong> :Executor could be null if command can't be executed
*/
@NotNull
Pair<Executor, List<RangeInfo>> getCommandLineInfo(@NotNull String commandLineText);
}

View File

@@ -1,79 +0,0 @@
/*
* Copyright 2000-2015 JetBrains s.r.o.
*
* Licensed 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 com.jetbrains.python.commandInterface.rangeBasedPresenter;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
/**
* Information about suggestions
*
* @author Ilya.Kazakevich
*/
public final class SuggestionInfo {
@NotNull
private final List<String> mySuggestions = new ArrayList<String>();
private final boolean myShowSuggestionsAutomatically;
private final boolean myShowAbsolute;
/**
* @param showSuggestionsAutomatically true it suggestions should be displayed even if user did not ask for that
* @param showAbsolute show suggestions at the absolute position (not relative to caret).
* @param suggestions List of suggestions to display
*/
public SuggestionInfo(final boolean showSuggestionsAutomatically,
final boolean showAbsolute,
@NotNull final Collection<String> suggestions) {
myShowSuggestionsAutomatically = showSuggestionsAutomatically;
myShowAbsolute = showAbsolute;
mySuggestions.addAll(suggestions);
}
/**
* @return List of suggestions to display
*/
@NotNull
public List<String> getSuggestions() {
return Collections.unmodifiableList(mySuggestions);
}
/**
* @return true it suggestions should be displayed even if user did not ask for that
*/
public boolean isShowSuggestionsAutomatically() {
return myShowSuggestionsAutomatically;
}
/**
* @return show suggestions at the absolute position (not relative to caret).
*/
public boolean isShowAbsolute() {
return myShowAbsolute;
}
@Override
public String toString() {
return "SuggestionInfo{" +
"mySuggestions=" + mySuggestions +
", myShowSuggestionsAutomatically=" + myShowSuggestionsAutomatically +
", myShowAbsolute=" + myShowAbsolute +
'}';
}
}

View File

@@ -1,32 +0,0 @@
/*
* Copyright 2000-2015 JetBrains s.r.o.
*
* Licensed 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.
*/
/**
* {@link com.jetbrains.python.commandInterface.CommandInterfacePresenter} implementation based on ideas of <strong>range info</strong>
* and {@link com.jetbrains.python.commandInterface.rangeBasedPresenter.RangeInfoDriver}.
* This presenter passes command line to driver, and it returns pack of range information.
* Each record contains everything presenter needs to know about certain range (i.e. error between 2 and 5 chars).
* Special type of range {@link com.jetbrains.python.commandInterface.rangeBasedPresenter.RangeInfo#TERMINATION_RANGE} exists, that
* you may need to check.
* <p/>
* Presenter uses this information to display chunks correctly using view.
* To use this package, {@link com.jetbrains.python.commandInterface.rangeBasedPresenter.RangeInfoDriver} should be implemented.
* <p/>
* See {@link com.jetbrains.python.commandInterface.rangeBasedPresenter.RangeBasedPresenter} as entry point
*
* @author Ilya.Kazakevich
*/
package com.jetbrains.python.commandInterface.rangeBasedPresenter;

View File

@@ -1,58 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<form xmlns="http://www.intellij.com/uidesigner/form/" version="1" bind-to-class="com.jetbrains.python.commandInterface.swingView.CommandInterfaceViewSwingImpl">
<grid id="27dc6" binding="myPanel" layout-manager="GridLayoutManager" row-count="6" column-count="1" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
<margin top="4" left="4" bottom="4" right="2"/>
<constraints>
<xy x="1" y="67" width="404" height="245"/>
</constraints>
<properties/>
<border type="none"/>
<children>
<hspacer id="91552">
<constraints>
<grid row="0" column="0" row-span="1" col-span="1" vsize-policy="1" hsize-policy="6" anchor="0" fill="1" indent="0" use-parent-layout="false">
<minimum-size width="-1" height="5"/>
</grid>
</constraints>
</hspacer>
<component id="4067e" class="javax.swing.JLabel" binding="myLabel">
<constraints>
<grid row="1" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
</constraints>
<properties>
<text value="Label"/>
</properties>
</component>
<grid id="30372" class="com.jetbrains.python.commandInterface.swingView.SmartTextField" binding="myMainTextField" layout-manager="GridLayoutManager" row-count="1" column-count="1" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
<margin top="0" left="0" bottom="0" right="0"/>
<constraints>
<grid row="2" column="0" row-span="1" col-span="1" vsize-policy="3" hsize-policy="3" anchor="0" fill="1" indent="0" use-parent-layout="false"/>
</constraints>
<properties>
<enabled value="true"/>
<font/>
</properties>
<border type="none"/>
<children/>
</grid>
<hspacer id="4b381">
<constraints>
<grid row="3" column="0" row-span="1" col-span="1" vsize-policy="1" hsize-policy="6" anchor="0" fill="1" indent="0" use-parent-layout="false"/>
</constraints>
</hspacer>
<component id="7a794" class="javax.swing.JLabel" binding="mySubLabel">
<constraints>
<grid row="4" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
</constraints>
<properties>
<text value="Label"/>
</properties>
</component>
<hspacer id="c568">
<constraints>
<grid row="5" column="0" row-span="1" col-span="1" vsize-policy="1" hsize-policy="6" anchor="0" fill="1" indent="0" use-parent-layout="false"/>
</constraints>
</hspacer>
</children>
</grid>
</form>

View File

@@ -1,434 +0,0 @@
/*
* Copyright 2000-2014 JetBrains s.r.o.
*
* Licensed 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 com.jetbrains.python.commandInterface.swingView;
import com.google.common.base.Preconditions;
import com.intellij.openapi.fileEditor.ex.FileEditorManagerEx;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.MessageType;
import com.intellij.openapi.ui.popup.*;
import com.intellij.openapi.ui.popup.Balloon.Position;
import com.intellij.ui.JBColor;
import com.intellij.ui.awt.RelativePoint;
import com.intellij.util.Range;
import com.intellij.util.containers.HashSet;
import com.jetbrains.python.PyBundle;
import com.jetbrains.python.WordWithPosition;
import com.jetbrains.python.commandInterface.CommandInterfacePresenter;
import com.jetbrains.python.commandInterface.CommandInterfaceView;
import com.jetbrains.python.suggestionList.SuggestionList;
import com.jetbrains.python.suggestionList.SuggestionsBuilder;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import javax.swing.event.CaretEvent;
import javax.swing.event.CaretListener;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.text.BadLocationException;
import java.awt.*;
import java.awt.event.FocusAdapter;
import java.awt.event.FocusEvent;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Set;
/**
* Command-interface view implementation based on Swing.
* It uses balloons to display errors and infos, drop-down for suggestions and also underlines errors
*
* @author Ilya.Kazakevich
*/
public class CommandInterfaceViewSwingImpl extends JBPopupAdapter implements CommandInterfaceView, DocumentListener, CaretListener {
private static final JBColor ERROR_COLOR = JBColor.RED;
/**
* We need to track balloons, so we have field with callback
*/
@NotNull
private final BalloonManager myBalloonManager = new BalloonManager();
/**
* Pop-up we displayed in
*/
@NotNull
private final JBPopup myMainPopUp;
private JPanel myPanel;
/**
* Upper label
*/
private JLabel myLabel;
/**
* Text field
*/
private SmartTextField myMainTextField;
/**
* Lower (sub) label
*/
private JLabel mySubLabel;
/**
* "Suggestion area". Suggestion status is displayed when caret meets this area
*/
private final List<Range<Integer>> myPlacesWhereSuggestionsAvailable = new ArrayList<Range<Integer>>();
@NotNull
private final CommandInterfacePresenter myPresenter;
/**
* List to display suggestions
*/
@NotNull
private final SuggestionList mySuggestionList;
/**
* Displayed when there is no text
*/
@Nullable
private final String myPlaceHolderText;
/**
* Information balloons that should be displayed when caret meets their boundaries.
*/
@NotNull
private final List<WordWithPosition> myInfoBalloons = new ArrayList<WordWithPosition>();
/**
* Error balloons that should be displayed when caret meets their boundaries.
* Errors are always underlined, but balloons are displayed only if caret meets error
*/
private final List<WordWithPosition> myErrorBalloons = new ArrayList<WordWithPosition>();
/**
* Default subtext to display when caret is out of {@link #myPlacesWhereSuggestionsAvailable "suggestion" area}
*/
@Nullable
private String myDefaultSubText;
/**
* @param presenter our presenter
* @param title view title to display
* @param project project
* @param placeholderText text for placeholder (to be displayed when there is not text)
*/
public CommandInterfaceViewSwingImpl(@NotNull final CommandInterfacePresenter presenter,
@NotNull final String title,
@NotNull final Project project,
@Nullable final String placeholderText) {
myPresenter = presenter;
myLabel.setText(title);
myPlaceHolderText = placeholderText;
myMainPopUp = JBPopupFactory.getInstance().createComponentPopupBuilder(myPanel, myMainTextField)
.setFocusable(true)
.setRequestFocus(true)
.createPopup();
myMainTextField.setRequestFocusEnabled(true);
myMainTextField.setFocusable(true);
final int windowWidth = FileEditorManagerEx.getInstanceEx(project).getComponent().getRootPane().getWidth() - 10; // Little gap
myMainTextField
.setPreferredWidthInPx(windowWidth);
mySuggestionList = new SuggestionList(new MySuggestionListListener());
}
@Override
public void show() {
myMainTextField.getDocument().addDocumentListener(this);
myMainTextField.addCaretListener(this);
myMainPopUp.addListener(this);
if (myPlaceHolderText != null) {
myMainTextField.setWaterMarkPlaceHolderText(myPlaceHolderText);
}
myMainTextField.addFocusListener(new FocusAdapter() {
@Override
public void focusLost(final FocusEvent e) {
super.focusLost(e);
myMainPopUp.cancel();
}
});
myMainTextField.setFocusTraversalKeysEnabled(false);
myMainTextField.addKeyListener(new MyKeyListener()); // Up/down arrows are not handles with actions
// Register all available actions
for (final KeyStrokeInfo strokeInfo : KeyStrokeInfo.values()) {
strokeInfo.register(myPresenter, mySuggestionList, myMainTextField);
}
myMainPopUp.showInFocusCenter();
}
@Override
public void displaySuggestions(@NotNull final SuggestionsBuilder suggestions, final boolean absolute, @Nullable final String toSelect) {
int left = 0;
// Display text right after caret
if (!absolute) {
left = myMainTextField.getTextCaretPositionInPx();
}
mySuggestionList.showSuggestions(suggestions, new RelativePoint(myPanel, new Point(left, myPanel.getHeight())), toSelect);
configureAppropriateStatus();
}
@Override
public void onClosed(final LightweightWindowEvent event) {
super.onClosed(event);
mySuggestionList.close();
}
@Override
public void insertUpdate(final DocumentEvent e) {
processDocumentChange();
}
@Override
public void removeUpdate(final DocumentEvent e) {
processDocumentChange();
}
private void processDocumentChange() {
myMainTextField.hideUnderline();
myPresenter.textChanged();
}
@Override
public void changedUpdate(final DocumentEvent e) {
}
@Override
public void removeSuggestions() {
mySuggestionList.close();
configureAppropriateStatus();
}
@Override
public final void caretUpdate(final CaretEvent e) {
// When caret moved, we need to check if balloon has to be displayed
displayBalloonsIfRequired();
configureAppropriateStatus();
}
private void configureAppropriateStatus() {
if (!mySuggestionList.isClosed()) {
// Tell user she may use TAB to complete
mySubLabel.setText(PyBundle.message("commandLine.subText.key.complete", KeyStrokeInfo.COMPLETION.getText()));
return;
}
// If we are in "suggestion available" place -- tell it
for (final Range<Integer> range : myPlacesWhereSuggestionsAvailable) {
final boolean specialCaseAfterLastChar = isAfterLastCharRange(range) && getCaretPosition() == myMainTextField.getText().length();
if (range.isWithin(getCaretPosition()) || specialCaseAfterLastChar) {
mySubLabel.setText(PyBundle.message("commandLine.subText.key.suggestions", KeyStrokeInfo.SUGGESTION.getText()));
return;
}
}
// We may simply tell user she may execute command
if (myDefaultSubText != null) {
mySubLabel.setText(PyBundle.message("commandLine.subText.key.executeCommand", KeyStrokeInfo.EXECUTION.getText(), myDefaultSubText));
}
else {
mySubLabel.setText(PyBundle.message("commandLine.subText.key.executeUnknown", KeyStrokeInfo.EXECUTION.getText()));
}
}
private void displayBalloonsIfRequired() {
synchronized (myErrorBalloons) {
if (mySuggestionList.isClosed()) { // No need to display error popups when suggestion list is displayed. It intersects.
showBalloons(myErrorBalloons, Position.below, MessageType.ERROR);
}
}
synchronized (myInfoBalloons) {
showBalloons(myInfoBalloons, Position.above, MessageType.INFO);
}
}
/**
* Displays some balloons
*
* @param balloons balloons to display
* @param popUpPosition where ti display them. Only {@link Position#above} and {@link Position#below} are supported!
* @param messageType may be {@link MessageType#ERROR} or {@link MessageType#INFO} for example
*/
private void showBalloons(@NotNull final List<WordWithPosition> balloons,
@NotNull final Position popUpPosition,
@NotNull final MessageType messageType) {
Preconditions.checkArgument(popUpPosition == Position.above || popUpPosition == Position.below, "Only above or below is supported");
for (final WordWithPosition balloon : balloons) {
if (balloon.getText().isEmpty()) {
continue; // Can't be displayed if empty
}
final int caretPosition = myMainTextField.getCaretPosition();
if ((caretPosition >= balloon.getFrom() && caretPosition <= balloon.getTo())) {
final int top = (popUpPosition == Position.above ? 0 : myMainTextField.getHeight() * 2); // Display below a little bit lower
final RelativePoint point = new RelativePoint(myMainTextField, new Point(myMainTextField.getTextCaretPositionInPx(), top));
final Balloon balloonToShow =
JBPopupFactory.getInstance().createBalloonBuilder(new JLabel(balloon.getText())).setFillColor(messageType.getPopupBackground())
.createBalloon();
balloonToShow.setAnimationEnabled(false);
myBalloonManager.registerBalloon(balloonToShow);
balloonToShow.show(point, popUpPosition);
}
}
}
@Override
public final void setInfoAndErrors(@NotNull final Collection<WordWithPosition> infoBalloons,
@NotNull final Collection<WordWithPosition> errors) {
synchronized (myInfoBalloons) {
myInfoBalloons.clear();
myInfoBalloons.addAll(infoBalloons);
}
synchronized (myErrorBalloons) {
myErrorBalloons.clear();
myErrorBalloons.addAll(errors);
}
for (final WordWithPosition error : errors) {
if (isAfterLastCharRange(error)) {
// In "special" case we use last char
myMainTextField.underlineText(ERROR_COLOR, myMainTextField.getText().length(), myMainTextField.getText().length() + 1);
}
else {
myMainTextField.underlineText(ERROR_COLOR, error.getFrom(), error.getTo());
}
}
}
/**
* Checks if some range is <strong>special case</strong> {@link #AFTER_LAST_CHARACTER_RANGE}.
*
* @param range range to check
* @return true if special case
*/
private static boolean isAfterLastCharRange(@NotNull final Range<Integer> range) {
return AFTER_LAST_CHARACTER_RANGE.getFrom().equals(range.getFrom()) && AFTER_LAST_CHARACTER_RANGE.getTo().equals(range.getTo());
}
@Override
public final void insertTextAfterCaret(@NotNull final String text) {
try {
myMainTextField.getDocument().insertString(myMainTextField.getCaretPosition(), text, null);
}
catch (final BadLocationException e) {
// TODO: Display error somehow!
e.printStackTrace();
}
}
@Override
public final void replaceText(final int from, final int to, @NotNull final String newText) {
myMainTextField.select(from, to);
myMainTextField.replaceSelection(newText);
myBalloonManager.closeAllBalloons();
myPresenter.textChanged();
displayBalloonsIfRequired(); // This crunch but we need to recalculate balloons in this case (position is changed!)
}
@Override
public final int getCaretPosition() {
return myMainTextField.getCaretPosition();
}
@Override
public final void configureSubTexts(@Nullable final String defaultSubText,
@NotNull final List<Range<Integer>> suggestionAvailablePlaces) {
synchronized (myPlacesWhereSuggestionsAvailable) {
myPlacesWhereSuggestionsAvailable.clear();
myPlacesWhereSuggestionsAvailable.addAll(suggestionAvailablePlaces);
myDefaultSubText = defaultSubText;
}
configureAppropriateStatus();
}
/**
* Reacts on keys, pressed by user
*/
private class MyKeyListener extends KeyAdapter {
@Override
public void keyPressed(final KeyEvent e) {
super.keyPressed(e);
final int keyCode = e.getKeyCode();
if (keyCode == KeyEvent.VK_UP) {
mySuggestionList.moveSelection(true);
}
else if (keyCode == KeyEvent.VK_DOWN) {
mySuggestionList.moveSelection(false);
}
}
}
@NotNull
@Override
public String getText() {
return myMainTextField.getText();
}
/**
* Listener for suggestion list
*/
private class MySuggestionListListener extends JBPopupAdapter {
@Override
public void onClosed(final LightweightWindowEvent event) {
super.onClosed(event);
removeSuggestions();
}
}
/**
* Keeps tracks for baloons to close all of them in case of text inserion
*/
private static final class BalloonManager extends JBPopupAdapter {
@NotNull
private final Set<Balloon> myCurrentBaloons = new HashSet<Balloon>();
void registerBalloon(final Balloon balloon) {
synchronized (myCurrentBaloons) {
myCurrentBaloons.add(balloon);
balloon.addListener(this);
}
}
@Override
public void onClosed(final LightweightWindowEvent event) {
synchronized (myCurrentBaloons) {
myCurrentBaloons.remove(event.asBalloon());
}
super.onClosed(event);
}
void closeAllBalloons() {
synchronized (myCurrentBaloons) {
for (final Balloon balloon : myCurrentBaloons) {
balloon.dispose();
}
}
}
}
}

View File

@@ -1,34 +0,0 @@
/*
* Copyright 2000-2015 JetBrains s.r.o.
*
* Licensed 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 com.jetbrains.python.commandInterface.swingView;
import java.awt.event.ActionEvent;
/**
* "Complete current command or argument" action
*
* @author Ilya.Kazakevich
*/
final class CompletionKeyStrokeAction extends KeyStrokeAction {
CompletionKeyStrokeAction() {
super(KeyStrokeInfo.COMPLETION);
}
@Override
public void actionPerformed(final ActionEvent e) {
myPresenter.completionRequested(mySuggestionList.getValue());
}
}

View File

@@ -1,72 +0,0 @@
/*
* Copyright 2000-2015 JetBrains s.r.o.
*
* Licensed 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 com.jetbrains.python.commandInterface.swingView;
import com.jetbrains.python.commandInterface.CommandInterfacePresenter;
import com.jetbrains.python.suggestionList.SuggestionList;
import org.jetbrains.annotations.NotNull;
import javax.swing.text.TextAction;
/**
* Action that should be taken for certain {@link javax.swing.KeyStroke} (wrapped in {@link com.jetbrains.python.commandInterface.swingView.KeyStrokeInfo}).
* You need to call {@link #configure(com.jetbrains.python.commandInterface.CommandInterfacePresenter, com.jetbrains.python.suggestionList.SuggestionList)}
* to enable one.
*
* @author Ilya.Kazakevich
*/
@SuppressWarnings({"InstanceVariableMayNotBeInitialized", "NonSerializableFieldInSerializableClass"}) // Will never serialize
abstract class KeyStrokeAction extends TextAction {
@NotNull
private final String myName;
@NotNull
private final KeyStrokeInfo myStroke;
protected CommandInterfacePresenter myPresenter;
protected SuggestionList mySuggestionList;
/**
* @param stroke key stroke to bind this info to
*/
KeyStrokeAction(@NotNull final KeyStrokeInfo stroke) {
super(stroke.name());
myName = stroke.name();
myStroke = stroke;
}
/**
* Configures action.
*
* @param presenter presenter to be used for call back.
* @param suggestionList list of suggestions to be used for call back
* @return name of this action to add to {@link javax.swing.InputMap}
*/
@NotNull
final String configure(@NotNull final CommandInterfacePresenter presenter, @NotNull final SuggestionList suggestionList) {
myPresenter = presenter;
mySuggestionList = suggestionList;
return myName;
}
/**
* @return stroke bound to this action
*/
@NotNull
final KeyStrokeInfo getStroke() {
return myStroke;
}
}

View File

@@ -1,99 +0,0 @@
/*
* Copyright 2000-2015 JetBrains s.r.o.
*
* Licensed 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 com.jetbrains.python.commandInterface.swingView;
import com.intellij.openapi.keymap.KeymapUtil;
import com.jetbrains.python.commandInterface.CommandInterfacePresenter;
import com.jetbrains.python.suggestionList.SuggestionList;
import org.jetbrains.annotations.NotNull;
import javax.swing.*;
import java.awt.event.InputEvent;
import java.awt.event.KeyEvent;
/**
* Key strokes to be used with view.
* Strokes paired with action. You need to register each action via {@link javax.swing.InputMap}
*
* @author Ilya.Kazakevich
*/
enum KeyStrokeInfo {
/**
* "Execute command" keystroke
*/
EXECUTION(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0)),
/**
* "Complete current command or argument" keystroke
*/
COMPLETION(KeyStroke.getKeyStroke(KeyEvent.VK_TAB, 0)),
/**
* "Display suggestions" keystroke.
*/
SUGGESTION(KeyStroke.getKeyStroke(KeyEvent.VK_SPACE, InputEvent.CTRL_MASK)),;
/**
* List of actions. Each action should be bound to some {@link com.jetbrains.python.commandInterface.swingView.KeyStrokeInfo}
*/
@NotNull
private static final KeyStrokeAction[] ACTIONS = {
new CompletionKeyStrokeAction(),
new ExecutionKeyStrokeAction(),
new SuggestionKeyStrokeAction()};
private final KeyStroke myStroke;
KeyStrokeInfo(@NotNull final KeyStroke stroke) {
myStroke = stroke;
}
/**
* Registers action and binds it appropriate stroke. Call if for all instances to make all actions available.
*
* @param presenter presenter to be used as call back
* @param suggestionList suggestion list to be used as call back
* @param source Component with {@link javax.swing.InputMap} and {@link javax.swing.ActionMap} (swing view itself)
*/
void register(@NotNull final CommandInterfacePresenter presenter,
@NotNull final SuggestionList suggestionList,
@NotNull final JComponent source) {
final KeyStrokeAction action = getAction();
final String strokeName = action.configure(presenter, suggestionList);
source.getInputMap().put(myStroke, strokeName);
source.getActionMap().put(strokeName, action);
}
/**
* @return Human-readable name of this action (to display it to user)
*/
@NotNull
String getText() {
return KeymapUtil.getKeystrokeText(myStroke);
}
/**
* @return action paired with stroke
*/
@NotNull
private KeyStrokeAction getAction() {
for (final KeyStrokeAction action : ACTIONS) {
if (action.getStroke() == this) {
return action;
}
}
throw new IllegalStateException("Failed to find action for " + name());
}
}

View File

@@ -1,170 +0,0 @@
/*
* Copyright 2000-2014 JetBrains s.r.o.
*
* Licensed 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 com.jetbrains.python.commandInterface.swingView;
import com.intellij.openapi.editor.colors.EditorColorsManager;
import com.intellij.openapi.editor.colors.EditorFontType;
import com.intellij.util.Range;
import com.intellij.util.ui.StatusText;
import org.jetbrains.annotations.NotNull;
import javax.swing.*;
import java.awt.*;
import java.util.ArrayList;
import java.util.Collection;
/**
* Text field that has width to be changed and accepts error underline
*
* @author Ilya.Kazakevich
*/
@SuppressWarnings({"NonSerializableFieldInSerializableClass", "SerializableHasSerializationMethods"}) // Will never serialize it
public class SmartTextField extends JTextField {
/**
* Placeholder for this textbox
*/
private StatusText myPlaceHolder;
/**
* Error (underline) info to display. Check {@link UnderlineInfo} class for more info
*/
@NotNull
private final Collection<UnderlineInfo> myUnderlineInfo = new ArrayList<UnderlineInfo>();
private int myPreferredWidth;
public SmartTextField() {
setFont(EditorColorsManager.getInstance().getGlobalScheme().getFont(EditorFontType.CONSOLE_PLAIN));
}
@Override
protected void paintComponent(final Graphics g) {
super.paintComponent(g);
if (myPlaceHolder != null) {
myPlaceHolder.paint(this, g);
}
synchronized (myUnderlineInfo) {
for (final UnderlineInfo underlineInfo : myUnderlineInfo) {
g.setColor(underlineInfo.myColor);
// To prevent too long underlines: last char should really be last
underline(g, underlineInfo.getFrom(), underlineInfo.getTo());
}
}
}
/**
* Underlines certain place
*
* @param g canvas
* @param from from where (int px)
* @param to to where (int px)
*/
private void underline(@NotNull final Graphics g, final int from, final int to) {
final int verticalPosition = getHeight() - 5;
g.drawLine(from + getColumnWidth(), verticalPosition, to + getColumnWidth(), verticalPosition);
}
/**
* @return place (in px) where caret.
*/
int getTextCaretPositionInPx() {
return (getCaretPosition() + 1) * getColumnWidth();
}
void setWaterMarkPlaceHolderText(@NotNull final String watermark) {
myPlaceHolder = new MyStatusText(this);
myPlaceHolder.setText(watermark);
}
@Override
public Dimension getPreferredSize() {
final Dimension dimension = super.getPreferredSize();
final int placeHolderTextLength = ((myPlaceHolder != null) ? myPlaceHolder.getText().length() : 0);
final int columns = Math.max(Math.max(getText().length(), placeHolderTextLength), getColumns());
final int desiredSize = columns * getColumnWidth();
return new Dimension(Math.max(Math.max(myPreferredWidth, dimension.width), desiredSize), dimension.height);
}
/**
* Display underline
*
* @param color color to underline
* @param from from (in chars)
* @param to (in chars)
*/
final void underlineText(@NotNull final Color color, final int from, final int to) {
final int columnWidth = getColumnWidth();
synchronized (myUnderlineInfo) {
myUnderlineInfo.add(new UnderlineInfo(from * columnWidth, to * columnWidth, color));
}
}
/**
* Removes underline
*/
void hideUnderline() {
synchronized (myUnderlineInfo) {
myUnderlineInfo.clear();
}
}
/**
* Sets appropriate width in pixels
*
* @param width width in px
*/
void setPreferredWidthInPx(final int width) {
myPreferredWidth = width;
}
/**
* Wrapper to display placeholder
*/
private class MyStatusText extends StatusText {
MyStatusText(final JComponent owner) {
super(owner);
}
@Override
protected boolean isStatusVisible() {
return SmartTextField.this.getText().isEmpty();
}
}
/**
* Information about underline
*
* @author Ilya.Kazakevich
*/
private static final class UnderlineInfo extends Range<Integer> {
/**
* Color to use to underline
*/
@NotNull
private final Color myColor;
/**
* @param from underline from where (in px)
* @param to underline to where (in px)
* @param color color to use to underline
*/
UnderlineInfo(final int from, final int to, @NotNull final Color color) {
super(from, to);
myColor = color;
}
}
}

View File

@@ -1,34 +0,0 @@
/*
* Copyright 2000-2015 JetBrains s.r.o.
*
* Licensed 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 com.jetbrains.python.commandInterface.swingView;
import java.awt.event.ActionEvent;
/**
* "Suggestion request" action
*
* @author Ilya.Kazakevich
*/
final class SuggestionKeyStrokeAction extends KeyStrokeAction {
SuggestionKeyStrokeAction() {
super(KeyStrokeInfo.SUGGESTION);
}
@Override
public void actionPerformed(final ActionEvent e) {
myPresenter.suggestionRequested();
}
}

View File

@@ -1,21 +0,0 @@
/*
* Copyright 2000-2014 JetBrains s.r.o.
*
* Licensed 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.
*/
/**
* Command-line like interface view GUI. See {@link com.jetbrains.python.commandInterface.swingView.CommandInterfaceViewSwingImpl}
* @author Ilya.Kazakevich
*/
package com.jetbrains.python.commandInterface.swingView;

View File

@@ -1,78 +0,0 @@
/*
* Copyright 2000-2015 JetBrains s.r.o.
*
* Licensed 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 com.jetbrains.python.commandLineParser;
import com.intellij.openapi.util.text.StringUtil;
import com.jetbrains.python.WordWithPosition;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
/**
* Command line getCommandLineInfo result.
* It consists of command itself and its parts.
* Each part may be {@link com.jetbrains.python.commandLineParser.CommandLinePartType#ARGUMENT argument} or
* {@link com.jetbrains.python.commandLineParser.CommandLinePartType#OPTION option} or something else.
*
* @author Ilya.Kazakevich
*/
public final class CommandLine {
@NotNull
private final List<CommandLinePart> myParts = new ArrayList<CommandLinePart>();
@NotNull
private final WordWithPosition myCommand;
public CommandLine(
@NotNull final WordWithPosition command,
@NotNull final Collection<CommandLinePart> parts) {
myCommand = command;
myParts.addAll(parts);
}
/**
* @return command (i.e. "startapp" in "startapp my_app")
*/
@NotNull
public WordWithPosition getCommand() {
return myCommand;
}
@NotNull
public List<CommandLinePart> getParts() {
return Collections.unmodifiableList(myParts);
}
/**
* @return all command parts as text (actually by bindning them back together)
*/
@NotNull
public String getPartsAsText() {
final StringBuilder builder = new StringBuilder();
int lastPosition = 0;
for (final CommandLinePart part : getParts()) {
final WordWithPosition partWord = part.getWord();
if (lastPosition != partWord.getFrom()) {
builder.append(' ');
}
builder.append(partWord.getText());
lastPosition = partWord.getTo();
}
return StringUtil.trim(builder.toString());
}
}

View File

@@ -1,69 +0,0 @@
/*
* Copyright 2000-2015 JetBrains s.r.o.
*
* Licensed 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 com.jetbrains.python.commandLineParser;
import com.jetbrains.python.WordWithPosition;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* Option. Each option has name and may also have attached argument like "--long-option=attached_arg" or "-sATTACHED_ARG"
*
* @author Ilya.Kazakevich
*/
public final class CommandLineOption extends CommandLinePart {
@NotNull
private final String myOptionName;
@Nullable
private final WordWithPosition myAttachedArgument;
/**
* @param option option (text and position)
* @param optionName option name (like "--foo")
* @param attachedArgument option attached argument (like --foo=ATTACHED_ARG)
*/
public CommandLineOption(@NotNull final WordWithPosition option,
@NotNull final String optionName,
@Nullable final WordWithPosition attachedArgument) {
super(option);
myOptionName = optionName;
myAttachedArgument = attachedArgument;
}
/**
* @return option name (like "--foo")
*/
@NotNull
public String getOptionName() {
return myOptionName;
}
/**
* @return option attached argument (like --foo=ATTACHED_ARG)
*/
@Nullable
public WordWithPosition getAttachedArgument() {
return myAttachedArgument;
}
@Override
public void accept(@NotNull final CommandLinePartVisitor visitor) {
visitor.visitOption(this);
}
}

View File

@@ -1,34 +0,0 @@
/*
* Copyright 2000-2015 JetBrains s.r.o.
*
* Licensed 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 com.jetbrains.python.commandLineParser;
import org.jetbrains.annotations.NotNull;
/**
* Engine to {@link CommandLine} structure from text.
*
* @author Ilya.Kazakevich
*/
public interface CommandLineParser {
/**
*
* @param commandLineText command line to parse
* @return command line information
* @throws MalformedCommandLineException in case of bad commandline
*/
@NotNull
CommandLine parse(@NotNull String commandLineText) throws MalformedCommandLineException;
}

View File

@@ -1,51 +0,0 @@
/*
* Copyright 2000-2015 JetBrains s.r.o.
*
* Licensed 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 com.jetbrains.python.commandLineParser;
import com.jetbrains.python.WordWithPosition;
import org.jetbrains.annotations.NotNull;
/**
* Part of command line. Known subclasses are {@link CommandLineOption} and {@link CommandLineArgument}
*
* @author Ilya.Kazakevich
* @see CommandLineArgument
* @see CommandLineOption
*/
public abstract class CommandLinePart {
@NotNull
private final WordWithPosition myWord;
/**
* @param word word (and its position) this part represents
*/
protected CommandLinePart(@NotNull final WordWithPosition word) {
myWord = word;
}
/**
* @return word (and its position) this part represents
*/
@NotNull
public final WordWithPosition getWord() {
return myWord;
}
/**
* @param visitor visitor to accept
*/
public abstract void accept(@NotNull CommandLinePartVisitor visitor);
}

View File

@@ -1,110 +0,0 @@
/*
* Copyright 2000-2015 JetBrains s.r.o.
*
* Licensed 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 com.jetbrains.python.commandLineParser.optParse;
import com.intellij.openapi.util.Pair;
import com.jetbrains.python.WordWithPosition;
import com.jetbrains.python.commandLineParser.*;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Deque;
import java.util.List;
/**
* <p>
* <a href="https://docs.python.org/2/library/optparse.html">Optparse</a>-based commandline parses.
* According to optparse manual, commandline should look like:
* <pre>command arg1 arg2 --long-bool-opt -s --another-opt opt_arg1 opt_arg2 --yet-another-opt=opt_arg3 arg4 </pre>.
* You should understand difference between argument, long option, short option, and option argument before using this class.
* It is documented in optparse manual.
* </p>
* <p>
* This class provides not only args and options (like many other parsers do), but also <strong>position in command line</strong>
* which may be useful when you want to mark argument somehow.
* </p>
*
* @author Ilya.Kazakevich
*/
public final class OptParseCommandLineParser implements CommandLineParser {
/**
* Supported option parsers (option types, actually)
* Short (-o) and long (--option) strategies are used here
*/
private static final OptionParser[] OPTION_PARSERS = {new ShortOptionParser(), new LongOptionParser()};
@NotNull
@Override
public CommandLine parse(@NotNull final String commandLineText) throws MalformedCommandLineException {
final Deque<WordWithPosition> parts = new ArrayDeque<WordWithPosition>(WordWithPosition.splitText(commandLineText));
if (parts.isEmpty()) {
throw new MalformedCommandLineException("No command provided");
}
final WordWithPosition command = parts.pop();
final List<CommandLinePart> optionsAndArguments = new ArrayList<CommandLinePart>();
for (final WordWithPosition part : parts) {
final Pair<String, String> optionTextAndName = findOptionTextAndName(part.getText());
if (optionTextAndName != null) {
final String optionText = optionTextAndName.first;
final String optionName = optionTextAndName.second;
final WordWithPosition option = new WordWithPosition(optionText, part.getFrom(), part.getFrom() + optionText.length());
final WordWithPosition optionArgument;
if (optionText.length() == part.getText().length()) {
optionArgument = null;
}
else {
// Looks like we have option argument here.
final String argumentText = part.getText().substring(optionText.length());
optionArgument = new WordWithPosition(argumentText, option.getTo(), part.getTo());
}
optionsAndArguments.add(new CommandLineOption(option, optionName, optionArgument)); //Option
if (optionArgument != null) {
optionsAndArguments.add(new CommandLineArgument(optionArgument)); // And its argument
}
}
else {
optionsAndArguments.add(new CommandLineArgument(part)); //Not an option. Should be argument
}
}
return new CommandLine(command, optionsAndArguments);
}
/**
* Parse out option and its value iterating through the parsers
*
* @param text text believed to be an option like "--foo=123"
* @return [option_text, option_name] or null of no such option
* @see OptionParser#findOptionTextAndName(String)
*/
@Nullable
private static Pair<String, String> findOptionTextAndName(@NotNull final String optionText) {
for (final OptionParser parser : OPTION_PARSERS) {
final Pair<String, String> optionTextAndName = parser.findOptionTextAndName(optionText);
if (optionTextAndName != null) {
return optionTextAndName;
}
}
return null;
}
}

View File

@@ -1,34 +0,0 @@
/*
* Copyright 2000-2015 JetBrains s.r.o.
*
* Licensed 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 com.jetbrains.python.commandLineParser.optParse;
import com.intellij.openapi.util.Pair;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* Engine that knows how to deal with option of certain style (like long and short)
*
* @author Ilya.Kazakevich
*/
interface OptionParser {
/**
* @param optionText text to parse (like --foo=bar)
* @return null if option can't be parsed. Otherwise pair of [option_text, option_name]. That may match each other in some cases.
*/
@Nullable
Pair<String, String> findOptionTextAndName(@NotNull String optionText);
}

View File

@@ -1,60 +0,0 @@
/*
* Copyright 2000-2015 JetBrains s.r.o.
*
* Licensed 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 com.jetbrains.python.commandLineParser.optParse;
import com.intellij.openapi.util.Pair;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Parses options using regex and template method
*
* @author Ilya.Kazakevich
*/
abstract class OptionParserRegexBased implements OptionParser {
@NotNull
private final Pattern myPattern;
/**
* @param pattern regex. If option matches pattern, ({@link Matcher#find() opened matcher}) would be passed to template method
* {@link #getOptionTextAndNameFromMatcher(Matcher)}
*/
protected OptionParserRegexBased(@NotNull final Pattern pattern) {
myPattern = pattern;
}
@Nullable
@Override
public final Pair<String, String> findOptionTextAndName(@NotNull final String optionText) {
final Matcher matcher = myPattern.matcher(optionText);
if (!matcher.find()) {
return null;
}
return getOptionTextAndNameFromMatcher(matcher);
}
/**
* Obtains [option_text, option_name] from matcher
*
* @param matcher opened (with find() called) matcher
* @return pair [option_text, option_name]
*/
@NotNull
protected abstract Pair<String, String> getOptionTextAndNameFromMatcher(@NotNull Matcher matcher);
}

View File

@@ -1,29 +0,0 @@
/*
* Copyright 2000-2015 JetBrains s.r.o.
*
* Licensed 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.
*/
/**
* Engine to parse commandline from string to {@link com.jetbrains.python.commandLineParser.CommandLine} structure.
*
* Command line consists of command itself, {@link com.jetbrains.python.commandLineParser.CommandLineArgument arguments}
* and {@link com.jetbrains.python.commandLineParser.CommandLineOption options}.
* Use need to pass text to {@link com.jetbrains.python.commandLineParser.CommandLineParser parser} and obtain {@link com.jetbrains.python.commandLineParser.CommandLine}.
* <p/>
* Not like any other parsers, this package supports {@link com.jetbrains.python.WordWithPosition} telling you exactly with part of
* command line is command or argument. That helps you to underline or emphisize some parts.
*
* @author Ilya.Kazakevich
*/
package com.jetbrains.python.commandLineParser;

View File

@@ -0,0 +1 @@
<warning descr="Bad or unknown command. make sure this command really exists.">bad_command</warning> positional_argument --available-option=available_argument

View File

@@ -0,0 +1 @@
command <warning descr="Argument can't have this value. use autocompletion to check list of possible values.">bad_positional_value</warning> --available-option=<warning descr="Argument can't have this value. use autocompletion to check list of possible values.">bad_argument</warning> <warning descr="Bad or unknown option. make sure this option really exists.">--bad-option</warning> <warning descr="Excess argument or argument is not possible here">unexpected_argument</warning> --option-no-argument=<warning descr="Excess argument or argument is not possible here">asd</warning>

View File

@@ -0,0 +1 @@
command_name positional_argument_1 positional_argument_2 -sSMALL_OPTION_VALUE -a --long-option --long-option-2=long_option_value --long-option-3 argument_for_long_option

View File

@@ -0,0 +1,32 @@
FILE
CommandLineCommandImpl(COMMAND)
PsiElement(LITERAL_STARTS_FROM_LETTER)('command_name')
PsiWhiteSpace(' ')
CommandLineArgumentImpl(ARGUMENT)
PsiElement(LITERAL_STARTS_FROM_LETTER)('positional_argument_1')
PsiWhiteSpace(' ')
CommandLineArgumentImpl(ARGUMENT)
PsiElement(LITERAL_STARTS_FROM_LETTER)('positional_argument_2')
PsiWhiteSpace(' ')
CommandLineOptionImpl(OPTION)
PsiElement(SHORT_OPTION_NAME_TOKEN)('-s')
CommandLineArgumentImpl(ARGUMENT)
PsiElement(LITERAL_STARTS_FROM_LETTER)('SMALL_OPTION_VALUE')
PsiWhiteSpace(' ')
CommandLineOptionImpl(OPTION)
PsiElement(SHORT_OPTION_NAME_TOKEN)('-a')
PsiWhiteSpace(' ')
CommandLineOptionImpl(OPTION)
PsiElement(LONG_OPTION_NAME_TOKEN)('--long-option')
PsiWhiteSpace(' ')
CommandLineOptionImpl(OPTION)
PsiElement(LONG_OPTION_NAME_TOKEN)('--long-option-2')
PsiElement(=)('=')
CommandLineArgumentImpl(ARGUMENT)
PsiElement(LITERAL_STARTS_FROM_LETTER)('long_option_value')
PsiWhiteSpace(' ')
CommandLineOptionImpl(OPTION)
PsiElement(LONG_OPTION_NAME_TOKEN)('--long-option-3')
PsiWhiteSpace(' ')
CommandLineArgumentImpl(ARGUMENT)
PsiElement(LITERAL_STARTS_FROM_LETTER)('argument_for_long_option')

View File

@@ -0,0 +1 @@
command positional_argument --available-option=available_argument

View File

@@ -0,0 +1 @@
--can_t_be

View File

@@ -0,0 +1,3 @@
FILE
PsiErrorElement:LITERAL_STARTS_FROM_LETTER expected, got '--can_t_be'
PsiElement(LONG_OPTION_NAME_TOKEN)('--can_t_be')

View File

@@ -0,0 +1 @@
command --bad_value=

View File

@@ -0,0 +1,9 @@
FILE
CommandLineCommandImpl(COMMAND)
PsiElement(LITERAL_STARTS_FROM_LETTER)('command')
PsiWhiteSpace(' ')
CommandLineOptionImpl(OPTION)
PsiElement(LONG_OPTION_NAME_TOKEN)('--bad_value')
PsiErrorElement:Space between argument is its value is unexpected
<empty list>
PsiElement(=)('=')

View File

@@ -0,0 +1,75 @@
/*
* Copyright 2000-2015 JetBrains s.r.o.
*
* Licensed 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 com.jetbrains.commandInterface.gnuCommandLine;
import com.intellij.psi.PsiFile;
import com.jetbrains.commandInterface.gnuCommandLine.psi.CommandLineFile;
import com.jetbrains.python.fixtures.PyTestCase;
import org.junit.Assert;
/**
* Tests command line inspection
*
* @author Ilya.Kazakevich
*/
public final class CommandLineInspectionTest extends PyTestCase {
@Override
public void setUp() throws Exception {
super.setUp();
CommandTestTools.initFileType();
}
/**
* Everything should be ok
*/
public void testGoodCommandLine() throws Exception {
doTest();
}
/**
* No command provided in file
*/
public void testBadCommandLineNoCommand() throws Exception {
doTest();
}
/**
* Command provided, but args and opts have errors
*/
public void testBadCommandLineWithCommand() throws Exception {
doTest();
}
@Override
protected String getTestDataPath() {
return CommandTestTools.TEST_PATH;
}
/**
* Enables inspection on testName.cmdline and checks it.
*/
private void doTest() {
final PsiFile file = myFixture.configureByFile(getTestName(true) + '.' + CommandLineFileType.EXTENSION);
Assert.assertSame("Bad file type!", CommandLineFile.class, file.getClass());
final CommandLineFile commandLineFile = (CommandLineFile)file;
commandLineFile.setCommands(CommandTestTools.createCommands());
myFixture.enableInspections(CommandLineInspection.class);
myFixture.checkHighlighting();
}
}

View File

@@ -0,0 +1,60 @@
/*
* Copyright 2000-2015 JetBrains s.r.o.
*
* Licensed 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 com.jetbrains.commandInterface.gnuCommandLine;
import com.intellij.testFramework.ParsingTestCase;
import com.jetbrains.python.PythonTestUtil;
/**
* Tests command line parser
*
* @author Ilya.Kazakevich
*/
public final class CommandLineParserTest extends ParsingTestCase {
public CommandLineParserTest() {
super("", CommandLineFileType.EXTENSION, true, new CommandLineParserDefinition());
}
@Override
protected String getTestDataPath() {
return CommandTestTools.TEST_PATH;
}
/**
* Should be ok
*/
public void testCommandLine() throws Exception {
doTest(true);
}
/**
* Should have a lot of errors
*/
public void testJunk() throws Exception {
doTest(true);
}
/**
* Should have error because option ends with "="
*/
public void testOptionNoValueJunk() throws Exception {
doTest(true);
}
}

View File

@@ -0,0 +1,70 @@
/*
* Copyright 2000-2015 JetBrains s.r.o.
*
* Licensed 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 com.jetbrains.commandInterface.gnuCommandLine;
import com.intellij.codeInsight.lookup.LookupElement;
import com.intellij.util.containers.HashSet;
import com.jetbrains.commandInterface.gnuCommandLine.psi.CommandLineFile;
import com.jetbrains.python.fixtures.PyTestCase;
import org.hamcrest.Matchers;
import org.jetbrains.annotations.NotNull;
import org.junit.Assert;
import java.util.Set;
/**
* Tests references provide correct suggestions for commands, options and argument in command line
*
* @author Ilya.Kazakevich
*/
public final class CommandLineSuggestionTest extends PyTestCase {
/**
* Ensures suggestions are correct
*/
public void testSuggestions() throws Exception {
CommandTestTools.initFileType();
final CommandLineFile file =
(CommandLineFile)myFixture.configureByText(CommandLineFileType.INSTANCE, "command positional_ar --a");
file.setCommands(CommandTestTools.createCommands());
ensureSuggestions("command", "command");
ensureSuggestions("positional_ar", "positional_argument", "--option-no-argument", "--available-option");
ensureSuggestions("--a", "--option-no-argument", "--available-option");
}
/**
* @param initialPositionText place to move cusor to
* @param expectedSuggestions expected suggestions
*/
private void ensureSuggestions(@NotNull final String initialPositionText, @NotNull final String... expectedSuggestions) {
moveByText(initialPositionText);
final Set<String> completions = new HashSet<String>();
for (final LookupElement element : myFixture.completeBasic()) {
completions.add(element.getLookupString());
}
Assert.assertThat("Bad suggestions", completions,
Matchers.containsInAnyOrder(expectedSuggestions));
}
@Override
protected String getTestDataPath() {
return CommandTestTools.TEST_PATH;
}
}

Some files were not shown because too many files have changed in this diff Show More