mirror of
https://git.do.srb2.org/STJr/UltimateZoneBuilder.git
synced 2025-01-18 14:31:50 +00:00
Fixed, Visual mode: in some cases translucency was ignored on inner top sides of translucent 3d floors.
Internal, Script Editor: moved some setup/auto-completion logic to separate classes. Updated ZDoom_DECORATE.cfg (A_QuakeEx).
This commit is contained in:
parent
24439cd156
commit
411368307f
14 changed files with 753 additions and 541 deletions
|
@ -163,7 +163,7 @@ keywords
|
|||
A_PlayerSkinCheck = "state A_PlayerSkinCheck(str state)";
|
||||
A_SkullPop = "A_SkullPop[(str type = \"BloodySkull\")]";
|
||||
A_Quake = "A_Quake(int intensity, int duration, int damageradius, int tremorradius[, str sound = \"world/quake\"])";
|
||||
A_QuakeEx = "A_QuakeEx(int intensityX, int intensityY, int intensityZ, int duration, int damrad, int tremrad[, str sound = \"world/quake\"[, int flags = 0[, float mulwavex = 1.0[, float mulwavey = 1.0[, float mulwavez = 1.0[, int falloff = 0[, int highpoint = 0]]]]]]])";
|
||||
A_QuakeEx = "A_QuakeEx(int intensityX, int intensityY, int intensityZ, int duration, int damrad, int tremrad[, str sound = \"world/quake\"[, int flags = 0[, float mulwavex = 1.0[, float mulwavey = 1.0[, float mulwavez = 1.0[, int falloff = 0[, int highpoint = 0[, float rollintensity = 0.0[, float rollwave = 0.0]]]]]]]]])";
|
||||
//Spawn functions
|
||||
A_TossGib = "A_TossGib";
|
||||
A_SpawnDebris = "A_SpawnDebris(str type[, bool translation = false[, float horizontal_vel = 1.0[, float vertical_vel = 1.0]]])";
|
||||
|
|
|
@ -813,6 +813,10 @@
|
|||
<Compile Include="Data\ColormapImage.cs" />
|
||||
<Compile Include="Data\CvarsCollection.cs" />
|
||||
<Compile Include="Data\HiResImage.cs" />
|
||||
<Compile Include="Data\Scripting\AccScriptHandler.cs" />
|
||||
<Compile Include="Data\Scripting\DecorateScriptHandler.cs" />
|
||||
<Compile Include="Data\Scripting\ModeldefScriptHandler.cs" />
|
||||
<Compile Include="Data\Scripting\ScriptHandler.cs" />
|
||||
<Compile Include="Data\TEXTURESImage.cs" />
|
||||
<Compile Include="Data\PK3FileImage.cs" />
|
||||
<Compile Include="Data\PK3StructuredReader.cs" />
|
||||
|
@ -886,6 +890,7 @@
|
|||
<Compile Include="GZBuilder\Data\ThingCopyData.cs" />
|
||||
<Compile Include="Rendering\VisualVertexHandle.cs" />
|
||||
<Compile Include="Geometry\Line3D.cs" />
|
||||
<Compile Include="Data\Scripting\ScriptHandlerAttribute.cs" />
|
||||
<Compile Include="ZDoom\Scripting\DecorateParserSE.cs" />
|
||||
<Compile Include="ZDoom\GldefsParser.cs" />
|
||||
<Compile Include="ZDoom\MapinfoParser.cs" />
|
||||
|
|
|
@ -26,7 +26,6 @@ using CodeImp.DoomBuilder.Data;
|
|||
using CodeImp.DoomBuilder.Windows;
|
||||
using CodeImp.DoomBuilder.Config;
|
||||
using CodeImp.DoomBuilder.Compilers;
|
||||
using CodeImp.DoomBuilder.GZBuilder.Data;
|
||||
using CodeImp.DoomBuilder.ZDoom.Scripting;
|
||||
using ScintillaNET;
|
||||
|
||||
|
@ -89,10 +88,11 @@ namespace CodeImp.DoomBuilder.Controls
|
|||
#region ================== Constructor
|
||||
|
||||
// Constructor
|
||||
protected ScriptDocumentTab(ScriptEditorPanel panel)
|
||||
protected ScriptDocumentTab(ScriptEditorPanel panel, ScriptConfiguration config)
|
||||
{
|
||||
// Keep panel
|
||||
// Keep panel and config
|
||||
this.panel = panel;
|
||||
this.config = config; //mxd
|
||||
|
||||
// Make the script control
|
||||
editor = new ScriptEditorControl();
|
||||
|
@ -114,8 +114,10 @@ namespace CodeImp.DoomBuilder.Controls
|
|||
editor.OnTextChanged += editor_TextChanged; //mxd
|
||||
|
||||
//mxd. Bind functionbar events
|
||||
editor.FunctionBar.DropDown += functionbar_DropDown;
|
||||
editor.FunctionBar.SelectedIndexChanged += functionbar_SelectedIndexChanged;
|
||||
editor.OnFunctionBarDropDown += functionbar_DropDown;
|
||||
|
||||
//mxd. Setup styles
|
||||
editor.SetupStyles(config);
|
||||
}
|
||||
|
||||
// Disposer
|
||||
|
@ -203,6 +205,8 @@ namespace CodeImp.DoomBuilder.Controls
|
|||
// This changes the script configurations
|
||||
public virtual void ChangeScriptConfig(ScriptConfiguration newconfig)
|
||||
{
|
||||
config = newconfig; //mxd
|
||||
editor.SetupStyles(newconfig); //mxd
|
||||
List<CompilerError> errors = UpdateNavigator(); //mxd
|
||||
if(panel.ActiveTab == this) panel.ShowErrors(errors); //mxd
|
||||
}
|
||||
|
@ -275,131 +279,12 @@ namespace CodeImp.DoomBuilder.Controls
|
|||
}
|
||||
|
||||
//mxd
|
||||
//TODO: rewrite this using reflection, move UpdateNavigator[Type] to appropriate parsers
|
||||
internal List<CompilerError> UpdateNavigator()
|
||||
{
|
||||
List<CompilerError> result = new List<CompilerError>();
|
||||
|
||||
// Just clear the navigator when current tab has no text
|
||||
if(editor.Text.Length == 0)
|
||||
{
|
||||
editor.FunctionBar.Items.Clear();
|
||||
editor.FunctionBar.Enabled = false;
|
||||
return result;
|
||||
}
|
||||
|
||||
// Store currently selected item name
|
||||
string prevtext = editor.FunctionBar.Text;
|
||||
switch(config.ScriptType)
|
||||
{
|
||||
case ScriptType.ACS:
|
||||
result = UpdateNavigatorAcs(new MemoryStream(editor.GetText()));
|
||||
break;
|
||||
|
||||
case ScriptType.DECORATE:
|
||||
result = UpdateNavigatorDecorate(new MemoryStream(editor.GetText()));
|
||||
break;
|
||||
|
||||
case ScriptType.MODELDEF:
|
||||
result = UpdateNavigatorModeldef(new MemoryStream(editor.GetText()));
|
||||
break;
|
||||
|
||||
default: // Unsupported script type. Just clear the items
|
||||
editor.FunctionBar.Items.Clear();
|
||||
break;
|
||||
}
|
||||
|
||||
// Put some text in the navigator (but don't actually trigger selection event)
|
||||
editor.FunctionBar.Enabled = (editor.FunctionBar.Items.Count > 0);
|
||||
if(editor.FunctionBar.Items.Count > 0)
|
||||
{
|
||||
preventchanges = true;
|
||||
|
||||
// Put the text back if we still have the corresponding item
|
||||
if(!string.IsNullOrEmpty(prevtext))
|
||||
{
|
||||
foreach(var item in editor.FunctionBar.Items)
|
||||
{
|
||||
if(item.ToString() == prevtext)
|
||||
{
|
||||
editor.FunctionBar.Text = item.ToString();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// No dice. Use the first item
|
||||
if(string.IsNullOrEmpty(editor.FunctionBar.Text))
|
||||
editor.FunctionBar.Text = editor.FunctionBar.Items[0].ToString();
|
||||
|
||||
preventchanges = false;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
//mxd
|
||||
private List<CompilerError> UpdateNavigatorDecorate(MemoryStream stream)
|
||||
{
|
||||
List<CompilerError> result = new List<CompilerError>();
|
||||
if(stream == null) return result;
|
||||
editor.FunctionBar.Items.Clear();
|
||||
|
||||
DecorateParserSE parser = new DecorateParserSE();
|
||||
TextResourceData data = new TextResourceData(stream, new DataLocation(), "DECORATE", false);
|
||||
|
||||
if(parser.Parse(data, false))
|
||||
editor.FunctionBar.Items.AddRange(parser.Actors.ToArray());
|
||||
|
||||
if(parser.HasError)
|
||||
result.Add(new CompilerError(parser.ErrorDescription, parser.ErrorSource, parser.ErrorLine));
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
//mxd
|
||||
private List<CompilerError> UpdateNavigatorModeldef(MemoryStream stream)
|
||||
{
|
||||
List<CompilerError> result = new List<CompilerError>();
|
||||
if(stream == null) return result;
|
||||
editor.FunctionBar.Items.Clear();
|
||||
|
||||
ModeldefParserSE parser = new ModeldefParserSE();
|
||||
TextResourceData data = new TextResourceData(stream, new DataLocation(), "MODELDEF", false);
|
||||
|
||||
if(parser.Parse(data, false))
|
||||
editor.FunctionBar.Items.AddRange(parser.Models.ToArray());
|
||||
|
||||
if(parser.HasError)
|
||||
result.Add(new CompilerError(parser.ErrorDescription, parser.ErrorSource, parser.ErrorLine));
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
//mxd
|
||||
private List<CompilerError> UpdateNavigatorAcs(MemoryStream stream)
|
||||
{
|
||||
List<CompilerError> result = new List<CompilerError>();
|
||||
if(stream == null) return result;
|
||||
editor.FunctionBar.Items.Clear();
|
||||
|
||||
AcsParserSE parser = new AcsParserSE { AddArgumentsToScriptNames = true, IsMapScriptsLump = this is ScriptLumpDocumentTab, IgnoreErrors = true };
|
||||
TextResourceData data = new TextResourceData(stream, new DataLocation(), (parser.IsMapScriptsLump ? "?SCRIPTS" : Filename), false);
|
||||
|
||||
if(parser.Parse(data, false))
|
||||
{
|
||||
editor.FunctionBar.Items.AddRange(parser.NamedScripts.ToArray());
|
||||
editor.FunctionBar.Items.AddRange(parser.NumberedScripts.ToArray());
|
||||
editor.FunctionBar.Items.AddRange(parser.Functions.ToArray());
|
||||
}
|
||||
|
||||
if(parser.HasError)
|
||||
result.Add(new CompilerError(parser.ErrorDescription, parser.ErrorSource, parser.ErrorLine));
|
||||
|
||||
return result;
|
||||
return editor.UpdateNavigator(this);
|
||||
}
|
||||
|
||||
//mxd
|
||||
//mxd. TODO: remove this
|
||||
internal ScriptType VerifyScriptType()
|
||||
{
|
||||
ScriptTypeParserSE parser = new ScriptTypeParserSE();
|
||||
|
@ -549,22 +434,6 @@ namespace CodeImp.DoomBuilder.Controls
|
|||
editor.GrabFocus();
|
||||
}
|
||||
|
||||
//mxd
|
||||
private void functionbar_SelectedIndexChanged(object sender, EventArgs e)
|
||||
{
|
||||
if(!preventchanges && editor.FunctionBar.SelectedItem is ScriptItem)
|
||||
{
|
||||
ScriptItem si = (ScriptItem)editor.FunctionBar.SelectedItem;
|
||||
editor.EnsureLineVisible(editor.LineFromPosition(si.CursorPosition));
|
||||
editor.SelectionStart = si.CursorPosition;
|
||||
editor.SelectionEnd = si.CursorPosition;
|
||||
|
||||
// Focus to the editor!
|
||||
editor.Focus();
|
||||
editor.GrabFocus();
|
||||
}
|
||||
}
|
||||
|
||||
//mxd
|
||||
private void functionbar_DropDown(object sender, EventArgs e)
|
||||
{
|
||||
|
|
|
@ -57,7 +57,6 @@ namespace CodeImp.DoomBuilder.Controls
|
|||
this.scriptedit.CharAdded += new System.EventHandler<ScintillaNET.CharAddedEventArgs>(this.scriptedit_CharAdded);
|
||||
this.scriptedit.AutoCCompleted += new System.EventHandler<ScintillaNET.AutoCSelectionEventArgs>(this.scriptedit_AutoCCompleted);
|
||||
this.scriptedit.InsertCheck += new System.EventHandler<ScintillaNET.InsertCheckEventArgs>(this.scriptedit_InsertCheck);
|
||||
this.scriptedit.KeyUp += new System.Windows.Forms.KeyEventHandler(this.scriptedit_KeyUp);
|
||||
this.scriptedit.UpdateUI += new System.EventHandler<ScintillaNET.UpdateUIEventArgs>(this.scriptedit_UpdateUI);
|
||||
this.scriptedit.KeyDown += new System.Windows.Forms.KeyEventHandler(this.scriptedit_KeyDown);
|
||||
//
|
||||
|
@ -83,6 +82,8 @@ namespace CodeImp.DoomBuilder.Controls
|
|||
this.functionbar.Size = new System.Drawing.Size(474, 21);
|
||||
this.functionbar.TabIndex = 2;
|
||||
this.functionbar.TabStop = false;
|
||||
this.functionbar.SelectedIndexChanged += new System.EventHandler(this.functionbar_SelectedIndexChanged);
|
||||
this.functionbar.DropDown += new System.EventHandler(this.functionbar_DropDown);
|
||||
//
|
||||
// ScriptEditorControl
|
||||
//
|
||||
|
|
|
@ -20,11 +20,13 @@ using System;
|
|||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Windows.Forms;
|
||||
using CodeImp.DoomBuilder.Compilers;
|
||||
using CodeImp.DoomBuilder.Config;
|
||||
using CodeImp.DoomBuilder.Data.Scripting;
|
||||
using CodeImp.DoomBuilder.GZBuilder.Data;
|
||||
using CodeImp.DoomBuilder.IO;
|
||||
using CodeImp.DoomBuilder.Rendering;
|
||||
using CodeImp.DoomBuilder.Properties;
|
||||
|
@ -53,7 +55,7 @@ namespace CodeImp.DoomBuilder.Controls
|
|||
#region ================== Enums
|
||||
|
||||
// Index for registered images
|
||||
private enum ImageIndex
|
||||
internal enum ImageIndex
|
||||
{
|
||||
ScriptConstant = 0,
|
||||
ScriptKeyword = 1,
|
||||
|
@ -67,7 +69,6 @@ namespace CodeImp.DoomBuilder.Controls
|
|||
#region ================== Constants
|
||||
|
||||
private const string LEXERS_RESOURCE = "Lexers.cfg";
|
||||
private const int MAX_BACKTRACK_LENGTH = 200;
|
||||
private const int HIGHLIGHT_INDICATOR = 8; //mxd. Indicators 0-7 could be in use by a lexer so we'll use indicator 8 to highlight words.
|
||||
private const string ENTRY_POSITION_MARKER = "[EP]"; //mxd
|
||||
private const string LINE_BREAK_MARKER = "[LB]"; //mxd
|
||||
|
@ -88,6 +89,7 @@ namespace CodeImp.DoomBuilder.Controls
|
|||
public event FindNextDelegate OnFindNext;
|
||||
public event FindPreviousDelegate OnFindPrevious; //mxd
|
||||
public new event EventHandler OnTextChanged; //mxd
|
||||
public event EventHandler OnFunctionBarDropDown; //mxd
|
||||
|
||||
#endregion
|
||||
|
||||
|
@ -95,17 +97,14 @@ namespace CodeImp.DoomBuilder.Controls
|
|||
|
||||
// Script configuration
|
||||
private ScriptConfiguration scriptconfig;
|
||||
|
||||
// List of keywords and constants
|
||||
private List<string> autocompletelist;
|
||||
|
||||
//mxd. Handles script type-specific stuff
|
||||
private ScriptHandler handler;
|
||||
|
||||
// Style translation from Scintilla style to ScriptStyleType
|
||||
private Dictionary<int, ScriptStyleType> stylelookup;
|
||||
|
||||
// Current position information
|
||||
private string curfunctionname = "";
|
||||
private int curargumentindex;
|
||||
private int curfunctionstartpos;
|
||||
private int linenumbercharlength; //mxd. Current max number of chars in the line number
|
||||
private int lastcaretpos; //mxd. Used in brace matching
|
||||
private int caretoffset; //mxd. Used to modify caret position after autogenerating stuff
|
||||
|
@ -113,6 +112,9 @@ namespace CodeImp.DoomBuilder.Controls
|
|||
private bool expandcodeblock; //mxd. More gross hacks
|
||||
private string highlightedword; //mxd
|
||||
private Encoding encoding; //mxd
|
||||
|
||||
//mxd. Event propagation
|
||||
private bool preventchanges;
|
||||
|
||||
#endregion
|
||||
|
||||
|
@ -125,7 +127,6 @@ namespace CodeImp.DoomBuilder.Controls
|
|||
public string SelectedText { get { return scriptedit.SelectedText; } } //mxd
|
||||
public bool ShowWhitespace { get { return scriptedit.ViewWhitespace != WhitespaceMode.Invisible; } set { scriptedit.ViewWhitespace = value ? WhitespaceMode.VisibleAlways : WhitespaceMode.Invisible; } }
|
||||
public bool WrapLongLines { get { return scriptedit.WrapMode != WrapMode.None; } set { scriptedit.WrapMode = (value ? WrapMode.Char : WrapMode.None); } }
|
||||
public ComboBox FunctionBar { get { return functionbar; } } //mxd
|
||||
public Scintilla Scintilla { get { return scriptedit; } } //mxd
|
||||
|
||||
#endregion
|
||||
|
@ -316,11 +317,17 @@ namespace CodeImp.DoomBuilder.Controls
|
|||
// This sets up the script editor with a script configuration
|
||||
public void SetupStyles(ScriptConfiguration config)
|
||||
{
|
||||
//mxd. Update script handler
|
||||
handler = General.Types.GetScriptHandler(config.ScriptType);
|
||||
handler.Initialize(this, config);
|
||||
|
||||
//mxd
|
||||
functionbar.Enabled = (config.ScriptType != ScriptType.UNKNOWN);
|
||||
|
||||
Configuration lexercfg = new Configuration();
|
||||
|
||||
// Make collections
|
||||
stylelookup = new Dictionary<int, ScriptStyleType>();
|
||||
Dictionary<string, string> autocompletedict = new Dictionary<string, string>(StringComparer.Ordinal);
|
||||
|
||||
// Keep script configuration
|
||||
scriptconfig = config;
|
||||
|
@ -454,123 +461,9 @@ namespace CodeImp.DoomBuilder.Controls
|
|||
scriptedit.Styles[stylenum].ForeColor = General.Colors.Colors[colorindex].ToColor();
|
||||
}
|
||||
}
|
||||
|
||||
// Create the keywords list and apply it
|
||||
string imageindex = ((int)ImageIndex.ScriptKeyword).ToString(CultureInfo.InvariantCulture);
|
||||
int keywordsindex = lexercfg.ReadSetting(lexername + ".keywordsindex", -1);
|
||||
if(keywordsindex > -1)
|
||||
{
|
||||
StringBuilder keywordslist = new StringBuilder();
|
||||
foreach(string k in scriptconfig.Keywords)
|
||||
{
|
||||
if(keywordslist.Length > 0) keywordslist.Append(" ");
|
||||
keywordslist.Append(k);
|
||||
|
||||
//mxd. Skip adding the keyword if we have a snippet with the same name
|
||||
if(!scriptconfig.Snippets.Contains(k))
|
||||
autocompletedict.Add(k, k + "?" + imageindex);
|
||||
}
|
||||
string words = keywordslist.ToString();
|
||||
scriptedit.SetKeywords(keywordsindex, (scriptconfig.CaseSensitive ? words : words.ToLowerInvariant()));
|
||||
}
|
||||
|
||||
//mxd. Create the properties list and apply it
|
||||
imageindex = ((int)ImageIndex.ScriptProperty).ToString(CultureInfo.InvariantCulture);
|
||||
int propertiesindex = lexercfg.ReadSetting(lexername + ".propertiesindex", -1);
|
||||
if(propertiesindex > -1)
|
||||
{
|
||||
StringBuilder propertieslist = new StringBuilder();
|
||||
HashSet<string> addedprops = new HashSet<string>();
|
||||
char[] dot = {'.'};
|
||||
foreach(string p in scriptconfig.Properties)
|
||||
{
|
||||
if(propertieslist.Length > 0) propertieslist.Append(" ");
|
||||
|
||||
// Scintilla doesn't highlight keywords with '.' or ':', so get rid of those
|
||||
if(scriptconfig.ScriptType == ScriptType.DECORATE)
|
||||
{
|
||||
string prop = p;
|
||||
if(prop.Contains(":")) prop = prop.Replace(":", string.Empty);
|
||||
if(prop.Contains("."))
|
||||
{
|
||||
// Split dotted properties into separate entries
|
||||
string[] parts = prop.Split(dot, StringSplitOptions.RemoveEmptyEntries);
|
||||
List<string> result = new List<string>();
|
||||
foreach(string part in parts)
|
||||
{
|
||||
if(!addedprops.Contains(part))
|
||||
{
|
||||
result.Add(part);
|
||||
addedprops.Add(part);
|
||||
}
|
||||
}
|
||||
|
||||
if(result.Count > 0) propertieslist.Append(string.Join(" ", result.ToArray()));
|
||||
}
|
||||
else
|
||||
{
|
||||
addedprops.Add(prop);
|
||||
propertieslist.Append(prop);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
propertieslist.Append(p);
|
||||
}
|
||||
|
||||
// Autocomplete doesn't mind '.' or ':'
|
||||
// Skip adding the keyword if we have a snippet with the same name
|
||||
if(!scriptconfig.Snippets.Contains(p))
|
||||
{
|
||||
if(autocompletedict.ContainsKey(p))
|
||||
General.ErrorLogger.Add(ErrorType.Warning, "Property \"" + p + "\" is double defined in \"" + scriptconfig.Description + "\" script configuration.");
|
||||
else
|
||||
autocompletedict.Add(p, p + "?" + imageindex);
|
||||
}
|
||||
}
|
||||
string words = propertieslist.ToString();
|
||||
scriptedit.SetKeywords(propertiesindex, (scriptconfig.CaseSensitive ? words : words.ToLowerInvariant()));
|
||||
}
|
||||
|
||||
// Create the constants list and apply it
|
||||
imageindex = ((int)ImageIndex.ScriptConstant).ToString(CultureInfo.InvariantCulture);
|
||||
int constantsindex = lexercfg.ReadSetting(lexername + ".constantsindex", -1);
|
||||
if(constantsindex > -1)
|
||||
{
|
||||
StringBuilder constantslist = new StringBuilder();
|
||||
foreach(string c in scriptconfig.Constants)
|
||||
{
|
||||
if(autocompletedict.ContainsKey(c)) continue; //mxd. This happens when there's a keyword and a constant with the same name...
|
||||
|
||||
if(constantslist.Length > 0) constantslist.Append(" ");
|
||||
constantslist.Append(c);
|
||||
|
||||
//mxd. Skip adding the constant if we have a snippet with the same name
|
||||
if(!scriptconfig.Snippets.Contains(c))
|
||||
autocompletedict.Add(c, c + "?" + imageindex);
|
||||
}
|
||||
string words = constantslist.ToString();
|
||||
scriptedit.SetKeywords(constantsindex, (scriptconfig.CaseSensitive ? words : words.ToLowerInvariant()));
|
||||
}
|
||||
|
||||
//mxd. Create the snippets list and apply it
|
||||
imageindex = ((int)ImageIndex.ScriptSnippet).ToString(CultureInfo.InvariantCulture);
|
||||
int snippetindex = lexercfg.ReadSetting(lexername + ".snippetindex", -1);
|
||||
if(snippetindex > -1 && scriptconfig.Snippets.Count > 0)
|
||||
{
|
||||
StringBuilder snippetslist = new StringBuilder();
|
||||
foreach(string s in scriptconfig.Snippets)
|
||||
{
|
||||
if(snippetslist.Length > 0) snippetslist.Append(" ");
|
||||
snippetslist.Append(s);
|
||||
autocompletedict.Add(s, s + "?" + imageindex);
|
||||
}
|
||||
string words = snippetslist.ToString();
|
||||
scriptedit.SetKeywords(snippetindex, (scriptconfig.CaseSensitive ? words : words.ToLowerInvariant()));
|
||||
}
|
||||
|
||||
// Make autocomplete list
|
||||
autocompletelist = new List<string>(autocompletedict.Values);
|
||||
//mxd. Set keywords
|
||||
handler.SetKeywords(lexercfg, lexername);
|
||||
|
||||
// Setup folding (https://github.com/jacobslusser/ScintillaNET/wiki/Automatic-Code-Folding)
|
||||
if(General.Settings.ScriptShowFolding && (scriptconfig.Lexer == Lexer.Cpp || scriptconfig.Lexer == Lexer.CppNoCase))
|
||||
|
@ -842,141 +735,64 @@ namespace CodeImp.DoomBuilder.Controls
|
|||
}
|
||||
}
|
||||
|
||||
//mxd
|
||||
internal List<CompilerError> UpdateNavigator(ScriptDocumentTab tab)
|
||||
{
|
||||
List<CompilerError> result = new List<CompilerError>();
|
||||
|
||||
// Just clear the navigator when current tab has no text
|
||||
if(scriptedit.Text.Length == 0)
|
||||
{
|
||||
functionbar.Items.Clear();
|
||||
functionbar.Enabled = false;
|
||||
return result;
|
||||
}
|
||||
|
||||
// Store currently selected item name
|
||||
string prevtext = functionbar.Text;
|
||||
|
||||
// Repopulate FunctionBar
|
||||
result = handler.UpdateFunctionBarItems(tab, new MemoryStream(GetText()), functionbar);
|
||||
|
||||
// Put some text in the navigator (but don't actually trigger selection event)
|
||||
functionbar.Enabled = (functionbar.Items.Count > 0);
|
||||
if(functionbar.Items.Count > 0)
|
||||
{
|
||||
preventchanges = true;
|
||||
|
||||
// Put the text back if we still have the corresponding item
|
||||
if(!string.IsNullOrEmpty(prevtext))
|
||||
{
|
||||
foreach(var item in functionbar.Items)
|
||||
{
|
||||
if(item.ToString() == prevtext)
|
||||
{
|
||||
functionbar.Text = item.ToString();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// No dice. Use the first item
|
||||
if(string.IsNullOrEmpty(functionbar.Text))
|
||||
functionbar.Text = functionbar.Items[0].ToString();
|
||||
|
||||
preventchanges = false;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region ================== Utility methods
|
||||
|
||||
// This returns the ScriptStyleType for a given Scintilla style
|
||||
private ScriptStyleType GetScriptStyle(int scintillastyle)
|
||||
internal ScriptStyleType GetScriptStyle(int scintillastyle)
|
||||
{
|
||||
return (stylelookup.ContainsKey(scintillastyle) ? stylelookup[scintillastyle] : ScriptStyleType.PlainText);
|
||||
}
|
||||
|
||||
// This gathers information about the current caret position
|
||||
private void UpdatePositionInfo()
|
||||
{
|
||||
int bracketlevel = 0; // bracket level counting
|
||||
int argindex = 0; // function argument counting
|
||||
int pos = scriptedit.CurrentPosition;
|
||||
|
||||
// Get the text
|
||||
string scripttext = scriptedit.Text;
|
||||
|
||||
// Reset position info
|
||||
curfunctionname = "";
|
||||
curargumentindex = 0;
|
||||
curfunctionstartpos = 0;
|
||||
|
||||
// Determine lowest backtrack position
|
||||
int limitpos = scriptedit.CurrentPosition - MAX_BACKTRACK_LENGTH;
|
||||
if(limitpos < 0) limitpos = 0;
|
||||
|
||||
// We can only do this when we have function syntax information
|
||||
if((scriptconfig.ArgumentDelimiter.Length == 0) || (scriptconfig.FunctionClose.Length == 0) ||
|
||||
(scriptconfig.FunctionOpen.Length == 0) || (scriptconfig.Terminator.Length == 0)) return;
|
||||
|
||||
// Get int versions of the function syntax informantion
|
||||
int argumentdelimiter = scriptconfig.ArgumentDelimiter[0];
|
||||
int functionclose = scriptconfig.FunctionClose[0];
|
||||
int functionopen = scriptconfig.FunctionOpen[0];
|
||||
int terminator = scriptconfig.Terminator[0];
|
||||
|
||||
// Continue backtracking until we reached the limitpos
|
||||
while(pos >= limitpos)
|
||||
{
|
||||
// Backtrack 1 character
|
||||
pos--;
|
||||
|
||||
// Get the style and character at this position
|
||||
ScriptStyleType curstyle = GetScriptStyle(scriptedit.GetStyleAt(pos));
|
||||
int curchar = scriptedit.GetCharAt(pos);
|
||||
|
||||
// Then meeting ) then increase bracket level
|
||||
// When meeting ( then decrease bracket level
|
||||
// When bracket level goes -1, then the next word should be the function name
|
||||
// Only when at bracket level 0, count the comma's for argument index
|
||||
|
||||
// TODO:
|
||||
// Original code checked for scope character here and breaks if found
|
||||
|
||||
// Check if in plain text or keyword
|
||||
if((curstyle == ScriptStyleType.PlainText) || (curstyle == ScriptStyleType.Keyword))
|
||||
{
|
||||
// Closing bracket
|
||||
if(curchar == functionclose)
|
||||
{
|
||||
bracketlevel++;
|
||||
}
|
||||
// Opening bracket
|
||||
else if(curchar == functionopen)
|
||||
{
|
||||
bracketlevel--;
|
||||
|
||||
// Out of the brackets?
|
||||
if(bracketlevel < 0)
|
||||
{
|
||||
// Skip any whitespace before this bracket
|
||||
do
|
||||
{
|
||||
// Backtrack 1 character
|
||||
curchar = scriptedit.GetCharAt(--pos);
|
||||
}
|
||||
while((pos >= limitpos) && ((curchar == ' ') || (curchar == '\t') ||
|
||||
(curchar == '\r') || (curchar == '\n')));
|
||||
|
||||
// NOTE: We may need to set onlyWordCharacters argument in the
|
||||
// following calls to false to get any argument delimiter included,
|
||||
// but this may also cause a valid keyword to be combined with other
|
||||
// surrounding characters that do not belong to the keyword.
|
||||
|
||||
// Find the word before this bracket
|
||||
int wordstart = scriptedit.WordStartPosition(pos, true);
|
||||
int wordend = scriptedit.WordEndPosition(pos, true);
|
||||
string word = scripttext.Substring(wordstart, wordend - wordstart);
|
||||
if(word.Length > 0)
|
||||
{
|
||||
// Check if this is an argument delimiter
|
||||
// I can't remember why I did this, but I'll probably stumble
|
||||
// upon the problem if this doesn't work right (see note above)
|
||||
if(word[0] == argumentdelimiter)
|
||||
{
|
||||
// We are now in the parent function
|
||||
bracketlevel++;
|
||||
argindex = 0;
|
||||
}
|
||||
// Now check if this is a keyword
|
||||
else if(scriptconfig.IsKeyword(word))
|
||||
{
|
||||
// Found it!
|
||||
curfunctionname = scriptconfig.GetKeywordCase(word);
|
||||
curargumentindex = argindex;
|
||||
curfunctionstartpos = wordstart;
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Don't know this word
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Argument delimiter
|
||||
else if(curchar == argumentdelimiter)
|
||||
{
|
||||
// Only count these at brackt level 0
|
||||
if(bracketlevel == 0) argindex++;
|
||||
}
|
||||
// Terminator
|
||||
else if(curchar == terminator)
|
||||
{
|
||||
// Can't find anything, break now
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// This registers an image for the autocomplete list
|
||||
private void RegisterAutoCompleteImage(ImageIndex index, Bitmap image)
|
||||
{
|
||||
|
@ -992,65 +808,6 @@ namespace CodeImp.DoomBuilder.Controls
|
|||
scriptedit.Markers[(int)index].Symbol = MarkerSymbol.RgbaImage;
|
||||
}
|
||||
|
||||
//mxd. Autocompletion handling (https://github.com/jacobslusser/ScintillaNET/wiki/Basic-Autocompletion)
|
||||
private bool ShowAutoCompletionList()
|
||||
{
|
||||
int currentpos = scriptedit.CurrentPosition;
|
||||
int wordstartpos = scriptedit.WordStartPosition(currentpos, true);
|
||||
|
||||
if(wordstartpos >= currentpos)
|
||||
{
|
||||
// Hide the list
|
||||
scriptedit.AutoCCancel();
|
||||
return false;
|
||||
}
|
||||
|
||||
// Get entered text
|
||||
string start = scriptedit.GetTextRange(wordstartpos, currentpos - wordstartpos);
|
||||
if(string.IsNullOrEmpty(start))
|
||||
{
|
||||
// Hide the list
|
||||
scriptedit.AutoCCancel();
|
||||
return false;
|
||||
}
|
||||
|
||||
// Don't show Auto-completion list when editing comment, include or string
|
||||
switch(GetScriptStyle(scriptedit.GetStyleAt(currentpos)))
|
||||
{
|
||||
case ScriptStyleType.Comment:
|
||||
case ScriptStyleType.String:
|
||||
// Hide the list
|
||||
scriptedit.AutoCCancel();
|
||||
return false;
|
||||
|
||||
case ScriptStyleType.Include:
|
||||
// Hide the list unless current word is a keyword
|
||||
if(!start.StartsWith("#"))
|
||||
{
|
||||
scriptedit.AutoCCancel();
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// Filter the list
|
||||
List<string> filtered = new List<string>();
|
||||
foreach(string s in autocompletelist)
|
||||
if(s.IndexOf(start, StringComparison.OrdinalIgnoreCase) != -1) filtered.Add(s);
|
||||
|
||||
// Any matches?
|
||||
if(filtered.Count > 0)
|
||||
{
|
||||
// Show the list
|
||||
scriptedit.AutoCShow(currentpos - wordstartpos, string.Join(" ", filtered.ToArray()));
|
||||
return true;
|
||||
}
|
||||
|
||||
// Hide the list
|
||||
scriptedit.AutoCCancel();
|
||||
return false;
|
||||
}
|
||||
|
||||
//mxd
|
||||
private string GetIndentationString(int indent)
|
||||
{
|
||||
|
@ -1216,7 +973,7 @@ namespace CodeImp.DoomBuilder.Controls
|
|||
if(General.Settings.ScriptAutoShowAutocompletion)
|
||||
{
|
||||
// Display the autocompletion list
|
||||
ShowAutoCompletionList();
|
||||
handler.ShowAutoCompletionList();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1419,7 +1176,7 @@ namespace CodeImp.DoomBuilder.Controls
|
|||
scriptedit.CallTipCancel();
|
||||
|
||||
// Show autocomplete
|
||||
ShowAutoCompletionList();
|
||||
handler.ShowAutoCompletionList();
|
||||
skiptextinsert = true;
|
||||
}
|
||||
//mxd. Tab to expand code snippet. Do it only when the text cursor is at the end of a keyword.
|
||||
|
@ -1449,75 +1206,27 @@ namespace CodeImp.DoomBuilder.Controls
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Key released
|
||||
private void scriptedit_KeyUp(object sender, KeyEventArgs e)
|
||||
{
|
||||
bool showcalltip = false;
|
||||
int highlightstart = 0;
|
||||
int highlightend = 0;
|
||||
|
||||
UpdatePositionInfo();
|
||||
|
||||
// Call tip shown
|
||||
if(scriptedit.CallTipActive)
|
||||
{
|
||||
// Should we hide the call tip?
|
||||
if(curfunctionname.Length == 0)
|
||||
{
|
||||
// Hide the call tip
|
||||
scriptedit.CallTipCancel();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Update the call tip
|
||||
showcalltip = true;
|
||||
}
|
||||
}
|
||||
// No call tip
|
||||
else
|
||||
{
|
||||
// Should we show a call tip?
|
||||
showcalltip = (curfunctionname.Length > 0) && !scriptedit.AutoCActive;
|
||||
}
|
||||
|
||||
// Show or update call tip
|
||||
if(showcalltip)
|
||||
{
|
||||
string functiondef = scriptconfig.GetFunctionDefinition(curfunctionname);
|
||||
if(functiondef != null)
|
||||
{
|
||||
// Determine the range to highlight
|
||||
int argsopenpos = functiondef.IndexOf(scriptconfig.FunctionOpen, StringComparison.Ordinal);
|
||||
int argsclosepos = functiondef.LastIndexOf(scriptconfig.FunctionClose, StringComparison.Ordinal);
|
||||
if((argsopenpos > -1) && (argsclosepos > -1))
|
||||
{
|
||||
string argsstr = functiondef.Substring(argsopenpos + 1, argsclosepos - argsopenpos - 1);
|
||||
string[] args = argsstr.Split(scriptconfig.ArgumentDelimiter[0]);
|
||||
if((curargumentindex >= 0) && (curargumentindex < args.Length))
|
||||
{
|
||||
int argoffset = 0;
|
||||
for(int i = 0; i < curargumentindex; i++) argoffset += args[i].Length + 1;
|
||||
highlightstart = argsopenpos + argoffset + 1;
|
||||
highlightend = highlightstart + args[curargumentindex].Length;
|
||||
}
|
||||
}
|
||||
|
||||
//mxd. If the tip obscures the view, move it down
|
||||
int tippos;
|
||||
int funcline = scriptedit.LineFromPosition(curfunctionstartpos);
|
||||
if(scriptedit.CurrentLine > funcline)
|
||||
tippos = scriptedit.Lines[scriptedit.CurrentLine].Position + scriptedit.Lines[scriptedit.CurrentLine].Indentation; //scriptedit.PositionFromLine(curline) /*+ (curfunctionstartpos - scriptedit.PositionFromLine(funcline))*/;
|
||||
else
|
||||
tippos = curfunctionstartpos;
|
||||
|
||||
// Show tip
|
||||
scriptedit.CallTipShow(tippos, functiondef);
|
||||
scriptedit.CallTipSetHlt(highlightstart, highlightend);
|
||||
}
|
||||
//mxd
|
||||
private void functionbar_SelectedIndexChanged(object sender, EventArgs e)
|
||||
{
|
||||
if(!preventchanges && functionbar.SelectedItem is ScriptItem)
|
||||
{
|
||||
ScriptItem si = (ScriptItem)functionbar.SelectedItem;
|
||||
EnsureLineVisible(LineFromPosition(si.CursorPosition));
|
||||
scriptedit.SelectionStart = si.CursorPosition;
|
||||
scriptedit.SelectionEnd = si.CursorPosition;
|
||||
|
||||
// Focus to the editor!
|
||||
scriptedit.Focus();
|
||||
}
|
||||
}
|
||||
|
||||
private void functionbar_DropDown(object sender, EventArgs e)
|
||||
{
|
||||
if(OnFunctionBarDropDown != null) OnFunctionBarDropDown(sender, e);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
}
|
|
@ -51,18 +51,15 @@ namespace CodeImp.DoomBuilder.Controls
|
|||
#region ================== Constructor / Disposer
|
||||
|
||||
// Constructor
|
||||
public ScriptFileDocumentTab(ScriptEditorPanel panel, ScriptConfiguration config) : base(panel)
|
||||
public ScriptFileDocumentTab(ScriptEditorPanel panel, ScriptConfiguration config) : base(panel, config)
|
||||
{
|
||||
string ext = "";
|
||||
|
||||
// Initialize
|
||||
this.filepathname = "";
|
||||
this.config = config;
|
||||
editor.SetupStyles(config);
|
||||
if(config.Extensions.Length > 0) ext = "." + config.Extensions[0];
|
||||
SetTitle("Untitled" + ext);
|
||||
editor.ClearUndoRedo();
|
||||
editor.FunctionBar.Enabled = (config.ScriptType != ScriptType.UNKNOWN); //mxd
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
@ -359,12 +356,9 @@ namespace CodeImp.DoomBuilder.Controls
|
|||
// This changes the script configurations
|
||||
public override void ChangeScriptConfig(ScriptConfiguration newconfig)
|
||||
{
|
||||
this.config = newconfig;
|
||||
editor.SetupStyles(config);
|
||||
|
||||
if(filepathname.Length == 0)
|
||||
{
|
||||
string ext = (config.Extensions.Length > 0 ? "." + config.Extensions[0] : "");
|
||||
string ext = (newconfig.Extensions.Length > 0 ? "." + newconfig.Extensions[0] : "");
|
||||
SetTitle("Untitled" + ext);
|
||||
}
|
||||
else
|
||||
|
|
|
@ -51,7 +51,7 @@ namespace CodeImp.DoomBuilder.Controls
|
|||
#region ================== Constructor / Disposer
|
||||
|
||||
// Constructor
|
||||
public ScriptLumpDocumentTab(ScriptEditorPanel panel, string lumpname, ScriptConfiguration config) : base(panel)
|
||||
public ScriptLumpDocumentTab(ScriptEditorPanel panel, string lumpname, ScriptConfiguration config) : base(panel, config)
|
||||
{
|
||||
// Initialize
|
||||
if(lumpname == MapManager.CONFIG_MAP_HEADER)
|
||||
|
@ -65,9 +65,6 @@ namespace CodeImp.DoomBuilder.Controls
|
|||
this.ismapheader = false;
|
||||
}
|
||||
|
||||
this.config = config;
|
||||
editor.SetupStyles(config);
|
||||
|
||||
// Load the lump data
|
||||
MemoryStream stream = General.Map.GetLumpData(this.lumpname);
|
||||
if(stream != null)
|
||||
|
|
45
Source/Core/Data/Scripting/AccScriptHandler.cs
Normal file
45
Source/Core/Data/Scripting/AccScriptHandler.cs
Normal file
|
@ -0,0 +1,45 @@
|
|||
#region ================== Namespaces
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Windows.Forms;
|
||||
using CodeImp.DoomBuilder.Compilers;
|
||||
using CodeImp.DoomBuilder.Config;
|
||||
using CodeImp.DoomBuilder.Controls;
|
||||
using CodeImp.DoomBuilder.ZDoom.Scripting;
|
||||
|
||||
#endregion
|
||||
|
||||
namespace CodeImp.DoomBuilder.Data.Scripting
|
||||
{
|
||||
[ScriptHandler(ScriptType.ACS)]
|
||||
internal class AccScriptHandler : ScriptHandler
|
||||
{
|
||||
#region ================== Methods
|
||||
|
||||
//TODO: Remove ScriptDocumentTab from here
|
||||
public override List<CompilerError> UpdateFunctionBarItems(ScriptDocumentTab tab, MemoryStream stream, ComboBox target)
|
||||
{
|
||||
List<CompilerError> result = new List<CompilerError>();
|
||||
if(stream == null) return result;
|
||||
target.Items.Clear();
|
||||
|
||||
AcsParserSE parser = new AcsParserSE { AddArgumentsToScriptNames = true, IsMapScriptsLump = tab is ScriptLumpDocumentTab, IgnoreErrors = true };
|
||||
TextResourceData data = new TextResourceData(stream, new DataLocation(), (parser.IsMapScriptsLump ? "?SCRIPTS" : tab.Filename), false);
|
||||
|
||||
if(parser.Parse(data, false))
|
||||
{
|
||||
target.Items.AddRange(parser.NamedScripts.ToArray());
|
||||
target.Items.AddRange(parser.NumberedScripts.ToArray());
|
||||
target.Items.AddRange(parser.Functions.ToArray());
|
||||
}
|
||||
|
||||
if(parser.HasError)
|
||||
result.Add(new CompilerError(parser.ErrorDescription, parser.ErrorSource, parser.ErrorLine));
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
41
Source/Core/Data/Scripting/DecorateScriptHandler.cs
Normal file
41
Source/Core/Data/Scripting/DecorateScriptHandler.cs
Normal file
|
@ -0,0 +1,41 @@
|
|||
#region ================== Namespaces
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Windows.Forms;
|
||||
using CodeImp.DoomBuilder.Compilers;
|
||||
using CodeImp.DoomBuilder.Config;
|
||||
using CodeImp.DoomBuilder.Controls;
|
||||
using CodeImp.DoomBuilder.ZDoom.Scripting;
|
||||
|
||||
#endregion
|
||||
|
||||
namespace CodeImp.DoomBuilder.Data.Scripting
|
||||
{
|
||||
[ScriptHandler(ScriptType.DECORATE)]
|
||||
internal class DecorateScriptHandler : ScriptHandler
|
||||
{
|
||||
#region ================== Methods
|
||||
|
||||
//TODO: Remove ScriptDocumentTab from here
|
||||
public override List<CompilerError> UpdateFunctionBarItems(ScriptDocumentTab tab, MemoryStream stream, ComboBox target)
|
||||
{
|
||||
List<CompilerError> result = new List<CompilerError>();
|
||||
if(stream == null) return result;
|
||||
target.Items.Clear();
|
||||
|
||||
DecorateParserSE parser = new DecorateParserSE();
|
||||
TextResourceData data = new TextResourceData(stream, new DataLocation(), "DECORATE", false);
|
||||
|
||||
if(parser.Parse(data, false))
|
||||
target.Items.AddRange(parser.Actors.ToArray());
|
||||
|
||||
if(parser.HasError)
|
||||
result.Add(new CompilerError(parser.ErrorDescription, parser.ErrorSource, parser.ErrorLine));
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
41
Source/Core/Data/Scripting/ModeldefScriptHandler.cs
Normal file
41
Source/Core/Data/Scripting/ModeldefScriptHandler.cs
Normal file
|
@ -0,0 +1,41 @@
|
|||
#region ================== Namespaces
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Windows.Forms;
|
||||
using CodeImp.DoomBuilder.Compilers;
|
||||
using CodeImp.DoomBuilder.Config;
|
||||
using CodeImp.DoomBuilder.Controls;
|
||||
using CodeImp.DoomBuilder.ZDoom.Scripting;
|
||||
|
||||
#endregion
|
||||
|
||||
namespace CodeImp.DoomBuilder.Data.Scripting
|
||||
{
|
||||
[ScriptHandler(ScriptType.MODELDEF)]
|
||||
internal class ModeldefScriptHandler : ScriptHandler
|
||||
{
|
||||
#region ================== Methods
|
||||
|
||||
//TODO: Remove ScriptDocumentTab from here
|
||||
public override List<CompilerError> UpdateFunctionBarItems(ScriptDocumentTab tab, MemoryStream stream, ComboBox target)
|
||||
{
|
||||
List<CompilerError> result = new List<CompilerError>();
|
||||
if(stream == null) return result;
|
||||
target.Items.Clear();
|
||||
|
||||
ModeldefParserSE parser = new ModeldefParserSE();
|
||||
TextResourceData data = new TextResourceData(stream, new DataLocation(), "MODELDEF", false);
|
||||
|
||||
if(parser.Parse(data, false))
|
||||
target.Items.AddRange(parser.Models.ToArray());
|
||||
|
||||
if(parser.HasError)
|
||||
result.Add(new CompilerError(parser.ErrorDescription, parser.ErrorSource, parser.ErrorLine));
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
442
Source/Core/Data/Scripting/ScriptHandler.cs
Normal file
442
Source/Core/Data/Scripting/ScriptHandler.cs
Normal file
|
@ -0,0 +1,442 @@
|
|||
#region ================== Namespaces
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Windows.Forms;
|
||||
using CodeImp.DoomBuilder.Compilers;
|
||||
using CodeImp.DoomBuilder.Config;
|
||||
using CodeImp.DoomBuilder.Controls;
|
||||
using CodeImp.DoomBuilder.IO;
|
||||
using ScintillaNET;
|
||||
|
||||
#endregion
|
||||
|
||||
namespace CodeImp.DoomBuilder.Data.Scripting
|
||||
{
|
||||
[ScriptHandler(ScriptType.UNKNOWN)]
|
||||
internal class ScriptHandler
|
||||
{
|
||||
#region ================== Constants
|
||||
|
||||
protected const int MAX_BACKTRACK_LENGTH = 200;
|
||||
|
||||
#endregion
|
||||
|
||||
#region ================== Variables
|
||||
|
||||
private ScriptEditorControl scriptcontrol;
|
||||
protected Scintilla scriptedit;
|
||||
protected ScriptConfiguration scriptconfig;
|
||||
|
||||
// List of keywords and constants
|
||||
private List<string> autocompletelist;
|
||||
|
||||
// Current position information
|
||||
private string curfunctionname = "";
|
||||
private int curargumentindex;
|
||||
private int curfunctionstartpos;
|
||||
|
||||
#endregion
|
||||
|
||||
#region ================== Methods
|
||||
|
||||
public virtual void Initialize(ScriptEditorControl scriptcontrol, ScriptConfiguration scriptconfig)
|
||||
{
|
||||
this.scriptcontrol = scriptcontrol;
|
||||
this.scriptedit = scriptcontrol.Scintilla;
|
||||
this.scriptconfig = scriptconfig;
|
||||
|
||||
// Bind events
|
||||
this.scriptedit.KeyUp += scriptedit_KeyUp;
|
||||
}
|
||||
|
||||
//TODO: Remove ScriptDocumentTab from here
|
||||
public virtual List<CompilerError> UpdateFunctionBarItems(ScriptDocumentTab tab, MemoryStream stream, ComboBox target)
|
||||
{
|
||||
// Unsupported script type. Just clear the items
|
||||
target.Items.Clear();
|
||||
return new List<CompilerError>();
|
||||
}
|
||||
|
||||
//mxd. Autocompletion handling (https://github.com/jacobslusser/ScintillaNET/wiki/Basic-Autocompletion)
|
||||
public virtual bool ShowAutoCompletionList()
|
||||
{
|
||||
int currentpos = scriptedit.CurrentPosition;
|
||||
int wordstartpos = scriptedit.WordStartPosition(currentpos, true);
|
||||
|
||||
if(wordstartpos >= currentpos)
|
||||
{
|
||||
// Hide the list
|
||||
scriptedit.AutoCCancel();
|
||||
return false;
|
||||
}
|
||||
|
||||
// Get entered text
|
||||
string start = scriptedit.GetTextRange(wordstartpos, currentpos - wordstartpos);
|
||||
if(string.IsNullOrEmpty(start))
|
||||
{
|
||||
// Hide the list
|
||||
scriptedit.AutoCCancel();
|
||||
return false;
|
||||
}
|
||||
|
||||
// Don't show Auto-completion list when editing comment, include or string
|
||||
switch(scriptcontrol.GetScriptStyle(scriptedit.GetStyleAt(currentpos)))
|
||||
{
|
||||
case ScriptStyleType.Comment:
|
||||
case ScriptStyleType.String:
|
||||
// Hide the list
|
||||
scriptedit.AutoCCancel();
|
||||
return false;
|
||||
|
||||
case ScriptStyleType.Include:
|
||||
// Hide the list unless current word is a keyword
|
||||
if(!start.StartsWith("#"))
|
||||
{
|
||||
scriptedit.AutoCCancel();
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// Filter the list
|
||||
List<string> filtered = new List<string>();
|
||||
foreach(string s in autocompletelist)
|
||||
if(s.IndexOf(start, StringComparison.OrdinalIgnoreCase) != -1) filtered.Add(s);
|
||||
|
||||
// Any matches?
|
||||
if(filtered.Count > 0)
|
||||
{
|
||||
// Show the list
|
||||
scriptedit.AutoCShow(currentpos - wordstartpos, string.Join(" ", filtered.ToArray()));
|
||||
return true;
|
||||
}
|
||||
|
||||
// Hide the list
|
||||
scriptedit.AutoCCancel();
|
||||
return false;
|
||||
}
|
||||
|
||||
internal void SetKeywords(Configuration lexercfg, string lexername)
|
||||
{
|
||||
Dictionary<string, string> autocompletedict = new Dictionary<string, string>(StringComparer.Ordinal);
|
||||
|
||||
// Create the keywords list and apply it
|
||||
string imageindex = ((int)ScriptEditorControl.ImageIndex.ScriptKeyword).ToString(CultureInfo.InvariantCulture);
|
||||
int keywordsindex = lexercfg.ReadSetting(lexername + ".keywordsindex", -1);
|
||||
if(keywordsindex > -1)
|
||||
{
|
||||
StringBuilder keywordslist = new StringBuilder();
|
||||
foreach(string k in scriptconfig.Keywords)
|
||||
{
|
||||
if(keywordslist.Length > 0) keywordslist.Append(" ");
|
||||
keywordslist.Append(k);
|
||||
|
||||
//mxd. Skip adding the keyword if we have a snippet with the same name
|
||||
if(!scriptconfig.Snippets.Contains(k)) autocompletedict.Add(k, k + "?" + imageindex);
|
||||
}
|
||||
string words = keywordslist.ToString();
|
||||
scriptedit.SetKeywords(keywordsindex, (scriptconfig.CaseSensitive ? words : words.ToLowerInvariant()));
|
||||
}
|
||||
|
||||
//mxd. Create the properties list and apply it
|
||||
imageindex = ((int)ScriptEditorControl.ImageIndex.ScriptProperty).ToString(CultureInfo.InvariantCulture);
|
||||
int propertiesindex = lexercfg.ReadSetting(lexername + ".propertiesindex", -1);
|
||||
if(propertiesindex > -1)
|
||||
{
|
||||
StringBuilder propertieslist = new StringBuilder();
|
||||
HashSet<string> addedprops = new HashSet<string>();
|
||||
char[] dot = { '.' };
|
||||
foreach(string p in scriptconfig.Properties)
|
||||
{
|
||||
if(propertieslist.Length > 0) propertieslist.Append(" ");
|
||||
|
||||
// Scintilla doesn't highlight keywords with '.' or ':', so get rid of those
|
||||
if(scriptconfig.ScriptType == ScriptType.DECORATE)
|
||||
{
|
||||
string prop = p;
|
||||
if(prop.Contains(":")) prop = prop.Replace(":", string.Empty);
|
||||
if(prop.Contains("."))
|
||||
{
|
||||
// Split dotted properties into separate entries
|
||||
string[] parts = prop.Split(dot, StringSplitOptions.RemoveEmptyEntries);
|
||||
List<string> result = new List<string>();
|
||||
foreach(string part in parts)
|
||||
{
|
||||
if(!addedprops.Contains(part))
|
||||
{
|
||||
result.Add(part);
|
||||
addedprops.Add(part);
|
||||
}
|
||||
}
|
||||
|
||||
if(result.Count > 0) propertieslist.Append(string.Join(" ", result.ToArray()));
|
||||
}
|
||||
else
|
||||
{
|
||||
addedprops.Add(prop);
|
||||
propertieslist.Append(prop);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
propertieslist.Append(p);
|
||||
}
|
||||
|
||||
// Autocomplete doesn't mind '.' or ':'
|
||||
// Skip adding the keyword if we have a snippet with the same name
|
||||
if(!scriptconfig.Snippets.Contains(p))
|
||||
{
|
||||
if(autocompletedict.ContainsKey(p))
|
||||
General.ErrorLogger.Add(ErrorType.Warning, "Property \"" + p + "\" is double defined in \"" + scriptconfig.Description + "\" script configuration.");
|
||||
else
|
||||
autocompletedict.Add(p, p + "?" + imageindex);
|
||||
}
|
||||
}
|
||||
string words = propertieslist.ToString();
|
||||
scriptedit.SetKeywords(propertiesindex, (scriptconfig.CaseSensitive ? words : words.ToLowerInvariant()));
|
||||
}
|
||||
|
||||
// Create the constants list and apply it
|
||||
imageindex = ((int)ScriptEditorControl.ImageIndex.ScriptConstant).ToString(CultureInfo.InvariantCulture);
|
||||
int constantsindex = lexercfg.ReadSetting(lexername + ".constantsindex", -1);
|
||||
if(constantsindex > -1)
|
||||
{
|
||||
StringBuilder constantslist = new StringBuilder();
|
||||
foreach(string c in scriptconfig.Constants)
|
||||
{
|
||||
if(autocompletedict.ContainsKey(c))
|
||||
continue; //mxd. This happens when there's a keyword and a constant with the same name...
|
||||
|
||||
if(constantslist.Length > 0) constantslist.Append(" ");
|
||||
constantslist.Append(c);
|
||||
|
||||
//mxd. Skip adding the constant if we have a snippet with the same name
|
||||
if(!scriptconfig.Snippets.Contains(c)) autocompletedict.Add(c, c + "?" + imageindex);
|
||||
}
|
||||
string words = constantslist.ToString();
|
||||
scriptedit.SetKeywords(constantsindex, (scriptconfig.CaseSensitive ? words : words.ToLowerInvariant()));
|
||||
}
|
||||
|
||||
//mxd. Create the snippets list and apply it
|
||||
imageindex = ((int)ScriptEditorControl.ImageIndex.ScriptSnippet).ToString(CultureInfo.InvariantCulture);
|
||||
int snippetindex = lexercfg.ReadSetting(lexername + ".snippetindex", -1);
|
||||
if(snippetindex > -1 && scriptconfig.Snippets.Count > 0)
|
||||
{
|
||||
StringBuilder snippetslist = new StringBuilder();
|
||||
foreach(string s in scriptconfig.Snippets)
|
||||
{
|
||||
if(snippetslist.Length > 0) snippetslist.Append(" ");
|
||||
snippetslist.Append(s);
|
||||
autocompletedict.Add(s, s + "?" + imageindex);
|
||||
}
|
||||
string words = snippetslist.ToString();
|
||||
scriptedit.SetKeywords(snippetindex, (scriptconfig.CaseSensitive ? words : words.ToLowerInvariant()));
|
||||
}
|
||||
|
||||
// Make autocomplete list
|
||||
autocompletelist = new List<string>(autocompletedict.Values);
|
||||
}
|
||||
|
||||
// This gathers information about the current caret position
|
||||
protected void UpdatePositionInfo()
|
||||
{
|
||||
int bracketlevel = 0; // bracket level counting
|
||||
int argindex = 0; // function argument counting
|
||||
int pos = scriptedit.CurrentPosition;
|
||||
|
||||
// Get the text
|
||||
string scripttext = scriptedit.Text;
|
||||
|
||||
// Reset position info
|
||||
curfunctionname = "";
|
||||
curargumentindex = 0;
|
||||
curfunctionstartpos = 0;
|
||||
|
||||
// Determine lowest backtrack position
|
||||
int limitpos = Math.Max(0, scriptedit.CurrentPosition - MAX_BACKTRACK_LENGTH);
|
||||
|
||||
// We can only do this when we have function syntax information
|
||||
if((scriptconfig.ArgumentDelimiter.Length == 0) || (scriptconfig.FunctionClose.Length == 0) ||
|
||||
(scriptconfig.FunctionOpen.Length == 0) || (scriptconfig.Terminator.Length == 0))
|
||||
return;
|
||||
|
||||
// Get int versions of the function syntax informantion
|
||||
int argumentdelimiter = scriptconfig.ArgumentDelimiter[0];
|
||||
int functionclose = scriptconfig.FunctionClose[0];
|
||||
int functionopen = scriptconfig.FunctionOpen[0];
|
||||
int terminator = scriptconfig.Terminator[0];
|
||||
|
||||
// Continue backtracking until we reached the limitpos
|
||||
while(pos >= limitpos)
|
||||
{
|
||||
// Backtrack 1 character
|
||||
pos--;
|
||||
|
||||
// Get the style and character at this position
|
||||
ScriptStyleType curstyle = scriptcontrol.GetScriptStyle(scriptedit.GetStyleAt(pos));
|
||||
int curchar = scriptedit.GetCharAt(pos);
|
||||
|
||||
// Then meeting ) then increase bracket level
|
||||
// When meeting ( then decrease bracket level
|
||||
// When bracket level goes -1, then the next word should be the function name
|
||||
// Only when at bracket level 0, count the comma's for argument index
|
||||
|
||||
// TODO:
|
||||
// Original code checked for scope character here and breaks if found
|
||||
|
||||
// Check if in plain text or keyword
|
||||
if((curstyle == ScriptStyleType.PlainText) || (curstyle == ScriptStyleType.Keyword))
|
||||
{
|
||||
// Closing bracket
|
||||
if(curchar == functionclose)
|
||||
{
|
||||
bracketlevel++;
|
||||
}
|
||||
// Opening bracket
|
||||
else if(curchar == functionopen)
|
||||
{
|
||||
bracketlevel--;
|
||||
|
||||
// Out of the brackets?
|
||||
if(bracketlevel < 0)
|
||||
{
|
||||
// Skip any whitespace before this bracket
|
||||
do
|
||||
{
|
||||
// Backtrack 1 character
|
||||
curchar = scriptedit.GetCharAt(--pos);
|
||||
}
|
||||
while((pos >= limitpos) && ((curchar == ' ') || (curchar == '\t') ||
|
||||
(curchar == '\r') || (curchar == '\n')));
|
||||
|
||||
// NOTE: We may need to set onlyWordCharacters argument in the
|
||||
// following calls to false to get any argument delimiter included,
|
||||
// but this may also cause a valid keyword to be combined with other
|
||||
// surrounding characters that do not belong to the keyword.
|
||||
|
||||
// Find the word before this bracket
|
||||
int wordstart = scriptedit.WordStartPosition(pos, true);
|
||||
int wordend = scriptedit.WordEndPosition(pos, true);
|
||||
string word = scripttext.Substring(wordstart, wordend - wordstart);
|
||||
if(word.Length > 0)
|
||||
{
|
||||
// Check if this is an argument delimiter
|
||||
// I can't remember why I did this, but I'll probably stumble
|
||||
// upon the problem if this doesn't work right (see note above)
|
||||
if(word[0] == argumentdelimiter)
|
||||
{
|
||||
// We are now in the parent function
|
||||
bracketlevel++;
|
||||
argindex = 0;
|
||||
}
|
||||
// Now check if this is a keyword
|
||||
else if(scriptconfig.IsKeyword(word))
|
||||
{
|
||||
// Found it!
|
||||
curfunctionname = scriptconfig.GetKeywordCase(word);
|
||||
curargumentindex = argindex;
|
||||
curfunctionstartpos = wordstart;
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Don't know this word
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Argument delimiter
|
||||
else if(curchar == argumentdelimiter)
|
||||
{
|
||||
// Only count these at brackt level 0
|
||||
if(bracketlevel == 0) argindex++;
|
||||
}
|
||||
// Terminator
|
||||
else if(curchar == terminator)
|
||||
{
|
||||
// Can't find anything, break now
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region ================== Events
|
||||
|
||||
private void scriptedit_KeyUp(object sender, KeyEventArgs e)
|
||||
{
|
||||
bool showcalltip = false;
|
||||
int highlightstart = 0;
|
||||
int highlightend = 0;
|
||||
|
||||
UpdatePositionInfo();
|
||||
|
||||
// Call tip shown
|
||||
if(scriptedit.CallTipActive)
|
||||
{
|
||||
// Should we hide the call tip?
|
||||
if(curfunctionname.Length == 0)
|
||||
{
|
||||
// Hide the call tip
|
||||
scriptedit.CallTipCancel();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Update the call tip
|
||||
showcalltip = true;
|
||||
}
|
||||
}
|
||||
// No call tip
|
||||
else
|
||||
{
|
||||
// Should we show a call tip?
|
||||
showcalltip = (curfunctionname.Length > 0) && !scriptedit.AutoCActive;
|
||||
}
|
||||
|
||||
// Show or update call tip
|
||||
if(showcalltip)
|
||||
{
|
||||
string functiondef = scriptconfig.GetFunctionDefinition(curfunctionname);
|
||||
if(functiondef != null)
|
||||
{
|
||||
// Determine the range to highlight
|
||||
int argsopenpos = functiondef.IndexOf(scriptconfig.FunctionOpen, StringComparison.Ordinal);
|
||||
int argsclosepos = functiondef.LastIndexOf(scriptconfig.FunctionClose, StringComparison.Ordinal);
|
||||
if((argsopenpos > -1) && (argsclosepos > -1))
|
||||
{
|
||||
string argsstr = functiondef.Substring(argsopenpos + 1, argsclosepos - argsopenpos - 1);
|
||||
string[] args = argsstr.Split(scriptconfig.ArgumentDelimiter[0]);
|
||||
if((curargumentindex >= 0) && (curargumentindex < args.Length))
|
||||
{
|
||||
int argoffset = 0;
|
||||
for(int i = 0; i < curargumentindex; i++) argoffset += args[i].Length + 1;
|
||||
highlightstart = argsopenpos + argoffset + 1;
|
||||
highlightend = highlightstart + args[curargumentindex].Length;
|
||||
}
|
||||
}
|
||||
|
||||
//mxd. If the tip obscures the view, move it down
|
||||
int tippos;
|
||||
int funcline = scriptedit.LineFromPosition(curfunctionstartpos);
|
||||
if(scriptedit.CurrentLine > funcline)
|
||||
tippos = scriptedit.Lines[scriptedit.CurrentLine].Position + scriptedit.Lines[scriptedit.CurrentLine].Indentation; //scriptedit.PositionFromLine(curline) /*+ (curfunctionstartpos - scriptedit.PositionFromLine(funcline))*/;
|
||||
else
|
||||
tippos = curfunctionstartpos;
|
||||
|
||||
// Show tip
|
||||
scriptedit.CallTipShow(tippos, functiondef);
|
||||
scriptedit.CallTipSetHlt(highlightstart, highlightend);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
38
Source/Core/Data/Scripting/ScriptHandlerAttribute.cs
Normal file
38
Source/Core/Data/Scripting/ScriptHandlerAttribute.cs
Normal file
|
@ -0,0 +1,38 @@
|
|||
#region ================== Namespaces
|
||||
|
||||
using System;
|
||||
using CodeImp.DoomBuilder.Config;
|
||||
|
||||
#endregion
|
||||
|
||||
namespace CodeImp.DoomBuilder.Data.Scripting
|
||||
{
|
||||
[AttributeUsage(AttributeTargets.Class, Inherited = true, AllowMultiple = true)]
|
||||
public sealed class ScriptHandlerAttribute : Attribute
|
||||
{
|
||||
#region ================== Variables
|
||||
|
||||
private Type type;
|
||||
private ScriptType scripttype;
|
||||
|
||||
#endregion
|
||||
|
||||
#region ================== Properties
|
||||
|
||||
public Type Type { get { return type; } set { type = value; } }
|
||||
public ScriptType ScriptType { get { return scripttype; } }
|
||||
|
||||
#endregion
|
||||
|
||||
#region ================== Constructor / Destructor
|
||||
|
||||
// Constructor
|
||||
public ScriptHandlerAttribute(ScriptType scripttype)
|
||||
{
|
||||
// Initialize
|
||||
this.scripttype = scripttype;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
|
@ -19,6 +19,7 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using CodeImp.DoomBuilder.Config;
|
||||
using CodeImp.DoomBuilder.Data.Scripting;
|
||||
|
||||
#endregion
|
||||
|
||||
|
@ -34,7 +35,10 @@ namespace CodeImp.DoomBuilder.Types
|
|||
|
||||
// List of handler types
|
||||
private Dictionary<int, TypeHandlerAttribute> handlertypes;
|
||||
|
||||
|
||||
//mxd. List of script handler types
|
||||
private Dictionary<ScriptType, ScriptHandlerAttribute> scripthandlertypes;
|
||||
|
||||
// Disposing
|
||||
private bool isdisposed;
|
||||
|
||||
|
@ -53,6 +57,7 @@ namespace CodeImp.DoomBuilder.Types
|
|||
{
|
||||
// Initialize
|
||||
handlertypes = new Dictionary<int, TypeHandlerAttribute>();
|
||||
scripthandlertypes = new Dictionary<ScriptType, ScriptHandlerAttribute>(); //mxd
|
||||
|
||||
// Go for all types in this assembly
|
||||
Type[] types = General.ThisAssembly.GetTypes();
|
||||
|
@ -70,6 +75,15 @@ namespace CodeImp.DoomBuilder.Types
|
|||
attr.Type = tp;
|
||||
handlertypes.Add(attr.Index, attr);
|
||||
}
|
||||
//mxd. Check if class has an ScriptHandler attribute
|
||||
else if(Attribute.IsDefined(tp, typeof(ScriptHandlerAttribute), false))
|
||||
{
|
||||
// Add the type to the list
|
||||
object[] attribs = tp.GetCustomAttributes(typeof(ScriptHandlerAttribute), false);
|
||||
ScriptHandlerAttribute attr = (attribs[0] as ScriptHandlerAttribute);
|
||||
attr.Type = tp;
|
||||
scripthandlertypes[attr.ScriptType] = attr;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -179,8 +193,23 @@ namespace CodeImp.DoomBuilder.Types
|
|||
public TypeHandlerAttribute GetAttribute(int type)
|
||||
{
|
||||
// Do we have a handler type for this?
|
||||
if(handlertypes.ContainsKey(type)) return handlertypes[type];
|
||||
else return null;
|
||||
return (handlertypes.ContainsKey(type) ? handlertypes[type] : null);
|
||||
}
|
||||
|
||||
//mxd
|
||||
public ScriptHandler GetScriptHandler(ScriptType type)
|
||||
{
|
||||
Type t = typeof(ScriptHandler);
|
||||
|
||||
// Do we have a handler type for this?
|
||||
if(scripthandlertypes.ContainsKey(type))
|
||||
{
|
||||
t = scripthandlertypes[type].Type;
|
||||
}
|
||||
|
||||
// Create instance
|
||||
ScriptHandler th = (ScriptHandler)General.ThisAssembly.CreateInstance(t.FullName);
|
||||
return th;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
|
|
@ -112,7 +112,8 @@ namespace CodeImp.DoomBuilder.BuilderModes
|
|||
texscale = new Vector2D(1.0f / 64.0f, 1.0f / 64.0f);
|
||||
|
||||
// Determine brightness
|
||||
int color = PixelColor.FromInt(level.color).WithAlpha((byte)General.Clamp(level.alpha, 0, 255)).ToInt();
|
||||
byte alpha = (byte)General.Clamp(level.alpha, 0, 255);
|
||||
int color = PixelColor.FromInt(level.color).WithAlpha(alpha).ToInt();
|
||||
int targetbrightness;
|
||||
if(extrafloor != null && !extrafloor.VavoomType && !level.disablelighting)
|
||||
{
|
||||
|
@ -139,7 +140,7 @@ namespace CodeImp.DoomBuilder.BuilderModes
|
|||
{
|
||||
if(sd.LightLevels[i] == level)
|
||||
{
|
||||
if(i > 0) color = sd.LightLevels[i - 1].color;
|
||||
if(i > 0) color = PixelColor.FromInt(sd.LightLevels[i - 1].color).WithAlpha(alpha).ToInt();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue