Skip to content

Commit

Permalink
Added basic code completion
Browse files Browse the repository at this point in the history
  • Loading branch information
X39 committed Feb 24, 2020
1 parent e0a037f commit afa0db0
Show file tree
Hide file tree
Showing 10 changed files with 293 additions and 1 deletion.
7 changes: 7 additions & 0 deletions Arma.Studio.Data/ArmA.Studio.Data.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
<Reference Include="PresentationFramework" />
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Windows.Forms" />
<Reference Include="System.Xaml" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
Expand Down Expand Up @@ -79,7 +80,9 @@
<DesignTime>True</DesignTime>
<DependentUpon>Language.resx</DependentUpon>
</Compile>
<Compile Include="TextEditor\ICodeCompletionInfo.cs" />
<Compile Include="TextEditor\FoldingInfo.cs" />
<Compile Include="TextEditor\ICodeCompletable.cs" />
<Compile Include="TextEditor\IFoldable.cs" />
<Compile Include="TextEditor\ILintable.cs" />
<Compile Include="TextEditor\LintInfo.cs" />
Expand All @@ -99,6 +102,7 @@
<Compile Include="Dockable\IDockableProvider.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="TextEditor\ITextEditor.cs" />
<Compile Include="TextEditor\WordCompletionInfo.cs" />
<Compile Include="UI\AttachedProperties\Eventing\Checked.cs" />
<Compile Include="UI\AttachedProperties\Eventing\Closed.cs" />
<Compile Include="UI\AttachedProperties\Eventing\ContextMenuOpening.cs" />
Expand Down Expand Up @@ -172,5 +176,8 @@
<SubType>Designer</SubType>
</Page>
</ItemGroup>
<ItemGroup>
<None Include="packages.config" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project>
14 changes: 14 additions & 0 deletions Arma.Studio.Data/TextEditor/ICodeCompletable.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Arma.Studio.Data.TextEditor
{
public interface ICodeCompletable
{
IEnumerable<ICodeCompletionInfo> GetAutoCompleteInfos(string text, int caretOffset);
bool IsSeparatorCharacter(char c);
}
}
25 changes: 25 additions & 0 deletions Arma.Studio.Data/TextEditor/ICodeCompletionInfo.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Media;

namespace Arma.Studio.Data.TextEditor
{
public interface ICodeCompletionInfo
{
ImageSource ImageSource { get; }

string Text { get; }

object Content { get; }

object Description { get; }

double Priority { get; }

abstract string Complete(string input);
}
}
55 changes: 55 additions & 0 deletions Arma.Studio.Data/TextEditor/WordCompletionInfo.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Media;

namespace Arma.Studio.Data.TextEditor
{
public class WordCompletionInfo : ICodeCompletionInfo
{

public ImageSource ImageSource => null;
public string Text { get; private set; }
public object Content { get; private set; }
public object Description { get; private set; }
public double Priority => 0;

public WordCompletionInfo(string word)
{
this.Text = word;
this.Content = new System.Windows.Controls.TextBlock { Text = word };
}
public WordCompletionInfo(string word, string description)
{
this.Text = word;
this.Description = description;
this.Content = new System.Windows.Controls.TextBlock { Text = word };
}

public WordCompletionInfo(string ltype, string @operator, string rtype, string description)
{
this.Text = @operator;
this.Description = description;
var panel = new System.Windows.Controls.StackPanel { Orientation = System.Windows.Controls.Orientation.Horizontal };
if (!String.IsNullOrWhiteSpace(ltype) )
{
var textblock_ltype = new System.Windows.Controls.TextBlock { Text = ltype, FontStyle = System.Windows.FontStyles.Italic, Margin = new System.Windows.Thickness(0, 0, 6, 0) };
panel.Children.Add(textblock_ltype);
}
var textblock_operator = new System.Windows.Controls.TextBlock { Text = @operator, FontWeight = System.Windows.FontWeights.Bold };
panel.Children.Add(textblock_operator);
if (!String.IsNullOrWhiteSpace(rtype))
{
var textblock_rtype = new System.Windows.Controls.TextBlock { Text = rtype, FontStyle = System.Windows.FontStyles.Italic, Margin = new System.Windows.Thickness(6, 0, 0, 0) };
panel.Children.Add(textblock_rtype);
}
this.Content = panel;
}
public string Complete(string input)
{
return this.Text;
}
}
}
4 changes: 4 additions & 0 deletions Arma.Studio.Data/packages.config
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="AvalonEdit" version="6.0.1" targetFramework="net461" />
</packages>
34 changes: 33 additions & 1 deletion Arma.Studio.SqfEditor/SqfEditor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@

