From bb3b819d6f8caa36b0c407f1f82632c212461315 Mon Sep 17 00:00:00 2001 From: Robert van der Hulst Date: Fri, 14 Feb 2025 08:31:46 +0100 Subject: [PATCH] Dev (#1689) * SqlRdd updates * [Compiler] Simplified Keyword Rule (no need for separate rules for Base, Xpp, Fox). These is now a single rule for "soft" keywords. Added ThisForm keyword for FoxPro. The detection and translation is done in the TransformationFox. The implementation of the FindForm() call is inside the code that also handles Clipper arguments and PSZs inside TransformationRT. See https://www.xsharp.eu/forum/topic/5213 * [Tests] Adjust tests to work with X# 3: - Added method FindForm() for code that uses ThisForm - Cannot use .cs extension for source file - Disable an extra warning - * [Compiler tests] Added C934 for https://github.com/X-Sharp/XSharpPublic/issues/1673 * [Compiler tests] Added C935 for https://github.com/X-Sharp/XSharpPublic/issues/1677 * [XGui] Suppress wndproc because current code disables mouse move messages on fixed text controls * [Compiler] Fix for C935 and #1677 . The hascode is now calculated for the full filename including the path. Previously the hashcode was for the path only. * [Runtime] Added 2 functions (ASortFunc and ASortEx) to handle comparisons with duplicates better. * Fix for #1685 . Allow * comment lines to continue to the next line when the last non whitespace character is a semicolon. * [Runtime] Some changes to Asort() to handle duplicate elements, especially for multi dimensional arrays * Fix for #1686 . Added Delegate in the RuntimeState that gets called when an error occurs in the MacroCompiler. You can return a compile time codeblock, or a runtime codeblock created with MCompile(). * [Runtime] Improve performance of the __IsFoxArray property in the Array class, by making it virtual and overriding it in XSharp.VFP. * [Vsintegration] Do not mark region as shown in #1667 * [COdemodel] Fix error in Interpolated Strings * [Documentation] Adjusted short description for several entity type items. Also adjusted some body information. See https://github.com/X-Sharp/XSharpPublic/issues/1655 * [Runtime] fix for https://github.com/X-Sharp/XSharpPublic/issues/1676, Integer() function with SetFloatDelta() * [Vsintegration] Implement ToggleLineComment and ToggleBlockComment and also allow box selection * [Runtime] Small optimization: suppress creation of Float when all we want is the Real8 value inside it. Optimization for #1676 * [Runtime] Fix conversion error for latebound call with default parameters (#1684) * [Tests] Updated tests to add some Settings tests --------- Co-authored-by: cpyrgas --- docs/Help/Topics/command_CONSTRUCTOR.xml | 4 +- docs/Help/Topics/command_DESTRUCTOR.xml | 4 +- docs/Help/Topics/command_ENUM.xml | 4 +- docs/Help/Topics/command_EVENT.xml | 4 +- docs/Help/Topics/command_FUNCTION.xml | 4 +- docs/Help/Topics/command_METHOD.xml | 7 +- docs/Help/Topics/command_OPERATOR.xml | 4 +- docs/Help/Topics/command_PROPERTY.xml | 4 +- docs/Help/Topics/command_STRUCTURE.xml | 4 +- .../XSharp.RT.Tests/ConversionTests.prg | 58 ++++ src/Runtime/XSharp.RT/Functions/Math.prg | 45 +-- src/Runtime/XSharp.RT/Functions/OOP.prg | 2 +- src/Runtime/XSharp.RT/Functions/Set.prg | 4 +- .../Classifier/XSharpClassifier.cs | 1 + .../Commands/CommentCommand.cs | 266 +++++++++++------- .../ProjectSystem/XProject.prg | 16 +- 16 files changed, 282 insertions(+), 149 deletions(-) diff --git a/docs/Help/Topics/command_CONSTRUCTOR.xml b/docs/Help/Topics/command_CONSTRUCTOR.xml index 8fe93d34cd..3169c0eb62 100644 --- a/docs/Help/Topics/command_CONSTRUCTOR.xml +++ b/docs/Help/Topics/command_CONSTRUCTOR.xml @@ -1,6 +1,6 @@  - + ACCESS CONSTRUCTOR Compile-time declaration @@ -14,7 +14,7 @@ CONSTRUCTOR Statement Purpose - Declare a method to access a non-exported or virtual instance variable. + Declare a special method that is automatically invoked when a type is instantiated. Syntax [Attributes] [Modifiers] CONSTRUCTOR[([<idParam> [AS | REF|OUT|IN <idType>] [, ...])] [=> <expression>] diff --git a/docs/Help/Topics/command_DESTRUCTOR.xml b/docs/Help/Topics/command_DESTRUCTOR.xml index e60bb45a5e..bf9bb47f56 100644 --- a/docs/Help/Topics/command_DESTRUCTOR.xml +++ b/docs/Help/Topics/command_DESTRUCTOR.xml @@ -1,6 +1,6 @@  - + DESTRUCTOR Statement Compile-time declaration @@ -14,7 +14,7 @@ DESTRUCTOR Statement Purpose - Declare a method to access a non-exported or virtual instance variable. + Declare a special method that is automatically invoked when the Garbage Collector frees up the memory used by an object which is not needed anymore. Syntax [Attributes] [Modifiers] DESTRUCTOR [()] [=> <expression>] diff --git a/docs/Help/Topics/command_ENUM.xml b/docs/Help/Topics/command_ENUM.xml index 28c1cf26ff..84d5ac7d7b 100644 --- a/docs/Help/Topics/command_ENUM.xml +++ b/docs/Help/Topics/command_ENUM.xml @@ -1,6 +1,6 @@  - + ENUM Statement ENUM @@ -12,7 +12,7 @@ ENUM Statement Purpose - Declare an enum to  the compiler. + Declare an enumeration to the compiler. Syntax [attributes] [Modifiers] ENUM <idEnumName> [AS type]   diff --git a/docs/Help/Topics/command_EVENT.xml b/docs/Help/Topics/command_EVENT.xml index 454e189bdf..d1c4e33860 100644 --- a/docs/Help/Topics/command_EVENT.xml +++ b/docs/Help/Topics/command_EVENT.xml @@ -1,6 +1,6 @@  - + EVENT Statement Access() Methods @@ -15,7 +15,7 @@ EVENT Statement Purpose - Declare a method to access a non-exported or virtual instance variable. + Declare an event to a class, which can be used to notify other classes or objects when something of interest occurs. Syntax [Attributes] [Modifiers]  EVENT<idName> [AS <idType>] [<idConvention>]CRLF diff --git a/docs/Help/Topics/command_FUNCTION.xml b/docs/Help/Topics/command_FUNCTION.xml index 5bd7cac2cc..a2bf240a87 100644 --- a/docs/Help/Topics/command_FUNCTION.xml +++ b/docs/Help/Topics/command_FUNCTION.xml @@ -1,6 +1,6 @@  - + FUNCTION Statement Compiledeclaration @@ -51,7 +51,7 @@ Simply declare all support functions using STATIC FUNCTION. Doing this gives you two immediate advantages. First, no other module in the application will inadvertently call one of your support routines. Second, since static references are resolved at compile time and public references are resolved at link time, there is no possibility of a name conflict. For example, if you have a static Service() function declared in module X and a public Service() function declared in module Y, all references to Service() in X execute the static version and all other references to Service() in the application execute the public version. Notes - The Start() function: All applications must either have one function or procedure named Start() or be linked with the GUI Classes library and have a method Start() of CLASS App. Start() serves as the start-up routine when the application is executed. Start() cannot declare any parameters and, under normal circumstances, should not return a value. If you want to use strong typing in the declaration statement, you must specify AS USUAL PASCAL. + The Start() function: All applications must either have one function or procedure named Start(). FUNCTION Start() serves as the start-up routine when the application is executed. Start() must be declared without parameters or with an string array parameter and must either have an Int return value or of type Void. Exporting locals through code blocks: When you create a code block, you can access local variables defined in the creating entity within the code block definition without having to pass them as parameters (i.e., local variables are visible to the code block). Using this fact along with the fact that you can pass a code block as a parameter, you can export local variables. For example: diff --git a/docs/Help/Topics/command_METHOD.xml b/docs/Help/Topics/command_METHOD.xml index d149445c1f..532c5280f3 100644 --- a/docs/Help/Topics/command_METHOD.xml +++ b/docs/Help/Topics/command_METHOD.xml @@ -1,6 +1,6 @@  - + METHOD Statement Classesdeclaring() Methods @@ -45,8 +45,6 @@ Classes, instance variables (see the CLASS statement in this guide), and methods, are the basic object-oriented programming units. You will use methods in your applications to organize computational blocks of code for a specific class of objects. Notes - The Start() method: All applications must either have one function or procedure named Start().  
Start() serves as the startup routine when the application is executed. Start() must be declared without parameters or with an string array parameter and must either have an Int return value or of type Void
- VO Compatibility: VO has 2 special method names for constructing and destructing class objects: Init() and Axit().
In X#, these methods should be named CONSTRUCTOR and DESTRUCTOR.
@@ -98,6 +96,9 @@ Parameters: As an alternative to specifying method parameters in the METHOD declaration statement, you can use a PARAMETERS statement to specify them. This practice, however, is not recommended, because it is less efficient and provides no compile-time integrity validation. See the PARAMETERS statement in this guide for more information. + Methods with single expression instead of main body + Methods (and every other entity type that can contain code) may include code as a single expression, using the => operator in the same line of code that defines the entity, instead of using a regular body. The single expression may or may not return a value: + METHOD SimpleMethod() AS VOID => SELF:CallAnotherMethod()
METHOD SimpleMethodWithReturnValue() AS INT => SELF:nSomeIvar + 1
Examples This example creates a class of two-dimensional coordinates with methods to initialize the coordinates, draw a grid, and plot the point: diff --git a/docs/Help/Topics/command_OPERATOR.xml b/docs/Help/Topics/command_OPERATOR.xml index 57a3443bb8..5838a73a39 100644 --- a/docs/Help/Topics/command_OPERATOR.xml +++ b/docs/Help/Topics/command_OPERATOR.xml @@ -1,6 +1,6 @@  - + OPERATOR Statement Compile-time declaration @@ -14,7 +14,7 @@ OPERATOR Statement Purpose - Declare a method to access a non-exported or virtual instance variable. + Declare an operator method for a class which defines how standard operators like "+", "-", "++" etc operate when used with instances of the class as arguments Syntax [Attributes] [Modifiers] OPERATOR <operatortype> [([<idParam> [AS | REF <idType>] [, ...])] diff --git a/docs/Help/Topics/command_PROPERTY.xml b/docs/Help/Topics/command_PROPERTY.xml index d02bc04fac..4b4b28a239 100644 --- a/docs/Help/Topics/command_PROPERTY.xml +++ b/docs/Help/Topics/command_PROPERTY.xml @@ -1,6 +1,6 @@  - + PROPERTY Statement Access() Methods @@ -21,7 +21,7 @@ PROPERTY Statement Purpose - Declare a method to access a non-exported or virtual instance variable. + Declare a property to a class, which can be used for reading, writing and computing data from/to it. Syntax [Attributes] [Modifiers] PROPERTY<idName> [([<idParam> [AS | REF <idType>] [, ...])] diff --git a/docs/Help/Topics/command_STRUCTURE.xml b/docs/Help/Topics/command_STRUCTURE.xml index f42cb58218..50509b1ea1 100644 --- a/docs/Help/Topics/command_STRUCTURE.xml +++ b/docs/Help/Topics/command_STRUCTURE.xml @@ -1,6 +1,6 @@  - + STRUCTURE Statement END STRUCTURE @@ -11,7 +11,7 @@ STRUCTURE Statement Purpose - Declare a class name to the compiler. + Declare a structure name to the compiler. Syntax [Attributes] [Modifiers] STRUCTURE <idStructure>
[IMPLEMENTS <idInterface>[, <IdInterface2>,..]
[StructureMembers] diff --git a/src/Runtime/XSharp.RT.Tests/ConversionTests.prg b/src/Runtime/XSharp.RT.Tests/ConversionTests.prg index b83a57d2b7..691f8b3285 100644 --- a/src/Runtime/XSharp.RT.Tests/ConversionTests.prg +++ b/src/Runtime/XSharp.RT.Tests/ConversionTests.prg @@ -376,6 +376,64 @@ BEGIN NAMESPACE XSharp.RT.Tests s := u Assert.Equal(s, "") + [Fact, Trait("Category", "IntegerFunction")]; + METHOD IntgerFunctionTests() AS VOID + + Assert.True( Integer (1.9) == 1 ) + Assert.True( Integer (1.1) == 1 ) + Assert.True( Integer (0.1) == 0 ) + Assert.True( Integer (-0.1) == 0 ) + Assert.True( Integer (-1.1) == -1 ) + Assert.True( Integer (-1.9) == -1 ) + + LOCAL fd := SetFloatDelta(0.2) AS REAL8 + + Assert.True( Integer (1.9) == 1 ) + Assert.True( Integer (1.1) == 1 ) + Assert.True( Integer (0.1) == 0 ) + Assert.True( Integer (-0.1) == 0 ) + Assert.True( Integer (-1.1) == -1 ) + Assert.True( Integer (-1.9) == -1 ) + + Assert.True( Integer ($1.9) == 1 ) + Assert.True( Integer ($1.1) == 1 ) + Assert.True( Integer ($0.1) == 0 ) + Assert.True( Integer (-$0.1) == 0 ) + Assert.True( Integer (-$1.1) == -1 ) + Assert.True( Integer (-$1.9) == -1 ) + + Assert.True( Integer (1.9m) == 1 ) + Assert.True( Integer (1.1m) == 1 ) + Assert.True( Integer (0.1m) == 0 ) + Assert.True( Integer (-0.1m) == 0 ) + Assert.True( Integer (-1.1m) == -1 ) + Assert.True( Integer (-1.9m) == -1 ) + + SetFloatDelta(fd) + + [Fact, Trait("Category", "Settings")]; + METHOD SettingsTest() as VOID + var dec := Set(Set.Decimals) + Set(Set.Decimals, 1.0) + var dec1 := Set(Set.Decimals) + Assert.True( dec1 is Long) + Assert.True( dec1 == 1L) + Set(Set.Decimals, 2.0) + var dec2 := Set(Set.Decimals) + Assert.True( dec2 is Long) + Assert.True( dec2 == 2L) + Set(Set.Decimals, dec) + var ext := Set(Set.Exact) + Set(Set.Exact, "ON") + Assert.True( Set(Set.Exact) == TRUE) + Set(Set.Exact, "OFF") + Assert.True( Set(Set.Exact) == FALSE) + Set(Set.Exact,ext) + + RETURN + + + END CLASS END NAMESPACE // XSharp.Runtime.Tests diff --git a/src/Runtime/XSharp.RT/Functions/Math.prg b/src/Runtime/XSharp.RT/Functions/Math.prg index ac3d8fcfca..18cbed5aa8 100644 --- a/src/Runtime/XSharp.RT/Functions/Math.prg +++ b/src/Runtime/XSharp.RT/Functions/Math.prg @@ -50,15 +50,15 @@ FUNCTION ASin(nExpression AS USUAL) AS FLOAT RETURN Math.Asin((REAL8) nExpression) /// -FUNCTION Atan2(nY as USUAL, nX AS USUAL) AS FLOAT +FUNCTION Atan2(nY AS USUAL, nX AS USUAL) AS FLOAT RETURN Math.Atan2((REAL8) nY, (REAL8) nX) /// -FUNCTION DToR(nExpression as USUAL) AS REAL8 - RETURN (Real8) nExpression / 180.0 * Math.PI +FUNCTION DToR(nExpression AS USUAL) AS REAL8 + RETURN (REAL8) nExpression / 180.0 * Math.PI /// -FUNCTION RToD(nExpression as USUAL) AS REAL8 +FUNCTION RToD(nExpression AS USUAL) AS REAL8 RETURN ((REAL8) nExpression / Math.PI) * 180.0 /// @@ -158,7 +158,18 @@ FUNCTION Integer(nValue AS USUAL) AS USUAL IF nValue:IsInteger result := nValue ELSEIF nValue:IsFractional - IF nValue > 0.0 + LOCAL lPositive AS LOGIC + DO CASE + CASE nValue:IsFloat + lPositive := nValue:_r8Value > 0.0d + CASE nValue:IsDecimal + lPositive := nValue:_decimalValue > 0.0m + CASE nValue:IsCurrency + lPositive := nValue:_currencyValue > $0.0 + OTHERWISE // just in case a new type is introduced + lPositive := nValue > 0.0 + END CASE + IF lPositive result := Floor(nValue) ELSE result := Ceil(nValue) @@ -279,10 +290,10 @@ FUNCTION Tan(nNum AS USUAL) AS FLOAT /// FUNCTION Mod(nDividend AS USUAL, nDivisor AS USUAL) AS USUAL -local resType := __UsualType.Void as __UsualType -switch nDividend:_usualType +LOCAL resType := __UsualType.Void AS __UsualType +SWITCH nDividend:_usualType CASE __UsualType.Long - switch nDivisor:_usualType + SWITCH nDivisor:_usualType CASE __UsualType.Long resType := __UsualType.Long CASE __UsualType.Int64 @@ -293,9 +304,9 @@ CASE __UsualType.Long resType := __UsualType.Currency CASE __UsualType.Decimal resType := __UsualType.Decimal - end switch + END SWITCH CASE __UsualType.Int64 - switch nDivisor:_usualType + SWITCH nDivisor:_usualType CASE __UsualType.Long CASE __UsualType.Int64 resType := __UsualType.Int64 @@ -305,9 +316,9 @@ CASE __UsualType.Int64 resType := __UsualType.Currency CASE __UsualType.Decimal resType := __UsualType.Decimal - end switch + END SWITCH CASE __UsualType.Float - switch nDivisor:_usualType + SWITCH nDivisor:_usualType CASE __UsualType.Long CASE __UsualType.Int64 CASE __UsualType.Float @@ -316,14 +327,14 @@ CASE __UsualType.Float resType := __UsualType.Currency CASE __UsualType.Decimal resType := __UsualType.Decimal - end switch + END SWITCH CASE __UsualType.Currency resType := __UsualType.Currency CASE __UsualType.Decimal resType := __UsualType.Decimal -end switch +END SWITCH -switch resType +SWITCH resType CASE __UsualType.Long RETURN (LONG) nDividend % (LONG) nDivisor CASE __UsualType.Int64 @@ -334,10 +345,10 @@ CASE __UsualType.Currency RETURN (CURRENCY) nDividend % (CURRENCY) nDivisor CASE __UsualType.Decimal RETURN (DECIMAL) nDividend % (DECIMAL) nDivisor -end switch +END SWITCH IF !IsNumeric(nDividend) THROW Error.ArgumentError("Mod", nameof(nDividend),"nDividend must be numeric") -endif +ENDIF THROW Error.ArgumentError("Mod", nameof(nDivisor),"nDivisor must be numeric") diff --git a/src/Runtime/XSharp.RT/Functions/OOP.prg b/src/Runtime/XSharp.RT/Functions/OOP.prg index 2e5bee21fe..2fe0d52cb2 100644 --- a/src/Runtime/XSharp.RT/Functions/OOP.prg +++ b/src/Runtime/XSharp.RT/Functions/OOP.prg @@ -562,7 +562,7 @@ internal static class OOPHelpers result := oDefAttrib:Value // for usuals there is no need to convert. if oPar:ParameterType != typeof(usual) - result := Convert.ChangeType(result,oPar:ParameterType) + result := OOPHelpers.ValueConvert(result, oPar:ParameterType) endif end switch end if diff --git a/src/Runtime/XSharp.RT/Functions/Set.prg b/src/Runtime/XSharp.RT/Functions/Set.prg index ec9fb0c008..d197ccb507 100644 --- a/src/Runtime/XSharp.RT/Functions/Set.prg +++ b/src/Runtime/XSharp.RT/Functions/Set.prg @@ -128,7 +128,9 @@ FUNCTION Set(nDefine, newValue) AS USUAL CLIPPER state:Settings[nSetting] := cNew:ToUpper() == "ON" ELSE TRY - oNew := System.Convert.ChangeType( oNew, oOld:GetType()) + IF oNew:GetType() != oOld:GetType() + oNew := OOPHelpers.ValueConvert(oNew, oOld:GetType()) + ENDIF state:Settings[nSetting] := oNew CATCH NOP // can't convert, so ignore assignment diff --git a/src/VisualStudio/LanguageService/Classifier/XSharpClassifier.cs b/src/VisualStudio/LanguageService/Classifier/XSharpClassifier.cs index e9679bdda8..c4354efdfa 100644 --- a/src/VisualStudio/LanguageService/Classifier/XSharpClassifier.cs +++ b/src/VisualStudio/LanguageService/Classifier/XSharpClassifier.cs @@ -882,6 +882,7 @@ IToken ScanForLastToken(int type, int start, IList tokens, out int iLast nextToken = tokens[i]; if (nextToken.Line == lastFound.Line && nextToken.Type != XSharpLexer.NL + && nextToken.Type != XSharpLexer.WS && nextToken.Type != XSharpLexer.EOS) lastFound = nextToken; else diff --git a/src/VisualStudio/LanguageService/Commands/CommentCommand.cs b/src/VisualStudio/LanguageService/Commands/CommentCommand.cs index 5bd4b759e3..52685823e5 100644 --- a/src/VisualStudio/LanguageService/Commands/CommentCommand.cs +++ b/src/VisualStudio/LanguageService/Commands/CommentCommand.cs @@ -1,27 +1,35 @@  using Community.VisualStudio.Toolkit; + using LanguageService.CodeAnalysis.XSharp.SyntaxParser; -using Microsoft.VisualStudio; +using LanguageService.SyntaxTree; + using Microsoft.VisualStudio.Text; -using Microsoft.VisualStudio.Text.Editor; + using Task = System.Threading.Tasks.Task; namespace XSharp.LanguageService.Commands { internal class CommentCommand : AbstractCommand { + readonly static string[] CommentChars = new[] { "//", "&&", "*" }; public static async Task InitializeAsync() { + var cmdToggleBlock = await VS.Commands.FindCommandAsync("Edit.ToggleBlockComment"); + var cmdToggleLine = await VS.Commands.FindCommandAsync("Edit.ToggleLineComment"); + var cmdCommentSelection = await VS.Commands.FindCommandAsync("Edit.CommentSelection"); + var cmdUnCommentSelection = await VS.Commands.FindCommandAsync("Edit.UncommentSelection"); + // We need to manually intercept the commenting command, because language services swallow these commands. - await VS.Commands.InterceptAsync(VSConstants.VSStd2KCmdID.COMMENT_BLOCK, () => Execute(Comment)); - await VS.Commands.InterceptAsync(VSConstants.VSStd2KCmdID.COMMENTBLOCK, () => Execute(Comment)); - await VS.Commands.InterceptAsync(VSConstants.VSStd2KCmdID.UNCOMMENT_BLOCK, () => Execute(Uncomment)); - await VS.Commands.InterceptAsync(VSConstants.VSStd2KCmdID.UNCOMMENTBLOCK, () => Execute(Uncomment)); + await VS.Commands.InterceptAsync(cmdCommentSelection, () => Execute(CommentSelection)); + await VS.Commands.InterceptAsync(cmdUnCommentSelection, () => Execute(UncommentSelection)); + await VS.Commands.InterceptAsync(cmdToggleBlock, () => Execute(ToggleBlockComment)); + await VS.Commands.InterceptAsync(cmdToggleLine, () => Execute(ToggleLineComment)); } - private static (int, int) swapNumbers(int start, int end) + private static (int, int) SortLowHigh(int start, int end) { if (start > end) { @@ -30,132 +38,184 @@ private static (int, int) swapNumbers(int start, int end) return (start, end); } - private static void Comment(DocumentView doc) + + private static void UnCommentBlock(DocumentView doc, ITextEdit editsession, IToken token) + { + var text = token.Text; + if (text.StartsWith("/*") && text.EndsWith("*/")) + { + text = text.Substring(2, text.Length - 4); + editsession.Replace(token.StartIndex, token.Text.Length, text); + } + } + private static void CommentBlock(DocumentView doc, ITextEdit editsession, SnapshotSpan selection) + { + var text = selection.GetText(); + text = "/*" + text + "*/"; + editsession.Replace(selection, text); + } + private static void CommentLine(DocumentView doc, ITextEdit editsession, SnapshotSpan selection) + { + var text = selection.GetText(); + text = "// " + text; + editsession.Replace(selection, text); + } + private static void UnCommentLine(DocumentView doc, ITextEdit editsession, SnapshotSpan selection) + { + var text = selection.GetText(); + var ws = text.Substring(0, text.Length - text.TrimStart().Length); + text = text.TrimStart(); + text = text.Substring(2).TrimStart(); + text = ws + text; + editsession.Replace(selection, text); + + } + + private static void ToggleBlockComment(DocumentView doc) { - // Todo: add check to see if we have a block marked on a single line - // in that case surround with /* */ var snapshot = doc.TextBuffer.CurrentSnapshot; var sel = doc.TextView.Selection; - if (sel.Mode == TextSelectionMode.Box) + using (var editsession = doc.TextBuffer.CreateEdit()) { - return; + foreach (var selection in sel.SelectedSpans) + { + var (start, end) = SortLowHigh(selection.Start.Position, selection.End.Position); + if (OnMultiLineComment(doc, snapshot, start, out var token)) + { + UnCommentBlock(doc, editsession, token); + } + else + { + CommentBlock(doc, editsession, selection); + } + } + editsession.Apply(); } - var (start, end) = swapNumbers(sel.Start.Position.Position, sel.End.Position.Position); - using (var editsession = doc.TextBuffer.CreateEdit()) + + } + + private static bool OnMultiLineComment(DocumentView doc, ITextSnapshot snapshot, int start, out IToken token) + { + var xdoc = doc.TextBuffer.GetDocument(); + token = null; + var line = snapshot.GetLineFromPosition(start); + if (xdoc.LineState.Get(line.LineNumber, out var state)) { - var startLine = snapshot.GetLineFromPosition(start); - var endLine = snapshot.GetLineFromPosition(end); - if (startLine.LineNumber == endLine.LineNumber && start != end) + if (state.HasFlag(LineFlags.MultiLineComments)) { - var text = snapshot.GetText(start, end - start); - text = "/*" + text + "*/"; - var span = new SnapshotSpan(snapshot, start, end - start); - editsession.Replace(span, text); + do + { + // Find token that starts the ML comment + var tokens = xdoc.GetTokensInSingleLine(line, true); + foreach (XSharpToken t in tokens) + { + if (t.Type == XSharpLexer.ML_COMMENT) + { + token = t; + return true; + } + } + if (line.LineNumber > 0) + { + line = snapshot.GetLineFromLineNumber(line.LineNumber - 1); + } + else + { + break; + } + } while (true); } - else + } + return false; + } + + private static void ToggleLineComment(DocumentView doc) + { + var snapshot = doc.TextBuffer.CurrentSnapshot; + var sel = doc.TextView.Selection; + using (var editsession = doc.TextBuffer.CreateEdit()) + { + foreach (var selection in sel.SelectedSpans) { + bool done = false; + var (start, end) = SortLowHigh(selection.Start.Position, selection.End.Position); do { var line = snapshot.GetLineFromPosition(start); - - editsession.Insert(line.Start.Position, CommentChars[0] + " "); + var span = line.Extent; + var text = line.GetText(); + if (text.TrimStart().StartsWith("//")) + { + UnCommentLine(doc, editsession, span); + } + else + { + CommentLine(doc, editsession, span); + } start = line.EndIncludingLineBreak.Position; - if (start == end) - break; - } while (start < end); + } while (!done && start < end); } editsession.Apply(); } } - private static void Uncomment(DocumentView doc) + private static void CommentSelection(DocumentView doc) { + // Todo: add check to see if we have a block marked on a single line + // in that case surround with /* */ var snapshot = doc.TextBuffer.CurrentSnapshot; var sel = doc.TextView.Selection; - if (sel.Mode == TextSelectionMode.Box) + using (var editsession = doc.TextBuffer.CreateEdit()) { - return; + foreach (var selection in sel.SelectedSpans) + { + var (start, end) = SortLowHigh(selection.Start.Position, selection.End.Position); + bool singleLine = start != end && selection.Start.GetContainingLine().LineNumber == selection.End.GetContainingLine().LineNumber; + if (singleLine) + { + CommentBlock(doc, editsession, selection); + } + else + { + // selection may have multiple lines + do + { + var line = snapshot.GetLineFromPosition(start); + CommentLine(doc, editsession, line.Extent); + start = line.EndIncludingLineBreak.Position; + } while (start < end); + } + } + editsession.Apply(); } - var (start, end) = swapNumbers(sel.Start.Position.Position, sel.End.Position.Position); + } + private static void UncommentSelection(DocumentView doc) + { + var snapshot = doc.TextBuffer.CurrentSnapshot; + var sel = doc.TextView.Selection; using (var editsession = doc.TextBuffer.CreateEdit()) { - bool done = false; - // check to see if we are on a MultiLine commenttoken - var xdoc = doc.TextBuffer.GetDocument(); - do + foreach (var selection in sel.SelectedSpans) { - var line = snapshot.GetLineFromPosition(start); - if (xdoc.LineState.Get(line.LineNumber, out var state)) + var (start, end) = SortLowHigh(selection.Start.Position, selection.End.Position); + if (OnMultiLineComment(doc, snapshot, start, out var token)) + { + UnCommentBlock(doc, editsession, token); + } + else { - if (state.HasFlag(LineFlags.MultiLineComments)) + // selection may have multiple lines + do { - // remove multi line comments - do - { - var tokens = xdoc.GetTokensInSingleLine(line, true); - foreach (XSharpToken token in tokens) - { - if (token.Type == XSharpLexer.ML_COMMENT) - { - if (token.StartIndex <= start && token.StopIndex >= end) - { - var text = token.Text; - if (text.StartsWith("/*") && text.EndsWith("*/")) - { - text = text.Substring(2, text.Length - 4); - editsession.Replace(token.StartIndex, token.Text.Length, text); - done = true; - break; - } - } - } - } - if (!done) - { - if (line.LineNumber > 0) - { - line = xdoc.GetLine(line.LineNumber - 1); - } - else - { - done = true; - } - } - } while (!done); - } + var line = snapshot.GetLineFromPosition(start); + UnCommentLine(doc, editsession, line.Extent); + start = line.EndIncludingLineBreak.Position; + if (start == end) + break; + } while (start <= end); - if (!done) - { - var originalText = line.GetText(); - var trimmedText = originalText.TrimStart(new char[] { ' ', '\t' }); - string leading = ""; - if (trimmedText.Length < originalText.Length) - { - leading = originalText.Substring(0, originalText.Length - trimmedText.Length); - } - int lenToDelete = 0; - foreach (var str in CommentChars) - { - if (trimmedText.StartsWith(str)) - { - lenToDelete = str.Length; - // delete whitespace after comment chars? - if (trimmedText.Length > str.Length && char.IsWhiteSpace(trimmedText[lenToDelete])) - lenToDelete++; - break; - } - } - if (lenToDelete != 0) - { - var pos = line.Start.Position + leading.Length; - Span commentCharSpan = new Span(pos, lenToDelete); - editsession.Delete(commentCharSpan); - } - } } - start = line.EndIncludingLineBreak.Position; - } while (!done && start < end); - + } editsession.Apply(); } } diff --git a/src/VisualStudio/XSharpCodeModelXs/ProjectSystem/XProject.prg b/src/VisualStudio/XSharpCodeModelXs/ProjectSystem/XProject.prg index 8dbcab6583..397ab240cb 100644 --- a/src/VisualStudio/XSharpCodeModelXs/ProjectSystem/XProject.prg +++ b/src/VisualStudio/XSharpCodeModelXs/ProjectSystem/XProject.prg @@ -21,7 +21,7 @@ USING XSharp.Settings #pragma options ("az", ON) BEGIN NAMESPACE XSharpModel [DebuggerDisplay("{NameId,nq}")]; - CLASS XProject + CLASS XProject #region Fields // Fields PROTECTED _id := -1 AS INT64 @@ -411,10 +411,10 @@ BEGIN NAMESPACE XSharpModel RETURN NULL STATIC METHOD IsXSharpProject(fileName as string) AS LOGIC - if (String.IsNullOrEmpty(fileName)) - return false - endif - return String.Equals(System.IO.Path.GetExtension(fileName), ".xsproj", StringComparison.OrdinalIgnoreCase) + if (String.IsNullOrEmpty(fileName)) + return false + endif + return String.Equals(System.IO.Path.GetExtension(fileName), ".xsproj", StringComparison.OrdinalIgnoreCase) METHOD AddProjectReference(Url AS STRING) AS LOGIC IF !IsXSharpProject(Url) @@ -783,7 +783,7 @@ BEGIN NAMESPACE XSharpModel ENDIF VAR result := XDatabase.FindFunction(name, projectIds) VAR xmember := SELF:GetGlobalMember(result) - SELF:LogTypeMessage(ie"FindFunction {name}, result {iif (xmember != NULL, xmember.FullName, \"not found\"} ") + SELF:LogTypeMessage(ie"FindFunction {name}, result {iif (xmember != NULL, xmember.FullName, \"not found\")} ") RETURN xmember METHOD FindGlobalOrDefine(name AS STRING, lRecursive := TRUE AS LOGIC) AS IXMemberSymbol @@ -796,7 +796,7 @@ BEGIN NAMESPACE XSharpModel ENDIF VAR result := XDatabase.FindProjectGlobalOrDefine(name, projectIds) VAR xmember := SELF:GetGlobalMember(result) - SELF:LogTypeMessage(ie"FindGlobalOrDefine {name}, result {iif (xmember != NULL, xmember.FullName, \"not found\"} ") + SELF:LogTypeMessage(ie"FindGlobalOrDefine {name}, result {iif (xmember != NULL, xmember.FullName, \"not found\")} ") RETURN xmember PRIVATE METHOD GetGlobalMember(result AS IList) AS IXMemberSymbol @@ -1055,7 +1055,7 @@ BEGIN NAMESPACE XSharpModel return SELF:Lookup(typeName, usings) endif endif - SELF:LogTypeMessage(ie"Lookup {typeName}, result {iif(_lastFound != NULL, _lastFound.FullName, \"not found\" } ") + SELF:LogTypeMessage(ie"Lookup {typeName}, result {iif(_lastFound != NULL, _lastFound.FullName, \"not found\" )} ") RETURN _lastFound