Add .config/DankMaterialShell/firefox.css

Add .config/DankMaterialShell/plugin_settings.json
Add .config/DankMaterialShell/plugins/calculator/CalculatorLauncher.qml
Add .config/DankMaterialShell/plugins/calculator/CalculatorSettings.qml
Add .config/DankMaterialShell/plugins/calculator/README.md
Add .config/DankMaterialShell/plugins/calculator/calculator.js
Add .config/DankMaterialShell/plugins/calculator/.git/HEAD
Add .config/DankMaterialShell/plugins/calculator/.git/config
Add .config/DankMaterialShell/plugins/calculator/.git/index
Add .config/DankMaterialShell/plugins/calculator/.git/objects/info/.keep
Add .config/DankMaterialShell/plugins/calculator/.git/objects/pack/pack-67f644835e660794f65c9273e46788b0c3da57cf.idx
Add .config/DankMaterialShell/plugins/calculator/.git/objects/pack/pack-67f644835e660794f65c9273e46788b0c3da57cf.rev
Add .config/DankMaterialShell/plugins/calculator/.git/objects/pack/pack-67f644835e660794f65c9273e46788b0c3da57cf.pack
Add .config/DankMaterialShell/plugins/calculator/.git/refs/heads/main
Add .config/DankMaterialShell/plugins/calculator/.git/refs/remotes/origin/main
Add .config/DankMaterialShell/plugins/calculator/.git/refs/remotes/origin/qalc
Add .config/DankMaterialShell/plugins/calculator/.git/refs/tags/.keep
Add .config/DankMaterialShell/plugins/calculator/plugin.json
Add .config/DankMaterialShell/plugins/calculator/screenshot.png
Add .config/DankMaterialShell/plugins/calculator/test_precision.js
Add .config/DankMaterialShell/plugins/calculator/test_single.js
Add .config/DankMaterialShell/plugins/commandRunner/CommandRunner.qml
Add .config/DankMaterialShell/plugins/commandRunner/CommandRunnerSettings.qml
Add .config/DankMaterialShell/plugins/commandRunner/LICENSE
Add .config/DankMaterialShell/plugins/commandRunner/README.md
Add .config/DankMaterialShell/plugins/commandRunner/.git/HEAD
Add .config/DankMaterialShell/plugins/commandRunner/.git/config
Add .config/DankMaterialShell/plugins/commandRunner/.git/index
Add .config/DankMaterialShell/plugins/commandRunner/.git/objects/info/.keep
Add .config/DankMaterialShell/plugins/commandRunner/.git/objects/pack/pack-5a720f795fd2994ef506cd867d86ca7df8a25a31.idx
Add .config/DankMaterialShell/plugins/commandRunner/.git/objects/pack/pack-5a720f795fd2994ef506cd867d86ca7df8a25a31.rev
Add .config/DankMaterialShell/plugins/commandRunner/.git/objects/pack/pack-5a720f795fd2994ef506cd867d86ca7df8a25a31.pack
Add .config/DankMaterialShell/plugins/commandRunner/.git/refs/heads/main
Add .config/DankMaterialShell/plugins/commandRunner/.git/refs/remotes/origin/main
Add .config/DankMaterialShell/plugins/commandRunner/.git/refs/tags/.keep
Add .config/DankMaterialShell/plugins/commandRunner/plugin.json
Add .config/DankMaterialShell/plugins/commandRunner/screenshot.png
Add .config/DankMaterialShell/plugins/dankActions.meta
Add .config/DankMaterialShell/plugins/.repos/0026f1eba8dedaec/DankActions/DankActionsSettings.qml
Add .config/DankMaterialShell/plugins/.repos/0026f1eba8dedaec/DankActions/DankActionsWidget.qml
Add .config/DankMaterialShell/plugins/.repos/0026f1eba8dedaec/DankActions/plugin.json
Add .config/DankMaterialShell/plugins/.repos/0026f1eba8dedaec/DankBatteryAlerts/DankBatteryAlerts.qml
Add .config/DankMaterialShell/plugins/.repos/0026f1eba8dedaec/DankBatteryAlerts/DankBatteryAlertsSettings.qml
Add .config/DankMaterialShell/plugins/.repos/0026f1eba8dedaec/DankBatteryAlerts/plugin.json
Add .config/DankMaterialShell/plugins/.repos/0026f1eba8dedaec/DankHooks/DankHooks.qml
Add .config/DankMaterialShell/plugins/.repos/0026f1eba8dedaec/DankHooks/DankHooksSettings.qml
Add .config/DankMaterialShell/plugins/.repos/0026f1eba8dedaec/DankHooks/README.md
Add .config/DankMaterialShell/plugins/.repos/0026f1eba8dedaec/DankHooks/plugin.json
Add .config/DankMaterialShell/plugins/.repos/0026f1eba8dedaec/DankPomodoroTimer/DankPomodoroSettings.qml
Add .config/DankMaterialShell/plugins/.repos/0026f1eba8dedaec/DankPomodoroTimer/DankPomodoroWidget.qml
Add .config/DankMaterialShell/plugins/.repos/0026f1eba8dedaec/DankPomodoroTimer/plugin.json
Add .config/DankMaterialShell/plugins/.repos/0026f1eba8dedaec/LICENSE
Add .config/DankMaterialShell/plugins/.repos/0026f1eba8dedaec/README.md
Add .config/DankMaterialShell/plugins/.repos/0026f1eba8dedaec/.git/HEAD
Add .config/DankMaterialShell/plugins/.repos/0026f1eba8dedaec/.git/config
Add .config/DankMaterialShell/plugins/.repos/0026f1eba8dedaec/.git/index
Add .config/DankMaterialShell/plugins/.repos/0026f1eba8dedaec/.git/objects/info/.keep
Add .config/DankMaterialShell/plugins/.repos/0026f1eba8dedaec/.git/objects/pack/pack-3221a15c022ef4a7bb6bf2c47e40068b66b3588b.idx
Add .config/DankMaterialShell/plugins/.repos/0026f1eba8dedaec/.git/objects/pack/pack-3221a15c022ef4a7bb6bf2c47e40068b66b3588b.rev
Add .config/DankMaterialShell/plugins/.repos/0026f1eba8dedaec/.git/objects/pack/pack-3221a15c022ef4a7bb6bf2c47e40068b66b3588b.pack
Add .config/DankMaterialShell/plugins/.repos/0026f1eba8dedaec/.git/refs/heads/master
Add .config/DankMaterialShell/plugins/.repos/0026f1eba8dedaec/.git/refs/remotes/origin/master
Add .config/DankMaterialShell/plugins/.repos/0026f1eba8dedaec/.git/refs/tags/.keep
Add .config/DankMaterialShell/plugins/.repos/0026f1eba8dedaec/.gitignore
Add .config/DankMaterialShell/plugins/emojiLauncher/EmojiLauncher.qml
Add .config/DankMaterialShell/plugins/emojiLauncher/EmojiLauncherSettings.qml
Add .config/DankMaterialShell/plugins/emojiLauncher/LICENSE
Add .config/DankMaterialShell/plugins/emojiLauncher/README.md
Add .config/DankMaterialShell/plugins/emojiLauncher/catalog.js
Add .config/DankMaterialShell/plugins/emojiLauncher/data/emojis.txt
Add .config/DankMaterialShell/plugins/emojiLauncher/data/math.txt
Add .config/DankMaterialShell/plugins/emojiLauncher/data/nerdfont.txt
Add .config/DankMaterialShell/plugins/emojiLauncher/.git/HEAD
Add .config/DankMaterialShell/plugins/emojiLauncher/.git/config
Add .config/DankMaterialShell/plugins/emojiLauncher/.git/index
Add .config/DankMaterialShell/plugins/emojiLauncher/.git/objects/info/.keep
Add .config/DankMaterialShell/plugins/emojiLauncher/.git/objects/pack/pack-e04a5b1ea381dc3a792b8bf08cf70e735b195c0d.idx
Add .config/DankMaterialShell/plugins/emojiLauncher/.git/objects/pack/pack-e04a5b1ea381dc3a792b8bf08cf70e735b195c0d.rev
Add .config/DankMaterialShell/plugins/emojiLauncher/.git/objects/pack/pack-e04a5b1ea381dc3a792b8bf08cf70e735b195c0d.pack
Add .config/DankMaterialShell/plugins/emojiLauncher/.git/refs/heads/main
Add .config/DankMaterialShell/plugins/emojiLauncher/.git/refs/remotes/origin/main
Add .config/DankMaterialShell/plugins/emojiLauncher/.git/refs/tags/.keep
Add .config/DankMaterialShell/plugins/emojiLauncher/plugin.json
Add .config/DankMaterialShell/plugins/emojiLauncher/screenshot.png
Add .config/DankMaterialShell/plugins/emojiLauncher/scripts/generate_catalog.py
Add .config/DankMaterialShell/plugins/dankActions
Add .config/DankMaterialShell/plugins/webSearch/LICENSE
Add .config/DankMaterialShell/plugins/webSearch/README.md
Add .config/DankMaterialShell/plugins/webSearch/WebSearch.qml
Add .config/DankMaterialShell/plugins/webSearch/WebSearchSettings.qml
Add .config/DankMaterialShell/plugins/webSearch/.git/HEAD
Add .config/DankMaterialShell/plugins/webSearch/.git/config
Add .config/DankMaterialShell/plugins/webSearch/.git/index
Add .config/DankMaterialShell/plugins/webSearch/.git/objects/info/.keep
Add .config/DankMaterialShell/plugins/webSearch/.git/objects/pack/pack-6a60c736f418e5b4b1f0505f66c1e2a371d46fed.idx
Add .config/DankMaterialShell/plugins/webSearch/.git/objects/pack/pack-6a60c736f418e5b4b1f0505f66c1e2a371d46fed.rev
Add .config/DankMaterialShell/plugins/webSearch/.git/objects/pack/pack-6a60c736f418e5b4b1f0505f66c1e2a371d46fed.pack
Add .config/DankMaterialShell/plugins/webSearch/.git/refs/heads/main
Add .config/DankMaterialShell/plugins/webSearch/.git/refs/remotes/origin/main
Add .config/DankMaterialShell/plugins/webSearch/.git/refs/tags/.keep
Add .config/DankMaterialShell/plugins/webSearch/plugin.json
Add .config/DankMaterialShell/plugins/webSearch/screenshot.png
Add .config/DankMaterialShell/settings.json
This commit is contained in:
Lewis Wynne 2025-12-12 05:12:42 +00:00
parent b22161e0ca
commit 809b12203c
103 changed files with 25020 additions and 0 deletions