namespace Arma.Studio.SqfEditor
{
public class SqfEditor : ITextEditor, ILintable, IFoldable
public class SqfEditor : ITextEditor, ILintable, IFoldable, ICodeCompletable
{
private SqfVm.ClrVirtualmachine Virtualmachine { get; }
public SqfEditor()
Expand Down Expand Up @@ -253,6 +253,38 @@ void recursive()
}
}
}

#endregion
#region ICodeCompletable
public IEnumerable<ICodeCompletionInfo> GetAutoCompleteInfos(string text, int caretOffset)
{
// find start of word
var start = caretOffset-1;
while (start > 0 && !this.IsSeparatorCharacter(text[start]))
{
start--;
}
start++;
var word = (start == text.Length || start < 0) ? String.Empty : text.Substring(start, caretOffset - start);
return PluginMain.SqfDefinitionsFile
.ConcatAll()
.Where((it) => it.Name.StartsWith(word, StringComparison.InvariantCultureIgnoreCase))
.Select((it) =>
{
switch(it)
{
case SqfDefinitionsFile.Binary binary:
return new WordCompletionInfo(binary.Left, binary.Name, binary.Right, string.Empty);
case SqfDefinitionsFile.Unary unary:
return new WordCompletionInfo(string.Empty, unary.Name, unary.Right, string.Empty);
case SqfDefinitionsFile.Nular nular:
return new WordCompletionInfo(string.Empty, nular.Name, string.Empty, string.Empty);
default:
return new WordCompletionInfo(it.Name);
}
});
}
public bool IsSeparatorCharacter(char c) => char.IsWhiteSpace(c) || new char[] { ',', '(', ')', '[', ']', '{', '}', ';' }.Contains(c);
#endregion
}
}
1 change: 1 addition & 0 deletions Arma.Studio/ArmA.Studio.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,7 @@
<Compile Include="PluginManager.cs" />
<Compile Include="Solution.cs" />
<Compile Include="UI\BreakPointMargin.cs" />
<Compile Include="UI\CompletionData.cs" />
<Compile Include="UI\RuntimeBackgroundRenderer.cs" />
<Compile Include="UI\LineHighlighterBackgroundRenderer.cs" />
<Compile Include="UI\RuntimeExecutionMargin.cs" />
Expand Down
58 changes: 58 additions & 0 deletions Arma.Studio/Extensions.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using Arma.Studio.Data.TextEditor;
using ICSharpCode.AvalonEdit;
using ICSharpCode.AvalonEdit.Document;
using System;
using System.Collections.Generic;
Expand Down Expand Up @@ -34,5 +35,62 @@ public static ISegment GetSegment(this LintInfo lintInfo, TextDocument document)
EndOffset = offset + lintInfo.Length
};
}
public static int GetStartOffset(this TextEditor editor)
{
int off = editor.CaretOffset;
if (off <= 0 || off > editor.Document.TextLength)
{
return off;
}


int start;

//find start
for (start = off - 1; start >= 0; start--)
{
char c = editor.Document.GetCharAt(start);
if (Char.IsWhiteSpace(c))
{
start++;
return start;
}
}
return 0;
}
/// <summary>
/// Tries to find the start of a word.
/// </summary>
/// <param name="editor">A valid <see cref="TextEditor"/> instance.</param>
/// <param name="isSeparatorCharacter">The method to be used to test the characters. Should return True unless the char is not valid.</param>
/// <returns></returns>
public static int GetStartOffset(this TextEditor editor, Func<char, bool> isSeparatorCharacter = null)
{
if (isSeparatorCharacter == null)
{
isSeparatorCharacter = Char.IsLetter;
}
int off = editor.CaretOffset;
if (off <= 0 || off > editor.Document.TextLength)
{
return off;
}


int start;

// find start
for (start = off - 1; start >= 0; start--)
{
char c = editor.Document.GetCharAt(start);
if (isSeparatorCharacter(c))
{
start++;
return start;
}
}
return 0;
}

}
}
39 changes: 39 additions & 0 deletions Arma.Studio/UI/CompletionData.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
using Arma.Studio.Data.TextEditor;
using ICSharpCode.AvalonEdit.CodeCompletion;
using ICSharpCode.AvalonEdit.Document;
using ICSharpCode.AvalonEdit.Editing;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Media;

