mirror of
https://github.com/chylex/Query.git
synced 2025-07-05 06:38:54 +02:00
Compare commits
No commits in common. "2b9d5bba8f1277545fc1068ddb8b6e42e304b918" and "71bf0a5c4985d10ce126978970b219c03c4f6bbd" have entirely different histories.
2b9d5bba8f
...
71bf0a5c49
@ -7,7 +7,7 @@ public sealed class CalculatorExpressionVisitor : ExpressionVisitor<NumberWithUn
|
||||
public NumberWithUnit VisitNumber(Expression.Number number) {
|
||||
return new NumberWithUnit(number.NumberToken.Value, null);
|
||||
}
|
||||
|
||||
|
||||
public NumberWithUnit VisitNumbersWithUnits(Expression.NumbersWithUnits numbersWithUnits) {
|
||||
NumberWithUnit result = new Number.Rational(0);
|
||||
|
||||
@ -17,24 +17,24 @@ public sealed class CalculatorExpressionVisitor : ExpressionVisitor<NumberWithUn
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
public NumberWithUnit VisitGrouping(Expression.Grouping grouping) {
|
||||
return Evaluate(grouping.Expression);
|
||||
}
|
||||
|
||||
|
||||
public NumberWithUnit VisitUnary(Expression.Unary unary) {
|
||||
(Token.Simple op, Expression right) = unary;
|
||||
|
||||
|
||||
return op.Type switch {
|
||||
SimpleTokenType.PLUS => +Evaluate(right),
|
||||
SimpleTokenType.MINUS => -Evaluate(right),
|
||||
_ => throw new CalculatorException("Unsupported unary operator: " + op.Type)
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
public NumberWithUnit VisitBinary(Expression.Binary binary) {
|
||||
(Expression left, Token.Simple op, Expression right) = binary;
|
||||
|
||||
|
||||
return op.Type switch {
|
||||
SimpleTokenType.PLUS => Evaluate(left) + Evaluate(right),
|
||||
SimpleTokenType.MINUS => Evaluate(left) - Evaluate(right),
|
||||
@ -45,10 +45,10 @@ public sealed class CalculatorExpressionVisitor : ExpressionVisitor<NumberWithUn
|
||||
_ => throw new CalculatorException("Unsupported binary operator: " + op.Type)
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
public NumberWithUnit VisitUnitAssignment(Expression.UnitAssignment unitAssignment) {
|
||||
(Expression left, Unit right) = unitAssignment;
|
||||
|
||||
|
||||
NumberWithUnit number = Evaluate(left);
|
||||
|
||||
if (number.Unit is null) {
|
||||
@ -58,13 +58,13 @@ public sealed class CalculatorExpressionVisitor : ExpressionVisitor<NumberWithUn
|
||||
throw new CalculatorException("Expression already has a unit, cannot assign a new unit: " + right);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public NumberWithUnit VisitUnitConversion(Expression.UnitConversion unitConversion) {
|
||||
(Expression left, Unit unit) = unitConversion;
|
||||
|
||||
return Evaluate(left).ConvertTo(unit);
|
||||
}
|
||||
|
||||
|
||||
private NumberWithUnit Evaluate(Expression expression) {
|
||||
return expression.Accept(this);
|
||||
}
|
||||
|
@ -18,77 +18,63 @@ public abstract record Number : IAdditionOperators<Number, Number, Number>,
|
||||
IAdditiveIdentity<Number, Number.Rational>,
|
||||
IMultiplicativeIdentity<Number, Number.Rational> {
|
||||
protected abstract decimal AsDecimal { get; }
|
||||
|
||||
|
||||
public abstract Number Pow(Number exponent);
|
||||
|
||||
|
||||
public abstract string ToString(IFormatProvider? formatProvider);
|
||||
|
||||
|
||||
public sealed override string ToString() {
|
||||
return ToString(CultureInfo.InvariantCulture);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Represents an integer number with arbitrary precision.
|
||||
/// </summary>
|
||||
public sealed record Rational(BigRational Value) : Number {
|
||||
protected override decimal AsDecimal => (decimal) Value;
|
||||
|
||||
|
||||
public override Number Pow(Number exponent) {
|
||||
if (exponent is Rational { Value: {} rationalExponent }) {
|
||||
Fraction fractionExponent = rationalExponent.GetImproperFraction();
|
||||
|
||||
if (fractionExponent.Numerator >= 0 && fractionExponent.Denominator == 1) {
|
||||
if (fractionExponent.Denominator == 1 && fractionExponent.Numerator >= 0) {
|
||||
try {
|
||||
return new Rational(BigRational.Pow(Value, fractionExponent.Numerator));
|
||||
} catch (OverflowException) {}
|
||||
}
|
||||
|
||||
if (fractionExponent.Numerator == 1 && fractionExponent.Denominator > 1) {
|
||||
Number result = PowAsDecimal(exponent);
|
||||
|
||||
BigRational assumedPerfectPowerRoot = new BigRational(decimal.Floor(result.AsDecimal));
|
||||
BigRational assumedPerfectPower = BigRational.Pow(assumedPerfectPowerRoot, fractionExponent.Denominator);
|
||||
|
||||
return assumedPerfectPower == Value ? assumedPerfectPowerRoot : result;
|
||||
}
|
||||
}
|
||||
|
||||
return PowAsDecimal(exponent);
|
||||
|
||||
Number PowAsDecimal(Number number) {
|
||||
return new Decimal(AsDecimal).Pow(number);
|
||||
}
|
||||
|
||||
return new Decimal(AsDecimal).Pow(exponent);
|
||||
}
|
||||
|
||||
|
||||
public override string ToString(IFormatProvider? formatProvider) {
|
||||
Fraction fraction = Value.GetImproperFraction();
|
||||
return fraction.Denominator == 1 ? fraction.Numerator.ToString(formatProvider) : AsDecimal.ToString(formatProvider);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Represents a decimal number with limited precision.
|
||||
/// </summary>
|
||||
public sealed record Decimal(decimal Value) : Number {
|
||||
public Decimal(double value) : this((decimal) value) {}
|
||||
|
||||
|
||||
protected override decimal AsDecimal => Value;
|
||||
|
||||
|
||||
public override Number Pow(Number exponent) {
|
||||
double doubleValue = (double) Value;
|
||||
double doubleExponent = (double) exponent.AsDecimal;
|
||||
return new Decimal(System.Math.Pow(doubleValue, doubleExponent));
|
||||
}
|
||||
|
||||
|
||||
public override string ToString(IFormatProvider? formatProvider) {
|
||||
return Value.ToString(formatProvider);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static implicit operator Number(BigRational value) {
|
||||
return new Rational(value);
|
||||
}
|
||||
|
||||
|
||||
public static implicit operator Number(int value) {
|
||||
return new Rational(value);
|
||||
}
|
||||
@ -99,65 +85,65 @@ public abstract record Number : IAdditionOperators<Number, Number, Number>,
|
||||
|
||||
public static Rational AdditiveIdentity => new (BigInteger.Zero);
|
||||
public static Rational MultiplicativeIdentity => new (BigInteger.One);
|
||||
|
||||
|
||||
public static Number Add(Number left, Number right) {
|
||||
return left + right;
|
||||
}
|
||||
|
||||
|
||||
public static Number Subtract(Number left, Number right) {
|
||||
return left - right;
|
||||
}
|
||||
|
||||
|
||||
public static Number Multiply(Number left, Number right) {
|
||||
return left * right;
|
||||
}
|
||||
|
||||
|
||||
public static Number Divide(Number left, Number right) {
|
||||
return left / right;
|
||||
}
|
||||
|
||||
|
||||
public static Number Remainder(Number left, Number right) {
|
||||
return left % right;
|
||||
}
|
||||
|
||||
|
||||
public static Number Pow(Number left, Number right) {
|
||||
return left.Pow(right);
|
||||
}
|
||||
|
||||
|
||||
public static Number operator +(Number value) {
|
||||
return value;
|
||||
}
|
||||
|
||||
|
||||
public static Number operator -(Number value) {
|
||||
return Operate(value, BigRational.Negate, decimal.Negate);
|
||||
}
|
||||
|
||||
|
||||
public static Number operator +(Number left, Number right) {
|
||||
return Operate(left, right, BigRational.Add, decimal.Add);
|
||||
}
|
||||
|
||||
|
||||
public static Number operator -(Number left, Number right) {
|
||||
return Operate(left, right, BigRational.Subtract, decimal.Subtract);
|
||||
}
|
||||
|
||||
|
||||
public static Number operator *(Number left, Number right) {
|
||||
return Operate(left, right, BigRational.Multiply, decimal.Multiply);
|
||||
}
|
||||
|
||||
|
||||
public static Number operator /(Number left, Number right) {
|
||||
return Operate(left, right, BigRational.Divide, decimal.Divide);
|
||||
}
|
||||
|
||||
|
||||
public static Number operator %(Number left, Number right) {
|
||||
return Operate(left, right, BigRational.Mod, decimal.Remainder);
|
||||
}
|
||||
|
||||
|
||||
private static Number Operate(Number value, Func<BigRational, BigRational> rationalOperation, Func<decimal, decimal> decimalOperation) {
|
||||
return value is Rational rational
|
||||
? new Rational(rationalOperation(rational.Value))
|
||||
: new Decimal(decimalOperation(value.AsDecimal));
|
||||
}
|
||||
|
||||
|
||||
private static Number Operate(Number left, Number right, Func<BigRational, BigRational, BigRational> rationalOperation, Func<decimal, decimal, decimal> decimalOperation) {
|
||||
return left is Rational leftRational && right is Rational rightRational
|
||||
? new Rational(rationalOperation(leftRational.Value, rightRational.Value))
|
||||
|
@ -24,28 +24,28 @@ public readonly record struct NumberWithUnit(Number Number, Unit? Unit) : IAddit
|
||||
throw new ArithmeticException("Cannot convert '" + Unit + "' to '" + targetUnit + "'");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public string ToString(IFormatProvider? formatProvider) {
|
||||
string number = Number.ToString(formatProvider);
|
||||
return Unit == null ? number : number + " " + Unit;
|
||||
}
|
||||
|
||||
|
||||
public override string ToString() {
|
||||
return ToString(CultureInfo.InvariantCulture);
|
||||
}
|
||||
|
||||
|
||||
public static implicit operator NumberWithUnit(Number number) {
|
||||
return new NumberWithUnit(number, null);
|
||||
}
|
||||
|
||||
|
||||
public static NumberWithUnit operator +(NumberWithUnit value) {
|
||||
return value with { Number = +value.Number };
|
||||
}
|
||||
|
||||
|
||||
public static NumberWithUnit operator -(NumberWithUnit value) {
|
||||
return value with { Number = -value.Number };
|
||||
}
|
||||
|
||||
|
||||
public static NumberWithUnit operator +(NumberWithUnit left, NumberWithUnit right) {
|
||||
return Operate(left, right, Number.Add, static (leftNumber, leftUnit, rightNumber, rightUnit) => {
|
||||
if (leftUnit == rightUnit) {
|
||||
@ -59,7 +59,7 @@ public readonly record struct NumberWithUnit(Number Number, Unit? Unit) : IAddit
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
public static NumberWithUnit operator -(NumberWithUnit left, NumberWithUnit right) {
|
||||
return Operate(left, right, Number.Subtract, static (leftNumber, leftUnit, rightNumber, rightUnit) => {
|
||||
if (leftUnit == rightUnit) {
|
||||
@ -73,23 +73,23 @@ public readonly record struct NumberWithUnit(Number Number, Unit? Unit) : IAddit
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
public static NumberWithUnit operator *(NumberWithUnit left, NumberWithUnit right) {
|
||||
return OperateWithoutUnits(left, right, Number.Multiply, "Cannot multiply");
|
||||
}
|
||||
|
||||
|
||||
public static NumberWithUnit operator /(NumberWithUnit left, NumberWithUnit right) {
|
||||
return OperateWithoutUnits(left, right, Number.Divide, "Cannot divide");
|
||||
}
|
||||
|
||||
|
||||
public static NumberWithUnit operator %(NumberWithUnit left, NumberWithUnit right) {
|
||||
return OperateWithoutUnits(left, right, Number.Remainder, "Cannot modulo");
|
||||
}
|
||||
|
||||
|
||||
public NumberWithUnit Pow(NumberWithUnit exponent) {
|
||||
return OperateWithoutUnits(this, exponent, Number.Pow, "Cannot exponentiate");
|
||||
}
|
||||
|
||||
|
||||
private static NumberWithUnit Operate(NumberWithUnit left, NumberWithUnit right, Func<Number, Number, Number> withoutUnitsOperation, Func<Number, Unit, Number, Unit, NumberWithUnit> withUnitsOperation) {
|
||||
if (right.Unit is null) {
|
||||
return left with { Number = withoutUnitsOperation(left.Number, right.Number) };
|
||||
@ -101,7 +101,7 @@ public readonly record struct NumberWithUnit(Number Number, Unit? Unit) : IAddit
|
||||
return withUnitsOperation(left.Number, left.Unit, right.Number, right.Unit);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static NumberWithUnit OperateWithoutUnits(NumberWithUnit left, NumberWithUnit right, Func<Number, Number, Number> withoutUnitsOperation, string messagePrefix) {
|
||||
return Operate(left, right, withoutUnitsOperation, (_, leftUnit, _, rightUnit) => throw new ArithmeticException(messagePrefix + " '" + leftUnit + "' and '" + rightUnit + "'"));
|
||||
}
|
||||
|
@ -14,7 +14,7 @@ sealed class UnitUniverse(
|
||||
FrozenDictionary<Unit, Func<Number, Number>> unitToConversionFromPrimaryUnit
|
||||
) {
|
||||
public ImmutableArray<Unit> AllUnits => unitToConversionToPrimaryUnit.Keys;
|
||||
|
||||
|
||||
internal bool TryConvert(Number value, Unit fromUnit, Unit toUnit, [NotNullWhen(true)] out Number? converted) {
|
||||
if (fromUnit == toUnit) {
|
||||
converted = value;
|
||||
@ -29,7 +29,7 @@ sealed class UnitUniverse(
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
internal sealed record SI(string ShortPrefix, string LongPrefix, int Factor) {
|
||||
internal static readonly List<SI> All = [
|
||||
new SI("Q", "quetta", 30),
|
||||
@ -58,58 +58,58 @@ sealed class UnitUniverse(
|
||||
new SI("q", "quecto", -30)
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
internal sealed class Builder {
|
||||
private readonly Unit primaryUnit;
|
||||
private readonly Dictionary<Unit, Func<Number, Number>> unitToConversionToPrimaryUnit = new (ReferenceEqualityComparer.Instance);
|
||||
private readonly Dictionary<Unit, Func<Number, Number>> unitToConversionFromPrimaryUnit = new (ReferenceEqualityComparer.Instance);
|
||||
|
||||
|
||||
public Builder(Unit primaryUnit) {
|
||||
this.primaryUnit = primaryUnit;
|
||||
AddUnit(primaryUnit, 1);
|
||||
}
|
||||
|
||||
|
||||
public Builder AddUnit(Unit unit, Func<Number, Number> convertToPrimaryUnit, Func<Number, Number> convertFromPrimaryUnit) {
|
||||
unitToConversionToPrimaryUnit.Add(unit, convertToPrimaryUnit);
|
||||
unitToConversionFromPrimaryUnit.Add(unit, convertFromPrimaryUnit);
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
public Builder AddUnit(Unit unit, Number amountInPrimaryUnit) {
|
||||
return AddUnit(unit, number => number * amountInPrimaryUnit, number => number / amountInPrimaryUnit);
|
||||
}
|
||||
|
||||
|
||||
private void AddUnitSI(SI si, Func<SI, Unit> unitFactory, Func<int, int> factorModifier) {
|
||||
int factor = factorModifier(si.Factor);
|
||||
BigInteger powerOfTen = BigInteger.Pow(10, System.Math.Abs(factor));
|
||||
BigRational amountInPrimaryUnit = factor > 0 ? new BigRational(powerOfTen) : new BigRational(1, powerOfTen);
|
||||
AddUnit(unitFactory(si), amountInPrimaryUnit);
|
||||
}
|
||||
|
||||
|
||||
public Builder AddSI(Func<SI, Unit> unitFactory, Func<int, int> factorModifier) {
|
||||
foreach (SI si in SI.All) {
|
||||
AddUnitSI(si, unitFactory, factorModifier);
|
||||
}
|
||||
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
public Builder AddSI(Func<int, int> factorModifier) {
|
||||
Unit PrefixPrimaryUnit(SI si) {
|
||||
return new Unit(si.ShortPrefix + primaryUnit.ShortName, [..primaryUnit.LongNames.Select(longName => si.LongPrefix + longName)]);
|
||||
}
|
||||
|
||||
|
||||
foreach (SI si in SI.All) {
|
||||
AddUnitSI(si, PrefixPrimaryUnit, factorModifier);
|
||||
}
|
||||
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
public Builder AddSI() {
|
||||
return AddSI(static factor => factor);
|
||||
}
|
||||
|
||||
|
||||
public UnitUniverse Build() {
|
||||
return new UnitUniverse(
|
||||
unitToConversionToPrimaryUnit.ToFrozenDictionary(ReferenceEqualityComparer.Instance),
|
||||
|
@ -9,7 +9,7 @@ sealed class UnitUniverses {
|
||||
private readonly FrozenDictionary<Unit, UnitUniverse> unitToUniverse;
|
||||
|
||||
public WordLookupTrieNode UnitLookupByWords { get; }
|
||||
|
||||
|
||||
internal UnitUniverses(params UnitUniverse[] universes) {
|
||||
Dictionary<Unit, UnitUniverse> unitToUniverseBuilder = new (ReferenceEqualityComparer.Instance);
|
||||
WordLookupTrieNode.Builder unitLookupByWordsBuilder = new ();
|
||||
@ -20,7 +20,7 @@ sealed class UnitUniverses {
|
||||
unitLookupByWordsBuilder.Add(unit);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
unitToUniverse = unitToUniverseBuilder.ToFrozenDictionary(ReferenceEqualityComparer.Instance);
|
||||
UnitLookupByWords = unitLookupByWordsBuilder.Build();
|
||||
}
|
||||
@ -58,14 +58,14 @@ sealed class UnitUniverses {
|
||||
private void Add(string name, Unit unit) {
|
||||
Node node = root;
|
||||
string[] words = name.Split(' ');
|
||||
|
||||
|
||||
foreach (string word in words.AsSpan(..^1)) {
|
||||
node = node.Child(word);
|
||||
}
|
||||
|
||||
node.Children.Add(words[^1], Node.Create(unit));
|
||||
}
|
||||
|
||||
|
||||
public WordLookupTrieNode Build() {
|
||||
return Build(root);
|
||||
}
|
||||
|
@ -17,22 +17,22 @@ static class Units {
|
||||
public static UnitUniverse Angle { get; } = AngleUniverse().Build();
|
||||
public static UnitUniverse Temperature { get; } = TemperatureUniverse().Build();
|
||||
public static UnitUniverse InformationEntropy { get; } = InformationEntropyUniverse().Build();
|
||||
|
||||
|
||||
public static UnitUniverses All { get; } = new (Time, Length, Mass, Area, Volume, Angle, Temperature, InformationEntropy);
|
||||
|
||||
|
||||
private static UnitUniverse.Builder TimeUniverse() {
|
||||
var minute = 60;
|
||||
var hour = minute * 60;
|
||||
var day = hour * 24;
|
||||
var week = day * 7;
|
||||
|
||||
|
||||
return new UnitUniverse.Builder(new Unit("s", Pluralize("second")))
|
||||
.AddUnit(new Unit("min", Pluralize("minute")), minute)
|
||||
.AddUnit(new Unit("h", Pluralize("hour")), hour)
|
||||
.AddUnit(new Unit("d", Pluralize("day")), day)
|
||||
.AddUnit(new Unit("wk", Pluralize("week")), week);
|
||||
}
|
||||
|
||||
|
||||
private static UnitUniverse.Builder LengthUniverse() {
|
||||
var inch = Parse("0", "0254");
|
||||
var foot = inch * 12;
|
||||
@ -41,7 +41,7 @@ static class Units {
|
||||
var mile = yard * 1760;
|
||||
var nauticalMile = 1_852;
|
||||
var lightYear = 9_460_730_472_580_800;
|
||||
|
||||
|
||||
return new UnitUniverse.Builder(new Unit("m", Pluralize("meter", "metre")))
|
||||
.AddSI()
|
||||
.AddUnit(new Unit("in", [ "inch", "inches", "\"" ]), inch)
|
||||
@ -52,13 +52,13 @@ static class Units {
|
||||
.AddUnit(new Unit("nmi", Pluralize("nautical mile")), nauticalMile)
|
||||
.AddUnit(new Unit("ly", Pluralize("light-year", "light year")), lightYear);
|
||||
}
|
||||
|
||||
|
||||
private static UnitUniverse.Builder MassUniverse() {
|
||||
var pound = Parse("453", "59237");
|
||||
var stone = pound * 14;
|
||||
var ounce = pound / 16;
|
||||
var dram = ounce / 16;
|
||||
|
||||
|
||||
return new UnitUniverse.Builder(new Unit("g", Pluralize("gram")))
|
||||
.AddSI()
|
||||
.AddUnit(new Unit("lb", [ "lbs", "pound", "pounds" ]), pound)
|
||||
@ -66,7 +66,7 @@ static class Units {
|
||||
.AddUnit(new Unit("oz", Pluralize("ounce")), ounce)
|
||||
.AddUnit(new Unit("dr", Pluralize("dram")), dram);
|
||||
}
|
||||
|
||||
|
||||
private static UnitUniverse.Builder AreaUniverse() {
|
||||
static Unit SquareMeter(string shortPrefix, string longPrefix) {
|
||||
return new Unit(shortPrefix + "m2", [
|
||||
@ -80,13 +80,13 @@ static class Units {
|
||||
..Pluralize($"square {longPrefix}meter", $"square {longPrefix}metre")
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
return new UnitUniverse.Builder(SquareMeter(string.Empty, string.Empty))
|
||||
.AddSI(static si => SquareMeter(si.ShortPrefix, si.LongPrefix), static factor => factor * 2)
|
||||
.AddUnit(new Unit("a", Pluralize("are")), 100)
|
||||
.AddUnit(new Unit("ha", Pluralize("hectare")), 10_000);
|
||||
}
|
||||
|
||||
|
||||
private static UnitUniverse.Builder VolumeUniverse() {
|
||||
static Unit CubicMeter(string shortPrefix, string longPrefix) {
|
||||
return new Unit(shortPrefix + "m3", [
|
||||
@ -99,31 +99,31 @@ static class Units {
|
||||
..Pluralize($"cubic {longPrefix}meter", $"cubic {longPrefix}metre")
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
return new UnitUniverse.Builder(new Unit("l", Pluralize("litre", "liter")))
|
||||
.AddSI()
|
||||
.AddUnit(CubicMeter(string.Empty, string.Empty), 1000)
|
||||
.AddSI(static si => CubicMeter(si.ShortPrefix, si.LongPrefix), static factor => (factor * 3) + 3);
|
||||
}
|
||||
|
||||
|
||||
private static UnitUniverse.Builder AngleUniverse() {
|
||||
return new UnitUniverse.Builder(new Unit("deg", [ "°", "degree", "degrees" ]))
|
||||
.AddUnit(new Unit("rad", Pluralize("radian")), new Number.Decimal((decimal) System.Math.PI / 180M))
|
||||
.AddUnit(new Unit("grad", Pluralize("gradian", "grade", "gon")), Ratio(9, 10));
|
||||
}
|
||||
|
||||
|
||||
private static BigRational KelvinOffset { get; } = Parse("273", "15");
|
||||
|
||||
|
||||
private static UnitUniverse.Builder TemperatureUniverse() {
|
||||
return new UnitUniverse.Builder(new Unit("°C", [ "C", "Celsius", "celsius" ]))
|
||||
.AddUnit(new Unit("°F", [ "F", "Fahrenheit", "fahrenheit" ]), static f => (f - 32) * Ratio(5, 9), static c => c * Ratio(9, 5) + 32)
|
||||
.AddUnit(new Unit("K", [ "Kelvin", "kelvin" ]), static k => k - KelvinOffset, static c => c + KelvinOffset);
|
||||
}
|
||||
|
||||
|
||||
private static UnitUniverse.Builder InformationEntropyUniverse() {
|
||||
var bit = Ratio(1, 8);
|
||||
var nibble = bit * 4;
|
||||
|
||||
|
||||
return new UnitUniverse.Builder(new Unit("B", Pluralize("byte")))
|
||||
.AddSI()
|
||||
.AddUnit(new Unit("b", Pluralize("bit")), bit)
|
||||
@ -137,19 +137,19 @@ static class Units {
|
||||
.AddUnit(new Unit("ZiB", Pluralize("zebibyte")), Pow(1024, 7))
|
||||
.AddUnit(new Unit("YiB", Pluralize("yobibyte")), Pow(1024, 8));
|
||||
}
|
||||
|
||||
|
||||
private static BigRational Parse(string integerPart, string fractionalPart) {
|
||||
return Tokenizer.ParseNumber(integerPart, fractionalPart);
|
||||
}
|
||||
|
||||
|
||||
private static BigRational Ratio(long numerator, long denominator) {
|
||||
return new BigRational(numerator, denominator);
|
||||
}
|
||||
|
||||
|
||||
private static BigRational Pow(int value, int exponent) {
|
||||
return BigRational.Pow(value, exponent);
|
||||
}
|
||||
|
||||
|
||||
private static ImmutableArray<string> Pluralize(params string[] names) {
|
||||
return [..names.SelectMany(static name => new [] { name, name + "s" })];
|
||||
}
|
||||
|
@ -6,9 +6,9 @@ namespace Calculator.Parser;
|
||||
|
||||
public abstract record Expression {
|
||||
private Expression() {}
|
||||
|
||||
|
||||
public abstract T Accept<T>(ExpressionVisitor<T> visitor);
|
||||
|
||||
|
||||
public sealed record Number(Token.Number NumberToken) : Expression {
|
||||
public override T Accept<T>(ExpressionVisitor<T> visitor) {
|
||||
return visitor.VisitNumber(this);
|
||||
@ -19,7 +19,7 @@ public abstract record Expression {
|
||||
public override T Accept<T>(ExpressionVisitor<T> visitor) {
|
||||
return visitor.VisitNumbersWithUnits(this);
|
||||
}
|
||||
|
||||
|
||||
public override string ToString() {
|
||||
return nameof(NumbersWithUnits) + " { " + string.Join(", ", NumberTokensWithUnits.Select(static (number, unit) => number + " " + unit)) + " }";
|
||||
}
|
||||
@ -30,25 +30,25 @@ public abstract record Expression {
|
||||
return visitor.VisitGrouping(this);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public sealed record Unary(Token.Simple Operator, Expression Right) : Expression {
|
||||
public override T Accept<T>(ExpressionVisitor<T> visitor) {
|
||||
return visitor.VisitUnary(this);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public sealed record Binary(Expression Left, Token.Simple Operator, Expression Right) : Expression {
|
||||
public override T Accept<T>(ExpressionVisitor<T> visitor) {
|
||||
return visitor.VisitBinary(this);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public sealed record UnitAssignment(Expression Left, Unit Unit) : Expression {
|
||||
public override T Accept<T>(ExpressionVisitor<T> visitor) {
|
||||
return visitor.VisitUnitAssignment(this);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public sealed record UnitConversion(Expression Left, Unit Unit) : Expression {
|
||||
public override T Accept<T>(ExpressionVisitor<T> visitor) {
|
||||
return visitor.VisitUnitConversion(this);
|
||||
|
@ -2,16 +2,16 @@
|
||||
|
||||
public interface ExpressionVisitor<T> {
|
||||
T VisitNumber(Expression.Number number);
|
||||
|
||||
|
||||
T VisitNumbersWithUnits(Expression.NumbersWithUnits numbersWithUnits);
|
||||
|
||||
|
||||
T VisitGrouping(Expression.Grouping grouping);
|
||||
|
||||
|
||||
T VisitUnary(Expression.Unary unary);
|
||||
|
||||
|
||||
T VisitBinary(Expression.Binary binary);
|
||||
|
||||
|
||||
T VisitUnitAssignment(Expression.UnitAssignment unitAssignment);
|
||||
|
||||
|
||||
T VisitUnitConversion(Expression.UnitConversion unitConversion);
|
||||
}
|
||||
|
@ -7,94 +7,94 @@ namespace Calculator.Parser;
|
||||
|
||||
public sealed class Parser(ImmutableArray<Token> tokens) {
|
||||
private int current = 0;
|
||||
|
||||
|
||||
private bool IsEOF => current >= tokens.Length;
|
||||
|
||||
|
||||
private static readonly ImmutableArray<SimpleTokenType> PLUS_MINUS = [
|
||||
SimpleTokenType.PLUS,
|
||||
SimpleTokenType.MINUS
|
||||
];
|
||||
|
||||
|
||||
private static readonly ImmutableArray<SimpleTokenType> STAR_SLASH_PERCENT = [
|
||||
SimpleTokenType.STAR,
|
||||
SimpleTokenType.SLASH,
|
||||
SimpleTokenType.PERCENT
|
||||
];
|
||||
|
||||
|
||||
private static readonly ImmutableArray<SimpleTokenType> CARET = [
|
||||
SimpleTokenType.CARET
|
||||
];
|
||||
|
||||
|
||||
private bool Match(SimpleTokenType expectedTokenType, [NotNullWhen(true)] out Token.Simple? token) {
|
||||
return Match(simpleToken => simpleToken.Type == expectedTokenType, out token);
|
||||
}
|
||||
|
||||
|
||||
private bool Match(ImmutableArray<SimpleTokenType> expectedTokenTypes, [NotNullWhen(true)] out Token.Simple? token) {
|
||||
return Match(simpleToken => expectedTokenTypes.Contains(simpleToken.Type), out token);
|
||||
}
|
||||
|
||||
|
||||
private bool Match<T>([NotNullWhen(true)] out T? token) where T : Token {
|
||||
return Match(static _ => true, out token);
|
||||
}
|
||||
|
||||
|
||||
private bool Match<T>(Predicate<T> predicate, [NotNullWhen(true)] out T? token) where T : Token {
|
||||
if (!IsEOF && tokens[current] is T t && predicate(t)) {
|
||||
current++;
|
||||
token = t;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
token = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
public Expression Parse() {
|
||||
Expression term = Term();
|
||||
|
||||
|
||||
if (!IsEOF) {
|
||||
throw new ParseException("Incomplete expression");
|
||||
}
|
||||
|
||||
|
||||
return term;
|
||||
}
|
||||
|
||||
|
||||
private Expression Term() {
|
||||
return Binary(Factor, PLUS_MINUS);
|
||||
}
|
||||
|
||||
|
||||
private Expression Factor() {
|
||||
return Binary(Exponentiation, STAR_SLASH_PERCENT);
|
||||
}
|
||||
|
||||
|
||||
private Expression Exponentiation() {
|
||||
return Binary(Conversion, CARET);
|
||||
}
|
||||
|
||||
|
||||
private Expression Binary(Func<Expression> term, ImmutableArray<SimpleTokenType> expectedTokenTypes) {
|
||||
Expression left = term();
|
||||
|
||||
|
||||
while (Match(expectedTokenTypes, out Token.Simple? op)) {
|
||||
Expression right = term();
|
||||
left = new Expression.Binary(left, op, right);
|
||||
}
|
||||
|
||||
|
||||
return left;
|
||||
}
|
||||
|
||||
|
||||
private Expression Conversion() {
|
||||
Expression left = Unary();
|
||||
|
||||
|
||||
while (MatchUnitConversionOperator()) {
|
||||
if (!MatchUnit(out Unit? unit)) {
|
||||
throw new ParseException("Expected a unit literal");
|
||||
}
|
||||
|
||||
|
||||
left = new Expression.UnitConversion(left, unit);
|
||||
}
|
||||
|
||||
|
||||
return left;
|
||||
}
|
||||
|
||||
|
||||
private Expression Unary() {
|
||||
if (Match(PLUS_MINUS, out Token.Simple? op)) {
|
||||
Expression right = Unary();
|
||||
@ -104,56 +104,56 @@ public sealed class Parser(ImmutableArray<Token> tokens) {
|
||||
return Primary();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private Expression Primary() {
|
||||
if (Match(LiteralPredicate, out Token? literal)) {
|
||||
if (literal is not Token.Number number) {
|
||||
throw new ParseException("Expected a number literal");
|
||||
}
|
||||
|
||||
|
||||
Expression expression;
|
||||
|
||||
|
||||
if (!MatchUnit(out Unit? unit)) {
|
||||
expression = new Expression.Number(number);
|
||||
}
|
||||
else {
|
||||
var numbersWithUnits = ImmutableArray.CreateBuilder<(Token.Number, Unit)>();
|
||||
numbersWithUnits.Add((number, unit));
|
||||
|
||||
|
||||
while (MatchNumberWithUnit(out var numberWithUnit)) {
|
||||
numbersWithUnits.Add(numberWithUnit.Value);
|
||||
}
|
||||
|
||||
|
||||
expression = new Expression.NumbersWithUnits(numbersWithUnits.ToImmutable());
|
||||
}
|
||||
|
||||
|
||||
if (Match(SimpleTokenType.LEFT_PARENTHESIS, out _)) {
|
||||
expression = new Expression.Binary(expression, new Token.Simple(SimpleTokenType.STAR), InsideParentheses());
|
||||
}
|
||||
|
||||
|
||||
return expression;
|
||||
}
|
||||
|
||||
|
||||
if (Match(SimpleTokenType.LEFT_PARENTHESIS, out _)) {
|
||||
return new Expression.Grouping(InsideParentheses());
|
||||
}
|
||||
|
||||
|
||||
throw new ParseException("Unexpected token type: " + tokens[current]);
|
||||
}
|
||||
|
||||
|
||||
private static bool LiteralPredicate(Token token) {
|
||||
return token is Token.Text or Token.Number;
|
||||
}
|
||||
|
||||
|
||||
private Expression InsideParentheses() {
|
||||
Expression term = Term();
|
||||
|
||||
|
||||
if (!Match(SimpleTokenType.RIGHT_PARENTHESIS, out _)) {
|
||||
throw new ParseException("Expected ')' after expression.");
|
||||
}
|
||||
|
||||
|
||||
int position = current;
|
||||
|
||||
|
||||
if (MatchUnitConversionOperator()) {
|
||||
if (MatchUnit(out Unit? toUnit)) {
|
||||
return new Expression.UnitConversion(term, toUnit);
|
||||
@ -162,7 +162,7 @@ public sealed class Parser(ImmutableArray<Token> tokens) {
|
||||
current = position;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (MatchUnit(out Unit? unit)) {
|
||||
return new Expression.UnitAssignment(term, unit);
|
||||
}
|
||||
@ -170,33 +170,33 @@ public sealed class Parser(ImmutableArray<Token> tokens) {
|
||||
return term;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private bool MatchNumberWithUnit([NotNullWhen(true)] out (Token.Number, Unit)? numberWithUnit) {
|
||||
if (!Match(out Token.Number? number)) {
|
||||
numberWithUnit = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
if (!MatchUnit(out Unit? unit)) {
|
||||
throw new ParseException("Expected a unit literal");
|
||||
}
|
||||
|
||||
|
||||
numberWithUnit = (number, unit);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
private bool MatchUnit([NotNullWhen(true)] out Unit? unit) {
|
||||
int position = current;
|
||||
|
||||
|
||||
UnitUniverses.WordLookupTrieNode node = Units.All.UnitLookupByWords;
|
||||
|
||||
|
||||
// ReSharper disable once AccessToModifiedClosure
|
||||
while (Match(token => node.Children.ContainsKey(token.Value), out Token.Text? text)) {
|
||||
node = node.Children[text.Value];
|
||||
}
|
||||
|
||||
|
||||
unit = node.Unit;
|
||||
|
||||
|
||||
if (unit != null) {
|
||||
return true;
|
||||
}
|
||||
@ -205,7 +205,7 @@ public sealed class Parser(ImmutableArray<Token> tokens) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private bool MatchUnitConversionOperator() {
|
||||
return Match<Token.Text>(static text => text.Value is "to" or "in", out _);
|
||||
}
|
||||
|
@ -7,7 +7,7 @@ public enum SimpleTokenType {
|
||||
SLASH,
|
||||
PERCENT,
|
||||
CARET,
|
||||
|
||||
|
||||
LEFT_PARENTHESIS,
|
||||
RIGHT_PARENTHESIS
|
||||
}
|
||||
|
@ -5,7 +5,7 @@ namespace Calculator.Parser;
|
||||
|
||||
public abstract record Token {
|
||||
private Token() {}
|
||||
|
||||
|
||||
public sealed record Simple(SimpleTokenType Type) : Token {
|
||||
#pragma warning disable CS8524 // The switch expression does not handle some values of its input type (it is not exhaustive) involving an unnamed enum value.
|
||||
public override string ToString() {
|
||||
@ -23,13 +23,13 @@ public abstract record Token {
|
||||
}
|
||||
#pragma warning restore CS8524 // The switch expression does not handle some values of its input type (it is not exhaustive) involving an unnamed enum value.
|
||||
}
|
||||
|
||||
|
||||
public sealed record Text(string Value) : Token {
|
||||
public override string ToString() {
|
||||
return Value;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public sealed record Number(Math.Number Value) : Token {
|
||||
public override string ToString() {
|
||||
return Value.ToString(CultureInfo.InvariantCulture);
|
||||
|
@ -9,114 +9,114 @@ namespace Calculator.Parser;
|
||||
|
||||
public sealed class Tokenizer(string input) {
|
||||
private int position = 0;
|
||||
|
||||
|
||||
private bool IsEOF => position >= input.Length;
|
||||
|
||||
|
||||
private char Advance() {
|
||||
return input[position++];
|
||||
}
|
||||
|
||||
|
||||
private bool Match(char c) {
|
||||
return Match(found => found == c, out _);
|
||||
}
|
||||
|
||||
|
||||
private bool Match(Predicate<char> predicate, out char c) {
|
||||
if (IsEOF) {
|
||||
c = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
c = input[position];
|
||||
|
||||
|
||||
if (!predicate(c)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
position++;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
private void MatchWhile(StringBuilder result, Predicate<char> predicate) {
|
||||
while (Match(predicate, out char c)) {
|
||||
result.Append(c);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private string MatchWhile(Predicate<char> predicate) {
|
||||
var result = new StringBuilder();
|
||||
MatchWhile(result, predicate);
|
||||
return result.ToString();
|
||||
}
|
||||
|
||||
|
||||
private string MatchRest(char firstChar, Predicate<char> predicate) {
|
||||
var result = new StringBuilder();
|
||||
result.Append(firstChar);
|
||||
MatchWhile(result, predicate);
|
||||
return result.ToString();
|
||||
}
|
||||
|
||||
|
||||
public ImmutableArray<Token> Scan() {
|
||||
ImmutableArray<Token>.Builder tokens = ImmutableArray.CreateBuilder<Token>();
|
||||
|
||||
|
||||
void AddToken(Token token) {
|
||||
tokens.Add(token);
|
||||
}
|
||||
|
||||
|
||||
void AddSimpleToken(SimpleTokenType tokenType) {
|
||||
AddToken(new Token.Simple(tokenType));
|
||||
}
|
||||
|
||||
|
||||
while (!IsEOF) {
|
||||
char c = Advance();
|
||||
switch (c) {
|
||||
case ' ':
|
||||
// Ignore whitespace.
|
||||
break;
|
||||
|
||||
|
||||
case '+':
|
||||
AddSimpleToken(SimpleTokenType.PLUS);
|
||||
break;
|
||||
|
||||
|
||||
case '-':
|
||||
AddSimpleToken(SimpleTokenType.MINUS);
|
||||
break;
|
||||
|
||||
|
||||
case '*':
|
||||
AddSimpleToken(SimpleTokenType.STAR);
|
||||
break;
|
||||
|
||||
|
||||
case '/':
|
||||
AddSimpleToken(SimpleTokenType.SLASH);
|
||||
break;
|
||||
|
||||
|
||||
case '%':
|
||||
AddSimpleToken(SimpleTokenType.PERCENT);
|
||||
break;
|
||||
|
||||
|
||||
case '^':
|
||||
AddSimpleToken(SimpleTokenType.CARET);
|
||||
break;
|
||||
|
||||
|
||||
case '(':
|
||||
AddSimpleToken(SimpleTokenType.LEFT_PARENTHESIS);
|
||||
break;
|
||||
|
||||
|
||||
case ')':
|
||||
AddSimpleToken(SimpleTokenType.RIGHT_PARENTHESIS);
|
||||
break;
|
||||
|
||||
|
||||
case '"' or '\'':
|
||||
AddToken(new Token.Text(c.ToString()));
|
||||
break;
|
||||
|
||||
|
||||
case '°':
|
||||
case {} when char.IsLetter(c):
|
||||
AddToken(new Token.Text(MatchRest(c, char.IsLetterOrDigit)));
|
||||
break;
|
||||
|
||||
|
||||
case {} when char.IsAsciiDigit(c):
|
||||
string integerPart = MatchRest(c, char.IsAsciiDigit);
|
||||
|
||||
|
||||
if (Match('.')) {
|
||||
string fractionalPart = MatchWhile(char.IsAsciiDigit);
|
||||
AddToken(new Token.Number(ParseNumber(integerPart, fractionalPart)));
|
||||
@ -124,17 +124,17 @@ public sealed class Tokenizer(string input) {
|
||||
else {
|
||||
AddToken(new Token.Number(ParseNumber(integerPart)));
|
||||
}
|
||||
|
||||
|
||||
break;
|
||||
|
||||
|
||||
default:
|
||||
throw new TokenizationException("Unexpected character: " + c, c);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return tokens.ToImmutable();
|
||||
}
|
||||
|
||||
|
||||
internal static BigRational ParseNumber(string integerPart, string? fractionalPart = null) {
|
||||
if (fractionalPart == null) {
|
||||
return new BigRational(BigInteger.Parse(integerPart, NumberStyles.Integer, CultureInfo.InvariantCulture));
|
||||
|
@ -11,7 +11,7 @@ sealed class CalculatorApp : IApp {
|
||||
ImmutableArray<Token> tokens = new Tokenizer(command).Scan();
|
||||
Expression expression = new Parser(tokens).Parse();
|
||||
NumberWithUnit result = expression.Accept(new CalculatorExpressionVisitor());
|
||||
|
||||
|
||||
output = result.ToString();
|
||||
return true;
|
||||
}
|
||||
|
@ -14,11 +14,11 @@ sealed class KillProcessApp : IApp {
|
||||
}
|
||||
|
||||
int succeeded = 0, failed = 0;
|
||||
|
||||
|
||||
foreach (string processName in args[1..]) {
|
||||
try {
|
||||
Process[] processes = Process.GetProcessesByName(processName.EndsWith(".exe", StringComparison.InvariantCultureIgnoreCase) ? processName[..^4] : processName);
|
||||
|
||||
|
||||
foreach (Process process in processes) {
|
||||
try {
|
||||
process.Kill();
|
||||
@ -26,21 +26,21 @@ sealed class KillProcessApp : IApp {
|
||||
} catch {
|
||||
++failed;
|
||||
}
|
||||
|
||||
|
||||
process.Close();
|
||||
}
|
||||
} catch {
|
||||
++failed;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
var build = new StringBuilder();
|
||||
build.Append("Killed ").Append(succeeded).Append(" process").Append(succeeded == 1 ? "" : "es");
|
||||
|
||||
|
||||
if (failed > 0) {
|
||||
build.Append(", failed ").Append(failed);
|
||||
}
|
||||
|
||||
|
||||
output = build.Append('.').ToString();
|
||||
return true;
|
||||
}
|
||||
|
@ -10,7 +10,7 @@ sealed class MemeApp : IApp {
|
||||
{ "flip", @"(╯°□°)╯︵ ┻━┻" },
|
||||
{ "tableflip", @"(╯°□°)╯︵ ┻━┻" }
|
||||
};
|
||||
|
||||
|
||||
public bool TryRun(string command, [NotNullWhen(true)] out string? output) {
|
||||
return Map.TryGetValue(command, out output);
|
||||
}
|
||||
|
@ -5,19 +5,19 @@ namespace Query.Command;
|
||||
sealed class CommandHistory {
|
||||
private readonly List<string> queries = [];
|
||||
private readonly List<string> results = [];
|
||||
|
||||
|
||||
public IList<string> Queries => queries;
|
||||
|
||||
|
||||
public IList<string> Results => results;
|
||||
|
||||
|
||||
public void AddQuery(string text) {
|
||||
queries.Add(text);
|
||||
}
|
||||
|
||||
|
||||
public void AddResult(string text) {
|
||||
results.Add(text);
|
||||
}
|
||||
|
||||
|
||||
public void Clear() {
|
||||
queries.Clear();
|
||||
results.Clear();
|
||||
|
@ -10,7 +10,7 @@ sealed class CommandProcessor {
|
||||
new KillProcessApp(),
|
||||
new CalculatorApp()
|
||||
];
|
||||
|
||||
|
||||
public string Run(string command) {
|
||||
try {
|
||||
foreach (IApp app in apps) {
|
||||
@ -18,7 +18,7 @@ sealed class CommandProcessor {
|
||||
return output;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return "Unknown command.";
|
||||
} catch (Exception e) {
|
||||
throw new CommandException(e.Message, e);
|
||||
|
@ -6,57 +6,57 @@ namespace Query.Form;
|
||||
|
||||
sealed class KeyboardHook {
|
||||
public event EventHandler? Triggered;
|
||||
|
||||
|
||||
// ReSharper disable once PrivateFieldCanBeConvertedToLocalVariable
|
||||
private readonly NativeMethods.HookProc keyboardHookDelegate;
|
||||
private IntPtr keyboardHook;
|
||||
|
||||
|
||||
public KeyboardHook() {
|
||||
keyboardHookDelegate = KeyboardHookProc;
|
||||
}
|
||||
|
||||
|
||||
public void StartHook() {
|
||||
if (keyboardHook != IntPtr.Zero) {
|
||||
NativeMethods.UnhookWindowsHookEx(keyboardHook);
|
||||
}
|
||||
|
||||
|
||||
keyboardHook = NativeMethods.SetWindowsHookEx(NativeMethods.WH_KEYBOARD_LL, keyboardHookDelegate, IntPtr.Zero, 0);
|
||||
}
|
||||
|
||||
|
||||
public void StopHook() {
|
||||
if (keyboardHook != IntPtr.Zero) {
|
||||
NativeMethods.UnhookWindowsHookEx(keyboardHook);
|
||||
keyboardHook = IntPtr.Zero;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private IntPtr KeyboardHookProc(int nCode, IntPtr wParam, IntPtr lParam) {
|
||||
if (wParam == NativeMethods.WM_KEYDOWN) {
|
||||
Keys key = (Keys) Marshal.ReadInt32(lParam);
|
||||
|
||||
|
||||
if (key is Keys.LWin or Keys.RWin && Control.ModifierKeys.HasFlag(Keys.Control)) {
|
||||
Triggered?.Invoke(this, EventArgs.Empty);
|
||||
return NativeMethods.HookHandled;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return NativeMethods.CallNextHookEx(keyboardHook, nCode, wParam, lParam);
|
||||
}
|
||||
|
||||
|
||||
private static class NativeMethods {
|
||||
public const int WH_KEYBOARD_LL = 13;
|
||||
public const int WM_KEYDOWN = 0x0100;
|
||||
|
||||
|
||||
public static readonly IntPtr HookHandled = new (-1);
|
||||
|
||||
|
||||
public delegate IntPtr HookProc(int nCode, IntPtr wParam, IntPtr lParam);
|
||||
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
public static extern IntPtr SetWindowsHookEx(int idHook, HookProc lpfn, IntPtr hInstance, int threadId);
|
||||
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
public static extern bool UnhookWindowsHookEx(IntPtr idHook);
|
||||
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
public static extern IntPtr CallNextHookEx(IntPtr idHook, int nCode, IntPtr wParam, IntPtr lParam);
|
||||
}
|
||||
|
256
Query/Form/MainForm.Designer.cs
generated
256
Query/Form/MainForm.Designer.cs
generated
@ -1,133 +1,135 @@
|
||||
namespace Query.Form {
|
||||
sealed partial class MainForm {
|
||||
/// <summary>
|
||||
/// Required designer variable.
|
||||
/// </summary>
|
||||
private System.ComponentModel.IContainer components = null;
|
||||
/// <summary>
|
||||
/// Required designer variable.
|
||||
/// </summary>
|
||||
private System.ComponentModel.IContainer components = null;
|
||||
|
||||
/// <summary>
|
||||
/// Clean up any resources being used.
|
||||
/// </summary>
|
||||
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
|
||||
protected override void Dispose(bool disposing) {
|
||||
if (disposing && (components != null)) {
|
||||
components.Dispose();
|
||||
}
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
/// <summary>
|
||||
/// Clean up any resources being used.
|
||||
/// </summary>
|
||||
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
|
||||
protected override void Dispose(bool disposing) {
|
||||
if (disposing && (components != null)) {
|
||||
components.Dispose();
|
||||
}
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
|
||||
#region Windows Form Designer generated code
|
||||
|
||||
/// <summary>
|
||||
/// Required method for Designer support - do not modify
|
||||
/// the contents of this method with the code editor.
|
||||
/// </summary>
|
||||
private void InitializeComponent() {
|
||||
components = new System.ComponentModel.Container();
|
||||
System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(MainForm));
|
||||
queryLog = new Query.Form.QueryHistoryLog();
|
||||
queryBox = new Query.Form.QueryTextBox();
|
||||
trayIcon = new System.Windows.Forms.NotifyIcon(components);
|
||||
contextMenuTray = new System.Windows.Forms.ContextMenuStrip(components);
|
||||
showToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
|
||||
hookToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
|
||||
exitToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
|
||||
contextMenuTray.SuspendLayout();
|
||||
SuspendLayout();
|
||||
//
|
||||
// queryLog
|
||||
//
|
||||
queryLog.Anchor = ((System.Windows.Forms.AnchorStyles) (((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) | System.Windows.Forms.AnchorStyles.Left) | System.Windows.Forms.AnchorStyles.Right));
|
||||
queryLog.BackColor = System.Drawing.Color.Transparent;
|
||||
queryLog.Font = new System.Drawing.Font("Consolas", 14.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte) 238));
|
||||
queryLog.Location = new System.Drawing.Point(7, 8);
|
||||
queryLog.Margin = new System.Windows.Forms.Padding(7, 8, 7, 8);
|
||||
queryLog.Name = "queryLog";
|
||||
queryLog.Size = new System.Drawing.Size(696, 296);
|
||||
queryLog.TabIndex = 1;
|
||||
//
|
||||
// queryBox
|
||||
//
|
||||
queryBox.BackColor = System.Drawing.Color.FromArgb(((int) ((byte) 36)), ((int) ((byte) 36)), ((int) ((byte) 36)));
|
||||
queryBox.Dock = System.Windows.Forms.DockStyle.Bottom;
|
||||
queryBox.Location = new System.Drawing.Point(0, 312);
|
||||
queryBox.Margin = new System.Windows.Forms.Padding(0);
|
||||
queryBox.Name = "queryBox";
|
||||
queryBox.Size = new System.Drawing.Size(709, 50);
|
||||
queryBox.TabIndex = 0;
|
||||
//
|
||||
// trayIcon
|
||||
//
|
||||
trayIcon.ContextMenuStrip = contextMenuTray;
|
||||
trayIcon.Icon = ((System.Drawing.Icon) resources.GetObject("trayIcon.Icon"));
|
||||
trayIcon.Text = "Query";
|
||||
trayIcon.Visible = true;
|
||||
trayIcon.Click += trayIcon_Click;
|
||||
//
|
||||
// contextMenuTray
|
||||
//
|
||||
contextMenuTray.ImageScalingSize = new System.Drawing.Size(20, 20);
|
||||
contextMenuTray.Items.AddRange(new System.Windows.Forms.ToolStripItem[] {
|
||||
showToolStripMenuItem, hookToolStripMenuItem, exitToolStripMenuItem
|
||||
});
|
||||
contextMenuTray.Name = "contextMenuTray";
|
||||
contextMenuTray.ShowImageMargin = false;
|
||||
contextMenuTray.ShowItemToolTips = false;
|
||||
contextMenuTray.Size = new System.Drawing.Size(90, 76);
|
||||
//
|
||||
// showToolStripMenuItem
|
||||
//
|
||||
showToolStripMenuItem.Name = "showToolStripMenuItem";
|
||||
showToolStripMenuItem.Size = new System.Drawing.Size(89, 24);
|
||||
showToolStripMenuItem.Text = "Show";
|
||||
showToolStripMenuItem.Click += showToolStripMenuItem_Click;
|
||||
//
|
||||
// hookToolStripMenuItem
|
||||
//
|
||||
hookToolStripMenuItem.Name = "hookToolStripMenuItem";
|
||||
hookToolStripMenuItem.Size = new System.Drawing.Size(89, 24);
|
||||
hookToolStripMenuItem.Text = "Hook";
|
||||
hookToolStripMenuItem.Click += hookToolStripMenuItem_Click;
|
||||
//
|
||||
// exitToolStripMenuItem
|
||||
//
|
||||
exitToolStripMenuItem.Name = "exitToolStripMenuItem";
|
||||
exitToolStripMenuItem.Size = new System.Drawing.Size(89, 24);
|
||||
exitToolStripMenuItem.Text = "Exit";
|
||||
exitToolStripMenuItem.Click += exitToolStripMenuItem_Click;
|
||||
//
|
||||
// MainForm
|
||||
//
|
||||
AutoScaleDimensions = new System.Drawing.SizeF(8F, 20F);
|
||||
AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
|
||||
BackColor = System.Drawing.Color.FromArgb(((int) ((byte) 64)), ((int) ((byte) 64)), ((int) ((byte) 64)));
|
||||
ClientSize = new System.Drawing.Size(709, 362);
|
||||
ControlBox = false;
|
||||
Controls.Add(queryLog);
|
||||
Controls.Add(queryBox);
|
||||
FormBorderStyle = System.Windows.Forms.FormBorderStyle.None;
|
||||
Icon = ((System.Drawing.Icon) resources.GetObject("$this.Icon"));
|
||||
Margin = new System.Windows.Forms.Padding(4, 5, 4, 5);
|
||||
MaximizeBox = false;
|
||||
MinimizeBox = false;
|
||||
ShowInTaskbar = false;
|
||||
Text = "Query";
|
||||
Deactivate += MainForm_Deactivate;
|
||||
Shown += MainForm_Shown;
|
||||
LocationChanged += MainForm_LocationChanged;
|
||||
SizeChanged += MainForm_SizeChanged;
|
||||
contextMenuTray.ResumeLayout(false);
|
||||
ResumeLayout(false);
|
||||
}
|
||||
#region Windows Form Designer generated code
|
||||
|
||||
#endregion
|
||||
/// <summary>
|
||||
/// Required method for Designer support - do not modify
|
||||
/// the contents of this method with the code editor.
|
||||
/// </summary>
|
||||
private void InitializeComponent() {
|
||||
this.components = new System.ComponentModel.Container();
|
||||
System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(MainForm));
|
||||
this.queryLog = new QueryHistoryLog();
|
||||
this.queryBox = new QueryTextBox();
|
||||
this.trayIcon = new System.Windows.Forms.NotifyIcon(this.components);
|
||||
this.contextMenuTray = new System.Windows.Forms.ContextMenuStrip(this.components);
|
||||
this.showToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
|
||||
this.hookToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
|
||||
this.exitToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
|
||||
this.contextMenuTray.SuspendLayout();
|
||||
this.SuspendLayout();
|
||||
//
|
||||
// queryLog
|
||||
//
|
||||
this.queryLog.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom)
|
||||
| System.Windows.Forms.AnchorStyles.Left)
|
||||
| System.Windows.Forms.AnchorStyles.Right)));
|
||||
this.queryLog.BackColor = System.Drawing.Color.Transparent;
|
||||
this.queryLog.Font = new System.Drawing.Font("Consolas", 14.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(238)));
|
||||
this.queryLog.Location = new System.Drawing.Point(5, 5);
|
||||
this.queryLog.Margin = new System.Windows.Forms.Padding(5);
|
||||
this.queryLog.Name = "queryLog";
|
||||
this.queryLog.Size = new System.Drawing.Size(522, 192);
|
||||
this.queryLog.TabIndex = 1;
|
||||
//
|
||||
// queryBox
|
||||
//
|
||||
this.queryBox.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left)
|
||||
| System.Windows.Forms.AnchorStyles.Right)));
|
||||
this.queryBox.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(36)))), ((int)(((byte)(36)))), ((int)(((byte)(36)))));
|
||||
this.queryBox.Location = new System.Drawing.Point(0, 202);
|
||||
this.queryBox.Margin = new System.Windows.Forms.Padding(0);
|
||||
this.queryBox.Name = "queryBox";
|
||||
this.queryBox.Size = new System.Drawing.Size(532, 33);
|
||||
this.queryBox.TabIndex = 0;
|
||||
//
|
||||
// trayIcon
|
||||
//
|
||||
this.trayIcon.ContextMenuStrip = this.contextMenuTray;
|
||||
this.trayIcon.Icon = ((System.Drawing.Icon)(resources.GetObject("trayIcon.Icon")));
|
||||
this.trayIcon.Text = "Query";
|
||||
this.trayIcon.Visible = true;
|
||||
this.trayIcon.Click += new System.EventHandler(this.trayIcon_Click);
|
||||
//
|
||||
// contextMenuTray
|
||||
//
|
||||
this.contextMenuTray.Items.AddRange(new System.Windows.Forms.ToolStripItem[] {
|
||||
this.showToolStripMenuItem,
|
||||
this.hookToolStripMenuItem,
|
||||
this.exitToolStripMenuItem});
|
||||
this.contextMenuTray.Name = "contextMenuTray";
|
||||
this.contextMenuTray.ShowImageMargin = false;
|
||||
this.contextMenuTray.ShowItemToolTips = false;
|
||||
this.contextMenuTray.Size = new System.Drawing.Size(128, 92);
|
||||
//
|
||||
// showToolStripMenuItem
|
||||
//
|
||||
this.showToolStripMenuItem.Name = "showToolStripMenuItem";
|
||||
this.showToolStripMenuItem.Size = new System.Drawing.Size(127, 22);
|
||||
this.showToolStripMenuItem.Text = "Show";
|
||||
this.showToolStripMenuItem.Click += new System.EventHandler(this.showToolStripMenuItem_Click);
|
||||
//
|
||||
// hookToolStripMenuItem
|
||||
//
|
||||
this.hookToolStripMenuItem.Name = "hookToolStripMenuItem";
|
||||
this.hookToolStripMenuItem.Size = new System.Drawing.Size(127, 22);
|
||||
this.hookToolStripMenuItem.Text = "Hook";
|
||||
this.hookToolStripMenuItem.Click += new System.EventHandler(this.hookToolStripMenuItem_Click);
|
||||
//
|
||||
// exitToolStripMenuItem
|
||||
//
|
||||
this.exitToolStripMenuItem.Name = "exitToolStripMenuItem";
|
||||
this.exitToolStripMenuItem.Size = new System.Drawing.Size(127, 22);
|
||||
this.exitToolStripMenuItem.Text = "Exit";
|
||||
this.exitToolStripMenuItem.Click += new System.EventHandler(this.exitToolStripMenuItem_Click);
|
||||
//
|
||||
// MainForm
|
||||
//
|
||||
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
|
||||
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
|
||||
this.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(64)))), ((int)(((byte)(64)))), ((int)(((byte)(64)))));
|
||||
this.ClientSize = new System.Drawing.Size(532, 235);
|
||||
this.ControlBox = false;
|
||||
this.Controls.Add(this.queryLog);
|
||||
this.Controls.Add(this.queryBox);
|
||||
this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.None;
|
||||
this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon")));
|
||||
this.MaximizeBox = false;
|
||||
this.MinimizeBox = false;
|
||||
this.Name = "MainForm";
|
||||
this.ShowInTaskbar = false;
|
||||
this.Text = "Query";
|
||||
this.Deactivate += new System.EventHandler(this.MainForm_Deactivate);
|
||||
this.Shown += new System.EventHandler(this.MainForm_Shown);
|
||||
this.contextMenuTray.ResumeLayout(false);
|
||||
this.ResumeLayout(false);
|
||||
|
||||
private Query.Form.QueryTextBox queryBox;
|
||||
private Query.Form.QueryHistoryLog queryLog;
|
||||
private System.Windows.Forms.NotifyIcon trayIcon;
|
||||
private System.Windows.Forms.ContextMenuStrip contextMenuTray;
|
||||
private System.Windows.Forms.ToolStripMenuItem showToolStripMenuItem;
|
||||
private System.Windows.Forms.ToolStripMenuItem exitToolStripMenuItem;
|
||||
private System.Windows.Forms.ToolStripMenuItem hookToolStripMenuItem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private QueryTextBox queryBox;
|
||||
private QueryHistoryLog queryLog;
|
||||
private System.Windows.Forms.NotifyIcon trayIcon;
|
||||
private System.Windows.Forms.ContextMenuStrip contextMenuTray;
|
||||
private System.Windows.Forms.ToolStripMenuItem showToolStripMenuItem;
|
||||
private System.Windows.Forms.ToolStripMenuItem exitToolStripMenuItem;
|
||||
private System.Windows.Forms.ToolStripMenuItem hookToolStripMenuItem;
|
||||
}
|
||||
}
|
@ -8,33 +8,33 @@ namespace Query.Form;
|
||||
sealed partial class MainForm : System.Windows.Forms.Form {
|
||||
private readonly CommandProcessor processor;
|
||||
private readonly CommandHistory history;
|
||||
|
||||
|
||||
private readonly Timer focusTimer;
|
||||
private readonly KeyboardHook keyboardHook;
|
||||
|
||||
|
||||
private bool isLoaded;
|
||||
|
||||
|
||||
public MainForm() {
|
||||
InitializeComponent();
|
||||
|
||||
|
||||
processor = new CommandProcessor();
|
||||
|
||||
|
||||
history = new CommandHistory();
|
||||
queryBox.Setup(history, str => queryLog.AddEntry(str, QueryHistoryLog.EntryType.Information));
|
||||
|
||||
|
||||
keyboardHook = new KeyboardHook();
|
||||
keyboardHook.Triggered += keyboardHook_Triggered;
|
||||
|
||||
|
||||
focusTimer = new Timer {
|
||||
Interval = 1
|
||||
};
|
||||
|
||||
|
||||
focusTimer.Tick += focusTimer_Tick;
|
||||
|
||||
|
||||
Disposed += MainForm_Disposed;
|
||||
queryBox.CommandRan += queryBox_CommandRan;
|
||||
}
|
||||
|
||||
|
||||
private void SetShown(bool show) {
|
||||
if (show) {
|
||||
focusTimer.Start();
|
||||
@ -43,77 +43,63 @@ sealed partial class MainForm : System.Windows.Forms.Form {
|
||||
Hide();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void MainForm_Shown(object? sender, EventArgs e) {
|
||||
FixLocation();
|
||||
|
||||
if (Screen.PrimaryScreen is {} primaryScreen) {
|
||||
Rectangle screenRect = primaryScreen.WorkingArea;
|
||||
Location = new Point(screenRect.X + screenRect.Width - Width, screenRect.Y + screenRect.Height - Height);
|
||||
}
|
||||
|
||||
if (!isLoaded) {
|
||||
isLoaded = true;
|
||||
keyboardHook.StartHook();
|
||||
}
|
||||
}
|
||||
|
||||
private void MainForm_LocationChanged(object sender, EventArgs e) {
|
||||
FixLocation();
|
||||
}
|
||||
|
||||
private void MainForm_SizeChanged(object sender, EventArgs e) {
|
||||
FixLocation();
|
||||
}
|
||||
|
||||
private bool isMoving = false;
|
||||
|
||||
private void FixLocation() {
|
||||
if (!isMoving && Screen.FromControl(this) is {} screen) {
|
||||
Rectangle screenRect = screen.WorkingArea;
|
||||
isMoving = true;
|
||||
Location = new Point(screenRect.X + screenRect.Width - Width, screenRect.Y + screenRect.Height - Height);
|
||||
isMoving = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void MainForm_Deactivate(object? sender, EventArgs e) {
|
||||
SetShown(false);
|
||||
}
|
||||
|
||||
|
||||
private void MainForm_Disposed(object? sender, EventArgs e) {
|
||||
keyboardHook.StopHook();
|
||||
}
|
||||
|
||||
|
||||
private void trayIcon_Click(object? sender, EventArgs e) {
|
||||
if (((MouseEventArgs) e).Button == MouseButtons.Left) {
|
||||
SetShown(true);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void showToolStripMenuItem_Click(object? sender, EventArgs e) {
|
||||
SetShown(true);
|
||||
}
|
||||
|
||||
|
||||
private void hookToolStripMenuItem_Click(object? sender, EventArgs e) {
|
||||
keyboardHook.StopHook();
|
||||
keyboardHook.StartHook();
|
||||
}
|
||||
|
||||
|
||||
private void exitToolStripMenuItem_Click(object? sender, EventArgs e) {
|
||||
Application.Exit();
|
||||
}
|
||||
|
||||
|
||||
private void keyboardHook_Triggered(object? sender, EventArgs e) {
|
||||
SetShown(!Visible);
|
||||
}
|
||||
|
||||
|
||||
private void focusTimer_Tick(object? sender, EventArgs e) {
|
||||
WindowState = FormWindowState.Minimized;
|
||||
Show();
|
||||
Activate();
|
||||
|
||||
WindowState = FormWindowState.Normal;
|
||||
|
||||
queryBox.Focus();
|
||||
focusTimer.Stop();
|
||||
}
|
||||
|
||||
|
||||
private void queryBox_CommandRan(object? sender, CommandEventArgs e) {
|
||||
string command = e.Command;
|
||||
|
||||
|
||||
if (command is "exit" or "quit") {
|
||||
Application.Exit();
|
||||
}
|
||||
@ -127,16 +113,16 @@ sealed partial class MainForm : System.Windows.Forms.Form {
|
||||
else {
|
||||
try {
|
||||
string result = processor.Run(command);
|
||||
|
||||
|
||||
queryLog.AddEntry("> " + command, QueryHistoryLog.EntryType.UserInput);
|
||||
history.AddQuery(command);
|
||||
|
||||
|
||||
queryLog.AddEntry(result, QueryHistoryLog.EntryType.CommandResult);
|
||||
history.AddResult(result);
|
||||
} catch (CommandException ex) {
|
||||
queryLog.AddEntry("> " + command, QueryHistoryLog.EntryType.UserInput);
|
||||
history.AddQuery(command);
|
||||
|
||||
|
||||
queryLog.AddEntry(ex.Message, QueryHistoryLog.EntryType.Error);
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,64 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
|
||||
Version 2.0
|
||||
|
||||
The primary goals of this format is to allow a simple XML format
|
||||
that is mostly human readable. The generation and parsing of the
|
||||
various data types are done through the TypeConverter classes
|
||||
associated with the data types.
|
||||
|
||||
Example:
|
||||
|
||||
... ado.net/XML headers & schema ...
|
||||
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||
<resheader name="version">2.0</resheader>
|
||||
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
|
||||
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
|
||||
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
|
||||
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
|
||||
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
|
||||
<value>[base64 mime encoded serialized .NET Framework object]</value>
|
||||
</data>
|
||||
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||
<comment>This is a comment</comment>
|
||||
</data>
|
||||
|
||||
There are any number of "resheader" rows that contain simple
|
||||
name/value pairs.
|
||||
|
||||
Each data row contains a name, and value. The row also contains a
|
||||
type or mimetype. Type corresponds to a .NET class that support
|
||||
text/value conversion through the TypeConverter architecture.
|
||||
Classes that don't support this are serialized and stored with the
|
||||
mimetype set.
|
||||
|
||||
The mimetype is used for serialized objects, and tells the
|
||||
ResXResourceReader how to depersist the object. This is currently not
|
||||
extensible. For a given mimetype the value must be set accordingly:
|
||||
|
||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||
that the ResXResourceWriter will generate, however the reader can
|
||||
read any of the formats listed below.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.binary.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.soap.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||
value : The object must be serialized into a byte array
|
||||
: using a System.ComponentModel.TypeConverter
|
||||
: and then encoded with base64 encoding.
|
||||
-->
|
||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
@ -53,19 +112,19 @@
|
||||
<value>2.0</value>
|
||||
</resheader>
|
||||
<resheader name="reader">
|
||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<metadata name="trayIcon.TrayLocation" type="System.Drawing.Point, System.Drawing.Primitives, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
|
||||
<metadata name="trayIcon.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
|
||||
<value>17, 17</value>
|
||||
</metadata>
|
||||
<metadata name="contextMenuTray.TrayLocation" type="System.Drawing.Point, System.Drawing.Primitives, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
|
||||
<metadata name="contextMenuTray.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
|
||||
<value>112, 17</value>
|
||||
</metadata>
|
||||
<assembly alias="System.Drawing.Common" name="System.Drawing.Common, Version=8.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51" />
|
||||
<data name="trayIcon.Icon" type="System.Drawing.Icon, System.Drawing.Common" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
<assembly alias="System.Drawing" name="System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" />
|
||||
<data name="trayIcon.Icon" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
<value>
|
||||
AAABAAEAGBgAAAEAIACICQAAFgAAACgAAAAYAAAAMAAAAAEAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAkJCT/JCQk/yQkJP8kJCT/JCQk/yQkJP8kJCT/JCQk/yQkJP8kJCT/JCQk/yQkJP8kJCT/JCQk/yQk
|
||||
@ -111,7 +170,7 @@
|
||||
AEE=
|
||||
</value>
|
||||
</data>
|
||||
<data name="$this.Icon" type="System.Drawing.Icon, System.Drawing.Common" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
<data name="$this.Icon" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
<value>
|
||||
AAABAAEAGBgAAAEAIACICQAAFgAAACgAAAAYAAAAMAAAAAEAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAkJCT/JCQk/yQkJP8kJCT/JCQk/yQkJP8kJCT/JCQk/yQkJP8kJCT/JCQk/yQkJP8kJCT/JCQk/yQk
|
||||
|
103
Query/Form/QueryHistoryLog.Designer.cs
generated
103
Query/Form/QueryHistoryLog.Designer.cs
generated
@ -1,58 +1,57 @@
|
||||
namespace Query.Form {
|
||||
partial class QueryHistoryLog {
|
||||
/// <summary>
|
||||
/// Required designer variable.
|
||||
/// </summary>
|
||||
private System.ComponentModel.IContainer components = null;
|
||||
partial class QueryHistoryLog {
|
||||
/// <summary>
|
||||
/// Required designer variable.
|
||||
/// </summary>
|
||||
private System.ComponentModel.IContainer components = null;
|
||||
|
||||
/// <summary>
|
||||
/// Clean up any resources being used.
|
||||
/// </summary>
|
||||
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
|
||||
protected override void Dispose(bool disposing) {
|
||||
if (disposing && (components != null)) {
|
||||
components.Dispose();
|
||||
}
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
/// <summary>
|
||||
/// Clean up any resources being used.
|
||||
/// </summary>
|
||||
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
|
||||
protected override void Dispose(bool disposing) {
|
||||
if (disposing && (components != null)) {
|
||||
components.Dispose();
|
||||
}
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
|
||||
#region Component Designer generated code
|
||||
|
||||
/// <summary>
|
||||
/// Required method for Designer support - do not modify
|
||||
/// the contents of this method with the code editor.
|
||||
/// </summary>
|
||||
private void InitializeComponent() {
|
||||
container = new System.Windows.Forms.FlowLayoutPanel();
|
||||
SuspendLayout();
|
||||
//
|
||||
// container
|
||||
//
|
||||
container.AutoScroll = true;
|
||||
container.Dock = System.Windows.Forms.DockStyle.Fill;
|
||||
container.FlowDirection = System.Windows.Forms.FlowDirection.TopDown;
|
||||
container.ForeColor = System.Drawing.Color.FromArgb(((int) ((byte) 240)), ((int) ((byte) 240)), ((int) ((byte) 240)));
|
||||
container.Location = new System.Drawing.Point(0, 0);
|
||||
container.Margin = new System.Windows.Forms.Padding(4);
|
||||
container.Name = "container";
|
||||
container.Size = new System.Drawing.Size(195, 191);
|
||||
container.TabIndex = 0;
|
||||
container.WrapContents = false;
|
||||
//
|
||||
// QueryHistoryLog
|
||||
//
|
||||
AutoScaleDimensions = new System.Drawing.SizeF(8F, 20F);
|
||||
AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
|
||||
BackColor = System.Drawing.Color.Transparent;
|
||||
Controls.Add(container);
|
||||
Font = new System.Drawing.Font("Consolas", 14.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte) 238));
|
||||
Margin = new System.Windows.Forms.Padding(4);
|
||||
Size = new System.Drawing.Size(195, 191);
|
||||
ResumeLayout(false);
|
||||
}
|
||||
#region Component Designer generated code
|
||||
|
||||
#endregion
|
||||
/// <summary>
|
||||
/// Required method for Designer support - do not modify
|
||||
/// the contents of this method with the code editor.
|
||||
/// </summary>
|
||||
private void InitializeComponent() {
|
||||
this.container = new System.Windows.Forms.FlowLayoutPanel();
|
||||
this.SuspendLayout();
|
||||
//
|
||||
// container
|
||||
//
|
||||
this.container.AutoScroll = true;
|
||||
this.container.Dock = System.Windows.Forms.DockStyle.Fill;
|
||||
this.container.FlowDirection = System.Windows.Forms.FlowDirection.TopDown;
|
||||
this.container.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(240)))), ((int)(((byte)(240)))), ((int)(((byte)(240)))));
|
||||
this.container.Location = new System.Drawing.Point(0, 0);
|
||||
this.container.Name = "container";
|
||||
this.container.Size = new System.Drawing.Size(150, 150);
|
||||
this.container.TabIndex = 0;
|
||||
this.container.WrapContents = false;
|
||||
//
|
||||
// QueryHistoryLog
|
||||
//
|
||||
this.AutoScaleDimensions = new System.Drawing.SizeF(10F, 22F);
|
||||
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
|
||||
this.BackColor = System.Drawing.Color.Transparent;
|
||||
this.Controls.Add(this.container);
|
||||
this.Font = new System.Drawing.Font("Consolas", 14.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(238)));
|
||||
this.Name = "QueryHistoryLog";
|
||||
this.ResumeLayout(false);
|
||||
|
||||
private System.Windows.Forms.FlowLayoutPanel container;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private System.Windows.Forms.FlowLayoutPanel container;
|
||||
}
|
||||
}
|
||||
|
@ -11,21 +11,21 @@ sealed partial class QueryHistoryLog : UserControl {
|
||||
Information,
|
||||
Error
|
||||
}
|
||||
|
||||
|
||||
private static readonly Dictionary<EntryType, Color> EntryColorMap = new () {
|
||||
{ EntryType.UserInput, Color.FromArgb(160, 160, 160) },
|
||||
{ EntryType.CommandResult, Color.FromArgb(240, 240, 240) },
|
||||
{ EntryType.Information, Color.FromArgb(160, 255, 140) },
|
||||
{ EntryType.Error, Color.FromArgb(255, 40, 40) }
|
||||
};
|
||||
|
||||
|
||||
public QueryHistoryLog() {
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
|
||||
public void AddEntry(string text, EntryType type) {
|
||||
int width = container.Width - SystemInformation.VerticalScrollBarWidth;
|
||||
|
||||
|
||||
Label label = new Label {
|
||||
AutoSize = true,
|
||||
Font = container.Font,
|
||||
@ -35,11 +35,11 @@ sealed partial class QueryHistoryLog : UserControl {
|
||||
MaximumSize = new Size(width, 0),
|
||||
Width = width
|
||||
};
|
||||
|
||||
|
||||
container.Controls.Add(label);
|
||||
container.AutoScrollPosition = new Point(0, container.VerticalScroll.Maximum);
|
||||
}
|
||||
|
||||
|
||||
public void ClearEntries() {
|
||||
container.Controls.Clear();
|
||||
}
|
||||
|
107
Query/Form/QueryTextBox.Designer.cs
generated
107
Query/Form/QueryTextBox.Designer.cs
generated
@ -1,58 +1,61 @@
|
||||
namespace Query.Form {
|
||||
partial class QueryTextBox {
|
||||
/// <summary>
|
||||
/// Required designer variable.
|
||||
/// </summary>
|
||||
private System.ComponentModel.IContainer components = null;
|
||||
partial class QueryTextBox {
|
||||
/// <summary>
|
||||
/// Required designer variable.
|
||||
/// </summary>
|
||||
private System.ComponentModel.IContainer components = null;
|
||||
|
||||
/// <summary>
|
||||
/// Clean up any resources being used.
|
||||
/// </summary>
|
||||
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
|
||||
protected override void Dispose(bool disposing) {
|
||||
if (disposing && (components != null)) {
|
||||
components.Dispose();
|
||||
}
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
/// <summary>
|
||||
/// Clean up any resources being used.
|
||||
/// </summary>
|
||||
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
|
||||
protected override void Dispose(bool disposing) {
|
||||
if (disposing && (components != null)) {
|
||||
components.Dispose();
|
||||
}
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
|
||||
#region Component Designer generated code
|
||||
|
||||
/// <summary>
|
||||
/// Required method for Designer support - do not modify
|
||||
/// the contents of this method with the code editor.
|
||||
/// </summary>
|
||||
private void InitializeComponent() {
|
||||
tb = new Query.Form.QueryTextBox.CustomTextBox();
|
||||
SuspendLayout();
|
||||
//
|
||||
// tb
|
||||
//
|
||||
tb.Anchor = ((System.Windows.Forms.AnchorStyles) (((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) | System.Windows.Forms.AnchorStyles.Left) | System.Windows.Forms.AnchorStyles.Right));
|
||||
tb.BackColor = System.Drawing.Color.FromArgb(((int) ((byte) 36)), ((int) ((byte) 36)), ((int) ((byte) 36)));
|
||||
tb.BorderStyle = System.Windows.Forms.BorderStyle.None;
|
||||
tb.Font = new System.Drawing.Font("Consolas", 14.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte) 238));
|
||||
tb.ForeColor = System.Drawing.Color.FromArgb(((int) ((byte) 240)), ((int) ((byte) 240)), ((int) ((byte) 240)));
|
||||
tb.Location = new System.Drawing.Point(11, 11);
|
||||
tb.Margin = new System.Windows.Forms.Padding(11);
|
||||
tb.Name = "tb";
|
||||
tb.Size = new System.Drawing.Size(670, 28);
|
||||
tb.TabIndex = 0;
|
||||
//
|
||||
// QueryTextBox
|
||||
//
|
||||
AutoScaleDimensions = new System.Drawing.SizeF(8F, 20F);
|
||||
AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
|
||||
BackColor = System.Drawing.Color.FromArgb(((int) ((byte) 36)), ((int) ((byte) 36)), ((int) ((byte) 36)));
|
||||
Controls.Add(tb);
|
||||
Margin = new System.Windows.Forms.Padding(4, 5, 4, 5);
|
||||
Size = new System.Drawing.Size(692, 50);
|
||||
ResumeLayout(false);
|
||||
PerformLayout();
|
||||
}
|
||||
#region Component Designer generated code
|
||||
|
||||
#endregion
|
||||
/// <summary>
|
||||
/// Required method for Designer support - do not modify
|
||||
/// the contents of this method with the code editor.
|
||||
/// </summary>
|
||||
private void InitializeComponent() {
|
||||
this.tb = new CustomTextBox();
|
||||
this.SuspendLayout();
|
||||
//
|
||||
// tb
|
||||
//
|
||||
this.tb.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom)
|
||||
| System.Windows.Forms.AnchorStyles.Left)
|
||||
| System.Windows.Forms.AnchorStyles.Right)));
|
||||
this.tb.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(36)))), ((int)(((byte)(36)))), ((int)(((byte)(36)))));
|
||||
this.tb.BorderStyle = System.Windows.Forms.BorderStyle.None;
|
||||
this.tb.Font = new System.Drawing.Font("Consolas", 14.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(238)));
|
||||
this.tb.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(240)))), ((int)(((byte)(240)))), ((int)(((byte)(240)))));
|
||||
this.tb.Location = new System.Drawing.Point(5, 6);
|
||||
this.tb.Margin = new System.Windows.Forms.Padding(5);
|
||||
this.tb.Name = "tb";
|
||||
this.tb.Size = new System.Drawing.Size(509, 23);
|
||||
this.tb.TabIndex = 0;
|
||||
//
|
||||
// QueryTextBox
|
||||
//
|
||||
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
|
||||
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
|
||||
this.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(36)))), ((int)(((byte)(36)))), ((int)(((byte)(36)))));
|
||||
this.Controls.Add(this.tb);
|
||||
this.Name = "QueryTextBox";
|
||||
this.Size = new System.Drawing.Size(519, 33);
|
||||
this.ResumeLayout(false);
|
||||
this.PerformLayout();
|
||||
|
||||
private Query.Form.QueryTextBox.CustomTextBox tb;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private CustomTextBox tb;
|
||||
}
|
||||
}
|
||||
|
@ -8,84 +8,84 @@ namespace Query.Form;
|
||||
|
||||
sealed partial class QueryTextBox : UserControl {
|
||||
public event EventHandler<CommandEventArgs>? CommandRan;
|
||||
|
||||
|
||||
private CommandHistory history = null!;
|
||||
private Action<string> log = null!;
|
||||
|
||||
|
||||
public QueryTextBox() {
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
|
||||
public void Setup(CommandHistory historyObj, Action<string> logFunc) {
|
||||
history = historyObj;
|
||||
log = logFunc;
|
||||
}
|
||||
|
||||
|
||||
private void OnCommandRan() {
|
||||
CommandRan?.Invoke(this, new CommandEventArgs(tb.Text));
|
||||
}
|
||||
|
||||
|
||||
private sealed class CustomTextBox : TextBox {
|
||||
private string lastInputStr = string.Empty;
|
||||
private int lastInputPos = 0;
|
||||
|
||||
|
||||
private bool doResetHistoryMemory;
|
||||
private bool lastArrowShift;
|
||||
private int historyOffset;
|
||||
|
||||
|
||||
public CustomTextBox() {
|
||||
TextChanged += CustomTextBox_TextChanged;
|
||||
}
|
||||
|
||||
|
||||
protected override void OnKeyDown(KeyEventArgs e) {
|
||||
QueryTextBox input = (QueryTextBox) Parent!;
|
||||
CommandHistory history = input.history;
|
||||
|
||||
|
||||
Keys key = e.KeyCode;
|
||||
bool handled = false;
|
||||
|
||||
|
||||
switch (key) {
|
||||
case Keys.Enter:
|
||||
if (Text != string.Empty) {
|
||||
input.OnCommandRan();
|
||||
|
||||
|
||||
Text = string.Empty;
|
||||
doResetHistoryMemory = true;
|
||||
handled = true;
|
||||
}
|
||||
|
||||
|
||||
break;
|
||||
|
||||
|
||||
case Keys.Up:
|
||||
if (lastArrowShift != e.Shift) {
|
||||
lastArrowShift = e.Shift;
|
||||
historyOffset = 0;
|
||||
}
|
||||
|
||||
|
||||
--historyOffset;
|
||||
|
||||
|
||||
if (InsertFromHistory(e.Shift ? history.Results : history.Queries)) {
|
||||
++historyOffset;
|
||||
}
|
||||
|
||||
|
||||
handled = true;
|
||||
break;
|
||||
|
||||
|
||||
case Keys.Down:
|
||||
if (lastArrowShift != e.Shift) {
|
||||
lastArrowShift = e.Shift;
|
||||
historyOffset = 0;
|
||||
}
|
||||
|
||||
|
||||
++historyOffset;
|
||||
|
||||
|
||||
if (InsertFromHistory(e.Shift ? history.Results : history.Queries)) {
|
||||
--historyOffset;
|
||||
}
|
||||
|
||||
|
||||
handled = true;
|
||||
break;
|
||||
|
||||
|
||||
case Keys.C:
|
||||
if (e.Modifiers == Keys.Control) {
|
||||
if (SelectionLength == 0 && history.Results.Count > 0) {
|
||||
@ -94,47 +94,47 @@ sealed partial class QueryTextBox : UserControl {
|
||||
handled = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
if (!handled && key != Keys.ControlKey && key != Keys.ShiftKey && key != Keys.Menu) {
|
||||
doResetHistoryMemory = true;
|
||||
}
|
||||
|
||||
|
||||
e.Handled = e.SuppressKeyPress = handled;
|
||||
base.OnKeyDown(e);
|
||||
}
|
||||
|
||||
|
||||
protected override void OnKeyUp(KeyEventArgs e) {
|
||||
base.OnKeyUp(e);
|
||||
|
||||
|
||||
if (doResetHistoryMemory) {
|
||||
doResetHistoryMemory = false;
|
||||
ResetHistoryMemory();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void CustomTextBox_TextChanged(object? sender, EventArgs e) {
|
||||
ResetHistoryMemory();
|
||||
}
|
||||
|
||||
|
||||
// Management
|
||||
|
||||
|
||||
private void ResetHistoryMemory() {
|
||||
lastInputStr = Text;
|
||||
lastInputPos = SelectionStart;
|
||||
historyOffset = 0;
|
||||
}
|
||||
|
||||
|
||||
private bool InsertFromHistory(IList<string> collection) {
|
||||
if (collection.Count == 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
int index = collection.Count + historyOffset;
|
||||
bool wasClamped = false;
|
||||
|
||||
|
||||
if (index < 0) {
|
||||
index = 0;
|
||||
wasClamped = true;
|
||||
@ -143,13 +143,13 @@ sealed partial class QueryTextBox : UserControl {
|
||||
index = collection.Count - 1;
|
||||
wasClamped = true;
|
||||
}
|
||||
|
||||
|
||||
TextChanged -= CustomTextBox_TextChanged;
|
||||
|
||||
|
||||
Text = lastInputStr.Insert(lastInputPos, collection[index]);
|
||||
SelectionStart = lastInputPos + collection[index].Length;
|
||||
SelectionLength = 0;
|
||||
|
||||
|
||||
TextChanged += CustomTextBox_TextChanged;
|
||||
return wasClamped;
|
||||
}
|
||||
|
@ -8,7 +8,6 @@ static class Program {
|
||||
[STAThread]
|
||||
private static void Main() {
|
||||
Application.EnableVisualStyles();
|
||||
Application.SetHighDpiMode(HighDpiMode.PerMonitorV2);
|
||||
Application.SetCompatibleTextRenderingDefault(false);
|
||||
Application.Run(new MainForm());
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user