diff --git a/Wox.UI.Flutter/wox/lib/modules/launcher/wox_launcher_controller.dart b/Wox.UI.Flutter/wox/lib/modules/launcher/wox_launcher_controller.dart index 2e5568f3d..3fc046bd1 100644 --- a/Wox.UI.Flutter/wox/lib/modules/launcher/wox_launcher_controller.dart +++ b/Wox.UI.Flutter/wox/lib/modules/launcher/wox_launcher_controller.dart @@ -193,11 +193,7 @@ class WoxLauncherController extends GetxController implements WoxLauncherInterfa if (query.queryType == WoxQueryTypeEnum.WOX_QUERY_TYPE_INPUT.code) { if (queryBoxTextFieldController.text != query.queryText) { - // save the cursor position and then restore it after set text - var cursorPosition = queryBoxTextFieldController.selection.baseOffset; queryBoxTextFieldController.text = query.queryText; - cursorPosition = cursorPosition > queryBoxTextFieldController.text.length ? queryBoxTextFieldController.text.length : cursorPosition; - queryBoxTextFieldController.selection = TextSelection(baseOffset: cursorPosition, extentOffset: cursorPosition); } if (moveCursorToEnd) { moveQueryBoxCursorToEnd(); diff --git a/Wox/go.mod b/Wox/go.mod index c8c83e196..a83bd04c7 100644 --- a/Wox/go.mod +++ b/Wox/go.mod @@ -7,6 +7,7 @@ require ( github.com/cdfmlr/ellipsis v0.0.1 github.com/disintegration/imaging v1.6.2 github.com/fsnotify/fsnotify v1.7.0 + github.com/gen2brain/beeep v0.0.0-20230907135156-1a38885a97fc github.com/getlantern/systray v1.2.2 github.com/go-resty/resty/v2 v2.9.1 github.com/google/uuid v1.3.1 @@ -15,9 +16,9 @@ require ( github.com/jinzhu/now v1.1.5 github.com/mat/besticon v0.0.0-20231103204413-ee089084f347 github.com/mitchellh/go-homedir v1.1.0 - github.com/mnogu/go-calculator v0.0.1 github.com/mozillazg/go-pinyin v0.20.0 github.com/olahol/melody v1.1.4 + github.com/otiai10/copy v1.14.0 github.com/parsiya/golnk v0.0.0-20221103095132-740a4c27c4ff github.com/petermattis/goid v0.0.0-20230904192822-1876fd5063bc github.com/robotn/gohook v0.41.0 @@ -30,6 +31,7 @@ require ( github.com/tidwall/gjson v1.17.0 github.com/tidwall/pretty v1.2.1 github.com/wissance/stringFormatter v1.1.1 + github.com/xeonx/timeago v1.0.0-rc5 go.uber.org/zap v1.26.0 golang.design/x/hotkey v0.4.1 golang.org/x/image v0.13.0 @@ -40,7 +42,6 @@ require ( github.com/PuerkitoBio/goquery v1.8.1 // indirect github.com/andybalholm/cascadia v1.3.1 // indirect github.com/davecgh/go-spew v1.1.1 // indirect - github.com/gen2brain/beeep v0.0.0-20230907135156-1a38885a97fc // indirect github.com/getlantern/context v0.0.0-20190109183933-c447772a6520 // indirect github.com/getlantern/errors v0.0.0-20190325191628-abdb3e3e36f7 // indirect github.com/getlantern/golog v0.0.0-20190830074920-4ef2e798c2d7 // indirect @@ -58,14 +59,13 @@ require ( github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d // indirect github.com/olekukonko/tablewriter v0.0.5 // indirect - github.com/otiai10/copy v1.14.0 // indirect github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/saracen/zipextra v0.0.0-20220303013732-0187cb0159ea // indirect + github.com/shopspring/decimal v1.3.1 // indirect github.com/tadvi/systray v0.0.0-20190226123456-11a2b8fa57af // indirect github.com/tidwall/match v1.1.1 // indirect github.com/vcaesar/keycode v0.10.1 // indirect - github.com/xeonx/timeago v1.0.0-rc5 // indirect go.uber.org/multierr v1.10.0 // indirect golang.design/x/mainthread v0.3.0 // indirect golang.org/x/exp v0.0.0-20220303212507-bbda1eaf7a17 // indirect diff --git a/Wox/go.sum b/Wox/go.sum index 9eea50803..0a6cb3802 100644 --- a/Wox/go.sum +++ b/Wox/go.sum @@ -4,7 +4,6 @@ github.com/PuerkitoBio/goquery v1.8.1 h1:uQxhNlArOIdbrH1tr0UXwdVFgDcZDrZVdcpygAc github.com/PuerkitoBio/goquery v1.8.1/go.mod h1:Q8ICL1kNUJ2sXGoAhPGUdYDJvgQgHzJsnnd3H7Ho5jQ= github.com/andybalholm/cascadia v1.3.1 h1:nhxRkql1kdYCc8Snf7D5/D3spOX+dBgjA6u8x004T2c= github.com/andybalholm/cascadia v1.3.1/go.mod h1:R4bJ1UQfqADjvDa4P6HZHLh/3OxWWEqc0Sk8XGwHqvA= -github.com/c-bata/go-prompt v0.2.5/go.mod h1:vFnjEGDIIA/Lib7giyE4E9c50Lvl8j0S+7FVlAwDAVw= github.com/cdfmlr/ellipsis v0.0.1 h1:4pwrPbKPMd4mXSdJA4CSRjgEzCbXyRiFBkmgg2KclBI= github.com/cdfmlr/ellipsis v0.0.1/go.mod h1:hulYx9m/7Edoo2AkRzkJ/YPDlLB45BgjitI3z0sMVFI= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -66,19 +65,10 @@ github.com/lxn/walk v0.0.0-20210112085537-c389da54e794/go.mod h1:E23UucZGqpuUANJ github.com/lxn/win v0.0.0-20210218163916-a377121e959e/go.mod h1:KxxjdtRkfNoYDCUP5ryK7XJJNTnpC8atvtmTheChOtk= github.com/mat/besticon v0.0.0-20231103204413-ee089084f347 h1:FSfjh3/9PRsWzjCDdNYLsXeCz5S3D2/iwK/8lnzOu2U= github.com/mat/besticon v0.0.0-20231103204413-ee089084f347/go.mod h1:bzMBPMkFE6oCncbLySBPWc4XB5AuglYIkqKL/j9vp3c= -github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= -github.com/mattn/go-colorable v0.1.7/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= -github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= -github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84= -github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= -github.com/mattn/go-runewidth v0.0.6/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0= github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= -github.com/mattn/go-tty v0.0.3/go.mod h1:ihxohKRERHTVzN+aSVRwACLCeqIoZAWpoICkkvrWyR0= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= -github.com/mnogu/go-calculator v0.0.1 h1:paStmrI700koHPtMGNQoAlbQNV1OWjwmSTzeAGq/t+k= -github.com/mnogu/go-calculator v0.0.1/go.mod h1:ZZaGIAlygAGgwJq2XSwV/uTrnlmEvvAIM5g4kQK/dqk= github.com/mozillazg/go-pinyin v0.20.0 h1:BtR3DsxpApHfKReaPO1fCqF4pThRwH9uwvXzm+GnMFQ= github.com/mozillazg/go-pinyin v0.20.0/go.mod h1:iR4EnMMRXkfpFVV5FMi4FNB6wGq9NV6uDWbUuPhP4Yc= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= @@ -91,13 +81,14 @@ github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= github.com/otiai10/copy v1.14.0 h1:dCI/t1iTdYGtkvCuBG2BgR6KZa83PTclw4U5n2wAllU= github.com/otiai10/copy v1.14.0/go.mod h1:ECfuL02W+/FkTWZWgQqXPWZgW9oeKCSQ5qVfSc4qc4w= +github.com/otiai10/mint v1.5.1 h1:XaPLeE+9vGbuyEHem1JNk3bYc7KKqyI/na0/mLd/Kks= +github.com/otiai10/mint v1.5.1/go.mod h1:MJm72SBthJjz8qhefc4z1PYEieWmy8Bku7CjcAqyUSM= github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c h1:rp5dCmg/yLR3mgFuSOe4oEnDDmGLROTvMragMUXpTQw= github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c/go.mod h1:X07ZCGwUbLaax7L0S3Tw4hpejzu63ZrrQiUe6W0hcy0= github.com/parsiya/golnk v0.0.0-20221103095132-740a4c27c4ff h1:japdIZgV4tJIgn7NqUD7mAkLiPRsPK5LXVgjNwFtDA4= github.com/parsiya/golnk v0.0.0-20221103095132-740a4c27c4ff/go.mod h1:A24WXUol4NXZlK8grjh/CsZnPlimfwaQFt5PQsqS27s= github.com/petermattis/goid v0.0.0-20230904192822-1876fd5063bc h1:8bQZVK1X6BJR/6nYUPxQEP+ReTsceJTKizeuwjWOPUA= github.com/petermattis/goid v0.0.0-20230904192822-1876fd5063bc/go.mod h1:pxMtw7cyUw6B2bRH0ZBANSPg+AoSud1I1iyJHI69jH4= -github.com/pkg/term v1.1.0/go.mod h1:E25nymQcrSllhX42Ok8MRm1+hyBdHY0dCeiKZ9jpNGw= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/robotn/gohook v0.41.0 h1:h1vK3w/UQpq0YkIiGnxm9Awv85W54esL0/NUYGueggo= @@ -114,10 +105,11 @@ github.com/saracen/zipextra v0.0.0-20220303013732-0187cb0159ea h1:8czYLkvzZRE+AE github.com/saracen/zipextra v0.0.0-20220303013732-0187cb0159ea/go.mod h1:hnzuad9d2wdd3z8fC6UouHQK5qZxqv3F/E6MMzXc7q0= github.com/sashabaranov/go-openai v1.17.5 h1:ItBzlrrfTtkFWOFlgfOhk3y/xRBC4PJol4gdbiK7hgg= github.com/sashabaranov/go-openai v1.17.5/go.mod h1:lj5b/K+zjTSFxVLijLSTDZuP7adOgerWeFyZLUhAKRg= +github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8= +github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966/go.mod h1:sUM3LWHvSMaG192sy56D9F7CNvL7jUJVXoqM1QKLnog= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/tadvi/systray v0.0.0-20190226123456-11a2b8fa57af h1:6yITBqGTE2lEeTPG04SN9W+iWHCRyHqlVYILiSXziwk= @@ -169,21 +161,11 @@ golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.2.0 h1:PUR+T4wwASmuSTYdKjYHI5TD22Wy5ogLU5qZCOLxBrI= -golang.org/x/sync v0.2.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E= golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200909081042-eff7692f9009/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200918174421-af09f7315aff/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201018230417-eeed37f84f13/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201022201747-fb209a7c41cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -196,8 +178,6 @@ golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= -golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= @@ -231,7 +211,6 @@ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8 gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v1 v1.0.0-20140924161607-9f9df34309c0/go.mod h1:WDnlLJ4WF5VGsH/HVa3CI79GS0ol3YnhVnKP89i0kNg= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= howett.net/plist v1.0.0 h1:7CrbWYbPPO/PyNy38b2EB/+gYbjCe2DXBxgtOOZbSQM= diff --git a/Wox/main.go b/Wox/main.go index 2137a287d..2d3c8c1cc 100644 --- a/Wox/main.go +++ b/Wox/main.go @@ -16,9 +16,12 @@ import ( "wox/util/hotkey" ) -import _ "wox/plugin/host" // import all hosts -import _ "wox/plugin/system" // import all system plugins +import _ "wox/plugin/host" // import all hosts + +// import all system plugins +import _ "wox/plugin/system" import _ "wox/plugin/system/app" +import _ "wox/plugin/system/calculator" func main() { // logger depends on location, so location must be initialized first diff --git a/Wox/plugin/system/calculator/README.md b/Wox/plugin/system/calculator/README.md new file mode 100644 index 000000000..6e522dd94 --- /dev/null +++ b/Wox/plugin/system/calculator/README.md @@ -0,0 +1 @@ +Based on https://github.com/mnogu/go-calculator \ No newline at end of file diff --git a/Wox/plugin/system/calculator/calculator.go b/Wox/plugin/system/calculator/calculator.go new file mode 100644 index 000000000..55b7c9621 --- /dev/null +++ b/Wox/plugin/system/calculator/calculator.go @@ -0,0 +1,149 @@ +package calculator + +import ( + "fmt" + "github.com/shopspring/decimal" + "math" +) + +var functions = map[string]interface{}{ + "abs": math.Abs, + "acos": math.Acos, + "acosh": math.Acosh, + "asin": math.Asin, + "asinh": math.Asinh, + "atan": math.Atan, + "atan2": math.Atan2, + "atanh": math.Atanh, + "cbrt": math.Cbrt, + "ceil": math.Ceil, + "copysign": math.Copysign, + "cos": math.Cos, + "cosh": math.Cosh, + "dim": math.Dim, + "erf": math.Erf, + "erfc": math.Erfc, + "erfcinv": math.Erfcinv, // Go 1.10+ + "erfinv": math.Erfinv, // Go 1.10+ + "exp": math.Exp, + "exp2": math.Exp2, + "expm1": math.Expm1, + "fma": math.FMA, // Go 1.14+ + "floor": math.Floor, + "gamma": math.Gamma, + "hypot": math.Hypot, + "j0": math.J0, + "j1": math.J1, + "log": math.Log, + "log10": math.Log10, + "log1p": math.Log1p, + "log2": math.Log2, + "logb": math.Logb, + "max": math.Max, + "min": math.Min, + "mod": math.Mod, + "nan": math.NaN, + "nextafter": math.Nextafter, + "pow": math.Pow, + "remainder": math.Remainder, + "round": math.Round, // Go 1.10+ + "roundtoeven": math.RoundToEven, // Go 1.10+ + "sin": math.Sin, + "sinh": math.Sinh, + "sqrt": math.Sqrt, + "tan": math.Tan, + "tanh": math.Tanh, + "trunc": math.Trunc, + "y0": math.Y0, + "y1": math.Y1, +} + +func call(funcName string, args []decimal.Decimal) (decimal.Decimal, error) { + f, ok := functions[funcName] + if !ok { + return decimal.Zero, fmt.Errorf("unknown function %s", funcName) + } + switch f := f.(type) { + case func() float64: + return decimal.NewFromFloat(f()), nil + case func(float64) float64: + return decimal.NewFromFloat(f(args[0].InexactFloat64())), nil + case func(float64, float64) float64: + return decimal.NewFromFloat(f(args[0].InexactFloat64(), args[1].InexactFloat64())), nil + case func(float64, float64, float64) float64: + return decimal.NewFromFloat(f(args[0].InexactFloat64(), args[1].InexactFloat64(), args[2].InexactFloat64())), nil + default: + return decimal.Zero, fmt.Errorf("invalid function %s", funcName) + } +} + +func calculate(n *node) (decimal.Decimal, error) { + switch n.kind { + case addNode: + left, err := calculate(n.left) + if err != nil { + return decimal.Zero, err + } + right, err := calculate(n.right) + if err != nil { + return decimal.Zero, err + } + return left.Add(right), nil + case subNode: + left, err := calculate(n.left) + if err != nil { + return decimal.Zero, err + } + right, err := calculate(n.right) + if err != nil { + return decimal.Zero, err + } + return left.Sub(right), nil + case mulNode: + left, err := calculate(n.left) + if err != nil { + return decimal.Zero, err + } + right, err := calculate(n.right) + if err != nil { + return decimal.Zero, err + } + return left.Mul(right), nil + case divNode: + left, err := calculate(n.left) + if err != nil { + return decimal.Zero, err + } + right, err := calculate(n.right) + if err != nil { + return decimal.Zero, err + } + return left.Div(right), nil + case numNode: + return n.val, nil + case funcNode: + var args []decimal.Decimal + for _, arg := range n.args { + val, err := calculate(arg) + if err != nil { + return decimal.Zero, err + } + args = append(args, val) + } + return call(n.funcName, args) + } + return decimal.Zero, fmt.Errorf("unknown node type: %s", n.kind) +} + +func Calculate(expr string) (decimal.Decimal, error) { + tokens, err := tokenize(expr) + if err != nil { + return decimal.Zero, err + } + p := newParser(tokens) + n, err := p.parse() + if err != nil { + return decimal.Zero, err + } + return calculate(n) +} diff --git a/Wox/plugin/system/calculator.go b/Wox/plugin/system/calculator/calculator_plugin.go similarity index 97% rename from Wox/plugin/system/calculator.go rename to Wox/plugin/system/calculator/calculator_plugin.go index 380668aa2..1b71a2bde 100644 --- a/Wox/plugin/system/calculator.go +++ b/Wox/plugin/system/calculator/calculator_plugin.go @@ -1,9 +1,7 @@ -package system +package calculator import ( "context" - "github.com/mnogu/go-calculator" - "strconv" "strings" "wox/plugin" "wox/share" @@ -66,11 +64,11 @@ func (c *CalculatorPlugin) Query(ctx context.Context, query plugin.Query) []plug return []plugin.QueryResult{} } - val, err := calculator.Calculate(query.Search) + val, err := Calculate(query.Search) if err != nil { return []plugin.QueryResult{} } - result := strconv.FormatFloat(val, 'f', -1, 64) + result := val.String() results = append(results, plugin.QueryResult{ Title: result, @@ -92,9 +90,9 @@ func (c *CalculatorPlugin) Query(ctx context.Context, query plugin.Query) []plug // only show history if query has trigger keyword if query.TriggerKeyword != "" { - val, err := calculator.Calculate(query.Search) + val, err := Calculate(query.Search) if err == nil { - result := strconv.FormatFloat(val, 'f', -1, 64) + result := val.String() results = append(results, plugin.QueryResult{ Title: result, Icon: calculatorIcon, diff --git a/Wox/plugin/system/calculator/parser.go b/Wox/plugin/system/calculator/parser.go new file mode 100644 index 000000000..01df49758 --- /dev/null +++ b/Wox/plugin/system/calculator/parser.go @@ -0,0 +1,229 @@ +package calculator + +import ( + "fmt" + "github.com/shopspring/decimal" + "math" + "strings" +) + +type nodeKind string + +const ( + addNode nodeKind = "+" + subNode nodeKind = "-" + mulNode nodeKind = "*" + divNode nodeKind = "/" + funcNode nodeKind = "func" + numNode nodeKind = "num" +) + +type node struct { + kind nodeKind + left *node + right *node + + funcName string + args []*node + + val decimal.Decimal +} + +type parser struct { + tokens []token + i int +} + +func newParser(tokens []token) *parser { + return &parser{tokens: tokens, i: 0} +} + +func (p *parser) numberNode() (*node, error) { + t := p.tokens[p.i] + if t.kind != numberToken { + return nil, fmt.Errorf("expected a number: %s", t.str) + } + p.i++ + return &node{kind: numNode, val: t.val}, nil +} + +func (p *parser) constantNode(str string) (*node, error) { + constants := map[string]float64{ + "e": math.E, + "pi": math.Pi, + "phi": math.Phi, + + "sqrt2": math.Sqrt2, + "sqrte": math.SqrtE, + "sqrtpi": math.SqrtPi, + "sqrtphi": math.SqrtPhi, + + "ln2": math.Ln2, + "log2e": math.Log2E, + "ln10": math.Ln10, + "log10e": math.Log10E, + } + val, ok := constants[strings.ToLower(str)] + if !ok { + return nil, fmt.Errorf("unknown constant: %s", str) + } + p.i++ + return &node{kind: numNode, val: decimal.NewFromFloat(val)}, nil +} + +func argumentNumber(funcName string) (int, error) { + f, ok := functions[funcName] + if !ok { + return 0, fmt.Errorf("unknown function: %s", funcName) + } + + switch f.(type) { + case func() float64: + return 0, nil + case func(float64) float64: + return 1, nil + case func(float64, float64) float64: + return 2, nil + case func(float64, float64, float64) float64: + return 3, nil + default: + return 0, fmt.Errorf("invalid function: %s", funcName) + } +} + +func (p *parser) functionNode(str string) (*node, error) { + funcName := strings.ToLower(str) + num, err := argumentNumber(funcName) + if err != nil { + return nil, err + } + + if p.consume(")") { + if num != 0 { + return nil, fmt.Errorf("%s should have argument(s)", funcName) + } + return &node{kind: funcNode, funcName: funcName}, nil + } + + args := []*node{} + + n, err := p.add() + if err != nil { + return nil, err + } + args = append(args, n) + + for p.consume(",") { + n, err := p.add() + if err != nil { + return nil, err + } + args = append(args, n) + } + if len(args) != num { + return nil, fmt.Errorf("%s should have %d argument(s) but has %d arguments(s)", + funcName, num, len(args)) + } + p.consume(")") + return &node{kind: funcNode, funcName: funcName, args: args}, nil +} + +func (p *parser) consume(s string) bool { + t := p.tokens[p.i] + if t.kind != reservedToken || t.str != s { + return false + } + p.i++ + return true +} + +func (p *parser) parse() (*node, error) { + return p.add() + +} + +func (p *parser) insert(n *node, f func() (*node, error), kind nodeKind) (*node, error) { + left := n + right, err := f() + if err != nil { + return n, err + } + return &node{kind: kind, left: left, right: right}, err +} + +func (p *parser) add() (*node, error) { + n, err := p.mul() + if err != nil { + return nil, err + } + + for { + if p.consume("+") { + n, err = p.insert(n, p.mul, addNode) + if err != nil { + return nil, err + } + } else if p.consume("-") { + n, err = p.insert(n, p.mul, subNode) + if err != nil { + return nil, err + } + } else { + return n, nil + } + } +} + +func (p *parser) mul() (*node, error) { + n, err := p.unary() + if err != nil { + return nil, err + } + + for { + if p.consume("*") { + n, err = p.insert(n, p.unary, mulNode) + if err != nil { + return nil, err + } + } else if p.consume("/") { + n, err = p.insert(n, p.unary, divNode) + if err != nil { + return nil, err + } + } else { + return n, nil + } + } +} + +func (p *parser) unary() (*node, error) { + if p.consume("+") { + return p.primary() + } else if p.consume("-") { + return p.insert(&node{kind: numNode, val: decimal.Zero}, p.primary, subNode) + } + return p.primary() +} + +func (p *parser) primary() (*node, error) { + if p.consume("(") { + n, err := p.add() + if err != nil { + return nil, err + } + p.consume(")") + return n, nil + } + + if p.tokens[p.i].kind == identToken { + str := p.tokens[p.i].str + p.i++ + if p.i < len(p.tokens) && p.consume("(") { + return p.functionNode(str) + } + p.i-- + return p.constantNode(str) + } + return p.numberNode() +} diff --git a/Wox/plugin/system/calculator/tokenizer.go b/Wox/plugin/system/calculator/tokenizer.go new file mode 100644 index 000000000..6e10da6c7 --- /dev/null +++ b/Wox/plugin/system/calculator/tokenizer.go @@ -0,0 +1,120 @@ +package calculator + +import ( + "errors" + "github.com/shopspring/decimal" + "strconv" + "strings" + "unicode" +) + +type tokenKind string + +const ( + reservedToken tokenKind = "reserved" + numberToken tokenKind = "number" + identToken tokenKind = "ident" + eosToken tokenKind = "eos" +) + +type token struct { + kind tokenKind + val decimal.Decimal + str string +} + +type invalidTokenError struct { + input string + position int +} + +func (e *invalidTokenError) Error() string { + curr := "" + pos := e.position + for _, line := range strings.Split(e.input, "\n") { + len := len(line) + curr += line + "\n" + if pos < len { + return curr + strings.Repeat(" ", pos) + "^ invalid token" + } + pos -= len + 1 + } + return "" +} + +const operators = "+-*/()," + +func isOperator(char rune) bool { + for _, op := range operators { + if char == op { + return true + } + } + return false +} + +func numberPrefix(chars []rune, i *int, n int) (float64, error) { + val := 0.0 + len := 0 + for *i < n { + curr, err := strconv.ParseFloat(string(chars[*i-len:*i+1]), 64) + if err != nil { + break + } + val = curr + len++ + *i++ + } + if len > 0 { + return val, nil + } + return 0, errors.New("expected a number") +} + +func isAlpha(char rune) bool { + return (char >= 'a' && char <= 'z') || (char >= 'A' && char <= 'Z') +} + +func isAlNum(char rune) bool { + return isAlpha(char) || (char >= '0' && char <= '9') +} + +func tokenize(input string) ([]token, error) { + chars := []rune(input) + i := 0 + n := len(chars) + tokens := []token{} + for i < n { + char := chars[i] + if unicode.IsSpace(char) { + i++ + continue + } + + if isAlpha(char) { + start := i + i++ + for i < n && isAlNum(chars[i]) { + i++ + } + tokens = append(tokens, + token{kind: identToken, str: string(chars[start:i])}) + continue + } + + if isOperator(char) { + tokens = append(tokens, token{kind: reservedToken, str: string(char)}) + i++ + continue + } + + if val, err := numberPrefix(chars, &i, n); err == nil { + tokens = append(tokens, token{kind: numberToken, val: decimal.NewFromFloat(val)}) + continue + } + + return nil, &invalidTokenError{input: input, position: i} + } + tokens = append(tokens, token{kind: eosToken}) + return tokens, nil +}