mirror of
https://git.do.srb2.org/STJr/UltimateZoneBuilder.git
synced 2024-11-27 14:12:16 +00:00
dbcc57b7a6
Fixed, Script Editor: in some cases clicking on an error in the errors list didn't navigate to the error location. Fixed, Script Editor: in some cases incorrect error line number was shown. Fixed, Text lump parsers: fixed a crash when trying to get a filename from a quoted string with missing closing quote. Fixed, Text lump parsers: in several cases parsing errors were ignored by overlaying data structures. Fixed: in some cases Thing Filter thing flags were cleared when switching game configurations in the "Game Configurations" window. Changed, PK3 reader: loading of files with invalid path chars is now skipped instead of skipping loading of the whole resource. Also more helpful warning message is now displayed. Updated SharpCompress library to v.0.11.2.0.
957 lines
33 KiB
C#
957 lines
33 KiB
C#
|
|
#region ================== Copyright (c) 2007 Pascal vd Heiden
|
|
|
|
/*
|
|
* Copyright (c) 2007 Pascal vd Heiden, www.codeimp.com
|
|
* This program is released under GNU General Public License
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
*/
|
|
|
|
#endregion
|
|
|
|
#region ================== Namespaces
|
|
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Text;
|
|
using System.Windows.Forms;
|
|
using CodeImp.DoomBuilder.Config;
|
|
using System.IO;
|
|
using CodeImp.DoomBuilder.IO;
|
|
using System.Collections;
|
|
using System.Globalization;
|
|
using CodeImp.DoomBuilder.Rendering;
|
|
using CodeImp.DoomBuilder.Properties;
|
|
using CodeImp.DoomBuilder.Windows;
|
|
|
|
#endregion
|
|
|
|
namespace CodeImp.DoomBuilder.Controls
|
|
{
|
|
internal partial class ScriptEditorControl : UserControl
|
|
{
|
|
#region ================== Constants
|
|
|
|
private const string LEXERS_RESOURCE = "Lexers.cfg";
|
|
private const int DEFAULT_STYLE = (int)ScriptStylesCommon.Default;
|
|
private const int MAX_BACKTRACK_LENGTH = 200;
|
|
|
|
// Index for registered images
|
|
private enum ImageIndex
|
|
{
|
|
ScriptConstant = 0,
|
|
ScriptKeyword = 1,
|
|
ScriptError = 2,
|
|
ScriptSnippet = 3, //mxd
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region ================== Delegates / Events
|
|
|
|
public delegate void ExplicitSaveTabDelegate();
|
|
public delegate void OpenScriptBrowserDelegate();
|
|
public delegate void OpenFindReplaceDelegate();
|
|
public delegate void FindNextDelegate();
|
|
public delegate void FindPreviousDelegate(); //mxd
|
|
|
|
public event ExplicitSaveTabDelegate OnExplicitSaveTab;
|
|
public event OpenScriptBrowserDelegate OnOpenScriptBrowser;
|
|
public event OpenFindReplaceDelegate OnOpenFindAndReplace;
|
|
public event FindNextDelegate OnFindNext;
|
|
public event FindPreviousDelegate OnFindPrevious; //mxd
|
|
public new event EventHandler OnTextChanged; //mxd
|
|
|
|
#endregion
|
|
|
|
#region ================== Variables
|
|
|
|
// Script configuration
|
|
private ScriptConfiguration scriptconfig;
|
|
|
|
// List of keywords and constants, sorted as uppercase
|
|
private string autocompletestring;
|
|
|
|
// Style translation from Scintilla style to ScriptStyleType
|
|
private Dictionary<int, ScriptStyleType> stylelookup;
|
|
|
|
// Current position information
|
|
private string curfunctionname = "";
|
|
private int curargumentindex;
|
|
private int curfunctionstartpos;
|
|
|
|
// Status
|
|
private bool changed;
|
|
|
|
#endregion
|
|
|
|
#region ================== Properties
|
|
|
|
public bool IsChanged { get { return changed; } set { changed = value; } }
|
|
public int Position { get { return scriptedit.CurrentPos; } set { scriptedit.CurrentPos = value; } }
|
|
public int SelectionStart { get { return scriptedit.SelectionStart; } set { scriptedit.SelectionStart = value; } }
|
|
public int SelectionEnd { get { return scriptedit.SelectionEnd; } set { scriptedit.SelectionEnd = value; } }
|
|
|
|
#endregion
|
|
|
|
#region ================== Contructor / Disposer
|
|
|
|
// Constructor
|
|
public ScriptEditorControl()
|
|
{
|
|
// Initialize
|
|
InitializeComponent();
|
|
|
|
// Script editor properties
|
|
// Unfortunately, these cannot be set using the designer
|
|
// because the control is not really loaded in design mode
|
|
scriptedit.AutoCMaximumHeight = 8;
|
|
scriptedit.AutoCSeparator = ' ';
|
|
scriptedit.AutoCTypeSeparator = '?';
|
|
scriptedit.AutoCSetFillUps("\r\n();[]"); // I should put this in the script configs
|
|
scriptedit.CaretWidth = 2;
|
|
scriptedit.EndAtLastLine = 1;
|
|
scriptedit.EndOfLineMode = ScriptEndOfLine.CRLF;
|
|
scriptedit.IsAutoCGetChooseSingle = true;
|
|
scriptedit.IsAutoCGetIgnoreCase = true;
|
|
scriptedit.IsBackSpaceUnIndents = true;
|
|
scriptedit.IsBufferedDraw = true;
|
|
scriptedit.IsCaretLineVisible = false;
|
|
scriptedit.IsHScrollBar = true;
|
|
scriptedit.IndentationGuides = (int)ScriptIdentGuides.None;
|
|
scriptedit.IsMouseDownCaptures = true;
|
|
scriptedit.IsTabIndents = true;
|
|
scriptedit.IsUndoCollection = true;
|
|
scriptedit.IsUseTabs = true;
|
|
scriptedit.IsViewEOL = false;
|
|
scriptedit.IsVScrollBar = true;
|
|
scriptedit.SetFoldFlags((int)ScriptFoldFlag.Box);
|
|
scriptedit.TabWidth = 4;
|
|
scriptedit.Indent = 4;
|
|
scriptedit.ExtraAscent = 1;
|
|
scriptedit.ExtraDescent = 1;
|
|
scriptedit.CursorType = -1;
|
|
|
|
// Symbol margin
|
|
scriptedit.SetMarginTypeN(0, (int)ScriptMarginType.Symbol);
|
|
scriptedit.SetMarginWidthN(0, 20);
|
|
scriptedit.SetMarginMaskN(0, -1); // all
|
|
|
|
// Line numbers margin
|
|
scriptedit.SetMarginTypeN(1, (int)ScriptMarginType.Number);
|
|
scriptedit.SetMarginWidthN(1, 40);
|
|
scriptedit.SetMarginMaskN(1, 0); // none
|
|
|
|
// Spacing margin
|
|
scriptedit.SetMarginTypeN(2, (int)ScriptMarginType.Symbol);
|
|
scriptedit.SetMarginWidthN(2, 5);
|
|
scriptedit.SetMarginMaskN(2, 0); // none
|
|
|
|
// Setup with default script config
|
|
// Disabled, the form designer doesn't like this
|
|
//SetupStyles(new ScriptConfiguration());
|
|
|
|
// Images
|
|
RegisterAutoCompleteImage(ImageIndex.ScriptConstant, Resources.ScriptConstant);
|
|
RegisterAutoCompleteImage(ImageIndex.ScriptKeyword, Resources.ScriptKeyword);
|
|
RegisterAutoCompleteImage(ImageIndex.ScriptSnippet, Resources.ScriptSnippet); //mxd
|
|
RegisterMarkerImage(ImageIndex.ScriptError, Resources.ScriptError);
|
|
|
|
// Events
|
|
scriptedit.ModEventMask = 0x7FFFF; // Which events to receive (see also ScriptModificationFlags)
|
|
scriptedit.TextDeleted += scriptedit_TextChanged; //mxd
|
|
scriptedit.TextInserted += scriptedit_TextChanged; //mxd
|
|
scriptedit.UndoPerformed += scriptedit_UndoRedoPerformed; //mxd
|
|
scriptedit.RedoPerformed += scriptedit_UndoRedoPerformed; //mxd
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region ================== Methods
|
|
|
|
// This launches keyword help website
|
|
public bool LaunchKeywordHelp()
|
|
{
|
|
string helpsite = scriptconfig.KeywordHelp;
|
|
string currentword = GetCurrentWord();
|
|
if(!string.IsNullOrEmpty(currentword) && (currentword.Length > 1) && !string.IsNullOrEmpty(helpsite))
|
|
{
|
|
currentword = scriptconfig.GetKeywordCase(currentword);
|
|
helpsite = helpsite.Replace("%K", currentword);
|
|
General.OpenWebsite(helpsite);
|
|
return true;
|
|
}
|
|
return !string.IsNullOrEmpty(helpsite); //mxd
|
|
}
|
|
|
|
// This replaces the selection with the given text
|
|
public void ReplaceSelection(string replacement)
|
|
{
|
|
Encoding encoder = Encoding.GetEncoding(scriptedit.CodePage);
|
|
string text = encoder.GetString(GetText());
|
|
int selectionstart = scriptedit.SelectionStart;
|
|
|
|
// Make new text
|
|
StringBuilder newtext = new StringBuilder(text.Length + replacement.Length);
|
|
newtext.Append(text.Substring(0, scriptedit.SelectionStart));
|
|
newtext.Append(replacement);
|
|
newtext.Append(text.Substring(scriptedit.SelectionEnd));
|
|
|
|
SetText(encoder.GetBytes(newtext.ToString()));
|
|
|
|
// Adjust selection
|
|
scriptedit.SelectionStart = selectionstart;
|
|
scriptedit.SelectionEnd = selectionstart + replacement.Length;
|
|
}
|
|
|
|
// This moves the caret to a given line and ensures the line is visible
|
|
public void MoveToLine(int linenumber)
|
|
{
|
|
scriptedit.GotoLine(linenumber);
|
|
EnsureLineVisible(linenumber);
|
|
}
|
|
|
|
// This makes sure a line is visible
|
|
public void EnsureLineVisible(int linenumber)
|
|
{
|
|
scriptedit.EnsureVisibleEnforcePolicy(linenumber);
|
|
}
|
|
|
|
// This returns the line for a position
|
|
public int LineFromPosition(int position)
|
|
{
|
|
return scriptedit.LineFromPosition(position);
|
|
}
|
|
|
|
// This clears all marks
|
|
public void ClearMarks()
|
|
{
|
|
scriptedit.MarkerDeleteAll((int)ImageIndex.ScriptError);
|
|
}
|
|
|
|
// This adds a mark on the given line
|
|
public void AddMark(int linenumber)
|
|
{
|
|
scriptedit.MarkerAdd(linenumber, (int)ImageIndex.ScriptError);
|
|
}
|
|
|
|
// This refreshes the style setup
|
|
public void RefreshStyle()
|
|
{
|
|
// Re-setup with the same config
|
|
SetupStyles(scriptconfig);
|
|
}
|
|
|
|
// This sets up the script editor with a script configuration
|
|
public void SetupStyles(ScriptConfiguration config)
|
|
{
|
|
Configuration lexercfg = new Configuration();
|
|
|
|
// Make collections
|
|
stylelookup = new Dictionary<int, ScriptStyleType>();
|
|
SortedList<string, string> autocompletelist = new SortedList<string, string>(StringComparer.Ordinal);
|
|
|
|
// Keep script configuration
|
|
scriptconfig = config;
|
|
|
|
// Find a resource named Lexers.cfg
|
|
string[] resnames = General.ThisAssembly.GetManifestResourceNames();
|
|
foreach(string rn in resnames)
|
|
{
|
|
// Found one?
|
|
if(rn.EndsWith(LEXERS_RESOURCE, StringComparison.InvariantCultureIgnoreCase))
|
|
{
|
|
// Get a stream from the resource
|
|
Stream lexersdata = General.ThisAssembly.GetManifestResourceStream(rn);
|
|
StreamReader lexersreader = new StreamReader(lexersdata, Encoding.ASCII);
|
|
|
|
// Load configuration from stream
|
|
lexercfg.InputConfiguration(lexersreader.ReadToEnd());
|
|
|
|
// Done with the resource
|
|
lexersreader.Dispose();
|
|
lexersdata.Dispose();
|
|
}
|
|
}
|
|
|
|
// Check if specified lexer exists and set the lexer to use
|
|
string lexername = "lexer" + scriptconfig.Lexer.ToString(CultureInfo.InvariantCulture);
|
|
if(!lexercfg.SettingExists(lexername)) throw new InvalidOperationException("Unknown lexer " + scriptconfig.Lexer + " specified in script configuration!");
|
|
scriptedit.Lexer = scriptconfig.Lexer;
|
|
|
|
// Set the default style and settings
|
|
scriptedit.StyleSetFont(DEFAULT_STYLE, General.Settings.ScriptFontName);
|
|
scriptedit.StyleSetSize(DEFAULT_STYLE, General.Settings.ScriptFontSize);
|
|
scriptedit.StyleSetBold(DEFAULT_STYLE, General.Settings.ScriptFontBold);
|
|
scriptedit.StyleSetItalic(DEFAULT_STYLE, false);
|
|
scriptedit.StyleSetUnderline(DEFAULT_STYLE, false);
|
|
scriptedit.StyleSetCase(DEFAULT_STYLE, ScriptCaseVisible.Mixed);
|
|
scriptedit.StyleSetFore(DEFAULT_STYLE, General.Colors.PlainText.ToInversedColorRef());
|
|
scriptedit.StyleSetBack(DEFAULT_STYLE, General.Colors.ScriptBackground.ToInversedColorRef());
|
|
scriptedit.CaretPeriod = SystemInformation.CaretBlinkTime;
|
|
scriptedit.CaretFore = General.Colors.ScriptBackground.Inverse().ToInversedColorRef();
|
|
scriptedit.StyleBits = 7;
|
|
|
|
// These don't work?
|
|
scriptedit.TabWidth = General.Settings.ScriptTabWidth;
|
|
scriptedit.IsUseTabs = false;
|
|
scriptedit.IsTabIndents = true;
|
|
scriptedit.Indent = General.Settings.ScriptTabWidth;
|
|
scriptedit.IsBackSpaceUnIndents = true;
|
|
|
|
// This applies the default style to all styles
|
|
scriptedit.StyleClearAll();
|
|
|
|
// Set the code page to use
|
|
scriptedit.CodePage = scriptconfig.CodePage;
|
|
|
|
// Set the default to something normal (this is used by the autocomplete list)
|
|
scriptedit.StyleSetFont(DEFAULT_STYLE, this.Font.Name);
|
|
scriptedit.StyleSetBold(DEFAULT_STYLE, this.Font.Bold);
|
|
scriptedit.StyleSetItalic(DEFAULT_STYLE, this.Font.Italic);
|
|
scriptedit.StyleSetUnderline(DEFAULT_STYLE, this.Font.Underline);
|
|
scriptedit.StyleSetSize(DEFAULT_STYLE, (int)Math.Round(this.Font.SizeInPoints));
|
|
|
|
// Set style for linenumbers and margins
|
|
scriptedit.StyleSetBack((int)ScriptStylesCommon.LineNumber, General.Colors.ScriptBackground.ToInversedColorRef());
|
|
|
|
// Clear all keywords
|
|
for(int i = 0; i < 9; i++) scriptedit.KeyWords(i, null);
|
|
|
|
// Now go for all elements in the lexer configuration
|
|
// We are looking for the numeric keys, because these are the
|
|
// style index to set and the value is our ScriptStyleType
|
|
IDictionary dic = lexercfg.ReadSetting(lexername, new Hashtable());
|
|
foreach(DictionaryEntry de in dic)
|
|
{
|
|
// Check if this is a numeric key
|
|
int stylenum;
|
|
if(int.TryParse(de.Key.ToString(), out stylenum))
|
|
{
|
|
// Add style to lookup table
|
|
stylelookup.Add(stylenum, (ScriptStyleType)(int)de.Value);
|
|
|
|
// Apply color to style
|
|
int colorindex;
|
|
switch((ScriptStyleType)(int)de.Value)
|
|
{
|
|
case ScriptStyleType.PlainText: colorindex = ColorCollection.PLAINTEXT; break;
|
|
case ScriptStyleType.Comment: colorindex = ColorCollection.COMMENTS; break;
|
|
case ScriptStyleType.Constant: colorindex = ColorCollection.CONSTANTS; break;
|
|
case ScriptStyleType.Keyword: colorindex = ColorCollection.KEYWORDS; break;
|
|
case ScriptStyleType.LineNumber: colorindex = ColorCollection.LINENUMBERS; break;
|
|
case ScriptStyleType.Literal: colorindex = ColorCollection.LITERALS; break;
|
|
default: colorindex = ColorCollection.PLAINTEXT; break;
|
|
}
|
|
scriptedit.StyleSetFore(stylenum, General.Colors.Colors[colorindex].ToInversedColorRef());
|
|
}
|
|
}
|
|
|
|
// 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);
|
|
autocompletelist.Add(k.ToUpperInvariant(), k + "?" + imageindex);
|
|
}
|
|
string words = keywordslist.ToString();
|
|
if(scriptconfig.CaseSensitive)
|
|
scriptedit.KeyWords(keywordsindex, words);
|
|
else
|
|
scriptedit.KeyWords(keywordsindex, 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(autocompletelist.ContainsKey(c.ToUpperInvariant())) //mxd. This happens when there's a keyword and a constant with the same name...
|
|
{
|
|
General.ErrorLogger.Add(ErrorType.Error, "Constant '" + c + "' is double-defined in '" + scriptconfig.Description + "' script configuration!");
|
|
continue;
|
|
}
|
|
|
|
if(constantslist.Length > 0) constantslist.Append(" ");
|
|
constantslist.Append(c);
|
|
autocompletelist.Add(c.ToUpperInvariant(), c + "?" + imageindex);
|
|
}
|
|
string words = constantslist.ToString();
|
|
if(scriptconfig.CaseSensitive)
|
|
scriptedit.KeyWords(constantsindex, words);
|
|
else
|
|
scriptedit.KeyWords(constantsindex, 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 c in scriptconfig.Snippets.Keys)
|
|
{
|
|
if(autocompletelist.ContainsKey(c.ToUpperInvariant())) continue;
|
|
if(snippetslist.Length > 0) snippetslist.Append(" ");
|
|
snippetslist.Append(c);
|
|
autocompletelist.Add(c.ToUpperInvariant(), c + "?" + imageindex);
|
|
}
|
|
string words = snippetslist.ToString();
|
|
if(scriptconfig.CaseSensitive)
|
|
scriptedit.KeyWords(snippetindex, words);
|
|
else
|
|
scriptedit.KeyWords(snippetindex, words.ToLowerInvariant());
|
|
}
|
|
|
|
// Sort the autocomplete list
|
|
List<string> autocompleteplainlist = new List<string>(autocompletelist.Values);
|
|
autocompletestring = string.Join(" ", autocompleteplainlist.ToArray());
|
|
|
|
// Show/hide the functions bar
|
|
functionbar.Visible = (scriptconfig.FunctionRegEx.Length > 0);
|
|
|
|
// Rearrange the layout
|
|
bool ischanged = changed; //mxd. Don't want the "changed" status to change when changing text styles
|
|
scriptedit.ClearDocumentStyle();
|
|
scriptedit.SetText(scriptedit.GetText(scriptedit.TextSize));
|
|
changed = ischanged; //mxd
|
|
this.PerformLayout();
|
|
}
|
|
|
|
|
|
// This returns the current word (where the caret is at)
|
|
public string GetCurrentWord()
|
|
{
|
|
return GetWordAt(scriptedit.CurrentPos);
|
|
}
|
|
|
|
|
|
// This returns the word at the given position
|
|
public string GetWordAt(int position)
|
|
{
|
|
int wordstart = scriptedit.WordStartPosition(position, true);
|
|
int wordend = scriptedit.WordEndPosition(position, true);
|
|
|
|
// Decode the text
|
|
byte[] scripttextdata = scriptedit.GetText(scriptedit.TextSize);
|
|
Encoding encoder = Encoding.GetEncoding(scriptedit.CodePage);
|
|
string scripttext = encoder.GetString(scripttextdata);
|
|
|
|
if(wordstart < wordend)
|
|
return scripttext.Substring(wordstart, wordend - wordstart);
|
|
else
|
|
return "";
|
|
}
|
|
|
|
|
|
// This returns the ScriptStyleType for a given Scintilla style
|
|
private ScriptStyleType GetScriptStyle(int scintillastyle)
|
|
{
|
|
if(stylelookup.ContainsKey(scintillastyle))
|
|
return stylelookup[scintillastyle];
|
|
else
|
|
return 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.CurrentPos;
|
|
|
|
// Decode the text
|
|
byte[] scripttextdata = scriptedit.GetText(scriptedit.TextSize);
|
|
Encoding encoder = Encoding.GetEncoding(scriptedit.CodePage);
|
|
string scripttext = encoder.GetString(scripttextdata);
|
|
|
|
// Reset position info
|
|
curfunctionname = "";
|
|
curargumentindex = 0;
|
|
curfunctionstartpos = 0;
|
|
|
|
// Determine lowest backtrack position
|
|
int limitpos = scriptedit.CurrentPos - 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.StyleAt(pos));
|
|
int curchar = scriptedit.CharAt(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.CharAt(--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 clears all undo levels
|
|
public void ClearUndoRedo()
|
|
{
|
|
scriptedit.EmptyUndoBuffer();
|
|
}
|
|
|
|
// This registers an XPM image for the autocomplete list
|
|
private void RegisterAutoCompleteImage(ImageIndex index, byte[] imagedata)
|
|
{
|
|
// Convert to string
|
|
string bigstring = Encoding.UTF8.GetString(imagedata);
|
|
|
|
// Register image
|
|
scriptedit.RegisterImage((int)index, bigstring);
|
|
}
|
|
|
|
// This registers an XPM image for the markes list
|
|
private void RegisterMarkerImage(ImageIndex index, byte[] imagedata)
|
|
{
|
|
// Convert to string
|
|
string bigstring = Encoding.UTF8.GetString(imagedata);
|
|
|
|
// Register image
|
|
scriptedit.MarkerDefinePixmap((int)index, bigstring);
|
|
}
|
|
|
|
// Perform undo
|
|
public void Undo()
|
|
{
|
|
scriptedit.Undo();
|
|
}
|
|
|
|
// Perform redo
|
|
public void Redo()
|
|
{
|
|
scriptedit.Redo();
|
|
}
|
|
|
|
// Perform cut
|
|
public void Cut()
|
|
{
|
|
scriptedit.Cut();
|
|
}
|
|
|
|
// Perform copy
|
|
public void Copy()
|
|
{
|
|
scriptedit.Copy();
|
|
}
|
|
|
|
// Perform paste
|
|
public void Paste()
|
|
{
|
|
scriptedit.Paste();
|
|
}
|
|
|
|
// This steals the focus (use with care!)
|
|
public void GrabFocus()
|
|
{
|
|
scriptedit.GrabFocus();
|
|
}
|
|
|
|
public byte[] GetText()
|
|
{
|
|
return scriptedit.GetText(scriptedit.TextSize);
|
|
}
|
|
|
|
public void SetText(byte[] text)
|
|
{
|
|
scriptedit.SetText(text);
|
|
}
|
|
|
|
//mxd
|
|
public void InsertSnippet(string[] lines)
|
|
{
|
|
//insert the snippet
|
|
int curline = scriptedit.LineFromPosition(scriptedit.SelectionStart);
|
|
int numtabs = scriptedit.GetLineIndentation(curline);
|
|
string tabs = Environment.NewLine + new String(' ', numtabs);
|
|
string spaces = new String(' ', General.Settings.ScriptTabWidth);
|
|
int entrypos = -1;
|
|
int entryline = -1;
|
|
string[] processedlines = ProcessLineBreaks(lines);
|
|
|
|
for (int i = 0; i < lines.Length; i++)
|
|
{
|
|
processedlines[i] = processedlines[i].Replace("\t", spaces);
|
|
|
|
//check if we have the [EP] marker
|
|
if (entrypos == -1)
|
|
{
|
|
int pos = processedlines[i].IndexOf("[EP]");
|
|
if (pos != -1)
|
|
{
|
|
entryline = curline + i;
|
|
entrypos = pos + numtabs;
|
|
processedlines[i] = processedlines[i].Remove(pos, 4);
|
|
}
|
|
}
|
|
}
|
|
|
|
//replace the text
|
|
string text = string.Join(tabs, processedlines);
|
|
scriptedit.SelectionStart = scriptedit.WordStartPosition(scriptedit.CurrentPos, true);
|
|
scriptedit.SelectionEnd = scriptedit.WordEndPosition(scriptedit.CurrentPos, true);
|
|
scriptedit.ReplaceSel(text);
|
|
|
|
//move the cursor if we had the [EP] marker
|
|
if (entrypos != -1)
|
|
{
|
|
MoveToLine(entryline);
|
|
scriptedit.SelectionStart = scriptedit.PositionFromLine(entryline) + entrypos;
|
|
scriptedit.SelectionEnd = scriptedit.PositionFromLine(entryline) + entrypos;
|
|
}
|
|
}
|
|
|
|
//mxd. This converts [LB] markers to line breaks if necessary
|
|
private static string[] ProcessLineBreaks(string[] lines)
|
|
{
|
|
List<string> result = new List<string>(lines.Length);
|
|
string[] separator = new[] { "[LB]" };
|
|
|
|
foreach(string line in lines)
|
|
{
|
|
if(line.IndexOf(separator[0]) != -1)
|
|
{
|
|
if(General.Settings.SnippetsAllmanStyle)
|
|
result.AddRange(line.Split(separator, StringSplitOptions.RemoveEmptyEntries));
|
|
else
|
|
result.Add(line.Replace(separator[0], " "));
|
|
}
|
|
else
|
|
{
|
|
result.Add(line);
|
|
}
|
|
}
|
|
|
|
return result.ToArray();
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region ================== Events
|
|
|
|
// Layout needs to be re-organized
|
|
protected override void OnLayout(LayoutEventArgs e)
|
|
{
|
|
base.OnLayout(e);
|
|
|
|
// With or without functions bar?
|
|
if(functionbar.Visible)
|
|
{
|
|
scriptpanel.Top = functionbar.Bottom + 6;
|
|
scriptpanel.Height = this.ClientSize.Height - scriptpanel.Top;
|
|
}
|
|
else
|
|
{
|
|
scriptpanel.Top = 0;
|
|
scriptpanel.Height = this.ClientSize.Height;
|
|
}
|
|
}
|
|
|
|
//mxd. Script text changed
|
|
private void scriptedit_UndoRedoPerformed(ScintillaControl pSender)
|
|
{
|
|
changed = true;
|
|
if(OnTextChanged != null) OnTextChanged(this, EventArgs.Empty);
|
|
}
|
|
|
|
//mxd. Script text changed
|
|
private void scriptedit_TextChanged(ScintillaControl pSender, int position, int length, int linesAdded)
|
|
{
|
|
changed = true;
|
|
if(OnTextChanged != null) OnTextChanged(this, EventArgs.Empty);
|
|
}
|
|
|
|
// Key pressed down
|
|
private void scriptedit_KeyDown(object sender, KeyEventArgs e)
|
|
{
|
|
// These key combinations put odd characters in the script, so I disabled them
|
|
if((e.KeyCode == Keys.Q) && ((e.Modifiers & Keys.Control) == Keys.Control)) e.Handled = true;
|
|
else if((e.KeyCode == Keys.W) && ((e.Modifiers & Keys.Control) == Keys.Control)) e.Handled = true;
|
|
else if((e.KeyCode == Keys.E) && ((e.Modifiers & Keys.Control) == Keys.Control)) e.Handled = true;
|
|
else if((e.KeyCode == Keys.R) && ((e.Modifiers & Keys.Control) == Keys.Control)) e.Handled = true;
|
|
else if((e.KeyCode == Keys.Y) && ((e.Modifiers & Keys.Control) == Keys.Control)) e.Handled = true;
|
|
else if((e.KeyCode == Keys.U) && ((e.Modifiers & Keys.Control) == Keys.Control)) e.Handled = true;
|
|
else if((e.KeyCode == Keys.I) && ((e.Modifiers & Keys.Control) == Keys.Control)) e.Handled = true;
|
|
else if((e.KeyCode == Keys.P) && ((e.Modifiers & Keys.Control) == Keys.Control)) e.Handled = true;
|
|
else if((e.KeyCode == Keys.A) && ((e.Modifiers & Keys.Control) == Keys.Control) && ((e.Modifiers & Keys.Shift) == Keys.Shift)) e.Handled = true;
|
|
else if((e.KeyCode == Keys.D) && ((e.Modifiers & Keys.Control) == Keys.Control)) e.Handled = true;
|
|
else if((e.KeyCode == Keys.G) && ((e.Modifiers & Keys.Control) == Keys.Control)) e.Handled = true;
|
|
else if((e.KeyCode == Keys.H) && ((e.Modifiers & Keys.Control) == Keys.Control)) e.Handled = true;
|
|
else if((e.KeyCode == Keys.J) && ((e.Modifiers & Keys.Control) == Keys.Control)) e.Handled = true;
|
|
else if((e.KeyCode == Keys.K) && ((e.Modifiers & Keys.Control) == Keys.Control)) e.Handled = true;
|
|
else if((e.KeyCode == Keys.L) && ((e.Modifiers & Keys.Control) == Keys.Control)) e.Handled = true;
|
|
else if((e.KeyCode == Keys.Z) && ((e.Modifiers & Keys.Control) == Keys.Control) && ((e.Modifiers & Keys.Shift) == Keys.Shift)) e.Handled = true;
|
|
else if((e.KeyCode == Keys.X) && ((e.Modifiers & Keys.Control) == Keys.Control) && ((e.Modifiers & Keys.Shift) == Keys.Shift)) e.Handled = true;
|
|
else if((e.KeyCode == Keys.C) && ((e.Modifiers & Keys.Control) == Keys.Control) && ((e.Modifiers & Keys.Shift) == Keys.Shift)) e.Handled = true;
|
|
else if((e.KeyCode == Keys.V) && ((e.Modifiers & Keys.Control) == Keys.Control) && ((e.Modifiers & Keys.Shift) == Keys.Shift)) e.Handled = true;
|
|
else if((e.KeyCode == Keys.B) && ((e.Modifiers & Keys.Control) == Keys.Control)) e.Handled = true;
|
|
else if((e.KeyCode == Keys.N) && ((e.Modifiers & Keys.Control) == Keys.Control)) e.Handled = true;
|
|
else if((e.KeyCode == Keys.M) && ((e.Modifiers & Keys.Control) == Keys.Control)) e.Handled = true;
|
|
|
|
// F3 for Find Next
|
|
else if((e.KeyCode == Keys.F3) && (e.Modifiers == Keys.None))
|
|
{
|
|
if(OnFindNext != null) OnFindNext();
|
|
e.Handled = true;
|
|
}
|
|
|
|
// F2 for Find Previous (mxd)
|
|
else if((e.KeyCode == Keys.F2) && (e.Modifiers == Keys.None))
|
|
{
|
|
if(OnFindPrevious != null) OnFindPrevious();
|
|
e.Handled = true;
|
|
}
|
|
|
|
// CTRL+F for find & replace
|
|
else if((e.KeyCode == Keys.F) && ((e.Modifiers & Keys.Control) == Keys.Control))
|
|
{
|
|
if(OnOpenFindAndReplace != null) OnOpenFindAndReplace();
|
|
e.Handled = true;
|
|
}
|
|
|
|
// CTRL+S for save
|
|
else if((e.KeyCode == Keys.S) && ((e.Modifiers & Keys.Control) == Keys.Control))
|
|
{
|
|
if(OnExplicitSaveTab != null) OnExplicitSaveTab();
|
|
e.Handled = true;
|
|
}
|
|
|
|
// CTRL+O for open
|
|
else if((e.KeyCode == Keys.O) && ((e.Modifiers & Keys.Control) == Keys.Control))
|
|
{
|
|
if(OnOpenScriptBrowser != null) OnOpenScriptBrowser();
|
|
e.Handled = true;
|
|
}
|
|
|
|
// CTRL+Space to autocomplete
|
|
else if((e.KeyCode == Keys.Space) && (e.Modifiers == Keys.Control))
|
|
{
|
|
// Hide call tip if any
|
|
scriptedit.CallTipCancel();
|
|
|
|
// Show autocomplete
|
|
int currentpos = scriptedit.CurrentPos;
|
|
int wordstartpos = scriptedit.WordStartPosition(currentpos, true);
|
|
scriptedit.AutoCShow(currentpos - wordstartpos, autocompletestring);
|
|
|
|
e.Handled = true;
|
|
}
|
|
|
|
//mxd. Tab to expand code snippet
|
|
// Do it only when the text cursor is at the end of a keyword.
|
|
else if(e.KeyCode == Keys.Tab)
|
|
{
|
|
string curword = GetCurrentWord().ToLowerInvariant();
|
|
if(scriptconfig.Snippets.ContainsKey(curword) && scriptedit.CurrentPos == scriptedit.WordEndPosition(scriptedit.CurrentPos, true))
|
|
{
|
|
InsertSnippet(scriptconfig.Snippets[curword]);
|
|
e.Handled = true;
|
|
}
|
|
}
|
|
|
|
//mxd. Handle screenshot saving
|
|
else if(DelayedForm.ProcessSaveScreenshotAction((int)e.KeyData))
|
|
{
|
|
e.Handled = true;
|
|
}
|
|
}
|
|
|
|
// Key released
|
|
private void scriptedit_KeyUp(object sender, KeyEventArgs e)
|
|
{
|
|
bool showcalltip = false;
|
|
int highlightstart = 0;
|
|
int highlightend = 0;
|
|
|
|
// Enter pressed?
|
|
if((e.KeyCode == Keys.Enter) && (e.Modifiers == Keys.None))
|
|
{
|
|
// Do we want auto-indent?
|
|
if(General.Settings.ScriptAutoIndent)
|
|
{
|
|
// Get the current line index and check if its not the first line
|
|
int curline = scriptedit.LineFromPosition(scriptedit.CurrentPos);
|
|
if(curline > 0 && scriptedit.GetLineIndentation(curline) == 0)
|
|
{
|
|
// Apply identation of the previous line to this line
|
|
int ident = scriptedit.GetLineIndentation(curline - 1);
|
|
scriptedit.SetLineIndentation(curline, ident);
|
|
scriptedit.SetSel(scriptedit.SelectionStart + ident, scriptedit.SelectionStart + ident);
|
|
}
|
|
}
|
|
}
|
|
|
|
UpdatePositionInfo();
|
|
|
|
// Call tip shown
|
|
if(scriptedit.IsCallTipActive)
|
|
{
|
|
// 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.IsAutoCActive;
|
|
}
|
|
|
|
// 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);
|
|
int argsclosepos = functiondef.LastIndexOf(scriptconfig.FunctionClose);
|
|
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);
|
|
int curline = scriptedit.LineFromPosition(scriptedit.CurrentPos);
|
|
if(curline > funcline)
|
|
tippos = scriptedit.PositionFromLine(curline) + scriptedit.GetLineIndentation(curline); //scriptedit.PositionFromLine(curline) /*+ (curfunctionstartpos - scriptedit.PositionFromLine(funcline))*/;
|
|
else
|
|
tippos = curfunctionstartpos;
|
|
|
|
// Show tip
|
|
scriptedit.CallTipShow(tippos, functiondef);
|
|
scriptedit.CallTipSetHlt(highlightstart, highlightend);
|
|
}
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
}
|
|
}
|