View file

@ -0,0 +1,118 @@
import QtQuick
import Quickshell
import qs.Services
import "calculator.js" as Calculator
Item {
id: root
// Plugin properties
property var pluginService: null
property string trigger: ""
// Plugin interface signals
signal itemsChanged()
Component.onCompleted: {
console.log("Calculator: Plugin loaded")
// Load custom trigger from settings (default is "=")
if (pluginService) {
trigger = pluginService.loadPluginData("calculator", "trigger", "=")
}
}
// Required function: Get items for launcher
function getItems(query) {
console.log("Calculator: getItems called with query:", query)
// If query is empty, return nothing
if (!query || query.trim().length === 0) {
return []
}
const trimmedQuery = query.trim()
// Check if it looks like a math expression
if (!Calculator.isMathExpression(trimmedQuery)) {
return []
}
// Try to evaluate the expression
const result = Calculator.evaluate(trimmedQuery)
if (!result.success) {
// Don't show error items, just return empty
return []
}
// Format the result nicely
let resultString = result.result.toString()
// Only apply scientific notation formatting for actual numbers, not BigInt strings
if (typeof result.result === 'number') {
// For very long decimals or very large/small numbers, use scientific notation
if (resultString.length > 15 && Math.abs(result.result) >= 1e6) {
resultString = result.result.toExponential(6)
} else if (resultString.length > 15 && Math.abs(result.result) < 1e-6) {
resultString = result.result.toExponential(6)
}
}
// For BigInt string results, use as-is (already formatted)
return [
{
name: resultString,
icon: "material:equal",
comment: trimmedQuery + " = " + resultString,
action: "copy:" + resultString,
categories: ["Calculator"]
}
]
}
// Required function: Execute item action
function executeItem(item) {
if (!item || !item.action) {
console.warn("Calculator: Invalid item or action")
return
}
console.log("Calculator: Executing item:", item.name, "with action:", item.action)
const actionParts = item.action.split(":")
const actionType = actionParts[0]
const actionData = actionParts.slice(1).join(":")
switch (actionType) {
case "copy":
copyToClipboard(actionData)
break
default:
console.warn("Calculator: Unknown action type:", actionType)
showToast("Unknown action: " + actionType)
}
}
// Helper function to copy to clipboard
function copyToClipboard(text) {
Quickshell.execDetached(["sh", "-c", "echo -n '" + text + "' | wl-copy"])
showToast("Copied to clipboard: " + text)
}
// Helper function to show toast notification
function showToast(message) {
if (typeof ToastService !== "undefined") {
ToastService.showInfo("Calculator", message)
} else {
console.log("Calculator Toast:", message)
}
}
// Watch for trigger changes
onTriggerChanged: {
if (pluginService) {
pluginService.savePluginData("calculator", "trigger", trigger)
}
}
}

