-
Notifications
You must be signed in to change notification settings - Fork 142
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* Added intellisense to SwqlStudio * Updated intellisense to show only related words * Added enabling/disabling intellisense * Move "Enable Intellisense" setting to Settings
- Loading branch information
Showing
16 changed files
with
660 additions
and
266 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
namespace SwqlStudio.Intellisense | ||
{ | ||
// what do we detect user is/can be typing? | ||
internal struct ExpectedCaretPosition | ||
{ | ||
public ExpectedCaretPosition(ExpectedCaretPositionType type, string proposedEntity) | ||
{ | ||
Type = type; | ||
ProposedEntity = proposedEntity; | ||
} | ||
|
||
// what do we detect user is/can be typing? | ||
public ExpectedCaretPositionType Type { get; } | ||
// what entity columns to display? canonical name | ||
public string ProposedEntity { get; } | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
using System; | ||
|
||
namespace SwqlStudio.Intellisense | ||
{ | ||
[Flags] | ||
internal enum ExpectedCaretPositionType | ||
{ | ||
Keyword = 0x1, | ||
Column = 0x2, | ||
Entity = 0x4 | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,142 @@ | ||
using System; | ||
using System.Collections.Generic; | ||
using System.Linq; | ||
using System.Text; | ||
using System.Web; | ||
|
||
namespace SwqlStudio.Intellisense | ||
{ | ||
// we are not reusing full swis grammar, we can do more 'educated guess' here. | ||
internal class IntellisenseProvider | ||
{ | ||
private static readonly HashSet<string> _keyWords; | ||
private readonly string _text; | ||
|
||
public IntellisenseProvider(string text) | ||
{ | ||
_text = text; | ||
} | ||
|
||
static IntellisenseProvider() | ||
{ | ||
_keyWords = new HashSet<string>("all any and as asc between class desc distinct exists false full group having in inner into is isa from join left like not null or outer right select set some true union where end when then else case on top return xml raw auto with limitation rows to order by desc totalrows noplancache queryplan querystats".Split(' '), StringComparer.OrdinalIgnoreCase); | ||
} | ||
|
||
enum LastInterestingElement | ||
{ | ||
Nothing, // nothing interesting | ||
Dot, // last thing was dot, so when we find identifier, we append | ||
As // last thing was identifier (or AS keyword) , we want to detect alias | ||
} | ||
|
||
public ExpectedCaretPosition ParseFor(int caretPosition) | ||
{ | ||
var aliasList = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase); | ||
|
||
var rv = DoTheParsing(caretPosition, aliasList); | ||
string possibleAlias; | ||
|
||
switch (rv.Item2) | ||
{ | ||
case LastInterestingElement.Nothing: | ||
return new ExpectedCaretPosition(ExpectedCaretPositionType.Entity | ExpectedCaretPositionType.Keyword, null); | ||
case LastInterestingElement.Dot: | ||
if (!aliasList.TryGetValue(rv.Item1, out possibleAlias)) | ||
possibleAlias = rv.Item1; | ||
|
||
return new ExpectedCaretPosition(ExpectedCaretPositionType.Column, possibleAlias); | ||
case LastInterestingElement.As: | ||
|
||
if (!aliasList.TryGetValue(rv.Item1, out possibleAlias)) | ||
possibleAlias = rv.Item1; | ||
|
||
return new ExpectedCaretPosition(ExpectedCaretPositionType.Keyword | ExpectedCaretPositionType.Column, possibleAlias); | ||
default: | ||
throw new ArgumentOutOfRangeException(); | ||
} | ||
} | ||
|
||
private Tuple<string, LastInterestingElement> DoTheParsing(int caretPosition, | ||
Dictionary<string, string> aliasList) | ||
{ | ||
string lastIdentifier = ""; | ||
var lastInterestingElement = LastInterestingElement.Nothing; | ||
|
||
string underCaretIdentifier = ""; | ||
var underCaretInterestingElement = LastInterestingElement.Nothing; | ||
|
||
bool detected = false; | ||
|
||
foreach (var tok in new IntellisenseTokenizer(_text)) | ||
{ | ||
if (tok.Item3 == IntellisenseTokenizer.Token.Special) | ||
{ | ||
if (_text[tok.Item1] == '.') | ||
{ | ||
lastInterestingElement = LastInterestingElement.Dot; | ||
} | ||
else | ||
{ | ||
lastInterestingElement = LastInterestingElement.Nothing; | ||
} | ||
} | ||
|
||
if (!detected && tok.Item1 <= caretPosition && (tok.Item1 + tok.Item2) >= caretPosition) | ||
{ | ||
// here we are. what do we see right now? | ||
detected = true; | ||
underCaretIdentifier = lastIdentifier; | ||
underCaretInterestingElement = lastInterestingElement; | ||
} | ||
|
||
switch (tok.Item3) | ||
{ | ||
case IntellisenseTokenizer.Token.Identifier: | ||
var value = _text.Substring(tok.Item1, tok.Item2); | ||
if (value == "as") | ||
// alias. only interesting keyword for us. however, ignore, since Table X and Table as X are equivalent. | ||
// this may mean someone writing SELECT A B FROM D - A B are aliases - but, whatever. Full scan would be much worse. | ||
{ | ||
} | ||
else if (_keyWords.Contains(value)) // reset the interesting stuff state | ||
{ | ||
lastInterestingElement = LastInterestingElement.Nothing; | ||
lastIdentifier = ""; | ||
} | ||
else | ||
{ | ||
var realIdentifier = value[0] == '[' ? value.Substring(1, value.Length - 2) : value; | ||
|
||
switch (lastInterestingElement) | ||
{ | ||
case LastInterestingElement.Nothing: | ||
lastIdentifier = realIdentifier; | ||
lastInterestingElement = LastInterestingElement.As; | ||
break; | ||
case LastInterestingElement.Dot: | ||
lastIdentifier += "." + realIdentifier; | ||
lastInterestingElement = LastInterestingElement.As; | ||
break; | ||
case LastInterestingElement.As: | ||
aliasList[realIdentifier] = lastIdentifier; | ||
lastInterestingElement = LastInterestingElement.Nothing; | ||
lastIdentifier = ""; | ||
break; | ||
default: | ||
throw new ArgumentOutOfRangeException(); | ||
} | ||
} | ||
break; | ||
case IntellisenseTokenizer.Token.Number: | ||
case IntellisenseTokenizer.Token.String: | ||
// we do not care here | ||
lastInterestingElement = LastInterestingElement.Nothing; | ||
break; | ||
} | ||
} | ||
|
||
|
||
return Tuple.Create(underCaretIdentifier, underCaretInterestingElement); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,78 @@ | ||
using System; | ||
using System.Collections.Generic; | ||
using System.Text.RegularExpressions; | ||
|
||
namespace SwqlStudio.Intellisense | ||
{ | ||
internal class IntellisenseTokenizer | ||
{ | ||
public enum Token | ||
{ | ||
Special, | ||
Number, | ||
Identifier, | ||
String, | ||
EOF | ||
} | ||
|
||
private readonly string _input; | ||
private static readonly IEnumerable<Regex> _ignoredRegexes; | ||
private static readonly IEnumerable<Tuple<Regex, Token>> _regexes; | ||
static IntellisenseTokenizer() | ||
{ | ||
_ignoredRegexes = new[] | ||
{ | ||
new Regex(@"\G--.*\n"), // comment | ||
new Regex(@"\G\s+") // whitespace | ||
}; | ||
_regexes = new[] | ||
{ | ||
|
||
Tuple.Create(new Regex(@"\G[0-9]+.[0-9]*([eE][0-9]+)?"), Token.Number), // number | ||
Tuple.Create(new Regex(@"\G@[a-zA-Z_]\w*"), Token.Identifier), // ident | ||
Tuple.Create(new Regex(@"\G\[[^\]]*\]"), Token.Identifier), // quoted ident | ||
Tuple.Create(new Regex(@"\G""(?:[^""\\]|\\.)*"""), Token.String), // quoted string | ||
Tuple.Create(new Regex(@"\G[a-zA-Z_][a-zA-Z_0-9]*"), Token.Identifier) // ident | ||
}; | ||
} | ||
|
||
public IntellisenseTokenizer(string input) | ||
{ | ||
_input = input; | ||
} | ||
|
||
public IEnumerator<Tuple<int, int, Token>> GetEnumerator() | ||
{ | ||
int position = 0; | ||
while (position < _input.Length) | ||
{ | ||
foreach (var ignored in _ignoredRegexes) | ||
{ | ||
var m = ignored.Match(_input, position); | ||
if (m.Success) | ||
{ | ||
position += m.Groups[0].Length; | ||
goto end; | ||
} | ||
} | ||
|
||
foreach (var rx in _regexes) | ||
{ | ||
var m = rx.Item1.Match(_input, position); | ||
if (m.Success) | ||
{ | ||
yield return Tuple.Create(position, m.Groups[0].Length, rx.Item2); | ||
position += m.Groups[0].Length; | ||
goto end; | ||
} | ||
} | ||
yield return Tuple.Create(position, 1, Token.Special); | ||
position++; | ||
end: | ||
; | ||
} | ||
|
||
yield return Tuple.Create(position, 0, Token.EOF); | ||
} | ||
} | ||
} |
Oops, something went wrong.