namespace Arma.Studio.UI
{
public class CompletionData : ICompletionData
{
private readonly ICodeCompletionInfo InnerCompletionInfo;
public ImageSource Image => this.InnerCompletionInfo.ImageSource;

public string Text => this.InnerCompletionInfo.Text;

public object Content => this.InnerCompletionInfo.Content;

public object Description => this.InnerCompletionInfo.Description;

public double Priority => this.InnerCompletionInfo.Priority;

public void Complete(TextArea textArea, ISegment completionSegment, EventArgs insertionRequestEventArgs)
{
var textSegment = textArea.Document.GetText(completionSegment);
var text = this.InnerCompletionInfo.Complete(textSegment);
textArea.Document.Replace(completionSegment, text);
}

public CompletionData(ICodeCompletionInfo codeCompletionInfo)
{
this.InnerCompletionInfo = codeCompletionInfo;
}
}
}
57 changes: 57 additions & 0 deletions Arma.Studio/UI/TextEditorDataContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using Arma.Studio.Data.TextEditor;
using Arma.Studio.Data.UI;
using ICSharpCode.AvalonEdit;
using ICSharpCode.AvalonEdit.CodeCompletion;
using ICSharpCode.AvalonEdit.Document;
using ICSharpCode.AvalonEdit.Folding;
using ICSharpCode.AvalonEdit.Highlighting;
Expand Down Expand Up @@ -68,6 +69,16 @@ public TextEditor TextEditorControl
}
private TextEditor _TextEditorControl;
private Tuple<int, int> _TextEditorControl_ScrollToLine;
public CompletionWindow CompletionWindow
{
get => this._CompletionWindow;
set
{
this._CompletionWindow = value;
this.RaisePropertyChanged();
}
}
private CompletionWindow _CompletionWindow;
//IHighlightingDefinition

public bool IsReadOnly
Expand Down Expand Up @@ -426,6 +437,26 @@ public void OnInitialized(FrameworkElement sender, EventArgs e)
textEditor.TextArea.LeftMargins.Insert(0, bpm);
textEditor.TextArea.LeftMargins.Insert(1, new RuntimeExecutionMargin(this));
this.FoldingManager = ICSharpCode.AvalonEdit.Folding.FoldingManager.Install(textEditor.TextArea);

textEditor.TextArea.TextEntering += this.TextArea_TextEntering;
textEditor.TextArea.TextEntered += this.TextArea_TextEntered; ;
}
}

private void TextArea_TextEntered(object sender, TextCompositionEventArgs e)
{
this.ShowAutoCompletion();
}

private void TextArea_TextEntering(object sender, TextCompositionEventArgs e)
{
if (e.Text.Length > 0 && this.CompletionWindow != null)
{
char c = e.Text[0];
if (!char.IsLetterOrDigit(c) && c != '_')
{
this.CompletionWindow.CompletionList.RequestInsertion(e);
}
}
}

Expand Down Expand Up @@ -518,7 +549,33 @@ public void OnPreviewKeyDown(UIElement sender, KeyEventArgs e)
e.Handled = true;
}
break;
case Key.Space when Keyboard.Modifiers == ModifierKeys.Control:
{
this.ShowAutoCompletion();
e.Handled = true;
}
break;
}
}

private void ShowAutoCompletion()
{
if (this.TextEditorInstance is ICodeCompletable codeCompletable)
{
if (this.CompletionWindow is null)
{
this.CompletionWindow = new CompletionWindow(this.TextEditorControl.TextArea);
this.CompletionWindow.Closed += delegate {
this.CompletionWindow = null;
};
this.CompletionWindow.StartOffset = this.TextEditorControl.GetStartOffset(codeCompletable.IsSeparatorCharacter);
this.CompletionWindow.EndOffset = this.TextEditorControl.CaretOffset;
this.CompletionWindow.Show();
}
this.CompletionWindow.StartOffset = this.TextEditorControl.GetStartOffset(codeCompletable.IsSeparatorCharacter);
var data = this.CompletionWindow.CompletionList.CompletionData;
data.Clear();
data.AddRange(codeCompletable.GetAutoCompleteInfos(this.TextDocument.Text, this.TextEditorControl.CaretOffset).Select((it) => new CompletionData(it)));
}
}
}
Expand Down

0 comments on commit afa0db0

Please sign in to comment.