View file

@ -0,0 +1,275 @@
import QtQuick
import QtQuick.Controls
import qs.Widgets
FocusScope {
id: root
property var pluginService: null
implicitHeight: settingsColumn.implicitHeight
height: implicitHeight
Column {
id: settingsColumn
anchors.fill: parent
anchors.margins: 16
spacing: 16
Text {
text: "Calculator Plugin Settings"
font.pixelSize: 18
font.weight: Font.Bold
color: "#FFFFFF"
}
Text {
text: "This plugin evaluates mathematical expressions and copies the result to your clipboard."
font.pixelSize: 14
color: "#CCFFFFFF"
wrapMode: Text.WordWrap
width: parent.width - 32
}
Rectangle {
width: parent.width - 32
height: 1
color: "#30FFFFFF"
}
Column {
spacing: 12
width: parent.width - 32
Text {
text: "Trigger Configuration"
font.pixelSize: 16
font.weight: Font.Medium
color: "#FFFFFF"
}
Text {
text: noTriggerToggle.checked ? "Calculator is always active. Simply type a math expression like '3 + 3' in the launcher." : "Set a trigger prefix to activate the calculator. Type the trigger before your expression."
font.pixelSize: 12
color: "#CCFFFFFF"
wrapMode: Text.WordWrap
width: parent.width
}
Row {
spacing: 12
CheckBox {
id: noTriggerToggle
text: "No trigger (always active)"
checked: loadSettings("noTrigger", false)
contentItem: Text {
text: noTriggerToggle.text
font.pixelSize: 14
color: "#FFFFFF"
leftPadding: noTriggerToggle.indicator.width + 8
verticalAlignment: Text.AlignVCenter
}
indicator: Rectangle {
implicitWidth: 20
implicitHeight: 20
radius: 4
border.color: noTriggerToggle.checked ? "#4CAF50" : "#60FFFFFF"
border.width: 2
color: noTriggerToggle.checked ? "#4CAF50" : "transparent"
Rectangle {
width: 12
height: 12
anchors.centerIn: parent
radius: 2
color: "#FFFFFF"
visible: noTriggerToggle.checked
}
}
onCheckedChanged: {
saveSettings("noTrigger", checked)
if (checked) {
saveSettings("trigger", "")
} else {
const currentTrigger = triggerField.text || "="
saveSettings("trigger", currentTrigger)
}
}
}
}
Row {
spacing: 12
anchors.left: parent.left
anchors.right: parent.right
visible: !noTriggerToggle.checked
Text {
text: "Trigger:"
font.pixelSize: 14
color: "#FFFFFF"
anchors.verticalCenter: parent.verticalCenter
}
DankTextField {
id: triggerField
width: 100
height: 40
text: loadSettings("trigger", "=")
placeholderText: "="
backgroundColor: "#30FFFFFF"
textColor: "#FFFFFF"
onTextEdited: {
const newTrigger = text.trim()
saveSettings("trigger", newTrigger || "=")
saveSettings("noTrigger", newTrigger === "")
}
}
Text {
text: "Examples: =, calc, c"
font.pixelSize: 12
color: "#AAFFFFFF"
anchors.verticalCenter: parent.verticalCenter
}
}
}
Rectangle {
width: parent.width - 32
height: 1
color: "#30FFFFFF"
}
Column {
spacing: 8
width: parent.width - 32
Text {
text: "Supported Operations:"
font.pixelSize: 14
font.weight: Font.Medium
color: "#FFFFFF"
}
Column {
spacing: 4
leftPadding: 16
Text {
text: "• Addition: 3 + 3"
font.pixelSize: 12
color: "#CCFFFFFF"
}
Text {
text: "• Subtraction: 10 - 5"
font.pixelSize: 12
color: "#CCFFFFFF"
}
Text {
text: "• Multiplication: 4 * 7"
font.pixelSize: 12
color: "#CCFFFFFF"
}
Text {
text: "• Division: 20 / 4"
font.pixelSize: 12
color: "#CCFFFFFF"
}
Text {
text: "• Exponentiation: 2 ^ 8"
font.pixelSize: 12
color: "#CCFFFFFF"
}
Text {
text: "• Modulo: 17 % 5"
font.pixelSize: 12
color: "#CCFFFFFF"
}
Text {
text: "• Parentheses: (5 + 3) * 2"
font.pixelSize: 12
color: "#CCFFFFFF"
}
Text {
text: "• Decimals: 3.14 * 2"
font.pixelSize: 12
color: "#CCFFFFFF"
}
}
}
Rectangle {
width: parent.width - 32
height: 1
color: "#30FFFFFF"
}
Column {
spacing: 8
width: parent.width - 32
Text {
text: "Usage:"
font.pixelSize: 14
font.weight: Font.Medium
color: "#FFFFFF"
}
Column {
spacing: 4
leftPadding: 16
bottomPadding: 24
Text {
text: "1. Open Launcher (Ctrl+Space or click launcher button)"
font.pixelSize: 12
color: "#CCFFFFFF"
}
Text {
text: noTriggerToggle.checked ? "2. Type a mathematical expression (e.g., '3 + 3')" : "2. Type your trigger followed by the expression (e.g., '= 3 + 3')"
font.pixelSize: 12
color: "#CCFFFFFF"
}
Text {
text: "3. The result will appear as a launcher item"
font.pixelSize: 12
color: "#CCFFFFFF"
}
Text {
text: "4. Press Enter to copy the result to clipboard"
font.pixelSize: 12
color: "#CCFFFFFF"
}
}
}
}
function saveSettings(key, value) {
if (pluginService) {
pluginService.savePluginData("calculator", key, value)
}
}
function loadSettings(key, defaultValue) {
if (pluginService) {
return pluginService.loadPluginData("calculator", key, defaultValue)
}
return defaultValue
}
}

