diff --git a/Arma.Studio.Data/ArmA.Studio.Data.csproj b/Arma.Studio.Data/ArmA.Studio.Data.csproj
index ca52562..1777bb8 100644
--- a/Arma.Studio.Data/ArmA.Studio.Data.csproj
+++ b/Arma.Studio.Data/ArmA.Studio.Data.csproj
@@ -36,6 +36,7 @@
+
@@ -79,7 +80,9 @@
True
Language.resx
+
+
@@ -99,6 +102,7 @@
+
@@ -172,5 +176,8 @@
Designer
+
+
+
\ No newline at end of file
diff --git a/Arma.Studio.Data/TextEditor/ICodeCompletable.cs b/Arma.Studio.Data/TextEditor/ICodeCompletable.cs
new file mode 100644
index 0000000..628f27e
--- /dev/null
+++ b/Arma.Studio.Data/TextEditor/ICodeCompletable.cs
@@ -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 GetAutoCompleteInfos(string text, int caretOffset);
+ bool IsSeparatorCharacter(char c);
+ }
+}
diff --git a/Arma.Studio.Data/TextEditor/ICodeCompletionInfo.cs b/Arma.Studio.Data/TextEditor/ICodeCompletionInfo.cs
new file mode 100644
index 0000000..f6ba5ed
--- /dev/null
+++ b/Arma.Studio.Data/TextEditor/ICodeCompletionInfo.cs
@@ -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);
+ }
+}
diff --git a/Arma.Studio.Data/TextEditor/WordCompletionInfo.cs b/Arma.Studio.Data/TextEditor/WordCompletionInfo.cs
new file mode 100644
index 0000000..56e1a9e
--- /dev/null
+++ b/Arma.Studio.Data/TextEditor/WordCompletionInfo.cs
@@ -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;
+ }
+ }
+}
diff --git a/Arma.Studio.Data/packages.config b/Arma.Studio.Data/packages.config
new file mode 100644
index 0000000..af62e49
--- /dev/null
+++ b/Arma.Studio.Data/packages.config
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/Arma.Studio.SqfEditor/SqfEditor.cs b/Arma.Studio.SqfEditor/SqfEditor.cs
index aa15de9..cc5077a 100644
--- a/Arma.Studio.SqfEditor/SqfEditor.cs
+++ b/Arma.Studio.SqfEditor/SqfEditor.cs
@@ -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()
@@ -253,6 +253,38 @@ void recursive()
}
}
}
+
+ #endregion
+ #region ICodeCompletable
+ public IEnumerable 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
}
}
diff --git a/Arma.Studio/ArmA.Studio.csproj b/Arma.Studio/ArmA.Studio.csproj
index 186a0a8..cea798a 100644
--- a/Arma.Studio/ArmA.Studio.csproj
+++ b/Arma.Studio/ArmA.Studio.csproj
@@ -116,6 +116,7 @@
+
diff --git a/Arma.Studio/Extensions.cs b/Arma.Studio/Extensions.cs
index ba41280..e5843f1 100644
--- a/Arma.Studio/Extensions.cs
+++ b/Arma.Studio/Extensions.cs
@@ -1,4 +1,5 @@
using Arma.Studio.Data.TextEditor;
+using ICSharpCode.AvalonEdit;
using ICSharpCode.AvalonEdit.Document;
using System;
using System.Collections.Generic;
@@ -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;
+ }
+ ///
+ /// Tries to find the start of a word.
+ ///
+ /// A valid instance.
+ /// The method to be used to test the characters. Should return True unless the char is not valid.
+ ///
+ public static int GetStartOffset(this TextEditor editor, Func 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;
+ }
+
}
}
diff --git a/Arma.Studio/UI/CompletionData.cs b/Arma.Studio/UI/CompletionData.cs
new file mode 100644
index 0000000..452b2d2
--- /dev/null
+++ b/Arma.Studio/UI/CompletionData.cs
@@ -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;
+ }
+ }
+}
diff --git a/Arma.Studio/UI/TextEditorDataContext.cs b/Arma.Studio/UI/TextEditorDataContext.cs
index 25ed315..daba7a5 100644
--- a/Arma.Studio/UI/TextEditorDataContext.cs
+++ b/Arma.Studio/UI/TextEditorDataContext.cs
@@ -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;
@@ -68,6 +69,16 @@ public TextEditor TextEditorControl
}
private TextEditor _TextEditorControl;
private Tuple _TextEditorControl_ScrollToLine;
+ public CompletionWindow CompletionWindow
+ {
+ get => this._CompletionWindow;
+ set
+ {
+ this._CompletionWindow = value;
+ this.RaisePropertyChanged();
+ }
+ }
+ private CompletionWindow _CompletionWindow;
//IHighlightingDefinition
public bool IsReadOnly
@@ -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);
+ }
}
}
@@ -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)));
}
}
}