View file

@ -0,0 +1,152 @@
# Calculator Plugin for DMS Launcher
[![RELEASE](https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fgithub.com%2Frochacbruno%2FDankCalculator%2Fraw%2Frefs%2Fheads%2Fmain%2Fplugin.json&query=version&style=for-the-badge&label=RELEASE&labelColor=101418&color=9ccbfb)](https://plugins.danklinux.com/calculator.html)
A launcher plugin that evaluates mathematical expressions and copies results to the clipboard.
![Calculator Plugin Screenshot](screenshot.png)
## Features
- **Real-time calculation**: Type mathematical expressions directly in the launcher
- **Default prefix**: Uses `=` as the default trigger prefix (configurable)
- **Safe evaluation**: Only allows mathematical operations, preventing code injection
- **Clipboard integration**: Press Enter to copy the result to clipboard
- **Multiple operations**: Supports +, -, *, /, ^, %, and parentheses
## Installation
### Via DMS
```bash
dms plugins install Calculator
```
### Via DMS GUI
- Mod + ,
- Go to Plugins Tab
- Choose Browse
- Enable third party
- install Calculator
### Manually
```
cd ~/.config/DankMaterialShell/plugins
git clone https://github.com/rochacbruno/DankCalculator Calculator
```
1. Open DMS Settings (Ctrl+,)
2. Navigate to Plugins tab
3. Click "Scan for Plugins"
4. Enable the "Calculator" plugin with the toggle switch
## Usage
### With Default Settings (= Prefix)
1. Open the launcher (Ctrl+Space)
2. Type the `=` prefix followed by a mathematical expression: `= 3 + 3`
3. The result (`6`) appears as a launcher item
4. Press Enter to copy the result to clipboard
### Customizing the Trigger
You can configure a different trigger prefix or disable it entirely in the settings:
1. Open Settings → Plugins → Calculator
2. Change the trigger to a custom value (e.g., `calc`, `c`, `math`)
3. Or check "No trigger (always active)" to remove the prefix requirement
4. In the launcher, type your configured trigger: `calc 3 + 3` or just `3 + 3` (if no trigger)
5. Press Enter to copy the result
### Adding a keybinding (niri)
```kdl
binds {
Mod+Shift+C hotkey-overlay-title="Calculator" {
spawn "dms" "ipc" "call" "spotlight" "openQuery" "=";
}
}
```
## Supported Operations
- **Addition**: `= 3 + 3``6`
- **Subtraction**: `= 10 - 5``5`
- **Multiplication**: `= 4 * 7``28`
- **Division**: `= 20 / 4``5`
- **Exponentiation**: `= 2 ^ 8``256`
- **Modulo**: `= 17 % 5``2`
- **Parentheses**: `= (5 + 3) * 2``16`
- **Decimals**: `= 3.14 * 2``6.28`
- **Complex**: `= (10 + 5) * 2 - 3 / 3``29`
## Examples
| Expression | Result |
|------------|--------|
| `= 3 + 3` | `6` |
| `= 100 / 4` | `25` |
| `= 2 ^ 10` | `1024` |
| `= (5 + 3) * 2` | `16` |
| `= 3.14159 * 2` | `6.28318` |
| `= 16 ^ 0.5` | `4` (square root) |
## Security
The calculator uses safe expression evaluation:
- Only allows numbers, operators (+, -, *, /, ^, %), parentheses, and dots
- Rejects any expressions with letters or special characters (except operators)
- Prevents code injection by validating input before evaluation
## Files
- `plugin.json` - Plugin manifest
- `CalculatorLauncher.qml` - Main launcher component
- `CalculatorSettings.qml` - Settings UI
- `calculator.js` - Safe expression evaluation logic
- `README.md` - This file
## Configuration
Settings are stored in `~/.config/DankMaterialShell/plugin_settings.json` under the `calculator` plugin key:
```json
{
"pluginSettings": {
"calculator": {
"trigger": "=",
"noTrigger": false
}
}
}
```
## Troubleshooting
**Calculator items don't appear:**
- Make sure the plugin is enabled in Settings → Plugins
- Check that you're typing a valid mathematical expression
- Try disabling "No trigger" and setting a specific trigger
**Result shows wrong value:**
- JavaScript has floating-point precision limitations
- Very large or very small numbers may use scientific notation
**Copy to clipboard doesn't work:**
- Make sure your system clipboard is accessible
- Check console for error messages
## Version
1.0.0
## Author
Bruno Cesar Rocha
## License
Same as DankMaterialShell

View file

@ -0,0 +1,245 @@
// Calculator utility for safe mathematical expression evaluation
.pragma library
/**
* Checks if expression contains only integers (no decimals)
*/
function isIntegerOnly(expression) {
return !/\./.test(expression);
}
/**
* Evaluates integer expression using BigInt for precision
*/
function evaluateInteger(expression) {
try {
// Replace operators with BigInt-safe versions
let expr = expression.replace(/\s/g, '');
// Handle exponentiation separately (BigInt doesn't support **)
if (expr.includes('^')) {
return evaluateWithExponentiation(expr, true);
}
// For modulo, division, and basic arithmetic, try BigInt
// Note: BigInt division truncates, so we need to handle / carefully
if (expr.includes('/')) {
// If division exists, fall back to regular number for accuracy
return null;
}
// Replace numbers with BigInt literals
expr = expr.replace(/(\d+)/g, '$1n');
// Evaluate
const result = eval(expr);
// Convert back to string then number for display
// Check if result fits in safe integer range
const numResult = Number(result);
if (Number.isSafeInteger(numResult)) {
return numResult;
}
// Return as string for very large integers
return result.toString().replace(/n$/, '');
} catch (e) {
return null;
}
}
/**
* Handles exponentiation for both BigInt and regular numbers
*/
function evaluateWithExponentiation(expression, useBigInt) {
// Find exponentiation operations and evaluate them
let expr = expression;
// Handle ^ operator by converting to **
expr = expr.replace(/\^/g, '**');
if (useBigInt) {
// For BigInt, we need custom exponentiation
// This is complex, so fall back to regular evaluation
return null;
}
const result = eval(expr);
return result;
}
/**
* Performs precise decimal arithmetic by working with scaled integers
*/
function evaluatePrecise(expression) {
try {
// Replace ^ with ** for exponentiation
let cleaned = expression.replace(/\^/g, '**');
// Evaluate using JavaScript's eval (safe because we validated the input)
let result = eval(cleaned);
// Check if result is a valid number
if (typeof result !== 'number' || !isFinite(result)) {
return null;
}
// Handle floating point precision issues
// Round to 15 significant digits (JavaScript's max precision)
if (Math.abs(result) < 1e-10 && result !== 0) {
// Very small number, keep in scientific notation
return result;
}
// For regular decimals, use toPrecision to avoid floating point errors
// But only if the number has decimal places
if (result % 1 !== 0) {
// Count significant digits in result
const resultStr = result.toString();
if (resultStr.includes('e')) {
// Already in scientific notation
return result;
}
// Round to 15 significant figures to eliminate floating point errors
// e.g., 0.1 + 0.2 = 0.30000000000000004 becomes 0.3
const precision = 15;
const rounded = parseFloat(result.toPrecision(precision));
// If rounding made it a whole number, return as integer
if (rounded % 1 === 0 && Math.abs(rounded) < Number.MAX_SAFE_INTEGER) {
return Math.round(rounded);
}
return rounded;
}
return result;
} catch (e) {
return null;
}
}
/**
* Safely evaluates a mathematical expression
* @param {string} expression - The mathematical expression to evaluate
* @returns {object} - {success: boolean, result: number|string|null, error: string|null}
*/
function evaluate(expression) {
if (!expression || typeof expression !== 'string') {
return {
success: false,
result: null,
error: "Invalid expression"
};
}
// Clean the expression
let cleaned = expression.trim();
// Check if it's empty
if (cleaned.length === 0) {
return {
success: false,
result: null,
error: "Empty expression"
};
}
// Only allow numbers, basic operators, parentheses, dots, and spaces
const allowedChars = /^[0-9+\-*/().\s%^]+$/;
if (!allowedChars.test(cleaned)) {
return {
success: false,
result: null,
error: "Invalid characters in expression"
};
}
// Check if it looks like a mathematical expression
// Must contain at least one operator or be a simple number
const hasOperator = /[+\-*/^%]/.test(cleaned);
const isSimpleNumber = /^-?\d+\.?\d*$/.test(cleaned);
if (!hasOperator && !isSimpleNumber) {
return {
success: false,
result: null,
error: "Not a valid mathematical expression"
};
}
try {
let result;
// Try BigInt evaluation for integer-only expressions (better precision for large integers)
if (isIntegerOnly(cleaned) && !cleaned.includes('/')) {
result = evaluateInteger(cleaned);
}
// Fall back to precise decimal evaluation
if (result === null || result === undefined) {
result = evaluatePrecise(cleaned);
}
if (result === null || result === undefined) {
return {
success: false,
result: null,
error: "Evaluation failed"
};
}
// Check if result is valid
if (typeof result === 'number' && !isFinite(result)) {
return {
success: false,
result: null,
error: "Invalid result"
};
}
return {
success: true,
result: result,
error: null
};
} catch (e) {
return {
success: false,
result: null,
error: "Evaluation error: " + e.message
};
}
}
/**
* Checks if a string looks like it could be a mathematical expression
* @param {string} query - The query to check
* @returns {boolean} - True if it looks like a math expression
*/
function isMathExpression(query) {
if (!query || typeof query !== 'string') {
return false;
}
const cleaned = query.trim();
// Must contain only allowed characters
const allowedChars = /^[0-9+\-*/().\s%^]+$/;
if (!allowedChars.test(cleaned)) {
return false;
}
// Must have at least one digit
if (!/\d/.test(cleaned)) {
return false;
}
// Must be at least 3 characters for an expression (e.g., "1+1")
// or be a simple number
const hasOperator = /[+\-*/^%]/.test(cleaned);
const isSimpleNumber = /^-?\d+\.?\d*$/.test(cleaned);
return (hasOperator && cleaned.length >= 3) || isSimpleNumber;
}

View file

@ -0,0 +1 @@
ref: refs/heads/main

View file

@ -0,0 +1,9 @@
[core]
bare = false
filemode = true
[remote "origin"]
url = https://github.com/rochacbruno/DankCalculator
fetch = +refs/heads/*:refs/remotes/origin/*
[branch "main"]
remote = origin
merge = refs/heads/main

View file

@ -0,0 +1 @@
de6dbd59b7630e897a50e107f704c1cd4a131128

View file

@ -0,0 +1 @@
de6dbd59b7630e897a50e107f704c1cd4a131128

View file

@ -0,0 +1 @@
ea2857f89d1a0d00e01e6697f11da2d9f267e071

View file

@ -0,0 +1,19 @@
{
"id": "calculator",
"name": "Calculator",
"description": "A calculator plugin that evaluates mathematical expressions and copies results to clipboard",
"version": "0.1.3",
"author": "Bruno Cesar Rocha <rochacbruno>",
"icon": "calculate",
"type": "launcher",
"capabilities": ["launcher"],
"component": "./CalculatorLauncher.qml",
"settings": "./CalculatorSettings.qml",
"trigger": "=",
"requires_dms": ">0.1.18",
"requires": ["wl-copy"],
"permissions": [
"settings_read",
"settings_write"
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

View file

@ -0,0 +1,181 @@
#!/usr/bin/env node
// Test cases for calculator precision improvements
// Note: This is a Node.js test file for demonstration purposes
// The actual calculator.js is a QML JavaScript module
// Mock QML .pragma library behavior
const Calculator = {
isIntegerOnly: function(expression) {
return !/\./.test(expression);
},
evaluateInteger: function(expression) {
try {
let expr = expression.replace(/\s/g, '');
if (expr.includes('^') || expr.includes('/')) {
return null;
}
expr = expr.replace(/(\d+)/g, '$1n');
const result = eval(expr);
const numResult = Number(result);
if (Number.isSafeInteger(numResult)) {
return numResult;
}
return result.toString().replace(/n$/, '');
} catch (e) {
return null;
}
},
evaluatePrecise: function(expression) {
try {
let cleaned = expression.replace(/\^/g, '**');
let result = eval(cleaned);
if (typeof result !== 'number' || !isFinite(result)) {
return null;
}
if (Math.abs(result) < 1e-10 && result !== 0) {
return result;
}
if (result % 1 !== 0) {
const resultStr = result.toString();
if (resultStr.includes('e')) {
return result;
}
const precision = 15;
const rounded = parseFloat(result.toPrecision(precision));
if (rounded % 1 === 0 && Math.abs(rounded) < Number.MAX_SAFE_INTEGER) {
return Math.round(rounded);
}
return rounded;
}
return result;
} catch (e) {
return null;
}
},
evaluate: function(expression) {
if (!expression || typeof expression !== 'string') {
return { success: false, result: null, error: "Invalid expression" };
}
let cleaned = expression.trim();
if (cleaned.length === 0) {
return { success: false, result: null, error: "Empty expression" };
}
const allowedChars = /^[0-9+\-*/().\s%^]+$/;
if (!allowedChars.test(cleaned)) {
return { success: false, result: null, error: "Invalid characters" };
}
const hasOperator = /[+\-*/^%]/.test(cleaned);
const isSimpleNumber = /^-?\d+\.?\d*$/.test(cleaned);
if (!hasOperator && !isSimpleNumber) {
return { success: false, result: null, error: "Not a math expression" };
}
try {
let result;
if (this.isIntegerOnly(cleaned) && !cleaned.includes('/')) {
result = this.evaluateInteger(cleaned);
}
if (result === null || result === undefined) {
result = this.evaluatePrecise(cleaned);
}
if (result === null || result === undefined) {
return { success: false, result: null, error: "Evaluation failed" };
}
if (typeof result === 'number' && !isFinite(result)) {
return { success: false, result: null, error: "Invalid result" };
}
return { success: true, result: result, error: null };
} catch (e) {
return { success: false, result: null, error: "Evaluation error: " + e.message };
}
}
};
// Test cases
console.log("=== Calculator Precision Test Suite ===\n");
const tests = [
// Floating point precision tests
{ expr: "0.1 + 0.2", expected: "0.3", description: "Classic floating point precision issue" },
{ expr: "0.1 + 0.2 + 0.3", expected: "0.6", description: "Multiple decimal additions" },
{ expr: "1.1 + 2.2", expected: "3.3", description: "Simple decimal addition" },
{ expr: "3.3 - 1.1", expected: "2.2", description: "Decimal subtraction" },
{ expr: "0.3 - 0.1", expected: "0.2", description: "Another precision issue" },
// BigInt large integer tests
{ expr: "999999999999999999 + 1", expected: "1000000000000000000", description: "Large integer addition (BigInt)" },
{ expr: "123456789012345678 * 2", expected: "246913578024691356", description: "Large integer multiplication (BigInt)" },
{ expr: "999999999999999999 - 999999999999999998", expected: "1", description: "Large integer subtraction" },
// Regular operations
{ expr: "2 + 2", expected: "4", description: "Simple addition" },
{ expr: "10 - 3", expected: "7", description: "Simple subtraction" },
{ expr: "5 * 6", expected: "30", description: "Simple multiplication" },
{ expr: "20 / 4", expected: "5", description: "Simple division" },
{ expr: "2 ^ 10", expected: "1024", description: "Exponentiation" },
{ expr: "17 % 5", expected: "2", description: "Modulo" },
{ expr: "(5 + 3) * 2", expected: "16", description: "Parentheses" },
// Edge cases
{ expr: "100 / 3", expected: "33.3333333333333", description: "Repeating decimal (rounded to 15 sig figs)" },
{ expr: "1.23456789012345678", expected: "1.23456789012346", description: "High precision decimal (rounded to 15 sig figs)" }
];
let passed = 0;
let failed = 0;
tests.forEach((test, index) => {
const result = Calculator.evaluate(test.expr);
const resultStr = result.success ? result.result.toString() : "ERROR";
const matches = resultStr.startsWith(test.expected.substring(0, Math.min(test.expected.length, 10)));
if (matches) {
console.log(`✓ Test ${index + 1}: ${test.description}`);
console.log(` Expression: ${test.expr}`);
console.log(` Result: ${resultStr}`);
passed++;
} else {
console.log(`✗ Test ${index + 1}: ${test.description}`);
console.log(` Expression: ${test.expr}`);
console.log(` Expected: ${test.expected}`);
console.log(` Got: ${resultStr}`);
failed++;
}
console.log();
});
console.log(`=== Test Results ===`);
console.log(`Passed: ${passed}/${tests.length}`);
console.log(`Failed: ${failed}/${tests.length}`);
if (failed === 0) {
console.log("\n✓ All tests passed!");
} else {
console.log(`\n${failed} test(s) failed`);
process.exit(1);
}

View file

@ -0,0 +1,39 @@
// Quick test for the specific case
const fs = require('fs');
// Load and parse the calculator.js file (QML pragma library style)
const calcCode = fs.readFileSync('./calculator.js', 'utf8')
.replace('.pragma library', ''); // Remove QML pragma
// Create a function wrapper to execute the code
const Calculator = new Function(calcCode + '\nreturn { isMathExpression, evaluate };')();
// Test the specific case
const testValue = '99999999999';
console.log('Testing:', testValue);
console.log('---');
const isMath = Calculator.isMathExpression(testValue);
console.log('isMathExpression:', isMath);
if (isMath) {
const result = Calculator.evaluate(testValue);
console.log('Evaluation success:', result.success);
console.log('Result value:', result.result);
console.log('Result type:', typeof result.result);
if (result.success) {
const str = result.result.toString();
console.log('String representation:', str);
console.log('String length:', str.length);
console.log('---');
console.log('Would convert to scientific notation?');
console.log(' Length > 15?', str.length > 15, `(actual: ${str.length})`);
console.log(' Abs value >= 1e6?', Math.abs(result.result) >= 1e6, `(actual: ${result.result})`);
console.log(' RESULT:', (str.length > 15 && Math.abs(result.result) >= 1e6) ? 'YES - would use scientific' : 'NO - would display normally');
if (str.length > 15 && Math.abs(result.result) >= 1e6) {
console.log(' Scientific notation:', result.result.toExponential(6));
}
}
}