Compare commits

...

488 Commits

Author SHA1 Message Date
Zachary Yedidia
60846f549c Update plugin documentation 2020-06-24 17:24:45 -04:00
Zachary Yedidia
5f62f550f3 Add more functions to customize status bar
Adds `status.lines`, `status.vcol`, `status.bytes`, `status.size`,
and exposes some functions from go-humanize to plugins.

Ref #1727
2020-06-24 17:19:42 -04:00
Zachary Yedidia
db1df05017 Support month and day names in crontab syntax
Ref #1739
2020-06-24 16:33:22 -04:00
Zachary Yedidia
05cbc310f3 Use boundaries in cron syntax rules
Fixes #1739
2020-06-24 16:00:56 -04:00
Zachary Yedidia
3ddb2ee316 Add Search function to BufPane 2020-06-23 18:47:42 -04:00
Zachary Yedidia
a749786830 Update readme
Ref #1741
2020-06-23 17:37:58 -04:00
Zachary Yedidia
687e4bdc25 Don't delete user settings if a parse error occurs 2020-06-23 17:29:20 -04:00
Zachary Yedidia
37c754c7c7 Treat CRLF as LF when inserting text
In effect, pasting text with \r\n will remove the \r character and
delegate whether or not the file will be saved with CRLF or LF line
endings to the `fileformat` option.

Ref #1742
2020-06-23 17:17:22 -04:00
Zachary Yedidia
9cc7c9be2d Fix backup call in test 2020-06-22 18:20:01 -04:00
Zachary Yedidia
a8332fd316 Improve backup system
This commit introduces several improvements to the backup system.

* Backups are made every 8 seconds for buffers that have been modified
  since the last backup.

* The `permbackup` option allows users to specify that backups should
  be kept permanently.

* `The backupdir` option allows users to store backups in a custom
   directory.

Fixes #1641
Fixes #1536
Ref #1539 (removes possibility of race condition for backups)
2020-06-22 17:54:56 -04:00
Zachary Yedidia
c5136820c4 Don't use make in travis script 2020-06-20 20:39:20 -04:00
Zachary Yedidia
349fbb698c Upgrade travis to go 1.13.x 2020-06-20 20:29:58 -04:00
Zachary Yedidia
a1d863251f Upgrade Travis Go to 1.12.x
Fixes Travis Windows build problem because go modules are enabled
by default in 1.12.x and don't need an environment variable to be
set.
2020-06-20 20:22:31 -04:00
Zachary Yedidia
42cc50106e Include windows for travis 2020-06-20 20:12:15 -04:00
Zachary Yedidia
4d13308624 Persist plugin options correctly 2020-06-20 20:07:33 -04:00
Zachary Yedidia
d0b75bc09f Add simulation screen tests 2020-06-20 18:24:12 -04:00
Zachary Yedidia
a9ca57af6e Improve message in micro -clean
Ref #1736
2020-06-20 13:44:52 -04:00
Zachary Yedidia
bcc35c9f8c Fix backspace on Windows
Fixes #1735
2020-06-20 13:22:01 -04:00
Zachary Yedidia
fb258dd57a Clean default settings in micro -clean 2020-06-18 17:22:21 -04:00
Zachary Yedidia
891b117a33 Bind Ctrl-/ (CtrlUnderscore) in comment plugin 2020-06-18 17:18:34 -04:00
Zachary Yedidia
f5dc0a51ba Fix issue with search and replace at the end of a range 2020-06-18 16:38:10 -04:00
Zachary Yedidia
8cbe7fa92b Update tcell version 2020-06-17 23:24:53 -04:00
Zachary Yedidia
a584ff36de Merge 2020-06-17 23:14:03 -04:00
Zachary Yedidia
f5405cee18 Improve keybinding label consistency
The old notation (for example `CtrlG`) causes confusion when combined
with new notation needed for alt (`Alt-g`) due to Alt being case
sensitive. Previously both formats were supported, but the documentation
and defaults used a combination. This commit only uses the new notation
for consistency.

Ref #1470
2020-06-17 23:11:50 -04:00
Zachary Yedidia
3516c8a9a6 Start replacement search at cursor location
Fixes #1731
2020-06-17 22:43:22 -04:00
Matthias
c19dce87e4 Fix typo in defaultkeys (#1730) 2020-06-17 13:35:59 -04:00
Zachary Yedidia
2adba18159 Don't move nightly tag 2020-06-17 00:48:17 +00:00
Zachary Yedidia
f9f2ef02ac Edit nightly release instead of replacing 2020-06-16 20:33:59 -04:00
Zachary Yedidia
0976eb3e51 Cross compile binaries in release scripts 2020-06-16 19:55:12 -04:00
Zachary Yedidia
ac2d1491ff Use hub for creating releases 2020-06-16 19:55:12 -04:00
Hugo Locurcio
5bfc892a74 Add support for dozens more languages to the comment plugin (#1729) 2020-06-16 00:49:07 -04:00
Ryan Westlund
1793b6268b Add comment support for Haskell (#1728) 2020-06-15 16:11:51 -04:00
Zachary Yedidia
9b62aa4170 Merge branch 'p-e-w-faster-runewidth' 2020-06-13 17:00:03 -04:00
Zachary Yedidia
6fef5d6232 Merge branch 'faster-runewidth' of https://github.com/p-e-w/micro into p-e-w-faster-runewidth 2020-06-13 16:59:52 -04:00
Zachary Yedidia
fe19b13b3b Update go-shellquote for windows 2020-06-13 16:58:20 -04:00
Philipp Emanuel Weidmann
6559b116c0 Make determining rune width faster 2020-06-13 08:59:17 +05:30
Zachary Yedidia
ca976a8a3c Update runtime build script
Ref #1687
2020-06-12 20:54:37 -04:00
Zachary Yedidia
cfc595e80e Fix MoveLines on last line of buffer
Fixes #1723
Fixes #1724
2020-06-12 15:16:27 -04:00
Zachary Yedidia
fde4b92b9f More consistent key labels in docs 2020-06-12 14:41:57 -04:00
Zachary Yedidia
b8ec7b320a Add note for macOS terminals in docs 2020-06-12 14:20:26 -04:00
Zachary Yedidia
1786165d8b Merge branch 'master' of https://github.com/zyedidia/micro 2020-06-12 14:16:53 -04:00
Zachary Yedidia
0322e91933 Update readme 2020-06-12 14:16:47 -04:00
Ján Priner
b2261fc225 Add latex support in comment plugin (#1725) 2020-06-12 12:58:51 -04:00
Philipp Emanuel Weidmann
5ce26cca71 Make determining whether a code point represents a combining mark faster (#1719) 2020-06-12 00:10:00 -04:00
Zachary Yedidia
efb38b8636 Merge branch 'settings-config'
With these changes, settings.json should only contain options that
have been modified from their default values. Micro will actively
options that are set to default values from the settings.json file.
To see a full list of settings and their defaults, see the "options"
documentation, as well as `micro -options`.
2020-06-09 16:34:37 -04:00
Zachary Yedidia
0654db334a Show key name in raw pane 2020-06-09 15:57:52 -04:00
Zachary Yedidia
660d345880 Don't apply cli options to settings.json 2020-06-08 22:19:15 -04:00
Dmitry Maluka
1f58eecf3c Lower priority of cursorline and colorcolumn highlighting (#1697)
Fixes #1665
2020-06-08 16:15:54 -04:00
Zachary Yedidia
ae05ff1811 settings.json only contains modified settings
If a setting has a default value it will not be listed in settings.json.
2020-06-08 15:33:38 -04:00
Zachary Yedidia
43924646f6 Merge 2020-06-08 13:55:24 -04:00
Zachary Yedidia
79ee757757 Only start autocompletion for alphanumerics
Ref #1712
2020-06-08 13:54:31 -04:00
Ryan Westlund
006165230d python.yaml: add async as a keyword (#1713)
await is already a keyword, but async is not.
2020-06-08 13:45:05 -04:00
Zachary Yedidia
ead07e0b60 Expose ConfigDir and Tabs to plugins
Access with `micro.ConfigDir` (constant value) and `micro.Tabs()`.
2020-06-07 18:21:46 -04:00
Zachary Yedidia
140662f1ec Verify that all settings have correct type
This prevents crashes that occur when the user has put the wrong
type for a setting manually in the settings.json file.
2020-06-07 17:31:16 -04:00
Zachary Yedidia
44c1929f9d Fix mouse support in command bar 2020-06-07 15:46:12 -04:00
Zachary Yedidia
397fe634d7 Update tcell to fix escape sequence bug 2020-06-07 15:22:17 -04:00
Zachary Yedidia
2e3d08580e Merge 2020-06-06 15:56:36 -04:00
Zachary Yedidia
466889f540 Fix fileformat for newly created files
Fixes #1575
2020-06-06 15:56:13 -04:00
Dmitry Maluka
63900cb395 Fix highlighting at the end of line (#1705)
Fixes #1664
2020-06-04 23:32:31 -04:00
Chloe Kudryavtsev
07860b8973 Add mksh to the set of supported shells (#1703) 2020-06-03 13:30:42 -04:00
Zachary Yedidia
b473fe458d Merge 2020-06-03 00:27:51 -04:00
Zachary Yedidia
8cf56bfc56 Up arrow on first line brings to start
Fixes #1701
2020-06-03 00:27:24 -04:00
Sijmen J. Mulder
51050811eb Add pkgsrc instruction to readme (#1699) 2020-06-02 16:49:18 -04:00
Zachary Yedidia
14cd3cdbf8 Update readme 2020-06-01 00:17:52 -04:00
Zachary Yedidia
51ab8f9914 Unicode replacement char for non-displayable chars 2020-05-30 18:11:52 -04:00
Zachary Yedidia
afeb07a024 More fixes for parsecursor
Fixes #1695
Fixes #1696
2020-05-30 12:23:29 -04:00
Zachary Yedidia
3fc9a8ad9e Fix handling of +LINE:COL syntax
Fixes #1685
2020-05-29 22:48:23 -04:00
Zachary Yedidia
b05d3a5193 Slightly improve performance for very long lines 2020-05-29 15:31:13 -04:00
Zachary Yedidia
ffc922a7c5 Only perform save callback if save was successful
Fixes #1684
2020-05-29 15:02:38 -04:00
Zachary Yedidia
eeab114ed5 Add parsecursor option for file:line:col syntax
This option is disabled by default, and when enabled causes micro
to parse `:line:col` as a location for the cursor rather than
as part of the filename.

Closes #1650
Closes #1685
2020-05-29 14:55:24 -04:00
Zachary Yedidia
8bd7e5807c Always use current pane for keybinding actions
Fixes #1677
2020-05-29 14:38:29 -04:00
Andrew Clarke
9b59e07b47 Use "goto -1" to move cursor to end of document. (#1691) 2020-05-29 13:29:09 -04:00
Colin Hughes
00edf0207f Added hybrid line numbers (#1690)
* Added hybrid line numbers

* Changed rulerhybrid to relativeruler, modified documentation accordingly.

* Reverted go.mod and go.sum
I don't know how they got changed but they are good now.

Co-authored-by: Colin Hughes <semilin@pop-os.localdomain>
2020-05-28 22:24:09 -04:00
Zachary Yedidia
a95b17a4e7 Update readme 2020-05-28 18:39:17 -04:00
Zachary Yedidia
8956448fca UpdateRules after save is successful 2020-05-28 13:06:29 -04:00
Zachary Yedidia
a915cf9283 Fix '> save' command 2020-05-28 13:02:09 -04:00
Zachary Yedidia
dd10869eca Update readme 2020-05-28 11:59:31 -04:00
Peder B. Sundt
4356c7e434 Tweak railscast colorscheme to better reflect original (#1297) 2020-05-28 11:50:35 -04:00
axxx007xxxz
2e770ce9ce README: Add Fedora install instructions (#1671) 2020-05-28 11:49:18 -04:00
pyfisch
381e1b639d Rewrite TOML syntax file (#1681) 2020-05-26 14:10:27 -04:00
Shinichi TAMURA
cc09712d14 set bash-fc file's syntax as shell (#1679)
* set bash-fc file's syntax as shell

* remove dups from shell file matcher
2020-05-24 23:50:30 -04:00
Dmitry Maluka
c5b0c2d41f Fix dropped redraw events (#1675)
If screen.Redraw() is called very quickly after a key or mouse event,
it may send the redraw event while micro is not waiting for it but
still processing the key or mouse event. Since drawChan is non-buffered
and at the same time non-blocking, this redraw event will be simply lost,
so the screen content will not be up-to-date.
2020-05-23 14:59:23 -04:00
Zachary Yedidia
bd43a44194 Merge branch 'master' of https://github.com/zyedidia/micro 2020-05-21 14:36:34 -04:00
Zachary Yedidia
bfe68b1626 Allow divider customization with divchars option
Adds the `divchars` and `divreverse` options to customize divider
styles.
2020-05-21 14:35:54 -04:00
Zachary Yedidia
0064b8268f Improve unicode line array test 2020-05-20 19:53:54 -04:00
Zachary Yedidia
9a22d93ea2 Expose CharacterCount to plugins 2020-05-20 18:04:00 -04:00
Zachary Yedidia
5c8a2332d9 Use unicode.Mark for combining unicode range 2020-05-20 18:01:10 -04:00
Zachary Yedidia
ff0683d6d0 Final touches for combining character support 2020-05-20 17:00:56 -04:00
Zachary Yedidia
79c0ea17ad Use CharacterCount over RuneCount 2020-05-20 16:47:08 -04:00
Zachary Yedidia
bdff221870 Use DecodeCharacter over DecodeRune 2020-05-20 16:43:12 -04:00
Zachary Yedidia
65be5efd83 Merge branch 'p-e-w-buffer-benchmarks' 2020-05-20 15:29:02 -04:00
Zachary Yedidia
a491dd1c52 Merge branch 'buffer-benchmarks' of https://github.com/p-e-w/micro into p-e-w-buffer-benchmarks 2020-05-20 15:28:36 -04:00
Zachary Yedidia
d7ab44253f Update tcell and support italics in colorschemes
Closes #1640
2020-05-17 12:48:34 -04:00
Zachary Yedidia
0a6720498f Merge branch 'master' of https://github.com/zyedidia/micro 2020-05-17 12:23:21 -04:00
dmaluka
a150eef6f9 Fix end line number in HighlightMatches (#1662)
There is a bit of mess in the usage of HighlightMatches: in some places
we assume that it updates lines from startline to endline inclusive,
in other places we assume it's non-inclusive.
This fix makes it always inclusive.

In particular, it fixes a bug: when we open a file which has no
newline at the end, the last line isn't highlighted.
2020-05-17 16:05:34 -04:00
Zachary Yedidia
c46257222c Add support for FindLiteral
Use the FindLiteral action to use Find without regex support.

Fixes #1661
2020-05-17 12:22:33 -04:00
jsyedidia
299af4a3db Update hlint to 3.0 syntax (#1659) 2020-05-16 13:06:55 -04:00
Jeff Warner
d0f7ecf9ca Adds command "tabmove ±n", for better tab management (#1636)
* Adds command "tabmove ±n", for better tab management

* Added tabmove to help:commands

* Replace uses of util.Min, util.Max with util.Clamp

Browsing code and discovered `util.Clamp`, ideal for this section of my code

* oops, missed an arg

* Typo, again
2020-05-14 21:51:49 -04:00
dmaluka
fb35e0312a Fix unbind of a rune (#1649)
Fix problem with non-working unbind of a rune key.
E.g. after the following commands:

bind "n" "FindNext"
unbind "n"

Observed result: "n" key still triggers FindNext action
Expected result: "n" key inserts "n" rune
2020-05-14 21:50:28 -04:00
Zachary Yedidia
30395b1f67 Remove outdated c++ highlighter
Fixes #1652
2020-05-14 21:37:19 -04:00
Zachary Yedidia
ddf70953fe Support snake case autocompletion
Fixes #1655
2020-05-14 21:34:17 -04:00
Zachary Yedidia
55e97596d3 Fix movelinesup when selection is not complete 2020-05-07 19:39:17 -04:00
Zachary Yedidia
66dc48ce9b Improve readme 2020-05-04 22:21:46 -04:00
Zachary Yedidia
c490a94700 Update makefile 2020-05-04 10:34:16 -04:00
Zachary Yedidia
eff89a98a7 Fix v2 import path for go mod 2020-05-04 10:16:15 -04:00
Zachary Yedidia
221d8f462a Merge branch 'jwarner112-jwarner112-copyline' 2020-04-30 00:54:11 -04:00
Zachary Yedidia
7a23878250 gofmt 2020-04-30 00:54:02 -04:00
Jeff Warner
5d3e4fc3d9 Adds CopyLine action, the new default action for CtrlC if cursor has no selection 2020-04-29 21:06:54 -07:00
Indiana Kernick
f52fbfa1f0 Add .inl as a C++ file extension (#1630) 2020-04-29 20:01:59 -04:00
Zachary Yedidia
d60626c64b Merge 2020-04-25 17:01:20 -04:00
Zachary Yedidia
aaac0b1e6f Better actions error message 2020-04-25 17:01:16 -04:00
Some person
0f984131fb Update coffeescript.yaml (#1571)
* Update coffeescript.yaml

We need much much more modern coffeescript standards, the current one has broken `0x123456` (hex) and single quotes, and doesn't support multiline comments. This PR aims to fix that. I'm no regexp expert, I just based this off JS', so tell me if I did anything wrong.

* Update coffeescript.yaml
2020-04-22 23:12:56 -04:00
Zachary Yedidia
eb7189dcdb Make cursor follow selections
Fixes #1624
2020-04-21 09:33:21 -04:00
Zachary Yedidia
74523d28c5 Merge 2020-04-20 23:13:10 -04:00
Zachary Yedidia
f894f0a26e Update clipboard version 2020-04-20 23:13:01 -04:00
2pac
a067ce1f41 implemented circular tab movement (#1619)
Co-authored-by: 2pac <tarasyarema@pm.me>
2020-04-17 13:42:48 -04:00
Zachary Yedidia
f59468642d Update runtime 2020-04-10 17:27:57 -04:00
Zachary Yedidia
85e85b7ccc Merge 2020-04-10 17:27:34 -04:00
Zachary Yedidia
8f5888e7bf Use StartCol in colorcolumn calculation
Fixes #1615
2020-04-10 17:27:11 -04:00
Ján Jančár
f0da73bae2 Add StartOfTextToggle and SelectToStartOfTextToggle actions. (#1612)
These actions reintroduce the behavior of micro where the Home key
toggles between the start of text (first) and the start of the line.
The same applies for the variant with selection. This commit also
sets these bindings as the defaults.
2020-04-10 17:21:02 -04:00
Zachary Yedidia
d92deacf99 Ensure mouse release before focus change
Fixes #1613
2020-04-10 15:58:43 -04:00
trrbl
ffd7b5c770 Support csharp-script syntax. (#1425)
```
#!/usr/bin/env dotnet-script
// Set Runtime
#! "netcoreapp3.0"
// Imports
#load "myAssembly.dll"
#r "nuget:CliWrap,2.5.0"
```
This syntax file basically imports the `csharp` rules and adds it's custom pre-processors.
2020-04-10 13:57:36 -04:00
Ján Jančár
bd8c0d25c8 Add sagemath syntax highlight based on python3. (#1227) 2020-04-09 12:20:48 -04:00
Ján Jančár
052a36b896 Fix docs regarding "Home" key and "StartOfText" and "StartOfLine". (#1611) 2020-04-07 12:58:30 -04:00
jsyedidia
a76bf02f5f Add Haskell linter hlint to linter plugin (#1610) 2020-04-05 14:05:01 -04:00
Nikita Bobko
92f4cb7ef7 Clarify regex for git commit --verbose (#1606) 2020-04-05 14:04:36 -04:00
Zachary Yedidia
1cf9537340 Fix python3 syntax file and make python3 default
The python3 syntax had "filename" instead of "filetype"
as the header. This commit also makes standard py extensions
use the python3 highlighting and requires .py2 or a python2
env to use python2 highlighting because python3 is the standard
python now.

Fixes #1592
2020-03-24 11:42:23 -04:00
Zachary Yedidia
60c8c81da3 Relocate during replace
Fixes #1587
2020-03-24 11:33:52 -04:00
Zachary Yedidia
c76a973877 Merge 2020-03-24 11:17:12 -04:00
Zachary Yedidia
6def99ce24 Clarify replace message if replacing in selection 2020-03-24 11:14:54 -04:00
Koki Fushimi
26930ca81f Better Julia syntax. (#1567)
* Fix regex syntax and change to match one or more spaces.

* Add constant `nothing` and `missing`.

* Add Inf and NaN to constant numbers.
2020-03-24 10:59:48 -04:00
Hugo Locurcio
cd379cd838 Clarify the Find operation being regex-enabled (#1561)
This makes it more obvious that the Find option accepts regular
expressions as input.

See discussion in #1560.
2020-03-24 10:59:40 -04:00
allanderek
ee157f6503 Add elm as a default comment type in the comment plugin. (#1586) 2020-03-24 10:56:50 -04:00
Zachary Yedidia
48ca19873f Better ordering for reading syntax files
Ref #1580
2020-03-24 10:52:15 -04:00
Zachary Yedidia
fee5528309 Fix term emulator crash if invalid exec given
Ref #1583
2020-03-24 10:22:10 -04:00
Zachary Yedidia
671a188802 Support +LINE:COL flag syntax for cursor pos
Closes #1566
2020-03-24 10:10:44 -04:00
Zachary Yedidia
18d540583b Don't clear infobar if not enabled
Fixes #1584
2020-03-17 14:21:36 -04:00
Zachary Yedidia
943ea15fa3 Fix linter c++ entry
Fixes #1578
2020-03-14 15:40:05 -04:00
Cafe Duke
2ef57977d7 Add color schemes dukeubuntu-tc, dukedark-tc and dukelight-tc (#1547)
* Duke ubuntu, dark and light color schemes

* Duke color schemes: Change bgcolor for line number and cursor line

Co-authored-by: Raghunandan.Seshadri <raghubs81@gmail.com>
Co-authored-by: Raghunandan Seshadri <raghunandan.seshadri@oracle.com>
2020-03-07 22:07:43 -05:00
Zachary Yedidia
527750b68d Copy selection to primary on mouse release
Fixes #1558
2020-03-05 16:00:40 -05:00
Zachary Yedidia
629efe5eb7 Add JumpLine action back
You can bind to "command-edit:goto ", but binding to the action
"JumpLine" will have the same effect now.

Fixes #1550
2020-03-02 20:09:19 -05:00
Andrew Havens
a19cd2e6d0 Add support for Fastlane and Cocoapods file syntax highlighting (#1544) 2020-03-02 20:03:28 -05:00
Philipp Emanuel Weidmann
d038d3040f Add more sophisticated buffer benchmark system 2020-03-01 13:20:10 +05:30
Zachary Yedidia
9e8d76f2fa If stdout is a pipe, output to the pipe
If you run micro as `micro | cat` for example, micro will disallow
you from saving the file, and when you quit the buffer, the contents
will be sent to the pipe. This allows one to use micro as part of
an interactive unix pipeline.

Closes #1524
2020-02-27 12:39:19 -05:00
Zachary Yedidia
8a9a14562f Use bytes.Buffer for LineArray.Bytes 2020-02-27 11:27:00 -05:00
Zachary Yedidia
a6f5dee45c Fix custom syntax files not highlighting
Fixes #1530
2020-02-27 00:58:52 -05:00
Zachary Yedidia
b12886b066 Improve buffer test 2020-02-25 23:59:27 -05:00
Zachary Yedidia
56f5b475eb Improve buffer test 2020-02-25 23:21:50 -05:00
Zachary Yedidia
c51f84955e Update runtime 2020-02-25 21:08:22 -05:00
Zachary Yedidia
e4bf1e9984 Undo event chunks instead of single events 2020-02-25 20:53:48 -05:00
Zachary Yedidia
917e2922a1 Update readme 2020-02-25 20:40:57 -05:00
Zachary Yedidia
59e8f3aa3e mod tidy 2020-02-25 20:28:02 -05:00
Zachary Yedidia
53bda0cfa7 Fix buffer tests and selection bug
Fixes #1528
Ref #1526
2020-02-25 20:24:02 -05:00
Zachary Yedidia
f059541e0d Merge branch 'buffer-tests' of https://github.com/p-e-w/micro into buffer-unit-tests 2020-02-25 10:30:31 -05:00
Zachary Yedidia
d78fe81e21 line_array insert for eofnewline and make default
Makes the `eofnewline` option enabled by default.

Fixes #1525
2020-02-24 22:31:05 -05:00
josh
25b9342fbe fix eofnewline not running on files with 1 rune (#1535) 2020-02-24 22:26:51 -05:00
Zachary Yedidia
70bcf9f618 Fix text transformation bug
This fixes the remaining text transformation tests.

Ref #1526
2020-02-24 20:11:11 -05:00
Roman Kornev
8848388411 Hide ISSUE_TEMPLATE version help into a comment (#1532)
Because some people don't remove it
2020-02-24 13:49:45 -05:00
Zachary Yedidia
dff8b33e9c Apply basename option in tabbar as well 2020-02-24 13:48:37 -05:00
Zachary Yedidia
8a2048e7f6 Use tabbar color group, and mark modified tabs
Fixes #1523
2020-02-24 13:45:10 -05:00
Zachary Yedidia
0174d7dba4 Move multi-cursors correctly after newlines
Fixes #1527
2020-02-24 13:39:34 -05:00
Zachary Yedidia
e1827480c9 Filename completion for all non-command prompts
Fixes #1529
2020-02-24 13:00:55 -05:00
Zachary Yedidia
d8584d1ddb Debug off using default "go build"
Ref #1469
2020-02-24 12:55:59 -05:00
Philipp Emanuel Weidmann
f0cdc3cabb Add buffer test and benchmark suite (and tool to generate it) 2020-02-22 08:51:38 +05:30
Zachary Yedidia
2ef4f83358 Fix issue with simultaneous buffers 2020-02-19 17:40:54 -05:00
Zachary Yedidia
a9120ce270 Share more buffer elements and fix rehighlight
Fixes #1521
2020-02-19 14:41:30 -05:00
Zachary Yedidia
190d9d0609 Tweak version build script 2020-02-19 05:27:33 +00:00
Zachary Yedidia
cf3fdb344a Merge 2020-02-18 21:40:36 -05:00
Zachary Yedidia
b91242124c Go lint the current directory of file
Closes #1520
2020-02-18 21:40:14 -05:00
Zachary Yedidia
5ffc19f159 Use filecomplete for shell mode 2020-02-17 22:29:33 -05:00
Zachary Yedidia
cc994b6241 Fix relocation with softwrap on small buffers
Fixes #1512
2020-02-15 15:38:20 -05:00
Zachary Yedidia
087e7207f7 Add 'xterm' option
Ref #1489
2020-02-15 12:53:17 -05:00
Zachary Yedidia
db32b84cd1 Relocate after rune insert
Fixes #1510
2020-02-14 15:52:20 -05:00
Zachary Yedidia
00006aa2b4 Update snap version info 2020-02-13 21:27:07 -05:00
Zachary Yedidia
600d8558b2 Change some default option values 2020-02-13 20:51:56 -05:00
Zachary Yedidia
4874823240 Fix makefile tags dependencies 2020-02-13 20:00:35 -05:00
Zachary Yedidia
6a0e4b5564 Fetch tags before snapcraft build 2020-02-13 19:57:31 -05:00
Zachary Yedidia
b2d7c8c5d4 Merge 2020-02-13 19:49:27 -05:00
Zachary Yedidia
38f88ade60 Search and replace within a selection
Closes #1098
2020-02-13 19:48:48 -05:00
Tonus1
8348cc8ec2 Add transparency (#1509) 2020-02-13 18:15:32 -05:00
Zachary Yedidia
743d42e417 Syntax file change 2020-02-13 16:50:44 -05:00
Zachary Yedidia
faa207907c Handle terminal paste and raw events in info bar 2020-02-13 16:10:35 -05:00
Zachary Yedidia
9d1c489574 Merge 2020-02-13 16:09:16 -05:00
Zachary Yedidia
30ed25859a Support regex capture groups in replace command
See https://golang.org/pkg/regexp/syntax/ for the
supported syntax. Here are some examples:

```
replace "(foo)" "$1-bar"
replace "(foo)" "${1}-bar"
replace "(?P<group>foo)" "$group-bar"
replace "(?P<group>foo)" "$group-bar"
replace "(?P<key>\w+):\s+(?P<value>\w+)$" "$key=$value"
```

Closes #1115
2020-02-13 16:05:56 -05:00
Simon Ser
d2288c5f66 readme: document clipboard support on Wayland (#1508) 2020-02-13 14:00:18 -05:00
Zachary Yedidia
a07ee26b05 Fix gutter offset when softwrap is enabled 2020-02-13 11:04:10 -05:00
Zachary Yedidia
a7ce85d6f6 Make default plugin options more explicit
Ref #1305
2020-02-12 15:34:13 -05:00
Zachary Yedidia
7c71995aaf Merge branch 'utkarsh2102-update-readme' 2020-02-12 14:26:33 -05:00
Zachary Yedidia
357c4b0fcd Update readme 2020-02-12 14:26:17 -05:00
Zachary Yedidia
0d4f85304b Merge branch 'update-readme' of https://github.com/utkarsh2102/micro into utkarsh2102-update-readme 2020-02-12 14:22:33 -05:00
Zachary Yedidia
e18e41eb45 Merge branch 'seitokaichou-autosu' 2020-02-12 14:15:37 -05:00
Zachary Yedidia
5519f053ac Merge branch 'autosu' of https://github.com/seitokaichou/micro into seitokaichou-autosu 2020-02-12 14:15:30 -05:00
Zachary Yedidia
2f4b3b2a8c Merge branch 'jawahars16-bug-endless-reload-prompt' 2020-02-12 13:56:17 -05:00
Zachary Yedidia
ea290e4fb5 Merge branch 'bug-endless-reload-prompt' of https://github.com/jawahars16/micro into jawahars16-bug-endless-reload-prompt 2020-02-12 13:56:00 -05:00
Zachary Yedidia
8b414e6187 Update readme toc 2020-02-12 13:38:14 -05:00
Zachary Yedidia
e7ef81ed97 Share hash across equivalent buffers for fastdirty=off 2020-02-12 13:32:42 -05:00
Zachary Yedidia
12c286f9b1 Introduce IndentLine action
Closes #1476
2020-02-12 13:30:24 -05:00
Zachary Yedidia
7b5bc8fe37 Fix issue with global/local settings 2020-02-12 13:18:59 -05:00
Zachary Yedidia
bad78797bb Clicking tabbar arrow scrolls and fix multicursor
Closes #1503
2020-02-12 13:05:15 -05:00
Zachary Yedidia
bf1258578c Expose OpenLogBuf to plugins 2020-02-12 12:35:40 -05:00
Zachary Yedidia
6588f02f7b Only highlight matching brace if one is found
Fixes #1505
2020-02-12 01:32:23 -05:00
Zachary Yedidia
5e9c6375d0 Only fetch tags if no tags are found at all
Fixes #1504
2020-02-12 01:24:25 -05:00
Zachary Yedidia
7d47659481 Fix deleteLines off-by-one error
Fixes #1501
2020-02-12 01:16:11 -05:00
Zachary Yedidia
dcd4bae96f Clamp modifications
Fixes #1502
2020-02-12 00:55:52 -05:00
Zachary Yedidia
4f628bf30b Remove incorrect plugin documentation 2020-02-12 00:22:32 -05:00
Utkarsh Gupta
36e83a46e4 Update installation instruction on Debian systems 2020-02-12 07:45:07 +05:30
Zachary Yedidia
1a64ffb88b Don't expose draw channel to outside packages 2020-02-11 20:39:26 -05:00
Zachary Yedidia
7c77927913 Update tcell to v1.4.4 2020-02-11 20:34:22 -05:00
Zachary Yedidia
8224037080 Don't block when redraw channel becomes full
Fixes #1497
2020-02-11 20:03:32 -05:00
Zachary Yedidia
d7e3fc99f1 Merge 2020-02-11 19:13:41 -05:00
Zachary Yedidia
feaf3951d2 Update haskell syntax file 2020-02-11 19:13:36 -05:00
Zachary Yedidia
399c629076 Update readme 2020-02-11 16:40:15 -05:00
Zachary Yedidia
47ed7447f1 Update screenshot 2020-02-11 16:30:58 -05:00
Zachary Yedidia
62a98ea3c5 Typo 2020-02-11 14:50:15 -05:00
Zachary Yedidia
0a9194b883 Fix tag fetching in build-version.go 2020-02-11 14:46:19 -05:00
Zachary Yedidia
a938917b9b Fetch tags if none are found 2020-02-11 14:14:34 -05:00
Zachary Yedidia
695d4c2b1b Use filepath.Join more 2020-02-11 13:09:17 -05:00
Zachary Yedidia
34724b941a Recover from internal errors without crashing 2020-02-11 00:50:24 -05:00
Zachary Yedidia
8176e8c6f8 Improve one-dark colorscheme divider 2020-02-10 23:37:21 -05:00
Zachary Yedidia
0d5b1cd64d Update readme buttons 2020-02-10 22:58:25 -05:00
Zachary Yedidia
9fd5b133ea Add svg logo 2020-02-10 22:40:16 -05:00
Zachary Yedidia
432b57a070 Merge branch 'FJduFou-patch-1' 2020-02-10 22:37:03 -05:00
François-Joseph du Fou
ee55732be3 Update README.md 2020-02-10 22:36:57 -05:00
François-Joseph du Fou
90304fb472 better top
add a center logo + 3 news buttons
2020-02-10 22:36:02 -05:00
Zachary Yedidia
f6aec1af5f Update readme 2020-02-10 22:19:47 -05:00
Zachary Yedidia
b77b61d677 Merge branch 'Calinou-improve-readme' 2020-02-10 22:17:51 -05:00
Zachary Yedidia
05a0598f16 Merge branch 'improve-readme' of https://github.com/Calinou/micro into Calinou-improve-readme 2020-02-10 22:17:46 -05:00
Zachary Yedidia
daa6f122a7 Add note in readme about winpty 2020-02-10 22:10:01 -05:00
Zachary Yedidia
71f5f043fb Merge 2020-02-10 19:56:17 -05:00
Zachary Yedidia
f3eaf99665 Draw FakeCursor in infobar when on a character
Fixes #1496
2020-02-10 19:55:13 -05:00
Zachary Yedidia
c88c1b84da Term should return error on unsupported systems
Fixes #1494
2020-02-10 19:09:03 -05:00
Zachary Yedidia
e1e310a96e Document all options 2020-02-10 15:07:00 -05:00
Zachary Yedidia
d29b941be0 Merge 2020-02-10 14:59:47 -05:00
Zachary Yedidia
cde696915d Merge branch 'sum01-issue_1008' 2020-02-10 14:59:40 -05:00
Zachary Yedidia
185b8de17b Merge branch 'issue_1008' of https://github.com/sum01/micro into sum01-issue_1008 2020-02-10 14:59:31 -05:00
Kyle Barron
d65285ee54 Update documentation to include Material colorscheme (#1279) 2020-02-10 14:53:27 -05:00
Zachary Yedidia
848bd1ba8c Fix rehighlight for retab 2020-02-10 14:49:08 -05:00
Zachary Yedidia
00205aa6a7 Update readme 2020-02-10 00:38:57 -05:00
Zachary Yedidia
ecb9fd5a8a Change diffgutter default to false 2020-02-10 00:30:13 -05:00
Zachary Yedidia
bdf9e6d3a4 Merge branch 'diff-gutter' of https://github.com/p-e-w/micro 2020-02-10 00:28:43 -05:00
Zachary Yedidia
3ed77dbb2e Sanitize inputs to insert and remove 2020-02-10 00:18:08 -05:00
Zachary Yedidia
57a992c4a3 Merge 2020-02-09 22:40:20 -05:00
Zachary Yedidia
c63614213d Update tcell dep 2020-02-09 22:40:18 -05:00
Rein F
b7a54fa74a updated man page (#1492) 2020-02-09 21:53:42 -05:00
Zachary Yedidia
63046ae909 Don't autocomplete in the middle of a word
Fixes #1490
2020-02-09 16:46:53 -05:00
Zachary Yedidia
af48e4b79b Fix save callbacks
Fixes #1491
2020-02-09 16:36:15 -05:00
Zachary Yedidia
4e73d0779b Create bindings.json if it does not exist 2020-02-09 16:27:39 -05:00
Zachary Yedidia
6f424f3213 Properly flush bufio writer 2020-02-09 15:36:31 -05:00
Zachary Yedidia
e110e93e0f Improve disk performance with buffered io 2020-02-09 15:21:23 -05:00
Zachary Yedidia
8ddf335e68 Improve remove performance 2020-02-09 14:58:37 -05:00
Zachary Yedidia
ca9d102267 Start insert performance improvements 2020-02-09 14:30:20 -05:00
Zachary Yedidia
c3d120ccdf Fix errcheck in clean 2020-02-09 00:42:16 -05:00
Zachary Yedidia
a4a6445e1d Merge 2020-02-09 00:40:55 -05:00
Zachary Yedidia
13e30a63eb Minor improvements 2020-02-09 00:40:50 -05:00
Indiana Kernick
e561952781 Add jsonnet syntax file (#1320) 2020-02-09 00:23:49 -05:00
Tommy
9fe58bf84f csharp bracket highlighting problem #1172 (#1199) 2020-02-09 00:21:27 -05:00
Zachary Yedidia
e84f85340b Merge 2020-02-09 00:19:21 -05:00
Zachary Yedidia
aaf90c6f52 Merge branch 'Paalon-master' 2020-02-09 00:19:11 -05:00
Zachary Yedidia
0962e1bfba Merge branch 'master' of https://github.com/Paalon/micro into Paalon-master 2020-02-09 00:19:02 -05:00
Zachary Yedidia
2f45644d14 Merge pull request #1324 from konsumer/master
add support for input and scalar defintiions (for graphql-tools schema)
2020-02-09 00:18:09 -05:00
Zachary Yedidia
c2a2316c28 Merge pull request #1321 from zonuexe/add/php-fn-keyword
Add `fn` keyword (arrow function) added in PHP 7.4 to coloring
2020-02-09 00:17:41 -05:00
Zachary Yedidia
6957e83cdb Merge pull request #1333 from the-sushi/patch-1
Add Forth highlighting
2020-02-09 00:17:24 -05:00
Zachary Yedidia
ce91e41e5a Update third party licenses 2020-02-09 00:03:03 -05:00
Zachary Yedidia
6d99d34eb0 Fix unsplit crash
Fixes #1488
2020-02-08 21:06:13 -05:00
Zachary Yedidia
b77980082c Fix to allow readonly to be disabled 2020-02-08 19:37:37 -05:00
Rein F
2fd59adffa Show that the file is readonly (#1486)
* Show that the file is readonly)

* change the (readonly) statusline msg into [ro]
2020-02-08 19:34:35 -05:00
Zachary Yedidia
57c34e2248 More plugin docs and improve doc formatting 2020-02-08 18:31:06 -05:00
Zachary Yedidia
6514b77e0d Enable autosave option
The autosave option is now specified as an integer, which denotes
the number of seconds to wait between saving the file. If the option
is 0, then autosaving is disabled. If the option is given by the user
as a boolean, it will be converted to 8 if true, and 0 if false.

Fixes #1479
2020-02-08 16:53:08 -05:00
Zachary Yedidia
8a907956d1 Use actual lua functions for callbacks instead of strings 2020-02-08 15:49:41 -05:00
Philipp Emanuel Weidmann
de33eac058 Add diff gutter 2020-02-08 13:26:24 +05:30
Zachary Yedidia
c4bfa825a1 Merge pull request #1277 from coolreader18/patch-1
Update README about Windows terminal color support.
2020-02-07 20:27:51 -05:00
Zachary Yedidia
b0c50d371f Merge pull request #1423 from caligari87/patch-1
Add ZScript language highlighting
2020-02-07 20:18:05 -05:00
Zachary Yedidia
b4d4119572 Merge branch 'neutralinsomniac-fix_defaultkeys_help' 2020-02-07 20:17:17 -05:00
Zachary Yedidia
674258787e Add Mac special cases 2020-02-07 20:16:59 -05:00
Zachary Yedidia
2354412922 Add mac keybinds 2020-02-07 20:14:35 -05:00
Zachary Yedidia
3ac30343b8 Merge branch 'fix_defaultkeys_help' of https://github.com/neutralinsomniac/micro into neutralinsomniac-fix_defaultkeys_help 2020-02-07 20:09:44 -05:00
Zachary Yedidia
37343350ca Merge 2020-02-07 20:04:58 -05:00
Zachary Yedidia
d9e9d4403f Merge branch 'msiism-master' 2020-02-07 20:04:50 -05:00
Zachary Yedidia
741f494841 Merge branch 'master' of https://github.com/msiism/micro into msiism-master 2020-02-07 20:04:44 -05:00
Zachary Yedidia
69b6c724fc Merge pull request #1252 from SunflowerFuchs/patch-1
Update zsh.yaml
2020-02-07 20:03:53 -05:00
Zachary Yedidia
31936358c1 Merge pull request #1264 from krerkkiat/issue-1237
Update sh.yaml
2020-02-07 20:03:22 -05:00
Zachary Yedidia
fe58ff5753 Merge 2020-02-07 19:57:13 -05:00
Zachary Yedidia
9aaafe5dcf Merge 2020-02-07 19:56:57 -05:00
Zachary Yedidia
c2bd5e4eec Merge branch 'dbeef-master' 2020-02-07 19:42:14 -05:00
Zachary Yedidia
98ddb62af4 Update docs 2020-02-07 19:41:11 -05:00
Zachary Yedidia
24a684cff2 Merge branch 'master' of https://github.com/dbeef/micro into dbeef-master 2020-02-07 19:37:56 -05:00
Zachary Yedidia
b4e7e981f3 Support paste action in terminal 2020-02-07 19:17:17 -05:00
Zachary Yedidia
e73549c61a Merge pull request #1485 from LevitatingBusinessMan/terminal_impr
Terminal improvements
2020-02-07 19:12:05 -05:00
Rein F
e759d4087b Fix for issue 2 in #1484
Exit message when exiting terminal now isnt visibile in other views
2020-02-08 00:15:37 +01:00
Zachary Yedidia
106ba48079 Add some docs for linter, comment, status 2020-02-07 11:32:12 -05:00
Zachary Yedidia
ef768e36f3 Merge 2020-02-06 19:26:33 -05:00
Zachary Yedidia
f5e1f93ee5 Update docs 2020-02-06 19:26:27 -05:00
Zachary Yedidia
a52c0c2907 Add StartOfText options to multiactions 2020-02-06 17:10:32 -05:00
Zachary Yedidia
be7d27bc49 Action callbacks for lua actions 2020-02-06 11:12:34 -05:00
Zachary Yedidia
f6a9c482a6 Allow plugins to resize panes 2020-02-05 17:16:31 -05:00
Zachary Yedidia
6e3f38b271 Add scrolling to command bar autocompletion 2020-02-02 20:17:46 -05:00
Zachary Yedidia
8483f1da1e Make curpane only return bufpanes 2020-02-02 17:12:50 -05:00
Zachary Yedidia
28ed47e358 Fix cycleback in infopane 2020-02-02 16:16:53 -05:00
Zachary Yedidia
6a1b8f4a4f Add option to clean unused settings and other parts of config 2020-02-02 15:30:06 -05:00
Zachary Yedidia
dba8ef2fdd Use namespaces for plugin options 2020-02-02 14:35:30 -05:00
Zachary Yedidia
b0624cb66e Add support for plugin manager within micro 2020-02-02 14:20:39 -05:00
Zachary Yedidia
09ea82c97e Disable current line num style if no cursorline 2020-02-02 00:34:28 -05:00
Zachary Yedidia
d94b81b8e6 Synchronize undo and redo chunks
Fixes #1372
Fxies #1471
2020-02-02 00:14:56 -05:00
Zachary Yedidia
bcb1947a0a Add plugin manager 2020-02-01 23:54:38 -05:00
Zachary Yedidia
b0b5d7b392 Add CurPane and CurTab functions for plugins 2020-02-01 12:20:08 -05:00
Zachary Yedidia
2598d8ad70 Update colorschemes and add new ones
This commit updates the colorschemes and adds some new ones:

* gotham (https://github.com/novln/micro-gotham-colors)
* monokai-dark (https://github.com/Theodus/micro-monokai-dark)
* one-dark (https://github.com/joseluisq/micro-one-dark)
* sunny-day (https://github.com/dwwmmn/micro-sunny-day)
2020-01-31 15:05:55 -05:00
Zachary Yedidia
f731e422ea Improve lua interface 2020-01-31 14:21:27 -05:00
Jeremy O'Brien
abcdeb01e9 Fix defaultkeys help doc to match reality 2020-01-31 13:10:11 -05:00
Zachary Yedidia
d326a9cddd Merge 2020-01-31 00:56:20 -05:00
Zachary Yedidia
e3131a0779 Add text event callback 2020-01-31 00:56:15 -05:00
Zachary Yedidia
46c5a81b0d Fix callback cancelation 2020-01-30 18:04:17 -05:00
Zachary Yedidia
59146cabb1 Add callback option to linter 2020-01-30 18:00:17 -05:00
Zachary Yedidia
35e3bddea0 Modify linter and add plugin cmd 2020-01-30 17:51:04 -05:00
Zachary Yedidia
016b8dcc4c Do not add non-plugin directories in plug/ 2020-01-28 23:49:51 -05:00
Zachary Yedidia
03228762d4 Don't call plugin if nil 2020-01-28 22:06:58 -05:00
Zachary Yedidia
953f5a0eff Highlight in parallel 2020-01-28 20:54:14 -05:00
Zachary Yedidia
477bdb3dc8 Empty highlighting for unknown filetypes 2020-01-28 18:34:44 -05:00
Zachary Yedidia
d74f40882d Don't rehighlight if there are no modifications 2020-01-28 17:15:02 -05:00
Zachary Yedidia
d965e8de4f Fix copy-paste error in docs 2020-01-26 21:39:10 -05:00
Zachary Yedidia
866b3c9238 Resize tabbar properly
Ref #1467
2020-01-26 00:44:34 -05:00
Zachary Yedidia
3252324d24 Don't indent empty lines
Fixes #1472
2020-01-26 00:40:40 -05:00
Zachary Yedidia
8e7a016917 Tab horizontal scrolling should not be negative
Fixes #1467
2020-01-25 13:17:13 -05:00
Zachary Yedidia
cf41a587a3 Split the actions StartOfLine and StartOfText
The default keybindings now use StartOfText which moves the cursor
to the start of the text on the current line instead of the actual
start of the line (if the line begins with whitespace).

Fixes #1468
2020-01-25 13:02:13 -05:00
Zachary Yedidia
1dc1c65565 Update tutorial docs 2020-01-21 20:37:51 -05:00
Zachary Yedidia
97ee344268 Fix some issues with syntax highlighting regions
Fixes #1464
2020-01-20 23:43:47 -05:00
Zachary Yedidia
b658f94e5a Change ctrl-arrow default binding for non-Mac OSes
On non-Mac operating systems, the default for CtrlLeft/CtrlRight
is now WordLeft and WordRight instead of moving the cursor to the
start and end of lines (now rebound to Alt-Left/Right by default).
Default keybindings are unchanged on Mac.

Fixes #1465
2020-01-20 22:35:00 -05:00
Zachary Yedidia
0abe427026 Make readonly and filetype local-only 2020-01-20 22:03:32 -05:00
Rein F
063389afdf updated runtime 2020-01-18 19:05:54 +01:00
Rein F
1e998ab0e4 Updated javascript.yaml syntaf file 2020-01-18 19:05:33 +01:00
Zachary Yedidia
b3e40a2644 Make debug mode flag, plugins can access logbuf 2020-01-15 22:25:08 -05:00
Zachary Yedidia
fa4103f7aa Merge 2020-01-15 20:09:33 -05:00
Zachary Yedidia
17f0eb80cd Readonly should only apply to default buffers
Ref #1298
2020-01-15 20:09:17 -05:00
Zachary Yedidia
35dfb1830b Merge pull request #1459 from wgj/osx_terminal_opt_key
Add suggestions for MacOS Terminal.app users
2020-01-14 16:54:02 -05:00
Zachary Yedidia
76f09adb48 Merge pull request #1456 from srishanbhattarai/patch-2
Include mingw and fix README section link
2020-01-14 16:53:54 -05:00
Weston Johnson
289c7147e4 Add suggestions for MacOS Terminal.app users 2020-01-14 14:42:05 -07:00
Krerkkiat Chusap
be34e1241c Update julia.yml (#1262)
Add export to the keywords. This should fixes zyedidia/micro#1215.
2020-01-11 13:41:52 -05:00
Srishan Bhattarai
83ea8d8be9 Include mingw and fix README section link 2020-01-09 15:16:15 -06:00
Zachary Yedidia
7c90f4d6f1 Merge 2020-01-06 12:29:39 -05:00
Zachary Yedidia
61a90f7666 Update xml.yaml 2020-01-06 12:29:33 -05:00
Serge Voilokov
8d373cde6e Add golang keywords (#1455)
* Add golang keywords

* Update runtime
2020-01-06 12:06:44 -05:00
Zachary Yedidia
6a465500bc Properly handle empty args with new shellquote lib
Fixes #1454
2020-01-06 11:38:21 -05:00
Zachary Yedidia
f3e8413e77 More doc updates 2020-01-06 00:01:49 -05:00
Zachary Yedidia
f2a1e2337f Update tcell to include true color fix
Fixes #1452
2020-01-05 21:26:53 -05:00
Zachary Yedidia
c7f36f9480 Don't indent softwrap if ruler is off
Ref #1450
2020-01-05 20:32:29 -05:00
Zachary Yedidia
955bde4abc Minor view fix 2020-01-05 15:02:52 -05:00
Zachary Yedidia
afb03aa37f Small doc update 2020-01-05 13:21:46 -05:00
Zachary Yedidia
6c3814dfac Better message for gob error 2020-01-05 12:45:27 -05:00
Zachary Yedidia
d234e9ec41 Add cycleautocompleteback action 2020-01-04 15:51:15 -05:00
Bonnie
c2c0325384 Fix #1383: "Save with Sudo" rewrite (#1424)
* Rewrite save with sudo (Fixes #1383)

* Combine overrideFile & overrideFileAsRoot into 1 function
2020-01-03 17:39:12 -05:00
Zachary Yedidia
dfb6bc0312 Fix save callback issue 2020-01-03 17:38:50 -05:00
Zachary Yedidia
0c6a7e2837 Update options docs and new docs on copy-paste 2020-01-03 13:39:39 -05:00
Zachary Yedidia
ddc8bf455e Set filetype to 'off' to disable completely
Ref #1427
2020-01-02 19:00:42 -05:00
Zachary Yedidia
2855ae204c Replace shellwords with shellquote 2020-01-02 18:30:51 -05:00
Zachary Yedidia
0bf54ff0e7 Don't crash if only file to open is directory 2020-01-02 15:25:07 -05:00
Zachary Yedidia
50ff45c213 Some documentation updates 2020-01-02 15:10:28 -05:00
Zachary Yedidia
eb2b546600 Merge 2020-01-02 12:43:52 -05:00
Zachary Yedidia
dc4da37908 Add "paste" option to enable aggressive pasting
Ref #1043
2020-01-02 12:42:39 -05:00
Zachary Yedidia
9333354fc8 Fix save with sudo on mac 2020-01-02 01:25:00 -05:00
Zachary Yedidia
b557ed2221 Fix PluginAddRuntimeFile 2020-01-02 01:18:16 -05:00
Zachary Yedidia
021f8da6f1 update readme 2020-01-01 23:00:46 -05:00
Zachary Yedidia
6d0128059b Finish support for a fake cursor 2020-01-01 22:40:51 -05:00
Zachary Yedidia
d6dd838abd Better support for fake cursor 2020-01-01 21:29:18 -05:00
Zachary Yedidia
938fb7983a Remove no longer necessary terminfo package 2020-01-01 20:58:01 -05:00
Zachary Yedidia
d9e262c394 Use fake cursor for windows 2020-01-01 20:47:05 -05:00
Zachary Yedidia
e98be1a1e5 Update deps 2020-01-01 20:44:45 -05:00
Zachary Yedidia
41fe7d090e Update tcell
Ref #1447
2020-01-01 18:24:39 -05:00
Zachary Yedidia
aadf5b40ec Update tcell
This update includes a fix for screen flashing on Windows.

Fixes #1447
2020-01-01 17:57:16 -05:00
Zachary Yedidia
ebf6d69f26 Update building from source info 2020-01-01 17:41:59 -05:00
Zachary Yedidia
ebf616399e Update tcell 2020-01-01 17:26:49 -05:00
Zachary Yedidia
08708f79bf Update tcell 2020-01-01 17:16:18 -05:00
Zachary Yedidia
d7b39fe7a5 Disable true color by default 2019-12-31 23:09:33 -05:00
Zachary Yedidia
48ace1c530 Add extra nightly release message 2019-12-31 22:49:21 -05:00
Zachary Yedidia
fde1cc563f Update tcell 2019-12-31 22:46:30 -05:00
Zachary Yedidia
abf07a8357 Update runtime 2019-12-31 22:42:35 -05:00
Zachary Yedidia
a2f7080602 Raw event support with new tcell 2019-12-31 22:34:43 -05:00
Zachary Yedidia
a2916c0e32 Escape sequence support 2019-12-31 21:50:26 -05:00
Zachary Yedidia
0301e3539e Use upstream updated zyedidia tcell 2019-12-31 20:15:45 -05:00
Zachary Yedidia
6632ab0a77 Switch to gdamore/tcell 2019-12-31 17:53:16 -05:00
Zachary Yedidia
466c48da31 Merge 2019-12-31 17:53:00 -05:00
Zachary Yedidia
2c72a3755c Fix openbuffer view creation 2019-12-31 17:52:55 -05:00
Zachary Yedidia
92054aa649 Merge 2019-12-31 16:49:21 -05:00
Zachary Yedidia
4b5be43e60 Create all parents of micro config automatically
Ref #1184
2019-12-31 16:48:45 -05:00
Zachary Yedidia
d18864e607 Add linux static binary to release scripts 2019-12-31 16:23:37 -05:00
Zachary Yedidia
4010a16784 Add fully static linux build to automated builder
Ref #1184
2019-12-31 16:20:54 -05:00
Zachary Yedidia
01c6ea26b8 Merge 2019-12-31 16:12:59 -05:00
Zachary Yedidia
d83b912b3b Add xclip message for pasting if unsupported 2019-12-31 16:12:39 -05:00
Zachary Yedidia
604d78de0f Merge pull request #1327 from Osmose/git-commit-diff
Fix #1314: Add support for diffs from `git commit --verbose`.
2019-12-30 14:43:50 -05:00
Zachary Yedidia
9e8420aab5 Merge pull request #1437 from serebit/patch-2
Enable syntax highlighting for Kotlin script files
2019-12-30 14:42:48 -05:00
Zachary Yedidia
dc3d6f5bc3 Merge branch 'ariasuni-fix-xml-highlighting' 2019-12-30 14:40:58 -05:00
Zachary Yedidia
93431a9ddf Merge branch 'fix-xml-highlighting' of https://github.com/ariasuni/micro into ariasuni-fix-xml-highlighting 2019-12-30 14:40:41 -05:00
Zachary Yedidia
ec6be38391 Delete runtime.go 2019-12-30 14:32:36 -05:00
Zachary Yedidia
3777bcf295 Merge pull request #1386 from jncraton/docfix
Minor grammar fix addressing #1377
2019-12-30 14:28:46 -05:00
Zachary Yedidia
3d09dfe574 Merge pull request #1393 from raziel2244/patch-1
ES6 - ES2019 additions
2019-12-30 14:24:33 -05:00
Zachary Yedidia
532f932712 Merge branch 'master' into patch-1 2019-12-30 14:24:27 -05:00
Zachary Yedidia
eeeb927a1d Merge pull request #1361 from Lisiadito/master
fix #1318. fix html comments and make them work multiline
2019-12-30 14:23:27 -05:00
Zachary Yedidia
f9ce549663 Merge pull request #1287 from didactic-drunk/ruby_syntax
Ruby syntax improvements.
2019-12-30 14:23:06 -05:00
Zachary Yedidia
5a715e7a0b Merge pull request #1281 from Calinou/highlight-nimscript
Highlight NimScript files (.nims) as Nim
2019-12-30 14:22:38 -05:00
Zachary Yedidia
e420872a27 Merge branch 'master' into highlight-nimscript 2019-12-30 14:22:22 -05:00
Zachary Yedidia
cef32d4ac7 Merge pull request #1315 from matbesancon/patch-1
Update julia.yaml
2019-12-30 14:21:20 -05:00
Zachary Yedidia
35375a6ea2 Merge pull request #1406 from LeapofAzzam/LeapofAzzam-patch-1
Update vi syntax
2019-12-30 14:20:27 -05:00
Zachary Yedidia
60eec0eccd Merge pull request #1412 from tommyshem/batSyntaxHighlighting
Add windows .bat syntax highlighting file #1388
2019-12-30 14:20:13 -05:00
Zachary Yedidia
97665573c7 Merge pull request #1426 from Nergel3/master
vue syntax (+typescript) & svelte syntax
2019-12-30 14:19:59 -05:00
Zachary Yedidia
f874377573 add system verilog syntax file 2019-12-30 14:05:06 -05:00
Zachary Yedidia
ce868faece Merge pull request #1445 from spytheman/spytheman-v-micro-highlight-syntax
Added V syntax support
2019-12-30 12:27:31 -05:00
Delyan Angelov
e1c1e402a9 Add V syntax highlighting.
V is a new general purpose language, inspired mainly by Go, Rust, Pascal and C.
Main site: https://vlang.io/
Github: https://github.com/vlang/v
2019-12-30 16:13:15 +02:00
Zachary Yedidia
3b66a3364c Fix some formatting 2019-12-29 22:02:14 -05:00
Zachary Yedidia
9b03a3dc6d Add message if xclip/xsel not found
Ref #1236
Fixes #1031
2019-12-29 21:43:29 -05:00
Zachary Yedidia
ff24ad5fa8 Fix race condition with events channel 2019-12-29 18:53:59 -05:00
Zachary Yedidia
5180634947 Merge 2019-12-29 18:23:22 -05:00
Zachary Yedidia
da643a0c1f Run action completion on saves with prompts at the right time 2019-12-29 18:23:17 -05:00
Zachary Yedidia
cd6765673f Support tcell EventPaste 2019-12-29 13:45:08 -05:00
Zachary Yedidia
1b73abcfd0 Fix formatting in plugin info.json 2019-12-29 00:03:21 -05:00
Zachary Yedidia
f3778baaf4 SetGlobalOption access for plugins 2019-12-28 23:40:44 -05:00
Zachary Yedidia
cf1f9fa007 Use MICRO_CONFIG_HOME before trying XDG_CONFIG_HOME 2019-12-28 23:10:51 -05:00
Zachary Yedidia
34619e111f Add GetGlobalOption access for plugins 2019-12-28 22:48:38 -05:00
Zachary Yedidia
29a5cef559 Update default plugins slightly 2019-12-28 22:39:57 -05:00
Zachary Yedidia
bd83c6a8a9 Remove detect requirement and detect in jinja file
Ref #1415
2019-12-28 22:27:44 -05:00
Zachary Yedidia
4b0348f64a Merge 2019-12-28 21:57:11 -05:00
Zachary Yedidia
5b52b8a60f Support includes 2019-12-28 21:57:03 -05:00
Zachary Yedidia
c0e6dad88b Merge pull request #1443 from onodera-punpun/patch-1
Replace tab with spaces
2019-12-28 21:56:40 -05:00
Zachary Yedidia
a61616d79e More efficient loading for default syntax files
This change introduces header files for syntax files. The header
files only contain the filetype and detection info and can be
parsed much faster than parsing a full yaml file. To determine
which filetype a file is, only scanning the headers is necessary
and afterwards only one yaml file needs to be parsed. Use the
make_headers.go file to generate the header files. Micro expects
that all default syntax files will have header files and that
custom user syntax files may or may not have them. Resolving
includes within syntax has not yet been implemented. This
optimization improves startup time.

Ref #1427
2019-12-28 21:26:22 -05:00
Zachary Yedidia
8663014bbe Add support for syntax headers and update tcell 2019-12-28 18:53:51 -05:00
Camille
351c7b099a Replace tab with spaces 2019-12-28 22:38:41 +01:00
Zachary Yedidia
c2e7fd34a7 Fix issues related to tabbar/infobar mouse events
Fixes #1440
2019-12-28 15:56:56 -05:00
Zachary Yedidia
a3e61a6e71 Merge 2019-12-28 12:04:48 -05:00
Zachary Yedidia
bd0c172667 Improve mouse selection performance 2019-12-28 12:04:43 -05:00
Zachary Yedidia
7746039724 Fix windows compilation in auto-builder 2019-12-28 01:56:03 +00:00
Zachary Yedidia
629f20720a Fix add runtime file for local plugins 2019-12-27 20:28:25 -05:00
Zachary Yedidia
2a3d7720f3 Merge 2019-12-27 18:43:53 -05:00
Zachary Yedidia
b47cf33c3b Update tcell version 2019-12-27 18:43:47 -05:00
Zachary Yedidia
5fba432d78 Use makefile to build for cross compilation 2019-12-27 17:26:03 +00:00
Zachary Yedidia
b1efabaaed Command binding fix 2019-12-27 00:43:45 -05:00
Zachary Yedidia
185d54d664 Search and replace fixes 2019-12-27 00:06:02 -05:00
Zachary Yedidia
96322a6df9 Update makefile and vendor script 2019-12-26 22:03:30 -05:00
Zachary Yedidia
42af0816d5 No patchelf for snap build
Ref #1078
2019-12-26 21:41:19 -05:00
Zachary Yedidia
8368989341 Clean up build tools 2019-12-26 20:43:43 -05:00
Zachary Yedidia
9e60d468ca Use osusergo build tag 2019-12-26 20:32:33 -05:00
Campbell Jones
da32457037 Enable syntax highlighting for Kotlin script files
In addition, make the following changes to the kotlin syntax highlighting: 
- Add new unsigned types to type.storage
- Add const as a statement keyword
- Remove typeof from type keywords
2019-12-26 19:13:23 -05:00
Zachary Yedidia
2d2dbfebff Fix snap install metadata 2019-12-26 18:54:40 -05:00
Zachary Yedidia
6681387b47 Support for file reloading if changed externally 2019-12-26 17:59:23 -05:00
Zachary Yedidia
6a4a915188 Support arm64 in cross compilation script
Ref #1431
2019-12-26 17:06:55 -05:00
Zachary Yedidia
6ba66320f0 Don't forward tabbar mouse events to panes
Fixes #1435
2019-12-26 17:02:02 -05:00
Zachary Yedidia
1367084a18 Merge 2019-12-26 16:57:33 -05:00
Zachary Yedidia
ec2976b069 Scroll up as much as possible
Fixes #1434
2019-12-26 16:57:09 -05:00
Zachary Yedidia
bad8ed7473 Fix poller mod version for osx 2019-12-26 21:53:45 +00:00
Zachary Yedidia
e912b7de12 Fix go module issue 2019-12-26 16:37:02 -05:00
Zachary Yedidia
8570ff9a8c Remove autosave option
With the new backup option, the autosave option is no longer useful.
Since it never really worked well in the first place, it has been
removed.

Closes #1420
2019-12-26 14:35:48 -05:00
Zachary Yedidia
7f7ad29671 Improve lua interface for statusline 2019-12-26 12:46:10 -05:00
Zachary Yedidia
a95dab078e Minor edit to statusline format
Ref #1432
2019-12-26 12:25:42 -05:00
Zachary Yedidia
f8218e0648 Fix bottomline when softwrap enabled 2019-12-25 19:44:58 -05:00
Zachary Yedidia
e66d01e989 Some documentation 2019-12-25 19:37:51 -05:00
Zachary Yedidia
05331e1fa2 Support rc tags in build version 2019-12-25 17:42:57 -05:00
Zachary Yedidia
c5d3008d85 Clean unused go modules 2019-12-25 17:23:39 -05:00
Zachary Yedidia
922a57b3e7 Merge branch refactor2.0
The code from the refactor that I have been working on is
now more or less ready to be merged. These changes make some
breaking changes, notably with regards to the plugin
interface. Once a lot more documentation has been written, I
will release this code as micro 2.0. There are a lot of new
features, and in the coming days I will try to go through
the open issues to see exactly which ones are addressed by
the new features, and write lots more documentation
regarding what has been implemented.

Some highlights include:

* Simple autocompletion.
    * Autocompletion (tab by default) will do a simple
      "buffer completion" which will autocomplete according
      to words used elsewhere in the buffer. In the future
      plugin support could be added along with support for
      interfacing with language-specific autocompletion
      tools.
* Automatic backups.
    * Backup files are stored in `~/.config/micro/backups`
      for every open buffer and are saved roughly every 8
      seconds if the buffer is being modified. Backups
      are removed when the buffer is closed, but if micro
      or the system crashes, any unsaved changes can be
      recovered by re-opening the file (micro will auto-
      recover) or by manually viewing the backup in the
      `~/.config/micro/backups` directory.
* Configurable statusline.
* Configurable linter plugin.
* Resizeable splits.
* Complete re-organization of the code to support better go
  modules and maintain a better directory structure.
* Better plugin interface with better access to the Go
  standard library and internal Micro functions (lots of
  documentation still needs to be written).
    * Documentation still needs to be written, but in the
      meantime please see the default plugins as examples
      as they have been converted from their old versions
      to be compatible with the new interface.
* Buffer synchronization when the same file is opened
  multiple times.
* Keybindings and mouse support in the command bar.
* Support for non-utf8 encodings.
* General QoL improvements and bug fixes.
    * Notably I believe the autoclose plugin crash issue is
      fixed.
* No more plugin manager.
    * Plugin installation will now be performed manually
      by git cloning into the `~/.config/micro/plug`
      directory. This may not be a highlight for some but
      I believe it is much simpler, and there is no need
      to have a heavyweight dependency manager. Perhaps
      in the future, a good command-line tool can be made
      to manage plugins if people would find it useful.
* Other features that I have forgotten.

Next I plan to write up more documentation for all the new
features, and make a "release candidate" for micro 2.0. I
will also be working to fix any bugs that come up (hopefully
not too many, but this is a big change and bound to have
some issues). After release I hope to focus more on
optimization (for example loading syntax files is currently
somewhat inefficient, and the bottleneck for startup time #1427).

Sorry for not being so active recently, but I hope merging
this big change can help me get back to more regular
development. Thanks to everyone for using micro and for
giving feedback and engaging with development online (even
if I don't always respond).

Merry Christmas!

Issues that are fixed/affected by this change:

Ref #1419 (configurable statusline)
Ref #1413 (cursor behaves better)
Ref #1401 (softwrap problems)
Ref #1383 (better save with sudo)
Ref #1424 (better save with sudo)
Ref #1382 (go modules)
Ref #1381 (install plugins from command line)
Ref #1357 (sorting -- textfilter)
Ref #1351 (custom linting)
Ref #1350 (sudo problem might be fixed)
Ref #1298 (readonly option)
Ref #1250 (autoclose bug)
Ref #1239 (go modules)
Ref #813  (autoclose bug)
Ref #812  (cursor sync across same buffers)
Ref #770  (resizeable panes)
Ref #635  (keybindings in infobar)
Ref #596  (disable builtin plugins)
Ref #550  (backups)
Ref #174  (autocompletion)
2019-12-25 17:07:30 -05:00
nergel3
5d81fc7815 vue syntax (+typescript) & svelte syntax 2019-12-17 11:38:22 +01:00
Sterling Parker
0827968f6b Create zscript.yaml 2019-12-04 11:52:22 -07:00
ariasuni
2d475fbca8 Remove unreliable XML entity handling from XML syntax 2019-11-27 17:39:31 +01:00
ariasuni
36862137db Improve XML character entities highlighting and comment 2019-11-27 14:54:53 +01:00
ariasuni
34c12e1282 Fix XML character entities (e.g. &lt;) highlighting 2019-11-27 14:54:53 +01:00
ariasuni
8b0c858f28 Make XML highlighting more fine-grained 2019-11-27 14:54:53 +01:00
ariasuni
daa0d67c6f Fix XML highlighting when tags are spread on multiple lines 2019-11-27 14:54:53 +01:00
ariasuni
aaac5466d1 Set XML filetype for .svg and files with xml version tag 2019-11-27 14:54:53 +01:00
0xdbeef
89ac5d7de2 SpawnMultiCursorDown / SpawnMultiCursorUp 2019-11-17 11:22:25 +01:00
tommyshem
be0dcd5d10 Add windows .bat syntax highlighting file #1388 2019-11-14 01:11:02 +00:00
Leap of Azzam
9e0d3c7cbe Update vi syntax 2019-10-26 15:51:38 +07:00
Elliot Thomas
f12061ea88 ES6 - ES2019 additions
symbol.operator: Spread/rest operator.
statement: Generator function, Promise resolve and reject.
type: New built-in types, including section for TypedArrays.
constant.string: Template literal.
2019-10-02 09:50:46 +01:00
Jon Craton
e87917f1e1 Minor grammar fix addressing #1377 2019-09-13 11:07:11 -04:00
prez
aae0f4630e Added option to automatically save files with sucmd 2019-09-11 15:38:15 +02:00
Patrick Weingaertner
523592be26 fix #1318. fix html comments and make them work multiline 2019-09-05 09:41:35 +02:00
Ender - Josh Pritsker
612ebb2e17 Add Forth highlighting 2019-05-25 21:01:24 -07:00
Osmose
a189d08c30 Fix #1314: Add support for diffs from git commit --verbose. 2019-05-21 17:26:06 -07:00
David Konsumer
26172b5101 add support for input and scalar defintiions (for graphql-tools schema) 2019-05-16 16:25:51 -07:00
USAMI Kenta
51691ed7bf Reclassified PHP keywords 2019-05-12 20:14:17 +09:00
USAMI Kenta
5acbccf0b2 Add fn keyword (arrow function) added in PHP 7.4 to coloring
https://wiki.php.net/rfc/arrow_functions_v2
2019-05-12 18:58:25 +09:00
Didactic Drunk
e33489c04f Ruby syntax improvements.
String interpolation for Crystal syntax.
2019-04-30 11:29:42 -07:00
Mathieu Besançon
bcb8765049 Update julia.yaml
Some keywords are not in Julia and were removed. `include` is a standard function with no special property (no syntax-level highlight required)
2019-04-29 15:29:21 +02:00
Koki Fushimi
87661ef308 Update keywords for infix operators 2019-04-09 06:57:58 +09:00
Koki Fushimi
58e11c0b2d Update Julia keywords 2019-04-09 06:39:46 +09:00
Hugo Locurcio
1739e0c09c Highlight NimScript files (.nims) as Nim 2019-02-16 20:00:38 +01:00
Jawahar Suresh Babu
c46695bb57 Fixed the issue #1049 - Endless prompt to reload changes 2019-02-12 07:20:07 +05:30
coolreader18
457a4f8f98 Update README regarding windows terminal colors 2019-02-09 21:39:28 -06:00
Krerkkiat Chusap
e3fd914e0b Update sh.yaml
This adds back the lowercase characters to the identifier name.
2019-01-14 04:03:11 -05:00
Pascal
6aa5aa540b Update zsh.yaml
Copied comment start from sh.yaml so that stuff like `$#` doesn't count as comments
2018-12-13 09:55:19 +01:00
Michael Siegel
81bad4d089 Merge pull request #1 from msiism/msiism-patch-1
Say "syntax highlighting" instead of just "syntax"
2018-11-25 19:13:26 +00:00
Michael Siegel
9f4da789db Say "syntax highlighting" instead of just "syntax" 2018-11-25 19:06:10 +00:00
Hugo Locurcio
a732d03b4d Improve writing style and formatting in README 2018-08-07 14:35:07 +02:00
sum01
28267b9eb2 Add back ?
Accidentally removed
2018-02-06 13:41:56 -05:00
sum01
cad43914b0 Html syntax fixes #1008
Note that there's a TODO with if/when 'limit-rules' are added.
Till that's added, any 'style' and 'script' blocks will be missing highlighting on their identifiers.
The actual contents (CSS or JS) will still work correctly though.
2018-02-05 23:13:57 -05:00
205 changed files with 14138 additions and 6536 deletions

View File

@@ -2,7 +2,7 @@
## Specifications
You can use `micro -version` to get the commit hash.
<!-- You can use `micro -version` to get the commit hash. -->
Commit hash:
OS:

3
.gitignore vendored
View File

@@ -11,7 +11,10 @@ todo.txt
test.txt
log.txt
*.old
benchmark_results*
tools/build-version
tools/build-date
tools/info-plist
tools/bindata
tools/vscode-tests/
*.hdr

View File

@@ -1,9 +1,11 @@
language: go
go:
- "1.11.x"
- "1.13.x"
os:
- linux
- osx
- windows
script:
- env GO111MODULE=on make build
- env GO111MODULE=on make test
- go build ./cmd/micro
- go test ./internal/...
- go test ./cmd/...

View File

@@ -1,6 +1,6 @@
MIT License
Copyright (c) 2016-2017: Zachary Yedidia, et al.
Copyright (c) 2016-2020: Zachary Yedidia, et al.
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the

View File

@@ -1,4 +1,4 @@
Third party libraries
Third party libraries directly used by micro and their licenses
================
github.com/golang/go/LICENSE
@@ -637,37 +637,6 @@ github.com/zyedidia/tcell/LICENSE (fork)
limitations under the License.
golang.org/x/net/LICENSE
================
Copyright (c) 2009 The Go Authors. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the
distribution.
* Neither the name of Google Inc. nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
golang.org/x/text/LICENSE
================
@@ -1079,6 +1048,8 @@ Exhibit B - "Incompatible With Secondary Licenses" Notice
github.com/flynn/json5/LICENSE
================
github.com/zyedidia/json5/LICENSE (fork)
================
Decoder code based on package encoding/json from the Go language.
@@ -1189,82 +1160,6 @@ OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
github.com/smartystreets/goconvey/LICENSE.md
================
Copyright (c) 2016 SmartyStreets, LLC
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
NOTE: Various optional and subordinate components carry their own licensing
requirements and restrictions. Use of those components is subject to the terms
and conditions outlined the respective license of each component.
github.com/smartystreets/assertions/LICENSE.md
================
Copyright (c) 2016 SmartyStreets, LLC
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
NOTE: Various optional and subordinate components carry their own licensing
requirements and restrictions. Use of those components is subject to the terms
and conditions outlined the respective license of each component.
github.com/jtolds/gls/LICENSE
================
Copyright (c) 2013, Space Monkey, Inc.
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
github.com/npat-efault/poller/LICENSE.txt
================
github.com/zyedidia/poller/LICENSE.txt (fork)
@@ -1362,10 +1257,35 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
github.com/lucasb-eyer/go-colorful/LICENSE
================
github.com/kballard/go-shellquote/LICENSE
===============
Copyright (c) 2013 Lucas Beyer
Copyright (C) 2014 Kevin Ballard
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included
in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
github.com/stretchr/testify
=================
MIT License
Copyright (c) 2012-2018 Mat Ryer and Tyler Bunnell
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
@@ -1374,8 +1294,8 @@ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,

View File

@@ -1,22 +1,26 @@
.PHONY: runtime
VERSION := $(shell GOOS=$(shell go env GOHOSTOS) GOARCH=$(shell go env GOHOSTARCH) \
VERSION = $(shell GOOS=$(shell go env GOHOSTOS) GOARCH=$(shell go env GOHOSTARCH) \
go run tools/build-version.go)
HASH := $(shell git rev-parse --short HEAD)
DATE := $(shell GOOS=$(shell go env GOHOSTOS) GOARCH=$(shell go env GOHOSTARCH) \
HASH = $(shell git rev-parse --short HEAD)
DATE = $(shell GOOS=$(shell go env GOHOSTOS) GOARCH=$(shell go env GOHOSTARCH) \
go run tools/build-date.go)
ADDITIONAL_GO_LINKER_FLAGS := $(shell GOOS=$(shell go env GOHOSTOS) \
GOARCH=$(shell go env GOHOSTARCH) \
go run tools/info-plist.go "$(VERSION)")
ADDITIONAL_GO_LINKER_FLAGS = $(shell GOOS=$(shell go env GOHOSTOS) \
GOARCH=$(shell go env GOHOSTARCH))
GOBIN ?= $(shell go env GOPATH)/bin
GOVARS := -X github.com/zyedidia/micro/internal/util.Version=$(VERSION) -X github.com/zyedidia/micro/internal/util.CommitHash=$(HASH) -X 'github.com/zyedidia/micro/internal/util.CompileDate=$(DATE)' -X github.com/zyedidia/micro/internal/util.Debug=OFF
GOVARS = -X github.com/zyedidia/micro/v2/internal/util.Version=$(VERSION) -X github.com/zyedidia/micro/v2/internal/util.CommitHash=$(HASH) -X 'github.com/zyedidia/micro/v2/internal/util.CompileDate=$(DATE)'
DEBUGVAR = -X github.com/zyedidia/micro/v2/internal/util.Debug=ON
VSCODE_TESTS_BASE_URL = 'https://raw.githubusercontent.com/microsoft/vscode/e6a45f4242ebddb7aa9a229f85555e8a3bd987e2/src/vs/editor/test/common/model/'
# Builds micro after checking dependencies but without updating the runtime
build:
go build -ldflags "-s -w $(GOVARS) $(ADDITIONAL_GO_LINKER_FLAGS)" ./cmd/micro
build-dbg:
go build -ldflags "-s -w $(ADDITIONAL_GO_LINKER_FLAGS)" ./cmd/micro
go build -ldflags "-s -w $(ADDITIONAL_GO_LINKER_FLAGS) $(DEBUGVAR)" ./cmd/micro
build-tags: fetch-tags
go build -ldflags "-s -w $(GOVARS) $(ADDITIONAL_GO_LINKER_FLAGS)" ./cmd/micro
# Builds micro after building the runtime and checking dependencies
build-all: runtime build
@@ -36,16 +40,48 @@ install-all: runtime install
install-quick:
go install -ldflags "-s -w $(GOVARS) $(ADDITIONAL_GO_LINKER_FLAGS)" ./cmd/micro
fetch-tags:
git fetch --tags
# Builds the runtime
runtime:
git submodule update --init
rm -f runtime/syntax/*.hdr
go run runtime/syntax/make_headers.go runtime/syntax
go build -o tools/bindata ./tools/go-bindata
tools/bindata -pkg config -nomemcopy -nometadata -o runtime.go runtime/...
mv runtime.go internal/config
gofmt -w internal/config/runtime.go
testgen:
mkdir -p tools/vscode-tests
cd tools/vscode-tests && \
curl --remote-name-all $(VSCODE_TESTS_BASE_URL){editableTextModelAuto,editableTextModel,model.line}.test.ts
tsc tools/vscode-tests/*.ts > /dev/null; true
go run tools/testgen.go tools/vscode-tests/*.js > buffer_generated_test.go
mv buffer_generated_test.go internal/buffer
gofmt -w internal/buffer/buffer_generated_test.go
test:
go test ./internal/...
go test ./cmd/...
bench:
for i in 1 2 3; do \
go test -bench=. ./internal/...; \
done > benchmark_results
benchstat benchmark_results
bench-baseline:
for i in 1 2 3; do \
go test -bench=. ./internal/...; \
done > benchmark_results_baseline
bench-compare:
for i in 1 2 3; do \
go test -bench=. ./internal/...; \
done > benchmark_results
benchstat -alpha 0.15 benchmark_results_baseline benchmark_results
clean:
rm -f micro

238
README.md
View File

@@ -1,102 +1,105 @@
# ![Micro](./assets/logo.png)
<img alt="micro logo" src="./assets/micro-logo.svg" width="500px"/>
[![Build Status](https://travis-ci.org/zyedidia/micro.svg?branch=master)](https://travis-ci.org/zyedidia/micro)
[![Go Report Card](https://goreportcard.com/badge/github.com/zyedidia/micro)](https://goreportcard.com/report/github.com/zyedidia/micro)
[![Join the chat at https://gitter.im/zyedidia/micro](https://badges.gitter.im/zyedidia/micro.svg)](https://gitter.im/zyedidia/micro?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
[![Release](https://img.shields.io/github/release/zyedidia/micro.svg?label=Release)](https://github.com/zyedidia/micro/releases)
[![MIT License](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/zyedidia/micro/blob/master/LICENSE)
[![Join the chat at https://gitter.im/zyedidia/micro](https://badges.gitter.im/zyedidia/micro.svg)](https://gitter.im/zyedidia/micro?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
[![Snap Status](https://build.snapcraft.io/badge/zyedidia/micro.svg)](https://build.snapcraft.io/user/zyedidia/micro)
Micro is a terminal-based text editor that aims to be easy to use and intuitive, while also taking advantage of the full capabilities
of modern terminals. It comes as one single, batteries-included, static binary with no dependencies, and you can download and use it right now.
**micro** is a terminal-based text editor that aims to be easy to use and intuitive, while also taking advantage of the capabilities
of modern terminals. It comes as a single, batteries-included, static binary with no dependencies; you can download and use it right now!
As the name indicates, micro aims to be somewhat of a successor to the nano editor by being easy to install and use in a pinch, but micro also aims to be
enjoyable to use full time, whether you work in the terminal because you prefer it (like me), or because you need to (over ssh).
As its name indicates, micro aims to be somewhat of a successor to the nano editor by being easy to install and use.
It strives to be enjoyable as a full-time editor for people who prefer to work in a terminal, or those who regularly edit files over SSH.
Here is a picture of micro editing its source code.
![Screenshot](./assets/micro-solarized.png)
To see more screenshots of micro, showcasing all of the default colorschemes, see [here](http://zbyedidia.webfactional.com/micro/screenshots.html).
To see more screenshots of micro, showcasing some of the default color schemes, see [here](http://zbyedidia.webfactional.com/micro/screenshots.html).
You can also check out the website for Micro at https://micro-editor.github.io.
# Table of Contents
## Table of Contents
- [Features](#features)
- [Installation](#installation)
- [Prebuilt binaries](#prebuilt-binaries)
- [Package Managers](#package-managers)
- [Building from source](#building-from-source)
- [MacOS terminal](#macos-terminal)
- [Fully static binary](#fully-static-binary)
- [macOS terminal](#macos-terminal)
- [Linux clipboard support](#linux-clipboard-support)
- [Colors and syntax highlighting](#colors-and-syntax-highlighting)
- [Plan9, Cygwin](#plan9-cygwin)
- [Plan9, Cygwin, Mingw](#plan9-cygwin-mingw)
- [Usage](#usage)
- [Documentation and Help](#documentation-and-help)
- [Contributing](#contributing)
- - -
# Features
## Features
* Easy to use and to install
* No dependencies or external files are needed -- just the binary you can download further down the page
* Multiple cursors
* Common keybindings (ctrl-s, ctrl-c, ctrl-v, ctrl-z...)
* Keybindings can be rebound to your liking
* Sane defaults
* You shouldn't have to configure much out of the box (and it is extremely easy to configure)
* Splits and tabs
* Nano-like menu to help you remember the keybindings
* Extremely good mouse support
* This means mouse dragging to create a selection, double click to select by word, and triple click to select by line
* Cross platform (It should work on all the platforms Go runs on)
* Note that while Windows is supported, there are still some bugs that need to be worked out
* Plugin system (plugins are written in Lua)
* Micro has a built-in plugin manager to automatically install, remove, and update all your plugins
* Persistent undo
* Automatic linting and error notifications
* Syntax highlighting (for over [90 languages](runtime/syntax)!)
* Colorscheme support
* By default, micro comes with 16, 256, and true color themes.
* True color support (set the `MICRO_TRUECOLOR` env variable to 1 to enable it)
* Snippets
* The snippet plugin can be installed with `> plugin install snippets`
* Copy and paste with the system clipboard
* Small and simple
* Easily configurable
* Macros
* Common editor things such as undo/redo, line numbers, Unicode support, softwrap...
- Easy to use and install.
- No dependencies or external files are needed just the binary you can download further down the page.
- Multiple cursors.
- Common keybindings (<kbd>Ctrl-s</kbd>, <kbd>Ctrl-c</kbd>, <kbd>Ctrl-v</kbd>, <kbd>Ctrl-z</kbd>, …).
- Keybindings can be rebound to your liking.
- Sane defaults.
- You shouldn't have to configure much out of the box (and it is extremely easy to configure).
- Splits and tabs.
- nano-like menu to help you remember the keybindings.
- Extremely good mouse support.
- This means mouse dragging to create a selection, double click to select by word, and triple click to select by line.
- Cross-platform (it should work on all the platforms Go runs on).
- Note that while Windows is supported Mingw/Cygwin is not (see below).
- Plugin system (plugins are written in Lua).
- micro has a built-in plugin manager to automatically install, remove, and update plugins.
- Built-in diff gutter.
- Simple autocompletion.
- Persistent undo.
- Automatic linting and error notifications.
- Syntax highlighting for over [130 languages](runtime/syntax).
- Color scheme support.
- By default, micro comes with 16, 256, and true color themes.
- True color support (set the `MICRO_TRUECOLOR` environment variable to 1 to enable it).
- Copy and paste with the system clipboard.
- Small and simple.
- Easily configurable.
- Macros.
- Common editor features such as undo/redo, line numbers, Unicode support, soft wrapping, …
Although not yet implemented, I hope to add more features such as autocompletion ([#174](https://github.com/zyedidia/micro/issues/174)) or a tree view ([#249](https://github.com/zyedidia/micro/issues/249)) in the future.
# Installation
## Installation
To install micro, you can download a [prebuilt binary](https://github.com/zyedidia/micro/releases), or you can build it from source.
If you want more information about ways to install micro, see this [wiki page](https://github.com/zyedidia/micro/wiki/Installing-Micro).
Use `micro -version` to get the version information after installing. It is only guaranteed that you are installing the most recent
stable version if you install from the prebuilt binaries, Homebrew, or Snap.
A desktop entry file and man page can be found in the [assets/packaging](https://github.com/zyedidia/micro/tree/master/assets/packaging) directory.
### Prebuilt binaries
All you need to install micro is one file, the binary itself. It's as simple as that!
Download the binary from the [releases](https://github.com/zyedidia/micro/releases) page.
On that page you'll see the nightly release, which contains binaries for micro which are built every night,
and you'll see all the stable releases with the corresponding binaries.
If you'd like to see more information after installing micro, run `micro -version`.
### Installation script
There is a great script which can install micro for you by downloading the latest prebuilt binary. You can find it at https://getmic.ro (the github repo for it is [here](https://github.com/benweissmann/getmic.ro)).
There is a script which can install micro for you by downloading the latest prebuilt binary. You can find it at <https://getmic.ro>.
Then you can easily install micro:
You can easily install micro by running
$ curl https://getmic.ro | bash
```bash
curl https://getmic.ro | bash
```
The script will install the micro binary to the current directory.
The script will place the micro binary in the current directory. From there, you can move it to a directory on your path of your choosing (e.g. `sudo mv micro /usr/bin`). See its [GitHub repository](https://github.com/benweissmann/getmic.ro) for more information.
See the [Github page](https://github.com/benweissmann/getmic.ro) for more information.
To uninstall micro, simply remove the binary, and the configuration directory at `~/.config/micro`.
### Package managers
@@ -106,17 +109,9 @@ You can install micro using Homebrew on Mac:
brew install micro
```
On Windows, you can install micro through [Chocolatey](https://chocolatey.org/) or [Scoop](https://github.com/lukesampson/scoop):
```
choco install micro
```
or
```
scoop install micro
```
**Note for Mac:** All micro keybindings use the control or alt (option) key, not the command
key. By default, macOS terminals do not forward alt key events. To fix this, please see
the section on [macOS terminals](https://github.com/zyedidia/micro#macos-terminal) further below.
On Linux, you can install micro through [snap](https://snapcraft.io/docs/core/install)
@@ -124,71 +119,106 @@ On Linux, you can install micro through [snap](https://snapcraft.io/docs/core/in
snap install micro --classic
```
On OpenBSD, micro is available in the ports tree. It is also available as a binary package.
**Note for Linux:** for interfacing with the local system clipboard, `xclip` or `xsel`
must be installed. Please see the section on [Linux clipboard support](https://github.com/zyedidia/micro#linux-clipboard-support)
further below.
```
pkg_add -v micro
```
Micro is also available through other package managers on Linux such as apt, dnf, AUR, Nix, and package managers
for other operating systems. These packages are not guaranteed to be up-to-date.
* Linux: Available in distro-specific package managers.
* `apt install micro` (Ubuntu 20.04 `focal`, and Debian `unstable | testing | buster-backports`). At the moment, this package (2.0.1-1) is outdated and has a known bug where debug mode is enabled.
* `dnf install micro` (Fedora).
* `yay -S micro` (Arch Linux).
* See [wiki](https://github.com/zyedidia/micro/wiki/Installing-Micro) for details about CRUX, Termux.
* Windows: [Chocolatey](https://chocolatey.org) and [Scoop](https://github.com/lukesampson/scoop).
* `choco install micro`.
* `scoop install micro`.
* OpenBSD: Available in the ports tree and also available as a binary package.
* `pkd_add -v micro`.
* NetBSD, macOS, Linux, Illumos, etc. with [pkgsrc](http://www.pkgsrc.org/)-current:
* `pkg_add micro`
### Building from source
If your operating system does not have a binary release, but does run Go, you can build from source.
Make sure that you have Go version 1.5 or greater (Go 1.4 will work if your version supports CGO) and that your `GOPATH` env variable is set (I recommend setting it to `~/go` if you don't have one).
Make sure that you have Go version 1.11 or greater and Go modules are enabled.
```
go get -d github.com/zyedidia/micro/cmd/micro
cd $GOPATH/src/github.com/zyedidia/micro
make install
git clone https://github.com/zyedidia/micro
cd micro
make build
sudo mv micro /usr/local/bin # optional
```
The binary will then be installed to `$GOPATH/bin` (or your `$GOBIN`).
The binary will be placed in the current directory and can be moved to
anywhere you like (for example `/usr/local/bin`).
Please make sure that when you are working with micro's code, you are working on your `GOPATH`.
The command `make install` will install the binary to `$GOPATH/bin` or `$GOBIN`.
You can install directly with `go get` (`go get -u github.com/zyedidia/micro/cmd/micro`) but this isn't recommended because it doesn't build micro with version information which is useful for the plugin manager.
You can install directly with `go get` (`go get github.com/zyedidia/micro/cmd/micro`) but this isn't
recommended because it doesn't build micro with version information (necessary for the plugin manager),
and doesn't disable debug mode.
### MacOS terminal
### Fully static binary
If you are using MacOS, you should consider using [iTerm2](http://iterm2.com/) instead of the default Mac terminal. The iTerm2 terminal has much better mouse support as well as better handling of key events. For best keybinding behavior, choose `xterm defaults` under `Preferences->Profiles->Keys->Load Preset`. The newest versions also support true color.
By default, the micro binary will dynamically link with core system libraries (this is generally
recommended for security and portability). However, there is a fully static prebuilt binary that
is provided for amd64 as `linux-static.tar.gz`, and to build a fully static binary from source, run
```
CGO_ENABLED=0 make build
```
### macOS terminal
If you are using macOS, you should consider using [iTerm2](http://iterm2.com/) instead of the default terminal (Terminal.app). The iTerm2 terminal has much better mouse support as well as better handling of key events. For best keybinding behavior, choose `xterm defaults` under `Preferences->Profiles->Keys->Presets...`, and select `Esc+` for `Left Option Key` in the same menu. The newest versions also support true color.
If you still insist on using the default Mac terminal, be sure to set `Use Option key as Meta key` under
`Preferences->Profiles->Keyboard` to use <kbd>option</kbd> as <kbd>alt</kbd>.
### Linux clipboard support
On Linux, clipboard support requires the 'xclip' or 'xsel' commands to be installed.
On Linux, clipboard support requires:
For Ubuntu:
- On X11, the `xclip` or `xsel` commands (for Ubuntu: `sudo apt install xclip`)
- On Wayland, the `wl-clipboard` command
```sh
sudo apt-get install xclip
```
If you don't have xclip or xsel, micro will use an internal clipboard for copy and paste, but it won't work with external applications.
If you don't have these commands, micro will use an internal clipboard for copy and paste, but it won't work with external applications.
### Colors and syntax highlighting
If you open micro and it doesn't seem like syntax highlighting is working, this is probably because
you are using a terminal which does not support 256 color. Try changing the colorscheme to `simple`
by pressing CtrlE in micro and typing `set colorscheme simple`.
you are using a terminal which does not support 256 color mode. Try changing the color scheme to `simple`
by pressing <kbd>Ctrl-e</kbd> in micro and typing `set colorscheme simple`.
If you are using the default Ubuntu terminal, to enable 256 make sure your `TERM` variable is set
to `xterm-256color`.
Many of the Windows terminals don't support more than 16 colors, which means
that micro's default colorscheme won't look very good. You can either set
the colorscheme to `simple`, or download a better terminal emulator, like
mintty.
that micro's default color scheme won't look very good. You can either set
the color scheme to `simple`, or download and configure a better terminal emulator
than the Windows default.
### Plan9, Cygwin
### Cygwin, Mingw, Plan9
Please note that micro uses the amazing [tcell library](https://github.com/gdamore/tcell), but this
Cygwin, Mingw, and Plan9 are unfortunately not officially supported. In Cygwin and Mingw, micro will often work when run using
the `winpty` utility:
```
winpty micro.exe ...
```
Micro uses the amazing [tcell library](https://github.com/gdamore/tcell), but this
means that micro is restricted to the platforms tcell supports. As a result, micro does not support
Plan9, and Cygwin (although this may change in the future). Micro also doesn't support NaCl (but NaCl is deprecated anyways).
Plan9, and Cygwin (although this may change in the future). Micro also doesn't support NaCl (which is deprecated anyway).
# Usage
## Usage
Once you have built the editor, simply start it by running `micro path/to/file.txt` or simply `micro` to open an empty buffer.
Once you have built the editor, start it by running `micro path/to/file.txt` or `micro` to open an empty buffer.
Micro also supports creating buffers from `stdin`:
micro also supports creating buffers from `stdin`:
```sh
ifconfig | micro
@@ -200,22 +230,22 @@ You can also use the mouse to manipulate the text. Simply clicking and dragging
will select text. You can also double click to enable word selection, and triple
click to enable line selection.
# Documentation and Help
## Documentation and Help
Micro has a built-in help system which you can access by pressing `Ctrl-E` and typing `help`. Additionally, you can
micro has a built-in help system which you can access by pressing <kbd>Ctrl-e</kbd> and typing `help`. Additionally, you can
view the help files here:
* [main help](https://github.com/zyedidia/micro/tree/master/runtime/help/help.md)
* [keybindings](https://github.com/zyedidia/micro/tree/master/runtime/help/keybindings.md)
* [commands](https://github.com/zyedidia/micro/tree/master/runtime/help/commands.md)
* [colors](https://github.com/zyedidia/micro/tree/master/runtime/help/colors.md)
* [options](https://github.com/zyedidia/micro/tree/master/runtime/help/options.md)
* [plugins](https://github.com/zyedidia/micro/tree/master/runtime/help/plugins.md)
- [main help](https://github.com/zyedidia/micro/tree/master/runtime/help/help.md)
- [keybindings](https://github.com/zyedidia/micro/tree/master/runtime/help/keybindings.md)
- [commands](https://github.com/zyedidia/micro/tree/master/runtime/help/commands.md)
- [colors](https://github.com/zyedidia/micro/tree/master/runtime/help/colors.md)
- [options](https://github.com/zyedidia/micro/tree/master/runtime/help/options.md)
- [plugins](https://github.com/zyedidia/micro/tree/master/runtime/help/plugins.md)
I also recommend reading the [tutorial](https://github.com/zyedidia/micro/tree/master/runtime/help/tutorial.md) for
a brief introduction to the more powerful configuration features micro offers.
# Contributing
## Contributing
If you find any bugs, please report them! I am also happy to accept pull requests from anyone.
@@ -223,3 +253,5 @@ You can use the [GitHub issue tracker](https://github.com/zyedidia/micro/issues)
to report bugs, ask questions, or suggest new features.
For a more informal setting to discuss the editor, you can join the [Gitter chat](https://gitter.im/zyedidia/micro).
Sometimes I am unresponsive, and I apologize! If that happens, please ping me.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

View File

@@ -1,63 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
id="svg3336"
version="1.1"
inkscape:version="0.91 r13725"
width="128"
height="128"
viewBox="0 0 128 128"
sodipodi:docname="logo.svg">
<metadata
id="metadata3342">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<defs
id="defs3340" />
<sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="1355"
inkscape:window-height="717"
id="namedview3338"
showgrid="false"
inkscape:zoom="1.6243169"
inkscape:cx="111.32302"
inkscape:cy="30.538264"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="svg3336" />
<path
style="fill:#2e3192;fill-opacity:1"
d="m 56.1,127.32358 c -13.68932,-1.70993 -27.156628,-8.3544 -37.112903,-18.31068 -25.0687936,-25.068788 -25.0687936,-65.95701 0,-91.025803 25.068793,-25.0687936 65.957015,-25.0687936 91.025803,0 25.0688,25.068793 25.0688,65.957015 0,91.025803 C 95.87457,123.15123 76.198116,129.83404 56.1,127.32358 Z"
id="path3364"
inkscape:connector-curvature="0" />
<path
style="fill:#ffffff"
d="m 40.756452,106.01908 c 1.442831,-1.83426 1.55476,-4.09687 0.414499,-8.37899 -0.678184,-2.546844 -0.684604,-4.05591 -0.03829,-9 1.276867,-9.767604 4.483143,-23.040636 5.565559,-23.039766 0.220979,1.74e-4 0.417725,2.092674 0.437213,4.65 0.04167,5.468298 1.558564,9.06891 4.638769,11.010942 2.551646,1.608774 9.15365,1.329324 12.80399,-0.541974 3.245124,-1.663572 7.649064,-6.112434 9.850956,-9.951438 L 76.188736,67.7 l 0.0054,3.922866 c 0.0042,2.867148 0.36894,4.642788 1.355628,6.59796 1.532058,3.035856 3.323226,4.15755 6.659322,4.17033 5.192928,0.01986 9.07014,-3.668676 10.866768,-10.338036 0.98277,-3.64821 1.064448,-11.21265 0.09235,-8.55312 -3.025218,8.276592 -4.468212,9.893562 -9.238056,10.351884 -2.629152,0.25263 -3.177804,0.08883 -4.921776,-1.469412 -1.609044,-1.437678 -2.016072,-2.308416 -2.258508,-4.8315 -0.262884,-2.73585 0.105942,-4.06497 3.32007,-11.964365 C 88.28388,40.315087 89.33625,35.536248 87,33.2 c -1.559352,-1.559353 -3.62787,-1.522741 -5.691792,0.10074 -2.295762,1.805846 -3.105984,4.070756 -5.14293,14.376662 -2.464164,12.46744 -6.525822,20.297092 -12.62193,24.331306 C 59.052142,74.98085 52.704914,73.6403 50.637191,69.282896 49.19967,66.253544 49.857706,62.552972 53.387813,53.814319 56.613526,45.829186 58.8,38.711369 58.8,36.195564 c 0,-4.161283 -4.366993,-5.665719 -7.364438,-2.537061 -2.183558,2.279144 -3.117251,5.256959 -4.280897,13.653016 -0.547956,3.953665 -1.259292,9.010489 -1.580746,11.237387 -0.321454,2.226896 -2.083918,8.706896 -3.916587,14.400002 -4.33165,13.456074 -6.85029,23.184822 -7.273674,28.096022 -0.325586,3.77675 -0.269352,4.00056 1.319044,5.25 2.187498,1.72068 3.541408,1.64679 5.05375,-0.27585 z"
id="path3362"
inkscape:connector-curvature="0" />
</svg>

Before

Width:  |  Height:  |  Size: 3.6 KiB

70
assets/micro-logo.svg Normal file
View File

@@ -0,0 +1,70 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Generator: Adobe Illustrator 18.1.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
version="1.1"
id="Layer_1"
x="0px"
y="0px"
viewBox="0 0 299.89999 103.2"
enable-background="new 0 0 960 560"
xml:space="preserve"
inkscape:version="0.91 r13725"
sodipodi:docname="micro-logo.svg"
width="299.89999"
height="103.2"><metadata
id="metadata21"><rdf:RDF><cc:Work
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title></dc:title></cc:Work></rdf:RDF></metadata><defs
id="defs19" /><sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="1237"
inkscape:window-height="867"
id="namedview17"
showgrid="false"
fit-margin-top="0"
fit-margin-left="0"
fit-margin-right="0"
fit-margin-bottom="0"
inkscape:zoom="1.1416667"
inkscape:cx="75.655934"
inkscape:cy="-4"
inkscape:window-x="1097"
inkscape:window-y="185"
inkscape:window-maximized="0"
inkscape:current-layer="Layer_1" /><g
id="g3"
transform="translate(-178,-172.8)"><path
d="m 306.8,213.8 0,-2.6 c 1.6,-0.1 2.9,-0.4 4.1,-0.8 1.2,-0.4 2.5,-1 4,-1.8 l 2.3,0 0,5.2 c 2.4,-1.9 4.2,-3.1 5.5,-3.8 2,-1 4,-1.5 5.8,-1.5 1.3,0 2.5,0.2 3.7,0.7 1.2,0.5 2.2,1 2.9,1.7 0.7,0.7 1.4,1.6 1.9,2.8 2.2,-1.9 4.2,-3.3 6,-4 1.9,-0.8 3.7,-1.2 5.6,-1.2 1.8,0 3.4,0.4 4.8,1.1 1.4,0.8 2.4,1.7 3,2.8 0.6,1.1 0.9,2.8 0.9,5 l 0,14.4 c 0,1.5 0,2.4 0.1,2.6 0.1,0.4 0.3,0.8 0.7,1.1 0.3,0.4 0.7,0.6 1.2,0.7 0.4,0.1 1.2,0.2 2.4,0.2 l 1,0 0,2.6 -15.5,0 0,-2.6 c 1.8,0 2.9,-0.1 3.5,-0.4 0.5,-0.2 0.9,-0.6 1.2,-1.2 0.3,-0.6 0.4,-1.6 0.4,-3.2 l 0,-13.7 c 0,-1.7 -0.2,-2.9 -0.5,-3.6 -0.3,-0.7 -0.9,-1.2 -1.7,-1.7 -0.8,-0.4 -1.8,-0.7 -3,-0.7 -1.5,0 -3,0.4 -4.6,1.2 -2.2,1.1 -3.9,2.3 -5.1,3.6 l 0,14.8 c 0,1.4 0.1,2.4 0.2,2.8 0.1,0.4 0.4,0.8 0.7,1.1 0.3,0.3 0.7,0.5 1.1,0.6 0.4,0.1 1.5,0.2 3.1,0.2 l 0,2.6 -15.3,0 0,-2.6 0.9,0 c 1.2,0 2.1,-0.1 2.6,-0.4 0.5,-0.3 0.9,-0.7 1.2,-1.3 0.2,-0.5 0.3,-1.5 0.3,-2.9 l 0,-13.2 c 0,-1.9 -0.2,-3.3 -0.5,-3.9 -0.3,-0.7 -0.9,-1.3 -1.7,-1.7 -0.8,-0.5 -1.8,-0.7 -3,-0.7 -1.3,0 -2.7,0.3 -4.1,1 -2,1 -3.9,2.2 -5.6,3.8 l 0,15.9 c 0,1 0.1,1.6 0.4,2.1 0.3,0.4 0.7,0.8 1.2,1.1 0.6,0.3 1.3,0.4 2.3,0.4 l 1.1,0 0,2.6 -15.6,0 0,-2.6 0.8,0 c 1.4,0 2.4,-0.1 2.8,-0.3 0.7,-0.3 1.1,-0.8 1.4,-1.5 0.2,-0.4 0.2,-1.3 0.2,-2.9 l 0,-18.1 -5.1,0 z"
id="path5"
inkscape:connector-curvature="0" /><path
d="m 366.4,213.7 0,-2.6 c 1.7,-0.2 3.2,-0.5 4.3,-0.9 1.2,-0.4 2.5,-1 4,-1.7 l 2.3,0 0,24.9 c 0,0.9 0.1,1.5 0.4,2 0.2,0.4 0.6,0.8 1,0.9 0.4,0.2 1.3,0.3 2.4,0.3 l 1.5,0 0,2.6 -15.9,0 0,-2.6 1.3,0 c 1.4,0 2.3,-0.1 2.8,-0.4 0.5,-0.2 0.8,-0.6 1,-1.1 0.2,-0.5 0.3,-1.5 0.3,-3.2 l 0,-18.3 -5.4,0 z m 7.9,-19.2 c 1,0 1.8,0.3 2.5,1 0.7,0.7 1.1,1.5 1.1,2.5 0,1 -0.4,1.8 -1.1,2.5 -0.7,0.7 -1.6,1.1 -2.5,1.1 -1,0 -1.8,-0.4 -2.5,-1.1 -0.7,-0.7 -1.1,-1.6 -1.1,-2.5 0,-1 0.4,-1.8 1.1,-2.5 0.6,-0.6 1.5,-1 2.5,-1 z"
id="path7"
inkscape:connector-curvature="0" /><path
d="m 413.1,230.6 2,1.6 c -3.9,5.2 -8.6,7.8 -14,7.8 -4.2,0 -7.8,-1.5 -10.7,-4.5 -2.9,-3 -4.4,-6.8 -4.4,-11.3 0,-3 0.7,-5.7 2,-8.1 1.3,-2.4 3.2,-4.2 5.6,-5.6 2.4,-1.3 5.2,-2 8.3,-2 3.6,0 6.5,0.9 8.9,2.6 2.4,1.7 3.6,3.5 3.6,5.3 0,1 -0.3,1.7 -0.8,2.2 -0.5,0.5 -1.2,0.8 -1.9,0.8 -0.4,0 -0.7,-0.1 -1.1,-0.3 -0.4,-0.2 -0.7,-0.5 -1.1,-0.9 -0.2,-0.2 -0.5,-0.8 -0.9,-1.7 -0.6,-1.2 -1,-2 -1.3,-2.4 -0.6,-0.8 -1.4,-1.5 -2.4,-2 -0.9,-0.5 -2,-0.7 -3.1,-0.7 -1.8,0 -3.4,0.5 -4.9,1.5 -1.5,1 -2.7,2.4 -3.6,4.3 -0.9,1.9 -1.3,4.2 -1.3,6.8 0,4.1 1.1,7.3 3.3,9.7 1.9,2.1 4.1,3.1 6.7,3.1 1.2,0 2.4,-0.2 3.6,-0.6 1.2,-0.4 2.4,-1 3.5,-1.8 0.9,-0.6 2.2,-1.8 4,-3.8 z"
id="path9"
inkscape:connector-curvature="0" /><path
d="m 418.7,213.7 0,-2.6 c 1.5,-0.1 2.8,-0.4 4,-0.8 1.2,-0.4 2.5,-1 4,-1.9 l 2.3,0 0,5.9 c 1.5,-1.8 3.2,-3.2 5.1,-4.3 1.9,-1.1 3.7,-1.6 5.2,-1.6 1.5,0 2.7,0.4 3.6,1.1 0.9,0.7 1.3,1.6 1.3,2.6 0,0.7 -0.3,1.4 -0.9,2 -0.6,0.6 -1.3,0.9 -2.1,0.9 -0.4,0 -0.7,-0.1 -1,-0.2 -0.3,-0.1 -0.7,-0.3 -1.2,-0.7 -1.1,-0.7 -2.1,-1.1 -2.9,-1.1 -1,0 -2.2,0.4 -3.4,1.3 -1.6,1.1 -2.8,2.2 -3.7,3.3 l 0,14.4 c 0,1.2 0.1,2.1 0.2,2.5 0.1,0.4 0.4,0.8 0.7,1.1 0.3,0.3 0.7,0.6 1.1,0.7 0.5,0.1 1.3,0.2 2.4,0.2 l 1,0 0,2.6 -16,0 0,-2.6 1.3,0 c 1.3,0 2.1,-0.1 2.5,-0.3 0.5,-0.3 0.9,-0.7 1.2,-1.3 0.3,-0.5 0.4,-1.5 0.4,-3 l 0,-18.3 -5.1,0 z"
id="path11"
inkscape:connector-curvature="0" /><path
d="m 462.8,208.5 c 3,0 5.7,0.6 7.9,1.9 2.2,1.3 4,3.1 5.3,5.5 1.3,2.4 1.9,5.2 1.9,8.3 0,3.1 -0.7,5.9 -2,8.3 -1.3,2.4 -3.1,4.3 -5.4,5.5 -2.3,1.3 -5,1.9 -8.1,1.9 -5,0 -8.8,-1.6 -11.3,-4.7 -2.5,-3.1 -3.8,-6.8 -3.8,-11 0,-3.1 0.7,-5.8 2,-8.2 1.3,-2.4 3.1,-4.2 5.5,-5.6 2.4,-1.2 5.1,-1.9 8,-1.9 z m -0.2,3 c -2.4,0 -4.4,0.9 -6,2.8 -2.1,2.3 -3.1,5.7 -3.1,10.1 0,4.3 0.9,7.5 2.6,9.7 1.6,2 3.8,3 6.5,3 1.8,0 3.3,-0.5 4.7,-1.4 1.4,-0.9 2.5,-2.4 3.3,-4.5 0.8,-2 1.3,-4.4 1.3,-7.2 0,-2.7 -0.5,-5.1 -1.4,-7.2 -0.7,-1.7 -1.8,-3 -3.2,-4 -1.3,-0.8 -2.9,-1.3 -4.7,-1.3 z"
id="path13"
inkscape:connector-curvature="0" /></g><path
d="M 51.6,0 C 23.1,0 0,23.1 0,51.6 c 0,28.5 23.1,51.6 51.6,51.6 28.5,0 51.6,-23.1 51.6,-51.6 C 103.2,23.1 80.1,0 51.6,0 Z m 24.5,58.6 c -0.5,2 -1.3,3.6 -2.4,4.9 -1,1.3 -2,2.1 -3.1,2.5 -1.1,0.4 -2.2,0.6 -3.4,0.6 -1.2,0 -2.2,-0.2 -3,-0.7 C 63.4,65.5 62.8,64.8 62.3,64 61.8,63.2 61.5,62.2 61.3,61.1 61.1,60 61,58.8 61,57.5 c 0,-0.5 0,-1 0.1,-1.7 0.1,-0.7 0.2,-1.6 0.3,-1.6 l -0.2,0 c -1.6,4 -3.8,6.9 -6.6,9.2 -2.8,2.3 -5.9,3.4 -9.3,3.4 -2.3,0 -4.2,-0.9 -5.5,-2.6 -1.4,-1.7 -2.1,-4.3 -2.1,-7.7 0,-0.5 0,-1 0.1,-1.6 0.1,-0.5 0.1,-0.7 0.2,-1.7 l -0.7,0 c -0.9,2 -1.7,4.8 -2.3,7.3 -0.6,2.5 -1.1,4.8 -1.4,6.9 -0.4,2.1 -0.6,4 -0.8,5.6 -0.2,1.6 -0.3,2.7 -0.4,3.3 0.1,0.5 0.2,1 0.3,1.6 0.2,0.6 0.3,1.2 0.5,1.7 0.2,0.5 0.3,1.1 0.4,1.6 0.1,0.5 0.2,0.9 0.2,1.2 0,1.4 -0.3,2.5 -0.9,3.2 -0.6,0.7 -1.3,1.1 -2,1.1 -0.9,0 -1.7,-0.3 -2.3,-0.8 -0.7,-0.6 -1,-1.5 -1,-2.7 0,-1.7 0.3,-3.9 0.9,-6.5 0.6,-2.6 1.5,-5.9 2.6,-9.8 0.6,-1.8 1.1,-3.6 1.7,-5.4 0.6,-1.8 1.1,-3.5 1.6,-5 0.5,-1.5 0.9,-2.9 1.3,-4.1 0.4,-1.2 0.6,-2.1 0.7,-2.8 0.1,-0.3 0.2,-1 0.3,-2 0.1,-1 0.2,-2.1 0.4,-3.4 0.2,-1.3 0.3,-2.7 0.5,-4.1 0.2,-1.5 0.4,-2.8 0.5,-4 0.2,-0.9 0.3,-1.9 0.5,-3 0.2,-1.1 0.5,-2.2 0.9,-3.1 0.4,-1 1,-1.8 1.7,-2.5 0.7,-0.7 1.6,-1 2.7,-1 1.2,0 2,0.4 2.4,1.1 0.4,0.7 0.6,1.6 0.5,2.6 -0.1,1 -0.2,2.1 -0.5,3.2 -0.3,1.1 -0.6,2.1 -0.9,2.9 -0.8,2.5 -1.6,4.8 -2.5,6.7 -0.9,1.9 -1.7,4 -2.4,6.2 -0.6,1.5 -0.8,2.9 -0.8,4.1 0,2.2 0.7,3.8 2,5 1.4,1.2 3,1.7 4.9,1.7 1.5,0 3,-0.5 4.4,-1.6 1.4,-1.1 2.7,-2.4 3.9,-3.9 1.2,-1.5 2.2,-3.1 3,-4.9 0.8,-1.7 1.4,-3.3 1.8,-4.6 0.1,-0.2 0.2,-0.6 0.3,-1.4 0.2,-0.8 0.3,-1.7 0.5,-2.7 0.2,-1 0.4,-2 0.6,-3.1 0.2,-1.1 0.4,-2 0.5,-2.7 0.2,-0.8 0.3,-1.6 0.5,-2.6 0.2,-1 0.5,-1.9 0.9,-2.8 0.4,-0.9 1,-1.6 1.6,-2.2 0.7,-0.6 1.5,-0.9 2.6,-0.9 1.3,0 2.1,0.4 2.6,1.1 0.4,0.7 0.6,1.6 0.6,2.6 -0.1,1 -0.2,2 -0.5,3 -0.3,1 -0.5,1.8 -0.7,2.4 -0.8,2.5 -1.6,4.7 -2.4,6.7 -0.8,2 -1.5,3.8 -2.2,5.2 -0.6,1.5 -1.1,2.6 -1.5,3.5 -0.4,0.9 -0.6,1.5 -0.6,1.8 0,2.6 0.6,4.5 1.7,5.6 1.1,1.1 2.3,1.7 3.6,1.7 2.2,0 3.9,-0.7 5.2,-2 1.3,-1.4 2.3,-3.9 2.9,-6.9 l 0.8,0 c 0.2,2.9 -0.1,5.3 -0.6,7.3 z"
id="path15"
inkscape:connector-curvature="0"
style="fill:#2e3192" /></svg>

After

Width:  |  Height:  |  Size: 7.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 68 KiB

After

Width:  |  Height:  |  Size: 253 KiB

View File

@@ -1,11 +1,4 @@
.\" micro manual page - micro(1)
.\"
.\" Copyright © 2017 Zachary Yedidia <zyedidia@gmail.com>
.\" Copyright © 2017 Collin Warren <anatoly@somethinghub.com>
.\"
.\" This document is provided under the same licensing as micro.
.\" See \usr\share\doc\micro\LICENSE for more information.
.TH micro 1 "2017-03-28"
.TH micro 1 "2020-02-10"
.SH NAME
micro \- A modern and intuitive terminal-based text editor
.SH SYNOPSIS
@@ -22,39 +15,96 @@ As the name indicates, micro aims to be somewhat of a successor to the nano edit
enjoyable to use full time, whether you work in the terminal because you prefer it (like me), or because you need to (over ssh).
.SH OPTIONS
.PP
\-clean
.RS 4
Cleans the configuration directory
.RE
.PP
\-config-dir dir
.RS 4
Specify a custom location for the configuration directory
.RE
.PP
\-startpos LINE,COL
[FILE]:LINE:COL
.RS 4
Specify a line and column to start the cursor at when opening a buffer
.RE
.PP
\-options
.RS 4
Show all option help
.RE
.PP
\-debug
.RS 4
Enable debug mode (enables logging to ./log.txt)
.RE
.PP
\-version
.RS 4
Show the version number and information
.RE
Micro's plugin's can be managed at the command line with the following commands.
.RS 4
.PP
\-plugin remove [PLUGIN]...
.RS 4
Remove plugin(s)
.RE
.PP
\-plugin update [PLUGIN]...
.RS 4
Update plugin(s) (if no argument is given, updates all plugins)
.RE
.PP
\-plugin search [PLUGIN]...
.RS 4
Search for a plugin
.RE
.PP
\-plugin list
.RS 4
List installed plugins
.RE
.PP
\-plugin available
.RS 4
List available plugins
.RE
.RE
Micro's options can also be set via command line arguments for quick
adjustments. For real configuration, please use the settings.json
file (see 'help options').
.RS 4
.PP
\-option value
.RS 4
Set `option` to `value` for this session
For example: `micro -syntax off file.c`
.RE
.SH CONFIGURATION
Micro uses
\fI$XDG_CONFIG_HOME/micro\fR
for configuration by default. If it is not set, micro uses ~/.config/micro.
Two main configuration files are settings.json, containing the user's
settings, and bindings.json, containing the user's custom keybindings.
.SH ENVIRONMENT
Micro's behaviour can be changed by setting environment variables, of which there is currently only one:
\fIMICRO_TRUECOLOR\fR.
When MICRO_TRUECOLOR is set to 1, micro will attempt to treat your terminal as a true-color terminal and will be able to make full use of the true-color colorschemes that are included with micro. If MICRO_TRUECOLOR is not set or is set to 0, then micro will only make use of 256 color features and will internally map true-color colorschemes to the nearest colors available. For more information see micro's documentation.
Micro uses $MICRO_CONFIG_HOME as the configuration directory.
If this environment variable is not set, it uses $XDG_CONFIG_HOME/micro instead.
If that environment variable is not set, it uses ~/.config/micro as the configuration directory.
In the documentation, we use ~/.config/micro to refer to the configuration directory
(even if it may in fact be somewhere else if you have set either of the above environment variables).
.SH NOTICE
This manpage is intended only to serve as a quick guide to the invocation of
@@ -69,5 +119,5 @@ and to report any newly encountered bugs you may find. We strive to correct
bugs as swiftly as possible.
.SH COPYRIGHT
Copyright \(co 2017 Zachary Yedidia, Collin Warren, et al.
Copyright \(co 2020 Zachary Yedidia, et al.
See /usr/share/doc/micro/LICENSE and /usr/share/doc/micro/AUTHORS for more information.

152
cmd/micro/clean.go Normal file
View File

@@ -0,0 +1,152 @@
package main
import (
"bufio"
"encoding/gob"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"sort"
"strings"
"github.com/zyedidia/micro/v2/internal/buffer"
"github.com/zyedidia/micro/v2/internal/config"
)
func shouldContinue() bool {
reader := bufio.NewReader(os.Stdin)
fmt.Print("Continue [Y/n]: ")
text, err := reader.ReadString('\n')
if err != nil {
fmt.Println(err)
return false
}
if len(text) <= 1 {
// default continue
return true
}
return strings.ToLower(text)[0] == 'y'
}
// CleanConfig performs cleanup in the user's configuration directory
func CleanConfig() {
fmt.Println("Cleaning your configuration directory at", config.ConfigDir)
fmt.Printf("Please consider backing up %s before continuing\n", config.ConfigDir)
if !shouldContinue() {
fmt.Println("Stopping early")
return
}
fmt.Println("Cleaning default settings")
config.WriteSettings(filepath.Join(config.ConfigDir, "settings.json"))
// detect unused options
var unusedOptions []string
defaultSettings := config.DefaultAllSettings()
for k := range config.GlobalSettings {
if _, ok := defaultSettings[k]; !ok {
valid := false
for _, p := range config.Plugins {
if strings.HasPrefix(k, p.Name+".") || k == p.Name {
valid = true
}
}
if !valid {
unusedOptions = append(unusedOptions, k)
}
}
}
if len(unusedOptions) > 0 {
fmt.Println("The following options are unused:")
sort.Strings(unusedOptions)
for _, s := range unusedOptions {
fmt.Printf("%s (value: %v)\n", s, config.GlobalSettings[s])
}
fmt.Printf("These options will be removed from %s\n", filepath.Join(config.ConfigDir, "settings.json"))
if shouldContinue() {
for _, s := range unusedOptions {
delete(config.GlobalSettings, s)
}
err := config.OverwriteSettings(filepath.Join(config.ConfigDir, "settings.json"))
if err != nil {
fmt.Println("Error writing settings.json file: " + err.Error())
}
fmt.Println("Removed unused options")
fmt.Print("\n\n")
}
}
// detect incorrectly formatted buffer/ files
files, err := ioutil.ReadDir(filepath.Join(config.ConfigDir, "buffers"))
if err == nil {
var badFiles []string
var buffer buffer.SerializedBuffer
for _, f := range files {
fname := filepath.Join(config.ConfigDir, "buffers", f.Name())
file, e := os.Open(fname)
if e == nil {
defer file.Close()
decoder := gob.NewDecoder(file)
err = decoder.Decode(&buffer)
if err != nil && f.Name() != "history" {
badFiles = append(badFiles, fname)
}
}
}
if len(badFiles) > 0 {
fmt.Printf("Detected %d files with an invalid format in %s\n", len(badFiles), filepath.Join(config.ConfigDir, "buffers"))
fmt.Println("These files store cursor and undo history.")
fmt.Printf("Removing badly formatted files in %s\n", filepath.Join(config.ConfigDir, "buffers"))
if shouldContinue() {
removed := 0
for _, f := range badFiles {
err := os.Remove(f)
if err != nil {
fmt.Println(err)
continue
}
removed++
}
if removed == 0 {
fmt.Println("Failed to remove files")
} else {
fmt.Printf("Removed %d badly formatted files\n", removed)
}
fmt.Print("\n\n")
}
}
}
// detect plugins/ directory
plugins := filepath.Join(config.ConfigDir, "plugins")
if stat, err := os.Stat(plugins); err == nil && stat.IsDir() {
fmt.Printf("Found directory %s\n", plugins)
fmt.Printf("Plugins should now be stored in %s\n", filepath.Join(config.ConfigDir, "plug"))
fmt.Printf("Removing %s\n", plugins)
if shouldContinue() {
os.RemoveAll(plugins)
}
fmt.Print("\n\n")
}
fmt.Println("Done cleaning")
}

View File

@@ -4,7 +4,7 @@ import (
"log"
"os"
"github.com/zyedidia/micro/internal/util"
"github.com/zyedidia/micro/v2/internal/util"
)
// NullWriter simply sends writes into the void

View File

@@ -6,14 +6,14 @@ import (
lua "github.com/yuin/gopher-lua"
luar "layeh.com/gopher-luar"
"github.com/zyedidia/micro/internal/action"
"github.com/zyedidia/micro/internal/buffer"
"github.com/zyedidia/micro/internal/config"
"github.com/zyedidia/micro/internal/display"
ulua "github.com/zyedidia/micro/internal/lua"
"github.com/zyedidia/micro/internal/screen"
"github.com/zyedidia/micro/internal/shell"
"github.com/zyedidia/micro/internal/util"
"github.com/zyedidia/micro/v2/internal/action"
"github.com/zyedidia/micro/v2/internal/buffer"
"github.com/zyedidia/micro/v2/internal/config"
"github.com/zyedidia/micro/v2/internal/display"
ulua "github.com/zyedidia/micro/v2/internal/lua"
"github.com/zyedidia/micro/v2/internal/screen"
"github.com/zyedidia/micro/v2/internal/shell"
"github.com/zyedidia/micro/v2/internal/util"
)
func init() {
@@ -21,6 +21,7 @@ func init() {
ulua.L.SetGlobal("import", luar.New(ulua.L, LuaImport))
}
// LuaImport is meant to be called from lua by a plugin and will import the given micro package
func LuaImport(pkg string) *lua.LTable {
switch pkg {
case "micro":
@@ -46,6 +47,15 @@ func luaImportMicro() *lua.LTable {
ulua.L.SetField(pkg, "InfoBar", luar.New(ulua.L, action.GetInfoBar))
ulua.L.SetField(pkg, "Log", luar.New(ulua.L, log.Println))
ulua.L.SetField(pkg, "SetStatusInfoFn", luar.New(ulua.L, display.SetStatusInfoFnLua))
ulua.L.SetField(pkg, "CurPane", luar.New(ulua.L, func() action.Pane {
return action.MainTab().CurPane()
}))
ulua.L.SetField(pkg, "CurTab", luar.New(ulua.L, func() *action.Tab {
return action.MainTab()
}))
ulua.L.SetField(pkg, "Tabs", luar.New(ulua.L, func() *action.TabList {
return action.Tabs
}))
return pkg
}
@@ -53,7 +63,7 @@ func luaImportMicro() *lua.LTable {
func luaImportMicroConfig() *lua.LTable {
pkg := ulua.L.NewTable()
ulua.L.SetField(pkg, "MakeCommand", luar.New(ulua.L, action.LuaMakeCommand))
ulua.L.SetField(pkg, "MakeCommand", luar.New(ulua.L, action.MakeCommand))
ulua.L.SetField(pkg, "FileComplete", luar.New(ulua.L, buffer.FileComplete))
ulua.L.SetField(pkg, "HelpComplete", luar.New(ulua.L, action.HelpComplete))
ulua.L.SetField(pkg, "OptionComplete", luar.New(ulua.L, action.OptionComplete))
@@ -62,16 +72,21 @@ func luaImportMicroConfig() *lua.LTable {
ulua.L.SetField(pkg, "TryBindKey", luar.New(ulua.L, action.TryBindKey))
ulua.L.SetField(pkg, "Reload", luar.New(ulua.L, action.ReloadConfig))
ulua.L.SetField(pkg, "AddRuntimeFileFromMemory", luar.New(ulua.L, config.PluginAddRuntimeFileFromMemory))
ulua.L.SetField(pkg, "AddRuntimeFilesFromDirectory", luar.New(ulua.L, config.PluginAddRuntimeFileFromMemory))
ulua.L.SetField(pkg, "AddRuntimeFilesFromDirectory", luar.New(ulua.L, config.PluginAddRuntimeFilesFromDirectory))
ulua.L.SetField(pkg, "AddRuntimeFile", luar.New(ulua.L, config.PluginAddRuntimeFile))
ulua.L.SetField(pkg, "ListRuntimeFiles", luar.New(ulua.L, config.PluginListRuntimeFiles))
ulua.L.SetField(pkg, "ReadRuntimeFile", luar.New(ulua.L, config.PluginReadRuntimeFile))
ulua.L.SetField(pkg, "NewRTFiletype", luar.New(ulua.L, config.NewRTFiletype))
ulua.L.SetField(pkg, "RTColorscheme", luar.New(ulua.L, config.RTColorscheme))
ulua.L.SetField(pkg, "RTSyntax", luar.New(ulua.L, config.RTSyntax))
ulua.L.SetField(pkg, "RTHelp", luar.New(ulua.L, config.RTHelp))
ulua.L.SetField(pkg, "RTPlugin", luar.New(ulua.L, config.RTPlugin))
ulua.L.SetField(pkg, "RegisterCommonOption", luar.New(ulua.L, config.RegisterCommonOption))
ulua.L.SetField(pkg, "RegisterGlobalOption", luar.New(ulua.L, config.RegisterGlobalOption))
ulua.L.SetField(pkg, "RegisterCommonOption", luar.New(ulua.L, config.RegisterCommonOptionPlug))
ulua.L.SetField(pkg, "RegisterGlobalOption", luar.New(ulua.L, config.RegisterGlobalOptionPlug))
ulua.L.SetField(pkg, "GetGlobalOption", luar.New(ulua.L, config.GetGlobalOption))
ulua.L.SetField(pkg, "SetGlobalOption", luar.New(ulua.L, action.SetGlobalOption))
ulua.L.SetField(pkg, "SetGlobalOptionNative", luar.New(ulua.L, action.SetGlobalOptionNative))
ulua.L.SetField(pkg, "ConfigDir", luar.New(ulua.L, config.ConfigDir))
return pkg
}
@@ -110,10 +125,15 @@ func luaImportMicroBuffer() *lua.LTable {
ulua.L.SetField(pkg, "BTScratch", luar.New(ulua.L, buffer.BTScratch.Kind))
ulua.L.SetField(pkg, "BTRaw", luar.New(ulua.L, buffer.BTRaw.Kind))
ulua.L.SetField(pkg, "BTInfo", luar.New(ulua.L, buffer.BTInfo.Kind))
ulua.L.SetField(pkg, "NewBuffer", luar.New(ulua.L, func(text, path string) *buffer.Buffer {
return buffer.NewBufferFromString(text, path, buffer.BTDefault)
}))
ulua.L.SetField(pkg, "NewBufferFromFile", luar.New(ulua.L, func(path string) (*buffer.Buffer, error) {
return buffer.NewBufferFromFile(path, buffer.BTDefault)
}))
ulua.L.SetField(pkg, "ByteOffset", luar.New(ulua.L, buffer.ByteOffset))
ulua.L.SetField(pkg, "Log", luar.New(ulua.L, buffer.WriteLog))
ulua.L.SetField(pkg, "LogBuf", luar.New(ulua.L, buffer.GetLogBuf))
return pkg
}
@@ -124,6 +144,11 @@ func luaImportMicroUtil() *lua.LTable {
ulua.L.SetField(pkg, "RuneAt", luar.New(ulua.L, util.LuaRuneAt))
ulua.L.SetField(pkg, "GetLeadingWhitespace", luar.New(ulua.L, util.LuaGetLeadingWhitespace))
ulua.L.SetField(pkg, "IsWordChar", luar.New(ulua.L, util.LuaIsWordChar))
ulua.L.SetField(pkg, "String", luar.New(ulua.L, util.String))
ulua.L.SetField(pkg, "CharacterCountInString", luar.New(ulua.L, util.CharacterCountInString))
ulua.L.SetField(pkg, "RuneStr", luar.New(ulua.L, func(r rune) string {
return string(r)
}))
return pkg
}

View File

@@ -5,16 +5,21 @@ import (
"fmt"
"io/ioutil"
"os"
"regexp"
"runtime"
"sort"
"strconv"
"time"
"github.com/go-errors/errors"
isatty "github.com/mattn/go-isatty"
"github.com/zyedidia/micro/internal/action"
"github.com/zyedidia/micro/internal/buffer"
"github.com/zyedidia/micro/internal/config"
"github.com/zyedidia/micro/internal/screen"
"github.com/zyedidia/micro/internal/shell"
"github.com/zyedidia/micro/internal/util"
lua "github.com/yuin/gopher-lua"
"github.com/zyedidia/micro/v2/internal/action"
"github.com/zyedidia/micro/v2/internal/buffer"
"github.com/zyedidia/micro/v2/internal/config"
"github.com/zyedidia/micro/v2/internal/screen"
"github.com/zyedidia/micro/v2/internal/shell"
"github.com/zyedidia/micro/v2/internal/util"
"github.com/zyedidia/tcell"
)
@@ -27,22 +32,43 @@ var (
flagVersion = flag.Bool("version", false, "Show the version number and information")
flagConfigDir = flag.String("config-dir", "", "Specify a custom location for the configuration directory")
flagOptions = flag.Bool("options", false, "Show all option help")
flagDebug = flag.Bool("debug", false, "Enable debug mode (prints debug info to ./log.txt)")
flagPlugin = flag.String("plugin", "", "Plugin command")
flagClean = flag.Bool("clean", false, "Clean configuration directory")
optionFlags map[string]*string
)
func InitFlags() {
flag.Usage = func() {
fmt.Println("Usage: micro [OPTIONS] [FILE]...")
fmt.Println("-clean")
fmt.Println(" \tCleans the configuration directory")
fmt.Println("-config-dir dir")
fmt.Println(" \tSpecify a custom location for the configuration directory")
fmt.Println("[FILE]:LINE:COL")
fmt.Println("[FILE]:LINE:COL (if the `parsecursor` option is enabled)")
fmt.Println("+LINE:COL")
fmt.Println(" \tSpecify a line and column to start the cursor at when opening a buffer")
fmt.Println(" \tThis can also be done by opening file:LINE:COL")
fmt.Println("-options")
fmt.Println(" \tShow all option help")
fmt.Println("-debug")
fmt.Println(" \tEnable debug mode (enables logging to ./log.txt)")
fmt.Println("-version")
fmt.Println(" \tShow the version number and information")
fmt.Print("\nMicro's plugin's can be managed at the command line with the following commands.\n")
fmt.Println("-plugin install [PLUGIN]...")
fmt.Println(" \tInstall plugin(s)")
fmt.Println("-plugin remove [PLUGIN]...")
fmt.Println(" \tRemove plugin(s)")
fmt.Println("-plugin update [PLUGIN]...")
fmt.Println(" \tUpdate plugin(s) (if no argument is given, updates all plugins)")
fmt.Println("-plugin search [PLUGIN]...")
fmt.Println(" \tSearch for a plugin")
fmt.Println("-plugin list")
fmt.Println(" \tList installed plugins")
fmt.Println("-plugin available")
fmt.Println(" \tList available plugins")
fmt.Print("\nMicro's options can also be set via command line arguments for quick\nadjustments. For real configuration, please use the settings.json\nfile (see 'help options').\n\n")
fmt.Println("-option value")
fmt.Println(" \tSet `option` to `value` for this session")
@@ -81,11 +107,32 @@ func InitFlags() {
}
os.Exit(0)
}
if util.Debug == "OFF" && *flagDebug {
util.Debug = "ON"
}
}
// DoPluginFlags parses and executes any flags that require LoadAllPlugins (-plugin and -clean)
func DoPluginFlags() {
if *flagClean || *flagPlugin != "" {
config.LoadAllPlugins()
if *flagPlugin != "" {
args := flag.Args()
config.PluginCommand(os.Stdout, *flagPlugin, args)
} else if *flagClean {
CleanConfig()
}
os.Exit(0)
}
}
// LoadInput determines which files should be loaded into buffers
// based on the input stored in flag.Args()
func LoadInput() []*buffer.Buffer {
func LoadInput(args []string) []*buffer.Buffer {
// There are a number of ways micro should start given its input
// 1. If it is given a files in flag.Args(), it should open those
@@ -100,14 +147,47 @@ func LoadInput() []*buffer.Buffer {
var filename string
var input []byte
var err error
args := flag.Args()
buffers := make([]*buffer.Buffer, 0, len(args))
if len(args) > 0 {
btype := buffer.BTDefault
if !isatty.IsTerminal(os.Stdout.Fd()) {
btype = buffer.BTStdout
}
files := make([]string, 0, len(args))
flagStartPos := buffer.Loc{-1, -1}
flagr := regexp.MustCompile(`^\+(\d+)(?::(\d+))?$`)
for _, a := range args {
match := flagr.FindStringSubmatch(a)
if len(match) == 3 && match[2] != "" {
line, err := strconv.Atoi(match[1])
if err != nil {
screen.TermMessage(err)
continue
}
col, err := strconv.Atoi(match[2])
if err != nil {
screen.TermMessage(err)
continue
}
flagStartPos = buffer.Loc{col - 1, line - 1}
} else if len(match) == 3 && match[2] == "" {
line, err := strconv.Atoi(match[1])
if err != nil {
screen.TermMessage(err)
continue
}
flagStartPos = buffer.Loc{0, line - 1}
} else {
files = append(files, a)
}
}
if len(files) > 0 {
// Option 1
// We go through each file and load it
for i := 0; i < len(args); i++ {
buf, err := buffer.NewBufferFromFile(args[i], buffer.BTDefault)
for i := 0; i < len(files); i++ {
buf, err := buffer.NewBufferFromFileAtLoc(files[i], btype, flagStartPos)
if err != nil {
screen.TermMessage(err)
continue
@@ -124,17 +204,22 @@ func LoadInput() []*buffer.Buffer {
screen.TermMessage("Error reading from stdin: ", err)
input = []byte{}
}
buffers = append(buffers, buffer.NewBufferFromString(string(input), filename, buffer.BTDefault))
buffers = append(buffers, buffer.NewBufferFromStringAtLoc(string(input), filename, btype, flagStartPos))
} else {
// Option 3, just open an empty buffer
buffers = append(buffers, buffer.NewBufferFromString(string(input), filename, buffer.BTDefault))
buffers = append(buffers, buffer.NewBufferFromStringAtLoc(string(input), filename, btype, flagStartPos))
}
return buffers
}
func main() {
defer os.Exit(0)
defer func() {
if util.Stdout.Len() > 0 {
fmt.Fprint(os.Stdout, util.Stdout.String())
}
os.Exit(0)
}()
// runtime.SetCPUProfileRate(400)
// f, _ := os.Create("micro.prof")
@@ -143,10 +228,10 @@ func main() {
var err error
InitLog()
InitFlags()
InitLog()
err = config.InitConfigDir(*flagConfigDir)
if err != nil {
screen.TermMessage(err)
@@ -157,7 +242,10 @@ func main() {
if err != nil {
screen.TermMessage(err)
}
config.InitGlobalSettings()
err = config.InitGlobalSettings()
if err != nil {
screen.TermMessage(err)
}
// flag options
for k, v := range optionFlags {
@@ -171,6 +259,34 @@ func main() {
}
}
DoPluginFlags()
err = screen.Init()
if err != nil {
fmt.Println(err)
fmt.Println("Fatal: Micro could not initialize a Screen.")
os.Exit(1)
}
defer func() {
if err := recover(); err != nil {
screen.Screen.Fini()
fmt.Println("Micro encountered an error:", err)
// backup all open buffers
for _, b := range buffer.OpenBuffers {
b.Backup()
}
// Print the stack trace too
fmt.Print(errors.Wrap(err, 2).ErrorStack())
os.Exit(1)
}
}()
err = config.LoadAllPlugins()
if err != nil {
screen.TermMessage(err)
}
action.InitBindings()
action.InitCommands()
@@ -179,41 +295,27 @@ func main() {
screen.TermMessage(err)
}
err = config.LoadAllPlugins()
if err != nil {
screen.TermMessage(err)
args := flag.Args()
b := LoadInput(args)
if len(b) == 0 {
// No buffers to open
screen.Screen.Fini()
runtime.Goexit()
}
action.InitTabs(b)
action.InitGlobals()
err = config.RunPluginFn("init")
if err != nil {
screen.TermMessage(err)
}
screen.Init()
// If we have an error, we can exit cleanly and not completely
// mess up the terminal being worked in
// In other words we need to shut down tcell before the program crashes
defer func() {
if err := recover(); err != nil {
screen.Screen.Fini()
fmt.Println("Micro encountered an error:", err)
// backup all open buffers
for _, b := range buffer.OpenBuffers {
b.Backup(false)
}
// Print the stack trace too
fmt.Print(errors.Wrap(err, 2).ErrorStack())
os.Exit(1)
}
}()
b := LoadInput()
action.InitTabs(b)
action.InitGlobals()
events = make(chan tcell.Event)
// Here is the event loop which runs in a separate thread
go func() {
events = make(chan tcell.Event)
for {
screen.Lock()
e := screen.Screen.PollEvent()
@@ -224,38 +326,73 @@ func main() {
}
}()
// clear the drawchan so we don't redraw excessively
// if someone requested a redraw before we started displaying
for len(screen.DrawChan()) > 0 {
<-screen.DrawChan()
}
// wait for initial resize event
select {
case event := <-events:
action.Tabs.HandleEvent(event)
case <-time.After(10 * time.Millisecond):
// time out after 10ms
}
// Since this loop is very slow (waits for user input every time) it's
// okay to be inefficient and run it via a function every time
// We do this so we can recover from panics without crashing the editor
for {
// Display everything
screen.Screen.Fill(' ', config.DefStyle)
screen.Screen.HideCursor()
action.Tabs.Display()
for _, ep := range action.MainTab().Panes {
ep.Display()
}
action.MainTab().Display()
action.InfoBar.Display()
screen.Screen.Show()
var event tcell.Event
// Check for new events
select {
case f := <-shell.Jobs:
// If a new job has finished while running in the background we should execute the callback
f.Function(f.Output, f.Args...)
case <-config.Autosave:
for _, b := range buffer.OpenBuffers {
b.Save()
}
case <-shell.CloseTerms:
case event = <-events:
case <-screen.DrawChan:
}
if action.InfoBar.HasPrompt {
action.InfoBar.HandleEvent(event)
} else {
action.Tabs.HandleEvent(event)
}
DoEvent()
}
}
// DoEvent runs the main action loop of the editor
func DoEvent() {
var event tcell.Event
// recover from errors without crashing the editor
defer func() {
if err := recover(); err != nil {
if e, ok := err.(*lua.ApiError); ok {
screen.TermMessage("Lua API error:", e)
} else {
screen.TermMessage("Micro encountered an error:", errors.Wrap(err, 2).ErrorStack(), "\nIf you can reproduce this error, please report it at https://github.com/zyedidia/micro/issues")
}
}
}()
// Display everything
screen.Screen.Fill(' ', config.DefStyle)
screen.Screen.HideCursor()
action.Tabs.Display()
for _, ep := range action.MainTab().Panes {
ep.Display()
}
action.MainTab().Display()
action.InfoBar.Display()
screen.Screen.Show()
// Check for new events
select {
case f := <-shell.Jobs:
// If a new job has finished while running in the background we should execute the callback
f.Function(f.Output, f.Args)
case <-config.Autosave:
for _, b := range buffer.OpenBuffers {
b.Save()
}
case <-shell.CloseTerms:
case event = <-events:
case <-screen.DrawChan():
for len(screen.DrawChan()) > 0 {
<-screen.DrawChan()
}
}
if action.InfoBar.HasPrompt {
action.InfoBar.HandleEvent(event)
} else {
action.Tabs.HandleEvent(event)
}
}

341
cmd/micro/micro_test.go Normal file
View File

@@ -0,0 +1,341 @@
package main
import (
"fmt"
"io/ioutil"
"log"
"os"
"testing"
"github.com/go-errors/errors"
"github.com/stretchr/testify/assert"
"github.com/zyedidia/micro/v2/internal/action"
"github.com/zyedidia/micro/v2/internal/buffer"
"github.com/zyedidia/micro/v2/internal/config"
"github.com/zyedidia/micro/v2/internal/screen"
"github.com/zyedidia/tcell"
)
var tempDir string
var sim tcell.SimulationScreen
func init() {
events = make(chan tcell.Event, 8)
}
func startup(args []string) (tcell.SimulationScreen, error) {
var err error
tempDir, err = ioutil.TempDir("", "micro_test")
if err != nil {
return nil, err
}
err = config.InitConfigDir(tempDir)
if err != nil {
return nil, err
}
config.InitRuntimeFiles()
err = config.ReadSettings()
if err != nil {
return nil, err
}
err = config.InitGlobalSettings()
if err != nil {
return nil, err
}
s, err := screen.InitSimScreen()
if err != nil {
return nil, err
}
defer func() {
if err := recover(); err != nil {
screen.Screen.Fini()
fmt.Println("Micro encountered an error:", err)
// backup all open buffers
for _, b := range buffer.OpenBuffers {
b.Backup()
}
// Print the stack trace too
log.Fatalf(errors.Wrap(err, 2).ErrorStack())
os.Exit(1)
}
}()
err = config.LoadAllPlugins()
if err != nil {
screen.TermMessage(err)
}
action.InitBindings()
action.InitCommands()
err = config.InitColorscheme()
if err != nil {
return nil, err
}
b := LoadInput(args)
if len(b) == 0 {
return nil, errors.New("No buffers opened")
}
action.InitTabs(b)
action.InitGlobals()
err = config.RunPluginFn("init")
if err != nil {
return nil, err
}
s.InjectResize()
handleEvent()
return s, nil
}
func cleanup() {
os.RemoveAll(tempDir)
}
func handleEvent() {
screen.Lock()
e := screen.Screen.PollEvent()
screen.Unlock()
if e != nil {
events <- e
}
DoEvent()
}
func injectKey(key tcell.Key, r rune, mod tcell.ModMask) {
sim.InjectKey(key, r, mod)
handleEvent()
}
func injectMouse(x, y int, buttons tcell.ButtonMask, mod tcell.ModMask) {
sim.InjectMouse(x, y, buttons, mod)
handleEvent()
}
func injectString(str string) {
// the tcell simulation screen event channel can only handle
// 10 events at once, so we need to divide up the key events
// into chunks of 10 and handle the 10 events before sending
// another chunk of events
iters := len(str) / 10
extra := len(str) % 10
for i := 0; i < iters; i++ {
s := i * 10
e := i*10 + 10
sim.InjectKeyBytes([]byte(str[s:e]))
for i := 0; i < 10; i++ {
handleEvent()
}
}
sim.InjectKeyBytes([]byte(str[len(str)-extra:]))
for i := 0; i < extra; i++ {
handleEvent()
}
}
func openFile(file string) {
injectKey(tcell.KeyCtrlE, rune(tcell.KeyCtrlE), tcell.ModCtrl)
injectString(fmt.Sprintf("open %s", file))
injectKey(tcell.KeyEnter, rune(tcell.KeyEnter), tcell.ModNone)
}
func createTestFile(name string, content string) (string, error) {
testf, err := ioutil.TempFile("", name)
if err != nil {
return "", err
}
if _, err := testf.Write([]byte(content)); err != nil {
return "", err
}
if err := testf.Close(); err != nil {
return "", err
}
return testf.Name(), nil
}
func TestMain(m *testing.M) {
var err error
sim, err = startup([]string{})
if err != nil {
log.Fatalln(err)
os.Exit(1)
}
retval := m.Run()
cleanup()
os.Exit(retval)
}
func TestSimpleEdit(t *testing.T) {
file, err := createTestFile("micro_simple_edit_test", "base content")
if err != nil {
t.Error(err)
return
}
defer os.Remove(file)
openFile(file)
var buf *buffer.Buffer
for _, b := range buffer.OpenBuffers {
if b.Path == file {
buf = b
}
}
if buf == nil {
t.Errorf("Could not find buffer %s", file)
return
}
injectKey(tcell.KeyEnter, rune(tcell.KeyEnter), tcell.ModNone)
injectKey(tcell.KeyUp, 0, tcell.ModNone)
injectString("first line")
// test both kinds of backspace
for i := 0; i < len("ne"); i++ {
injectKey(tcell.KeyBackspace, rune(tcell.KeyBackspace), tcell.ModNone)
}
for i := 0; i < len(" li"); i++ {
injectKey(tcell.KeyBackspace2, rune(tcell.KeyBackspace2), tcell.ModNone)
}
injectString("foobar")
injectKey(tcell.KeyCtrlS, rune(tcell.KeyCtrlS), tcell.ModCtrl)
data, err := ioutil.ReadFile(file)
if err != nil {
t.Error(err)
return
}
assert.Equal(t, "firstfoobar\nbase content\n", string(data))
}
func TestMouse(t *testing.T) {
file, err := createTestFile("micro_mouse_test", "base content")
if err != nil {
t.Error(err)
return
}
defer os.Remove(file)
openFile(file)
// buffer:
// base content
// the selections need to happen at different locations to avoid a double click
injectMouse(3, 0, tcell.Button1, tcell.ModNone)
injectKey(tcell.KeyLeft, 0, tcell.ModNone)
injectMouse(0, 0, tcell.ButtonNone, tcell.ModNone)
injectString("secondline")
// buffer:
// secondlinebase content
injectKey(tcell.KeyEnter, rune(tcell.KeyEnter), tcell.ModNone)
// buffer:
// secondline
// base content
injectMouse(2, 0, tcell.Button1, tcell.ModNone)
injectMouse(0, 0, tcell.ButtonNone, tcell.ModNone)
injectKey(tcell.KeyEnter, rune(tcell.KeyEnter), tcell.ModNone)
// buffer:
//
// secondline
// base content
injectKey(tcell.KeyUp, 0, tcell.ModNone)
injectString("firstline")
// buffer:
// firstline
// secondline
// base content
injectKey(tcell.KeyCtrlS, rune(tcell.KeyCtrlS), tcell.ModCtrl)
data, err := ioutil.ReadFile(file)
if err != nil {
t.Error(err)
return
}
assert.Equal(t, "firstline\nsecondline\nbase content\n", string(data))
}
var srTestStart = `foo
foo
foofoofoo
Ernleȝe foo æðelen
`
var srTest2 = `test_string
test_string
test_stringtest_stringtest_string
Ernleȝe test_string æðelen
`
var srTest3 = `test_foo
test_string
test_footest_stringtest_foo
Ernleȝe test_string æðelen
`
func TestSearchAndReplace(t *testing.T) {
file, err := createTestFile("micro_search_replace_test", srTestStart)
if err != nil {
t.Error(err)
return
}
defer os.Remove(file)
openFile(file)
injectKey(tcell.KeyCtrlE, rune(tcell.KeyCtrlE), tcell.ModCtrl)
injectString(fmt.Sprintf("replaceall %s %s", "foo", "test_string"))
injectKey(tcell.KeyEnter, rune(tcell.KeyEnter), tcell.ModNone)
injectKey(tcell.KeyCtrlS, rune(tcell.KeyCtrlS), tcell.ModCtrl)
data, err := ioutil.ReadFile(file)
if err != nil {
t.Error(err)
return
}
assert.Equal(t, srTest2, string(data))
injectKey(tcell.KeyCtrlE, rune(tcell.KeyCtrlE), tcell.ModCtrl)
injectString(fmt.Sprintf("replace %s %s", "string", "foo"))
injectKey(tcell.KeyEnter, rune(tcell.KeyEnter), tcell.ModNone)
injectString("ynyny")
injectKey(tcell.KeyEscape, 0, tcell.ModNone)
injectKey(tcell.KeyCtrlS, rune(tcell.KeyCtrlS), tcell.ModCtrl)
data, err = ioutil.ReadFile(file)
if err != nil {
t.Error(err)
return
}
assert.Equal(t, srTest3, string(data))
}
func TestMultiCursor(t *testing.T) {
// TODO
}
func TestSettingsPersistence(t *testing.T) {
// TODO
}
// more tests (rendering, tabs, plugins)?

50
go.mod
View File

@@ -1,38 +1,32 @@
module github.com/zyedidia/micro
module github.com/zyedidia/micro/v2
require (
github.com/blang/semver v3.5.1+incompatible
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/dustin/go-humanize v1.0.0
github.com/flynn/json5 v0.0.0-20160717195620-7620272ed633
github.com/gdamore/encoding v1.0.0 // indirect
github.com/go-errors/errors v1.0.1
github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e // indirect
github.com/jtolds/gls v4.2.1+incompatible // indirect
github.com/kr/pretty v0.1.0 // indirect
github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348 // indirect
github.com/lucasb-eyer/go-colorful v0.0.0-20181028223441-12d3b2882a08 // indirect
github.com/mattn/go-isatty v0.0.4
github.com/mattn/go-runewidth v0.0.4
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51
github.com/mattn/go-isatty v0.0.11
github.com/mattn/go-runewidth v0.0.7
github.com/mitchellh/go-homedir v1.1.0
github.com/npat-efault/poller v2.0.0+incompatible // indirect
github.com/robertkrimen/otto v0.0.0-20180617131154-15f95af6e78d // indirect
github.com/sergi/go-diff v1.0.0
github.com/smartystreets/assertions v0.0.0-20190116191733-b6c0e53d7304 // indirect
github.com/smartystreets/goconvey v0.0.0-20181108003508-044398e4856c // indirect
github.com/stretchr/testify v1.3.0
github.com/yuin/gopher-lua v0.0.0-20190125051437-7b9317363aa9
github.com/zyedidia/clipboard v0.0.0-20180718195219-bd31d747117d
github.com/robertkrimen/otto v0.0.0-20191219234010-c382bd3c16ff
github.com/sergi/go-diff v1.1.0
github.com/stretchr/testify v1.4.0
github.com/yuin/gopher-lua v0.0.0-20191220021717-ab39c6098bdb
github.com/zyedidia/clipboard v0.0.0-20200421031010-7c45b8673834
github.com/zyedidia/glob v0.0.0-20170209203856-dd4023a66dc3
github.com/zyedidia/poller v2.0.0+incompatible // indirect
github.com/zyedidia/pty v1.1.2-0.20180126010845-30364665a244 // indirect
github.com/zyedidia/tcell v0.0.0-20191219170756-59b50b23fa9b
github.com/zyedidia/highlight v0.0.0-20170330143449-201131ce5cf5
github.com/zyedidia/json5 v0.0.0-20200102012142-2da050b1a98d
github.com/zyedidia/pty v2.0.0+incompatible // indirect
github.com/zyedidia/tcell v1.4.8
github.com/zyedidia/terminal v0.0.0-20180726154117-533c623e2415
golang.org/x/sys v0.0.0-20180903190138-2b024373dcd9 // indirect
golang.org/x/text v0.3.0
gopkg.in/DATA-DOG/go-sqlmock.v1 v1.3.0 // indirect
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect
golang.org/x/text v0.3.2
gopkg.in/sourcemap.v1 v1.0.5 // indirect
gopkg.in/yaml.v2 v2.2.2
layeh.com/gopher-luar v1.0.4
gopkg.in/yaml.v2 v2.2.7
layeh.com/gopher-luar v1.0.7
)
replace github.com/kballard/go-shellquote => github.com/zyedidia/go-shellquote v0.0.0-20200613203517-eccd813c0655
replace github.com/mattn/go-runewidth => github.com/p-e-w/go-runewidth v0.0.10-0.20200613030200-3e1705c5c059
go 1.11

94
go.sum
View File

@@ -1,79 +1,77 @@
github.com/blang/semver v3.5.1+incompatible h1:cQNTCjp13qL8KC3Nbxr/y2Bqb63oX6wdnnjpJbkM4JQ=
github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo=
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
github.com/flynn/json5 v0.0.0-20160717195620-7620272ed633 h1:xJMmr4GMYIbALX5edyoDIOQpc2bOQTeJiWMeCl9lX/8=
github.com/flynn/json5 v0.0.0-20160717195620-7620272ed633/go.mod h1:NJDK3/o7abx6PP54EOe0G0n0RLmhCo9xv61gUYpI0EY=
github.com/gdamore/encoding v1.0.0 h1:+7OoQ1Bc6eTm5niUzBa0Ctsh6JbMW6Ra+YNuAtDBdko=
github.com/gdamore/encoding v1.0.0/go.mod h1:alR0ol34c49FCSBLjhosxzcPHQbf2trDkoo5dl+VrEg=
github.com/go-errors/errors v1.0.1 h1:LUHzmkK3GUKUrL/1gfBUxAHzcev3apQlezX/+O7ma6w=
github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q=
github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e h1:JKmoR8x90Iww1ks85zJ1lfDGgIiMDuIptTOhJq+zKyg=
github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/jtolds/gls v4.2.1+incompatible h1:fSuqC+Gmlu6l/ZYAoZzx2pyucC8Xza35fpRVWLVmUEE=
github.com/jtolds/gls v4.2.1+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348 h1:MtvEpTB6LX3vkb4ax0b5D2DHbNAUsen0Gx5wZoq3lV4=
github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348/go.mod h1:B69LEHPfb2qLo0BaaOLcbitczOKLWTsrBG9LczfCD4k=
github.com/lucasb-eyer/go-colorful v0.0.0-20181028223441-12d3b2882a08 h1:5MnxBC15uMxFv5FY/J/8vzyaBiArCOkMdFT9Jsw78iY=
github.com/lucasb-eyer/go-colorful v0.0.0-20181028223441-12d3b2882a08/go.mod h1:NXg0ArsFk0Y01623LgUqoqcouGDB+PwCCQlrwrG6xJ4=
github.com/mattn/go-isatty v0.0.4 h1:bnP0vzxcAdeI1zdubAl5PjU6zsERjGZb7raWodagDYs=
github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
github.com/mattn/go-runewidth v0.0.4 h1:2BvfKmzob6Bmd4YsL0zygOqfdFnK7GR4QL06Do4/p7Y=
github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
github.com/lucasb-eyer/go-colorful v1.0.3 h1:QIbQXiugsb+q10B+MI+7DI1oQLdmnep86tWFlaaUAac=
github.com/lucasb-eyer/go-colorful v1.0.3/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
github.com/mattn/go-isatty v0.0.11 h1:FxPOTFNqGkuDUGi3H/qkUbQO4ZiBa2brKq5r0l8TGeM=
github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE=
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/npat-efault/poller v2.0.0+incompatible h1:jtTdXWKgN5kDK41ts8hoY1rvTEi0K08MTB8/bRO9MqE=
github.com/npat-efault/poller v2.0.0+incompatible/go.mod h1:lni01B89P8PtVpwlAhdhK1niN5rPkDGGpGGgBJzpSgo=
github.com/p-e-w/go-runewidth v0.0.10-0.20200613030200-3e1705c5c059 h1:/+h2b6i15wh4EWsFkfdNdBE1jjGA872tpXEyhPM5aYg=
github.com/p-e-w/go-runewidth v0.0.10-0.20200613030200-3e1705c5c059/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
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/robertkrimen/otto v0.0.0-20180617131154-15f95af6e78d h1:1VUlQbCfkoSGv7qP7Y+ro3ap1P1pPZxgdGVqiTVy5C4=
github.com/robertkrimen/otto v0.0.0-20180617131154-15f95af6e78d/go.mod h1:xvqspoSXJTIpemEonrMDFq6XzwHYYgToXWj5eRX1OtY=
github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ=
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
github.com/smartystreets/assertions v0.0.0-20190116191733-b6c0e53d7304 h1:Jpy1PXuP99tXNrhbq2BaPz9B+jNAvH1JPQQpG/9GCXY=
github.com/smartystreets/assertions v0.0.0-20190116191733-b6c0e53d7304/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
github.com/smartystreets/goconvey v0.0.0-20181108003508-044398e4856c h1:Ho+uVpkel/udgjbwB5Lktg9BtvJSh2DT0Hi6LPSyI2w=
github.com/smartystreets/goconvey v0.0.0-20181108003508-044398e4856c/go.mod h1:XDJAKZRPZ1CvBcN2aX5YOUTYGHki24fSF0Iv48Ibg0s=
github.com/robertkrimen/otto v0.0.0-20191219234010-c382bd3c16ff h1:+6NUiITWwE5q1KO6SAfUX918c+Tab0+tGAM/mtdlUyA=
github.com/robertkrimen/otto v0.0.0-20191219234010-c382bd3c16ff/go.mod h1:xvqspoSXJTIpemEonrMDFq6XzwHYYgToXWj5eRX1OtY=
github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0=
github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
github.com/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/yuin/gopher-lua v0.0.0-20190125051437-7b9317363aa9 h1:Wy3fAQLBPP0JSWdq3kBnmbFgXDHcyhtPpd+8kENV7mU=
github.com/yuin/gopher-lua v0.0.0-20190125051437-7b9317363aa9/go.mod h1:fFiAh+CowNFr0NK5VASokuwKwkbacRmHsVA7Yb1Tqac=
github.com/zyedidia/clipboard v0.0.0-20180718195219-bd31d747117d h1:Lhqt2eo+rgM8aswvM7nTtAMVm8ARPWzkE9n6eZDOccY=
github.com/zyedidia/clipboard v0.0.0-20180718195219-bd31d747117d/go.mod h1:WDk3p8GiZV9+xFWlSo8qreeoLhW6Ik692rqXk+cNeRY=
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/yuin/gopher-lua v0.0.0-20190206043414-8bfc7677f583/go.mod h1:gqRgreBUhTSL0GeU64rtZ3Uq3wtjOa/TB2YfrtkCbVQ=
github.com/yuin/gopher-lua v0.0.0-20191220021717-ab39c6098bdb h1:ZkM6LRnq40pR1Ox0hTHlnpkcOTuFIDQpZ1IN8rKKhX0=
github.com/yuin/gopher-lua v0.0.0-20191220021717-ab39c6098bdb/go.mod h1:gqRgreBUhTSL0GeU64rtZ3Uq3wtjOa/TB2YfrtkCbVQ=
github.com/zyedidia/clipboard v0.0.0-20200421031010-7c45b8673834 h1:0nOfq3JwYRiY3+nwfWVQYEaXDmGCQgj3RKoqTifLzP4=
github.com/zyedidia/clipboard v0.0.0-20200421031010-7c45b8673834/go.mod h1:zykFnZUXX0ErxqvYLUFEq7QDJKId8rmh2FgD0/Y8cjA=
github.com/zyedidia/glob v0.0.0-20170209203856-dd4023a66dc3 h1:oMHjjTLfGXVuyOQBYj5/td9WC0mw4g1xDBPovIqmHew=
github.com/zyedidia/glob v0.0.0-20170209203856-dd4023a66dc3/go.mod h1:YKbIYP//Eln8eDgAJGI3IDvR3s4Tv9Z9TGIOumiyQ5c=
github.com/zyedidia/poller v2.0.0+incompatible h1:DMOvB0EXz2JTokqOKfxPWN/8xXFJbO+m4vNhMkOY7Lo=
github.com/zyedidia/poller v2.0.0+incompatible/go.mod h1:vZXJOHGDcuK08GXhF6IAY0ZFd2WcgOR5DOTp84Uk5eE=
github.com/zyedidia/pty v1.1.2-0.20180126010845-30364665a244 h1:DZ7mZvUV5+oXeXV1E1t6ZIXRihHYyqYVIOSA+RGo88A=
github.com/zyedidia/pty v1.1.2-0.20180126010845-30364665a244/go.mod h1:4y9l9yJZNxRa7GB/fB+mmDmGkG3CqmzLf4vUxGGotEA=
github.com/zyedidia/tcell v0.0.0-20190204041104-518c15c24302 h1:ruNSURcO81y+J+XnqrLLt+zxcdFtq8QNoZfWXSsybYQ=
github.com/zyedidia/tcell v0.0.0-20190204041104-518c15c24302/go.mod h1:yXgdp23+aW8OMENYVBvpKoeiBtjaVWJ9HhpPDu6LBfM=
github.com/zyedidia/tcell v0.0.0-20191219170756-59b50b23fa9b h1:cryFENlMxJJrkimVx/CUMFDCxC4vpmey2x3A3tAgTNM=
github.com/zyedidia/tcell v0.0.0-20191219170756-59b50b23fa9b/go.mod h1:yXgdp23+aW8OMENYVBvpKoeiBtjaVWJ9HhpPDu6LBfM=
github.com/zyedidia/go-shellquote v0.0.0-20200613203517-eccd813c0655 h1:Z3RhH6hvcSx7eX6Q/pP6YVsgea/1eMDG99vtWwi3nK4=
github.com/zyedidia/go-shellquote v0.0.0-20200613203517-eccd813c0655/go.mod h1:1sTqqO+kcYzZp43M5VsJe1tns9IzlSeC9jB6c2+o/5Y=
github.com/zyedidia/highlight v0.0.0-20170330143449-201131ce5cf5 h1:Zs6mpwXvlqpF9zHl5XaN0p5V4J9XvP+WBuiuXyIgqvc=
github.com/zyedidia/highlight v0.0.0-20170330143449-201131ce5cf5/go.mod h1:c1r+Ob9tUTPB0FKWO1+x+Hsc/zNa45WdGq7Y38Ybip0=
github.com/zyedidia/json5 v0.0.0-20200102012142-2da050b1a98d h1:zmDMkh22zXOB7gz8jFaI4GpI7llsPgzm38/jG0UgxjE=
github.com/zyedidia/json5 v0.0.0-20200102012142-2da050b1a98d/go.mod h1:NDJSTTYWivnza6zkRapeX2/LwhKPEMQ7bJxqgDVT78I=
github.com/zyedidia/poller v1.0.1 h1:Tt9S3AxAjXwWGNiC2TUdRJkQDZSzCBNVQ4xXiQ7440s=
github.com/zyedidia/poller v1.0.1/go.mod h1:vZXJOHGDcuK08GXhF6IAY0ZFd2WcgOR5DOTp84Uk5eE=
github.com/zyedidia/pty v2.0.0+incompatible h1:Ou5vXL6tvjst+RV8sUFISbuKDnUJPhnpygApMFGweqw=
github.com/zyedidia/pty v2.0.0+incompatible/go.mod h1:4y9l9yJZNxRa7GB/fB+mmDmGkG3CqmzLf4vUxGGotEA=
github.com/zyedidia/tcell v1.4.8 h1:s4zYGOyCNDK4cdrgNVME0SxGizuT/oKY3OyB4Ls2Qpg=
github.com/zyedidia/tcell v1.4.8/go.mod h1:HhlbMSCcGX15rFDB+Q1Lk3pKEOocsCUAQC3zhZ9sadA=
github.com/zyedidia/terminal v0.0.0-20180726154117-533c623e2415 h1:752dTQ5OatJ9M5ULK2+9lor+nzyZz+LYDo3WGngg3Rc=
github.com/zyedidia/terminal v0.0.0-20180726154117-533c623e2415/go.mod h1:8leT8G0Cm8NoJHdrrKHyR9MirWoF4YW7pZh06B6H+1E=
golang.org/x/sys v0.0.0-20180903190138-2b024373dcd9 h1:lkiLiLBHGoH3XnqSLUIaBsilGMUjI+Uy2Xu2JLUtTas=
golang.org/x/sys v0.0.0-20180903190138-2b024373dcd9/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
golang.org/x/sys v0.0.0-20190204203706-41f3e6584952/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190626150813-e07cf5db2756/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037 h1:YyJpGZS1sBuBCzLAR1VEpK193GlqGZbnPFnPV/5Rsb4=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
gopkg.in/DATA-DOG/go-sqlmock.v1 v1.3.0 h1:FVCohIoYO7IJoDDVpV2pdq7SgrMH6wHnuTyrdrxJNoY=
gopkg.in/DATA-DOG/go-sqlmock.v1 v1.3.0/go.mod h1:OdE7CF6DbADk7lN8LIKRzRJTTZXIjtWgA5THM5lhBAw=
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/sourcemap.v1 v1.0.5 h1:inv58fC9f9J3TK2Y2R1NPntXEn3/wjWHkonhIUODNTI=
gopkg.in/sourcemap.v1 v1.0.5/go.mod h1:2RlvNNSMglmRrcvhfuzp4hQHwOtjxlbjX7UPY/GXb78=
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
layeh.com/gopher-luar v1.0.4 h1:BFgt94J/CXh4HkDcE2b7A7pBaVeQKEVfHEBRKL/K/Tc=
layeh.com/gopher-luar v1.0.4/go.mod h1:N3rev/ttQd8yVluXaYsa0M/eknzRYWe+pxZ35ZFmaaI=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.7 h1:VUgggvou5XRW9mHwD/yXxIYSMtY0zoKQf/v226p2nyo=
gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
layeh.com/gopher-luar v1.0.7 h1:53iv6CCkRs5wyofZ+qVXcyAYQOIG52s6pt4xkqZdq7k=
layeh.com/gopher-luar v1.0.7/go.mod h1:TPnIVCZ2RJBndm7ohXyaqfhzjlZ+OA2SZR/YwL8tECk=

View File

@@ -5,15 +5,14 @@ import (
"runtime"
"strings"
"time"
"unicode/utf8"
shellquote "github.com/kballard/go-shellquote"
"github.com/zyedidia/clipboard"
"github.com/zyedidia/micro/internal/buffer"
"github.com/zyedidia/micro/internal/config"
"github.com/zyedidia/micro/internal/screen"
"github.com/zyedidia/micro/internal/shell"
"github.com/zyedidia/micro/internal/util"
"github.com/zyedidia/micro/pkg/shellwords"
"github.com/zyedidia/micro/v2/internal/buffer"
"github.com/zyedidia/micro/v2/internal/config"
"github.com/zyedidia/micro/v2/internal/screen"
"github.com/zyedidia/micro/v2/internal/shell"
"github.com/zyedidia/micro/v2/internal/util"
"github.com/zyedidia/tcell"
)
@@ -23,6 +22,8 @@ func (h *BufPane) ScrollUp(n int) {
if v.StartLine >= n {
v.StartLine -= n
h.SetView(v)
} else {
v.StartLine = 0
}
}
@@ -86,7 +87,6 @@ func (h *BufPane) MousePress(e *tcell.EventMouse) bool {
h.Cursor.AddWordToSelection()
} else {
h.Cursor.SetSelectionEnd(h.Cursor.Loc)
h.Cursor.CopySelection("primary")
}
}
@@ -174,7 +174,7 @@ func (h *BufPane) CursorRight() bool {
if tabstospaces && tabmovement {
tabsize := int(h.Buf.Settings["tabsize"].(float64))
line := h.Buf.LineBytes(h.Cursor.Y)
if h.Cursor.X+tabsize < utf8.RuneCount(line) && util.IsSpaces(line[h.Cursor.X:h.Cursor.X+tabsize]) && util.IsBytesWhitespace(line[0:h.Cursor.X]) {
if h.Cursor.X+tabsize < util.CharacterCount(line) && util.IsSpaces(line[h.Cursor.X:h.Cursor.X+tabsize]) && util.IsBytesWhitespace(line[0:h.Cursor.X]) {
for i := 0; i < tabsize; i++ {
h.Cursor.Right()
}
@@ -282,15 +282,31 @@ func (h *BufPane) SelectWordLeft() bool {
return true
}
// StartOfText moves the cursor to the start of the text of the line
func (h *BufPane) StartOfText() bool {
h.Cursor.Deselect(true)
h.Cursor.StartOfText()
h.Relocate()
return true
}
// StartOfTextToggle toggles the cursor between the start of the text of the line
// and the start of the line
func (h *BufPane) StartOfTextToggle() bool {
h.Cursor.Deselect(true)
if h.Cursor.IsStartOfText() {
h.Cursor.Start()
} else {
h.Cursor.StartOfText()
}
h.Relocate()
return true
}
// StartOfLine moves the cursor to the start of the line
func (h *BufPane) StartOfLine() bool {
h.Cursor.Deselect(true)
h.Cursor.StartOfText()
// if h.Cursor.X != 0 {
// h.Cursor.Start()
// } else {
// h.Cursor.StartOfText()
// }
h.Cursor.Start()
h.Relocate()
return true
}
@@ -310,6 +326,33 @@ func (h *BufPane) SelectLine() bool {
return true
}
// SelectToStartOfText selects to the start of the text on the current line
func (h *BufPane) SelectToStartOfText() bool {
if !h.Cursor.HasSelection() {
h.Cursor.OrigSelection[0] = h.Cursor.Loc
}
h.Cursor.StartOfText()
h.Cursor.SelectTo(h.Cursor.Loc)
h.Relocate()
return true
}
// SelectToStartOfTextToggle toggles the selection between the start of the text
// on the current line and the start of the line
func (h *BufPane) SelectToStartOfTextToggle() bool {
if !h.Cursor.HasSelection() {
h.Cursor.OrigSelection[0] = h.Cursor.Loc
}
if h.Cursor.IsStartOfText() {
h.Cursor.Start()
} else {
h.Cursor.StartOfText()
}
h.Cursor.SelectTo(h.Cursor.Loc)
h.Relocate()
return true
}
// SelectToStartOfLine selects to the start of the current line
func (h *BufPane) SelectToStartOfLine() bool {
if !h.Cursor.HasSelection() {
@@ -381,6 +424,7 @@ func (h *BufPane) CursorStart() bool {
h.Cursor.Deselect(true)
h.Cursor.X = 0
h.Cursor.Y = 0
h.Cursor.StoreVisualX()
h.Relocate()
return true
}
@@ -441,7 +485,7 @@ func (h *BufPane) InsertNewline() bool {
// Remove the whitespaces if keepautoindent setting is off
if util.IsSpacesOrTabs(h.Buf.LineBytes(h.Cursor.Y-1)) && !h.Buf.Settings["keepautoindent"].(bool) {
line := h.Buf.LineBytes(h.Cursor.Y - 1)
h.Buf.Remove(buffer.Loc{X: 0, Y: h.Cursor.Y - 1}, buffer.Loc{X: utf8.RuneCount(line), Y: h.Cursor.Y - 1})
h.Buf.Remove(buffer.Loc{X: 0, Y: h.Cursor.Y - 1}, buffer.Loc{X: util.CharacterCount(line), Y: h.Cursor.Y - 1})
}
}
h.Cursor.LastVisualX = h.Cursor.GetVisualX()
@@ -466,7 +510,7 @@ func (h *BufPane) Backspace() bool {
// tab (tabSize number of spaces)
lineStart := util.SliceStart(h.Buf.LineBytes(h.Cursor.Y), h.Cursor.X)
tabSize := int(h.Buf.Settings["tabsize"].(float64))
if h.Buf.Settings["tabstospaces"].(bool) && util.IsSpaces(lineStart) && len(lineStart) != 0 && utf8.RuneCount(lineStart)%tabSize == 0 {
if h.Buf.Settings["tabstospaces"].(bool) && util.IsSpaces(lineStart) && len(lineStart) != 0 && util.CharacterCount(lineStart)%tabSize == 0 {
loc := h.Cursor.Loc
h.Buf.Remove(loc.Move(-tabSize, h.Buf), loc)
} else {
@@ -533,12 +577,14 @@ func (h *BufPane) IndentSelection() bool {
tabsize := int(h.Buf.Settings["tabsize"].(float64))
indentsize := len(h.Buf.IndentString(tabsize))
for y := startY; y <= endY; y++ {
h.Buf.Insert(buffer.Loc{X: 0, Y: y}, h.Buf.IndentString(tabsize))
if y == startY && start.X > 0 {
h.Cursor.SetSelectionStart(start.Move(indentsize, h.Buf))
}
if y == endY {
h.Cursor.SetSelectionEnd(buffer.Loc{X: endX + indentsize + 1, Y: endY})
if len(h.Buf.LineBytes(y)) > 0 {
h.Buf.Insert(buffer.Loc{X: 0, Y: y}, h.Buf.IndentString(tabsize))
if y == startY && start.X > 0 {
h.Cursor.SetSelectionStart(start.Move(indentsize, h.Buf))
}
if y == endY {
h.Cursor.SetSelectionEnd(buffer.Loc{X: endX + indentsize + 1, Y: endY})
}
}
}
h.Buf.RelocateCursors()
@@ -549,6 +595,20 @@ func (h *BufPane) IndentSelection() bool {
return false
}
// IndentLine moves the current line forward one indentation
func (h *BufPane) IndentLine() bool {
if h.Cursor.HasSelection() {
return false
}
tabsize := int(h.Buf.Settings["tabsize"].(float64))
indentstr := h.Buf.IndentString(tabsize)
h.Buf.Insert(buffer.Loc{X: 0, Y: h.Cursor.Y}, indentstr)
h.Buf.RelocateCursors()
h.Relocate()
return true
}
// OutdentLine moves the current line back one indentation
func (h *BufPane) OutdentLine() bool {
if h.Cursor.HasSelection() {
@@ -603,6 +663,16 @@ func (h *BufPane) Autocomplete() bool {
return false
}
if h.Cursor.X == 0 {
return false
}
r := h.Cursor.RuneUnder(h.Cursor.X)
prev := h.Cursor.RuneUnder(h.Cursor.X - 1)
if !util.IsAutocomplete(prev) || !util.IsNonAlphaNumeric(r) {
// don't autocomplete if cursor is on alpha numeric character (middle of a word)
return false
}
if b.HasSuggestions {
b.CycleAutocomplete(true)
return true
@@ -610,11 +680,22 @@ func (h *BufPane) Autocomplete() bool {
return b.Autocomplete(buffer.BufferComplete)
}
// CycleAutocompleteBack cycles back in the autocomplete suggestion list
func (h *BufPane) CycleAutocompleteBack() bool {
if h.Cursor.HasSelection() {
return false
}
if h.Buf.HasSuggestions {
h.Buf.CycleAutocomplete(false)
return true
}
return false
}
// InsertTab inserts a tab or spaces
func (h *BufPane) InsertTab() bool {
b := h.Buf
l := b.LineBytes(h.Cursor.Y)
l = util.SliceStart(l, h.Cursor.X)
indent := b.IndentString(util.IntOpt(b.Settings["tabsize"]))
tabBytes := len(indent)
bytesUntilIndent := tabBytes - (h.Cursor.GetVisualX() % tabBytes)
@@ -631,54 +712,86 @@ func (h *BufPane) SaveAll() bool {
return true
}
// Save the buffer to disk
func (h *BufPane) Save() bool {
// SaveCB performs a save and does a callback at the very end (after all prompts have been resolved)
func (h *BufPane) SaveCB(action string, callback func()) bool {
// If this is an empty buffer, ask for a filename
if h.Buf.Path == "" {
h.SaveAs()
h.SaveAsCB(action, callback)
} else {
h.saveBufToFile(h.Buf.Path)
noPrompt := h.saveBufToFile(h.Buf.Path, action, callback)
if noPrompt {
return true
}
}
return true
return false
}
// SaveAs saves the buffer to disk with the given name
func (h *BufPane) SaveAs() bool {
// Save the buffer to disk
func (h *BufPane) Save() bool {
return h.SaveCB("Save", nil)
}
// SaveAsCB performs a save as and does a callback at the very end (after all prompts have been resolved)
// The callback is only called if the save was successful
func (h *BufPane) SaveAsCB(action string, callback func()) bool {
InfoBar.Prompt("Filename: ", "", "Save", nil, func(resp string, canceled bool) {
if !canceled {
// the filename might or might not be quoted, so unquote first then join the strings.
args, err := shellwords.Split(resp)
filename := strings.Join(args, " ")
args, err := shellquote.Split(resp)
if err != nil {
InfoBar.Error("Error parsing arguments: ", err)
return
}
h.saveBufToFile(filename)
if len(args) == 0 {
InfoBar.Error("No filename given")
return
}
filename := strings.Join(args, " ")
noPrompt := h.saveBufToFile(filename, action, callback)
if noPrompt {
h.completeAction(action)
}
}
})
return true
return false
}
// SaveAs saves the buffer to disk with the given name
func (h *BufPane) SaveAs() bool {
return h.SaveAsCB("SaveAs", nil)
}
// This function saves the buffer to `filename` and changes the buffer's path and name
// to `filename` if the save is successful
func (h *BufPane) saveBufToFile(filename string) {
// The callback is only called if the save was successful
func (h *BufPane) saveBufToFile(filename string, action string, callback func()) bool {
err := h.Buf.SaveAs(filename)
if err != nil {
if strings.HasSuffix(err.Error(), "permission denied") {
InfoBar.YNPrompt("Permission denied. Do you want to save this file using sudo? (y,n)", func(yes, canceled bool) {
if yes && !canceled {
err = h.Buf.SaveAsWithSudo(filename)
if err != nil {
InfoBar.Error(err)
} else {
h.Buf.Path = filename
h.Buf.SetName(filename)
InfoBar.Message("Saved " + filename)
saveWithSudo := func() {
err = h.Buf.SaveAsWithSudo(filename)
if err != nil {
InfoBar.Error(err)
} else {
h.Buf.Path = filename
h.Buf.SetName(filename)
InfoBar.Message("Saved " + filename)
if callback != nil {
callback()
}
}
})
}
if h.Buf.Settings["autosu"].(bool) {
saveWithSudo()
} else {
InfoBar.YNPrompt("Permission denied. Do you want to save this file using sudo? (y,n)", func(yes, canceled bool) {
if yes && !canceled {
saveWithSudo()
h.completeAction(action)
}
})
return false
}
} else {
InfoBar.Error(err)
}
@@ -686,15 +799,56 @@ func (h *BufPane) saveBufToFile(filename string) {
h.Buf.Path = filename
h.Buf.SetName(filename)
InfoBar.Message("Saved " + filename)
if callback != nil {
callback()
}
}
return true
}
// Find opens a prompt and searches forward for the input
func (h *BufPane) Find() bool {
return h.find(true)
}
// FindLiteral is the same as Find() but does not support regular expressions
func (h *BufPane) FindLiteral() bool {
return h.find(false)
}
// Search searches for a given string/regex in the buffer and selects the next
// match if a match is found
// This function affects lastSearch and lastSearchRegex (saved searches) for
// use with FindNext and FindPrevious
func (h *BufPane) Search(str string, useRegex bool, searchDown bool) error {
match, found, err := h.Buf.FindNext(str, h.Buf.Start(), h.Buf.End(), h.Cursor.Loc, searchDown, useRegex)
if err != nil {
return err
}
if found {
h.Cursor.SetSelectionStart(match[0])
h.Cursor.SetSelectionEnd(match[1])
h.Cursor.OrigSelection[0] = h.Cursor.CurSelection[0]
h.Cursor.OrigSelection[1] = h.Cursor.CurSelection[1]
h.Cursor.GotoLoc(h.Cursor.CurSelection[1])
h.lastSearch = str
h.lastSearchRegex = useRegex
h.Relocate()
} else {
h.Cursor.ResetSelection()
}
return nil
}
func (h *BufPane) find(useRegex bool) bool {
h.searchOrig = h.Cursor.Loc
InfoBar.Prompt("Find: ", "", "Find", func(resp string) {
prompt := "Find: "
if useRegex {
prompt = "Find (regex): "
}
InfoBar.Prompt(prompt, "", "Find", func(resp string) {
// Event callback
match, found, _ := h.Buf.FindNext(resp, h.Buf.Start(), h.Buf.End(), h.searchOrig, true, true)
match, found, _ := h.Buf.FindNext(resp, h.Buf.Start(), h.Buf.End(), h.searchOrig, true, useRegex)
if found {
h.Cursor.SetSelectionStart(match[0])
h.Cursor.SetSelectionEnd(match[1])
@@ -709,7 +863,7 @@ func (h *BufPane) Find() bool {
}, func(resp string, canceled bool) {
// Finished callback
if !canceled {
match, found, err := h.Buf.FindNext(resp, h.Buf.Start(), h.Buf.End(), h.searchOrig, true, true)
match, found, err := h.Buf.FindNext(resp, h.Buf.Start(), h.Buf.End(), h.searchOrig, true, useRegex)
if err != nil {
InfoBar.Error(err)
}
@@ -720,6 +874,7 @@ func (h *BufPane) Find() bool {
h.Cursor.OrigSelection[1] = h.Cursor.CurSelection[1]
h.Cursor.GotoLoc(h.Cursor.CurSelection[1])
h.lastSearch = resp
h.lastSearchRegex = useRegex
} else {
h.Cursor.ResetSelection()
InfoBar.Message("No matches found")
@@ -743,7 +898,7 @@ func (h *BufPane) FindNext() bool {
if h.Cursor.HasSelection() {
searchLoc = h.Cursor.CurSelection[1]
}
match, found, err := h.Buf.FindNext(h.lastSearch, h.Buf.Start(), h.Buf.End(), searchLoc, true, true)
match, found, err := h.Buf.FindNext(h.lastSearch, h.Buf.Start(), h.Buf.End(), searchLoc, true, h.lastSearchRegex)
if err != nil {
InfoBar.Error(err)
}
@@ -770,7 +925,7 @@ func (h *BufPane) FindPrevious() bool {
if h.Cursor.HasSelection() {
searchLoc = h.Cursor.CurSelection[0]
}
match, found, err := h.Buf.FindNext(h.lastSearch, h.Buf.Start(), h.Buf.End(), searchLoc, false, true)
match, found, err := h.Buf.FindNext(h.lastSearch, h.Buf.Start(), h.Buf.End(), searchLoc, false, h.lastSearchRegex)
if err != nil {
InfoBar.Error(err)
}
@@ -808,12 +963,35 @@ func (h *BufPane) Copy() bool {
if h.Cursor.HasSelection() {
h.Cursor.CopySelection("clipboard")
h.freshClip = true
InfoBar.Message("Copied selection")
if clipboard.Unsupported {
InfoBar.Message("Copied selection (install xclip for external clipboard)")
} else {
InfoBar.Message("Copied selection")
}
}
h.Relocate()
return true
}
// Copy the current line to the clipboard
func (h *BufPane) CopyLine() bool {
if h.Cursor.HasSelection() {
return false
} else {
h.Cursor.SelectLine()
h.Cursor.CopySelection("clipboard")
h.freshClip = true
if clipboard.Unsupported {
InfoBar.Message("Copied line (install xclip for external clipboard)")
} else {
InfoBar.Message("Copied line")
}
}
h.Cursor.Deselect(true)
h.Relocate()
return true
}
// CutLine cuts the current line to the clipboard
func (h *BufPane) CutLine() bool {
h.Cursor.SelectLine()
@@ -893,15 +1071,26 @@ func (h *BufPane) MoveLinesUp() bool {
}
start := h.Cursor.CurSelection[0].Y
end := h.Cursor.CurSelection[1].Y
sel := 1
if start > end {
end, start = start, end
sel = 0
}
compensate := false
if h.Cursor.CurSelection[sel].X != 0 {
end++
} else {
compensate = true
}
h.Buf.MoveLinesUp(
start,
end,
)
h.Cursor.CurSelection[1].Y -= 1
if compensate {
h.Cursor.CurSelection[sel].Y -= 1
}
} else {
if h.Cursor.Loc.Y == 0 {
InfoBar.Message("Cannot move further up")
@@ -926,8 +1115,14 @@ func (h *BufPane) MoveLinesDown() bool {
}
start := h.Cursor.CurSelection[0].Y
end := h.Cursor.CurSelection[1].Y
sel := 1
if start > end {
end, start = start, end
sel = 0
}
if h.Cursor.CurSelection[sel].X != 0 {
end++
}
h.Buf.MoveLinesDown(
@@ -982,7 +1177,11 @@ func (h *BufPane) paste(clip string) {
h.Buf.Insert(h.Cursor.Loc, clip)
// h.Cursor.Loc = h.Cursor.Loc.Move(Count(clip), h.Buf)
h.freshClip = false
InfoBar.Message("Pasted clipboard")
if clipboard.Unsupported {
InfoBar.Message("Pasted clipboard (install xclip for external clipboard)")
} else {
InfoBar.Message("Pasted clipboard")
}
}
// JumpToMatchingBrace moves the cursor to the matching brace if it is
@@ -992,11 +1191,15 @@ func (h *BufPane) JumpToMatchingBrace() bool {
r := h.Cursor.RuneUnder(h.Cursor.X)
rl := h.Cursor.RuneUnder(h.Cursor.X - 1)
if r == bp[0] || r == bp[1] || rl == bp[0] || rl == bp[1] {
matchingBrace, left := h.Buf.FindMatchingBrace(bp, h.Cursor.Loc)
if left {
h.Cursor.GotoLoc(matchingBrace)
matchingBrace, left, found := h.Buf.FindMatchingBrace(bp, h.Cursor.Loc)
if found {
if left {
h.Cursor.GotoLoc(matchingBrace)
} else {
h.Cursor.GotoLoc(matchingBrace.Move(1, h.Buf))
}
} else {
h.Cursor.GotoLoc(matchingBrace.Move(1, h.Buf))
return false
}
}
}
@@ -1026,6 +1229,16 @@ func (h *BufPane) OpenFile() bool {
return true
}
// OpenFile opens a new file in the buffer
func (h *BufPane) JumpLine() bool {
InfoBar.Prompt("> ", "goto ", "Command", nil, func(resp string, canceled bool) {
if !canceled {
h.HandleCommand(resp)
}
})
return true
}
// Start moves the viewport to the start of the buffer
func (h *BufPane) Start() bool {
v := h.GetView()
@@ -1147,6 +1360,21 @@ func (h *BufPane) HalfPageDown() bool {
return true
}
// ToggleDiffGutter turns the diff gutter off and on
func (h *BufPane) ToggleDiffGutter() bool {
if !h.Buf.Settings["diffgutter"].(bool) {
h.Buf.Settings["diffgutter"] = true
h.Buf.UpdateDiff(func(synchronous bool) {
screen.Redraw()
})
InfoBar.Message("Enabled diff gutter")
} else {
h.Buf.Settings["diffgutter"] = false
InfoBar.Message("Disabled diff gutter")
}
return true
}
// ToggleRuler turns line numbers off and on
func (h *BufPane) ToggleRuler() bool {
if !h.Buf.Settings["ruler"].(bool) {
@@ -1232,15 +1460,17 @@ func (h *BufPane) Quit() bool {
if h.Buf.Modified() {
if config.GlobalSettings["autosave"].(float64) > 0 {
// autosave on means we automatically save when quitting
h.Save()
quit()
h.SaveCB("Quit", func() {
quit()
})
} else {
InfoBar.YNPrompt("Save changes to "+h.Buf.GetName()+" before closing? (y,n,esc)", func(yes, canceled bool) {
if !canceled && !yes {
quit()
} else if !canceled && yes {
h.Save()
quit()
h.SaveCB("Quit", func() {
quit()
})
}
})
}
@@ -1296,8 +1526,9 @@ func (h *BufPane) AddTab() bool {
// PreviousTab switches to the previous tab in the tab list
func (h *BufPane) PreviousTab() bool {
a := Tabs.Active()
Tabs.SetActive(util.Clamp(a-1, 0, len(Tabs.List)-1))
tabsLen := len(Tabs.List)
a := Tabs.Active() + tabsLen
Tabs.SetActive((a - 1) % tabsLen)
return true
}
@@ -1305,7 +1536,8 @@ func (h *BufPane) PreviousTab() bool {
// NextTab switches to the next tab in the tab list
func (h *BufPane) NextTab() bool {
a := Tabs.Active()
Tabs.SetActive(util.Clamp(a+1, 0, len(Tabs.List)-1))
Tabs.SetActive((a + 1) % len(Tabs.List))
return true
}
@@ -1325,38 +1557,42 @@ func (h *BufPane) HSplitAction() bool {
// Unsplit closes all splits in the current tab except the active one
func (h *BufPane) Unsplit() bool {
n := MainTab().GetNode(h.splitID)
n.Unsplit()
tab := h.tab
n := tab.GetNode(h.splitID)
ok := n.Unsplit()
if ok {
tab.RemovePane(tab.GetPane(h.splitID))
tab.Resize()
tab.SetActive(len(tab.Panes) - 1)
MainTab().RemovePane(MainTab().GetPane(h.splitID))
MainTab().Resize()
MainTab().SetActive(len(MainTab().Panes) - 1)
return true
return true
}
return false
}
// NextSplit changes the view to the next split
func (h *BufPane) NextSplit() bool {
a := MainTab().active
if a < len(MainTab().Panes)-1 {
a := h.tab.active
if a < len(h.tab.Panes)-1 {
a++
} else {
a = 0
}
MainTab().SetActive(a)
h.tab.SetActive(a)
return true
}
// PreviousSplit changes the view to the previous split
func (h *BufPane) PreviousSplit() bool {
a := MainTab().active
a := h.tab.active
if a > 0 {
a--
} else {
a = len(MainTab().Panes) - 1
a = len(h.tab.Panes) - 1
}
MainTab().SetActive(a)
h.tab.SetActive(a)
return true
}
@@ -1435,6 +1671,41 @@ func (h *BufPane) SpawnMultiCursor() bool {
return true
}
// SpawnMultiCursorUp creates additional cursor, at the same X (if possible), one Y less.
func (h *BufPane) SpawnMultiCursorUp() bool {
if h.Cursor.Y == 0 {
return false
} else {
h.Cursor.GotoLoc(buffer.Loc{h.Cursor.X, h.Cursor.Y - 1})
h.Cursor.Relocate()
}
c := buffer.NewCursor(h.Buf, buffer.Loc{h.Cursor.X, h.Cursor.Y + 1})
h.Buf.AddCursor(c)
h.Buf.SetCurCursor(h.Buf.NumCursors() - 1)
h.Buf.MergeCursors()
h.Relocate()
return true
}
// SpawnMultiCursorDown creates additional cursor, at the same X (if possible), one Y more.
func (h *BufPane) SpawnMultiCursorDown() bool {
if h.Cursor.Y+1 == h.Buf.LinesNum() {
return false
} else {
h.Cursor.GotoLoc(buffer.Loc{h.Cursor.X, h.Cursor.Y + 1})
h.Cursor.Relocate()
}
c := buffer.NewCursor(h.Buf, buffer.Loc{h.Cursor.X, h.Cursor.Y - 1})
h.Buf.AddCursor(c)
h.Buf.SetCurCursor(h.Buf.NumCursors() - 1)
h.Buf.MergeCursors()
h.Relocate()
return true
}
// SpawnMultiCursorSelect adds a cursor at the beginning of each line of a selection
func (h *BufPane) SpawnMultiCursorSelect() bool {
// Avoid cases where multiple cursors already exist, that would create problems

View File

@@ -5,7 +5,7 @@ package action
import (
"syscall"
"github.com/zyedidia/micro/internal/screen"
"github.com/zyedidia/micro/v2/internal/screen"
)
// Suspend sends micro to the background. This is the same as pressing CtrlZ in most unix programs.

View File

@@ -5,22 +5,32 @@ import (
"errors"
"io/ioutil"
"os"
"path/filepath"
"strings"
"unicode"
"github.com/flynn/json5"
"github.com/zyedidia/micro/internal/config"
"github.com/zyedidia/micro/internal/screen"
"github.com/zyedidia/json5"
"github.com/zyedidia/micro/v2/internal/config"
"github.com/zyedidia/micro/v2/internal/screen"
"github.com/zyedidia/tcell"
)
func createBindingsIfNotExist(fname string) {
if _, e := os.Stat(fname); os.IsNotExist(e) {
ioutil.WriteFile(fname, []byte("{}"), 0644)
}
}
// InitBindings intializes the bindings map by reading from bindings.json
func InitBindings() {
config.Bindings = DefaultBindings()
var parsed map[string]string
defaults := DefaultBindings()
filename := config.ConfigDir + "/bindings.json"
filename := filepath.Join(config.ConfigDir, "bindings.json")
createBindingsIfNotExist(filename)
if _, e := os.Stat(filename); e == nil {
input, err := ioutil.ReadFile(filename)
if err != nil {
@@ -83,6 +93,7 @@ modSearch:
k = k[5:]
modifiers |= tcell.ModShift
case strings.HasPrefix(k, "\x1b"):
screen.Screen.RegisterRawSeq(k)
return RawEvent{
esc: k,
}, true
@@ -157,7 +168,8 @@ func TryBindKey(k, v string, overwrite bool) (bool, error) {
var e error
var parsed map[string]string
filename := config.ConfigDir + "/bindings.json"
filename := filepath.Join(config.ConfigDir, "bindings.json")
createBindingsIfNotExist(filename)
if _, e = os.Stat(filename); e == nil {
input, err := ioutil.ReadFile(filename)
if err != nil {
@@ -206,7 +218,8 @@ func UnbindKey(k string) error {
var e error
var parsed map[string]string
filename := config.ConfigDir + "/bindings.json"
filename := filepath.Join(config.ConfigDir, "bindings.json")
createBindingsIfNotExist(filename)
if _, e = os.Stat(filename); e == nil {
input, err := ioutil.ReadFile(filename)
if err != nil {
@@ -236,6 +249,7 @@ func UnbindKey(k string) error {
if a, ok := defaults[k]; ok {
BindKey(k, a)
} else if _, ok := config.Bindings[k]; ok {
BufUnmap(key)
delete(config.Bindings, k)
}
@@ -374,8 +388,6 @@ var keyEvents = map[string]tcell.Key{
"CtrlRightSq": tcell.KeyCtrlRightSq,
"CtrlCarat": tcell.KeyCtrlCarat,
"CtrlUnderscore": tcell.KeyCtrlUnderscore,
"CtrlPageUp": tcell.KeyCtrlPgUp,
"CtrlPageDown": tcell.KeyCtrlPgDn,
"Tab": tcell.KeyTab,
"Esc": tcell.KeyEsc,
"Escape": tcell.KeyEscape,
@@ -387,107 +399,3 @@ var keyEvents = map[string]tcell.Key{
"PgUp": tcell.KeyPgUp,
"PgDown": tcell.KeyPgDn,
}
// DefaultBindings returns a map containing micro's default keybindings
func DefaultBindings() map[string]string {
return map[string]string{
"Up": "CursorUp",
"Down": "CursorDown",
"Right": "CursorRight",
"Left": "CursorLeft",
"ShiftUp": "SelectUp",
"ShiftDown": "SelectDown",
"ShiftLeft": "SelectLeft",
"ShiftRight": "SelectRight",
"AltLeft": "WordLeft",
"AltRight": "WordRight",
"AltUp": "MoveLinesUp",
"AltDown": "MoveLinesDown",
"AltShiftRight": "SelectWordRight",
"AltShiftLeft": "SelectWordLeft",
"CtrlLeft": "StartOfLine",
"CtrlRight": "EndOfLine",
"CtrlShiftLeft": "SelectToStartOfLine",
"ShiftHome": "SelectToStartOfLine",
"CtrlShiftRight": "SelectToEndOfLine",
"ShiftEnd": "SelectToEndOfLine",
"CtrlUp": "CursorStart",
"CtrlDown": "CursorEnd",
"CtrlShiftUp": "SelectToStart",
"CtrlShiftDown": "SelectToEnd",
"Alt-{": "ParagraphPrevious",
"Alt-}": "ParagraphNext",
"Enter": "InsertNewline",
"CtrlH": "Backspace",
"Backspace": "Backspace",
"Alt-CtrlH": "DeleteWordLeft",
"Alt-Backspace": "DeleteWordLeft",
"Tab": "Autocomplete|IndentSelection|InsertTab",
"Backtab": "OutdentSelection|OutdentLine",
"CtrlO": "OpenFile",
"CtrlS": "Save",
"CtrlF": "Find",
"CtrlN": "FindNext",
"CtrlP": "FindPrevious",
"CtrlZ": "Undo",
"CtrlY": "Redo",
"CtrlC": "Copy",
"CtrlX": "Cut",
"CtrlK": "CutLine",
"CtrlD": "DuplicateLine",
"CtrlV": "Paste",
"CtrlA": "SelectAll",
"CtrlT": "AddTab",
"Alt,": "PreviousTab",
"Alt.": "NextTab",
"Home": "StartOfLine",
"End": "EndOfLine",
"CtrlHome": "CursorStart",
"CtrlEnd": "CursorEnd",
"PageUp": "CursorPageUp",
"PageDown": "CursorPageDown",
"CtrlPageUp": "PreviousTab",
"CtrlPageDown": "NextTab",
"CtrlG": "ToggleHelp",
"Alt-g": "ToggleKeyMenu",
"CtrlR": "ToggleRuler",
"CtrlL": "command-edit:goto ",
"Delete": "Delete",
"CtrlB": "ShellMode",
"CtrlQ": "Quit",
"CtrlE": "CommandMode",
"CtrlW": "NextSplit",
"CtrlU": "ToggleMacro",
"CtrlJ": "PlayMacro",
"Insert": "ToggleOverwriteMode",
// Emacs-style keybindings
"Alt-f": "WordRight",
"Alt-b": "WordLeft",
"Alt-a": "StartOfLine",
"Alt-e": "EndOfLine",
// "Alt-p": "CursorUp",
// "Alt-n": "CursorDown",
// Integration with file managers
"F2": "Save",
"F3": "Find",
"F4": "Quit",
"F7": "Find",
"F10": "Quit",
"Esc": "Escape",
// Mouse bindings
"MouseWheelUp": "ScrollUp",
"MouseWheelDown": "ScrollDown",
"MouseLeft": "MousePress",
"MouseMiddle": "PastePrimary",
"Ctrl-MouseLeft": "MouseMultiCursor",
"Alt-n": "SpawnMultiCursor",
"Alt-m": "SpawnMultiCursorSelect",
"Alt-p": "RemoveMultiCursor",
"Alt-c": "RemoveAllMultiCursors",
"Alt-x": "SkipMultiCursor",
}
}

View File

@@ -7,11 +7,11 @@ import (
luar "layeh.com/gopher-luar"
lua "github.com/yuin/gopher-lua"
"github.com/zyedidia/micro/internal/buffer"
"github.com/zyedidia/micro/internal/config"
"github.com/zyedidia/micro/internal/display"
ulua "github.com/zyedidia/micro/internal/lua"
"github.com/zyedidia/micro/internal/screen"
"github.com/zyedidia/micro/v2/internal/buffer"
"github.com/zyedidia/micro/v2/internal/config"
"github.com/zyedidia/micro/v2/internal/display"
ulua "github.com/zyedidia/micro/v2/internal/lua"
"github.com/zyedidia/micro/v2/internal/screen"
"github.com/zyedidia/tcell"
)
@@ -62,6 +62,8 @@ func BufMapKey(k Event, action string) {
break
}
// TODO: fix problem when complex bindings have these
// characters (escape them?)
idx := strings.IndexAny(action, "&|,")
a := action
if idx >= 0 {
@@ -74,7 +76,7 @@ func BufMapKey(k Event, action string) {
}
var afn func(*BufPane) bool
if strings.HasPrefix(action, "command:") {
if strings.HasPrefix(a, "command:") {
a = strings.SplitN(a, ":", 2)[1]
afn = CommandAction(a)
names = append(names, "")
@@ -86,15 +88,22 @@ func BufMapKey(k Event, action string) {
a = strings.SplitN(a, ":", 2)[1]
afn = LuaAction(a)
if afn == nil {
screen.TermMessage("Lua Error:", action, "does not exist")
screen.TermMessage("Lua Error:", a, "does not exist")
continue
}
names = append(names, "")
split := strings.SplitN(a, ".", 2)
if len(split) > 1 {
a = strings.Title(split[0]) + strings.Title(split[1])
} else {
a = strings.Title(a)
}
names = append(names, a)
} else if f, ok := BufKeyActions[a]; ok {
afn = f
names = append(names, a)
} else {
screen.TermMessage("Error:", action, "does not exist")
screen.TermMessage("Error in bindings: action", a, "does not exist")
continue
}
actionfns = append(actionfns, afn)
@@ -104,6 +113,9 @@ func BufMapKey(k Event, action string) {
success := true
for i, a := range actionfns {
for j, c := range cursors {
if c == nil {
continue
}
h.Buf.SetCurCursor(c.Num)
h.Cursor = c
if i == 0 || (success && types[i-1] == '&') || (!success && types[i-1] == '|') || (types[i-1] == ',') {
@@ -112,6 +124,8 @@ func BufMapKey(k Event, action string) {
break
}
}
// if the action changed the current pane, update the reference
h = MainTab().CurPane()
}
return true
}
@@ -127,6 +141,17 @@ func BufMapMouse(k MouseEvent, action string) {
}
}
// BufUnmap unmaps a key or mouse event from any action
func BufUnmap(k Event) {
delete(BufKeyBindings, k)
delete(BufKeyStrings, k)
switch e := k.(type) {
case MouseEvent:
delete(BufMouseBindings, e)
}
}
// The BufPane connects the buffer and the window
// It provides a cursor (or multiple) and defines a set of actions
// that can be taken on the buffer
@@ -167,21 +192,24 @@ type BufPane struct {
tripleClick bool
// Last search stores the last successful search for FindNext and FindPrev
lastSearch string
lastSearch string
lastSearchRegex bool
// Should the current multiple cursor selection search based on word or
// based on selection (false for selection, true for word)
multiWord bool
splitID uint64
tab *Tab
// remember original location of a search in case the search is canceled
searchOrig buffer.Loc
}
func NewBufPane(buf *buffer.Buffer, win display.BWindow) *BufPane {
func NewBufPane(buf *buffer.Buffer, win display.BWindow, tab *Tab) *BufPane {
h := new(BufPane)
h.Buf = buf
h.BWindow = win
h.tab = tab
h.Cursor = h.Buf.GetActiveCursor()
h.mouseReleased = true
@@ -191,9 +219,23 @@ func NewBufPane(buf *buffer.Buffer, win display.BWindow) *BufPane {
return h
}
func NewBufPaneFromBuf(buf *buffer.Buffer) *BufPane {
func NewBufPaneFromBuf(buf *buffer.Buffer, tab *Tab) *BufPane {
w := display.NewBufWindow(0, 0, 0, 0, buf)
return NewBufPane(buf, w)
return NewBufPane(buf, w, tab)
}
func (h *BufPane) SetTab(t *Tab) {
h.tab = t
}
func (h *BufPane) Tab() *Tab {
return h.tab
}
func (h *BufPane) ResizePane(size int) {
n := h.tab.GetNode(h.splitID)
n.ResizeSplit(size)
h.tab.Resize()
}
// PluginCB calls all plugin callbacks with a certain name and
@@ -223,8 +265,6 @@ func (h *BufPane) OpenBuffer(b *buffer.Buffer) {
h.BWindow.SetBuffer(b)
h.Cursor = b.GetActiveCursor()
h.Resize(h.GetView().Width, h.GetView().Height)
v := new(display.View)
h.SetView(v)
h.Relocate()
// Set mouseReleased to true because we assume the mouse is not being pressed when
// the editor is opened
@@ -244,17 +284,38 @@ func (h *BufPane) SetID(i uint64) {
}
func (h *BufPane) Name() string {
return h.Buf.GetName()
n := h.Buf.GetName()
if h.Buf.Modified() {
n += " +"
}
return n
}
// HandleEvent executes the tcell event properly
func (h *BufPane) HandleEvent(event tcell.Event) {
if h.Buf.ExternallyModified() && !h.Buf.ReloadDisabled {
InfoBar.YNPrompt("The file on disk has changed. Reload file? (y,n,esc)", func(yes, canceled bool) {
if canceled {
h.Buf.DisableReload()
}
if !yes || canceled {
h.Buf.UpdateModTime()
} else {
h.Buf.ReOpen()
}
})
}
switch e := event.(type) {
case *tcell.EventRaw:
re := RawEvent{
esc: e.EscSeq(),
}
h.DoKeyEvent(re)
case *tcell.EventPaste:
h.paste(e.Text())
h.Relocate()
case *tcell.EventKey:
ke := KeyEvent{
code: e.Key(),
@@ -267,14 +328,27 @@ func (h *BufPane) HandleEvent(event tcell.Event) {
h.DoRuneInsert(e.Rune())
}
case *tcell.EventMouse:
cancel := false
switch e.Buttons() {
case tcell.Button1:
_, my := e.Position()
if h.Buf.Type.Kind != buffer.BTInfo.Kind && h.Buf.Settings["statusline"].(bool) && my >= h.GetView().Y+h.GetView().Height-1 {
cancel = true
}
case tcell.ButtonNone:
// Mouse event with no click
if !h.mouseReleased {
// Mouse was just released
mx, my := e.Position()
mouseLoc := h.LocFromVisual(buffer.Loc{X: mx, Y: my})
// mx, my := e.Position()
// mouseLoc := h.LocFromVisual(buffer.Loc{X: mx, Y: my})
// we could finish the selection based on the release location as described
// below but when the mouse click is within the scroll margin this will
// cause a scroll and selection even for a simple mouse click which is
// not good
// for terminals that don't support mouse motion events, selection via
// the mouse won't work but this is ok
// Relocating here isn't really necessary because the cursor will
// be in the right place from the last mouse event
@@ -282,20 +356,23 @@ func (h *BufPane) HandleEvent(event tcell.Event) {
// events, this still allows the user to make selections, except only after they
// release the mouse
if !h.doubleClick && !h.tripleClick {
h.Cursor.Loc = mouseLoc
h.Cursor.SetSelectionEnd(h.Cursor.Loc)
// if !h.doubleClick && !h.tripleClick {
// h.Cursor.SetSelectionEnd(h.Cursor.Loc)
// }
if h.Cursor.HasSelection() {
h.Cursor.CopySelection("primary")
}
h.mouseReleased = true
}
}
me := MouseEvent{
btn: e.Buttons(),
mod: e.Modifiers(),
if !cancel {
me := MouseEvent{
btn: e.Buttons(),
mod: e.Modifiers(),
}
h.DoMouseEvent(me, e)
}
h.DoMouseEvent(me, e)
}
h.Buf.MergeCursors()
@@ -326,15 +403,15 @@ func (h *BufPane) DoKeyEvent(e Event) bool {
}
func (h *BufPane) execAction(action func(*BufPane) bool, name string, cursor int) bool {
if name != "Autocomplete" {
if name != "Autocomplete" && name != "CycleAutocompleteBack" {
h.Buf.HasSuggestions = false
}
_, isMulti := MultiActions[name]
if (!isMulti && cursor == 0) || isMulti {
if h.PluginCB("pre" + name) {
asuccess := action(h)
psuccess := h.PluginCB("on" + name)
success := action(h)
success = success && h.PluginCB("on"+name)
if isMulti {
if recording_macro {
@@ -344,13 +421,17 @@ func (h *BufPane) execAction(action func(*BufPane) bool, name string, cursor int
}
}
return asuccess && psuccess
return success
}
}
return false
}
func (h *BufPane) completeAction(action string) {
h.PluginCB("on" + action)
}
func (h *BufPane) HasKeyEvent(e Event) bool {
_, ok := BufKeyBindings[e]
return ok
@@ -396,26 +477,34 @@ func (h *BufPane) DoRuneInsert(r rune) {
if recording_macro {
curmacro = append(curmacro, r)
}
h.Relocate()
h.PluginCBRune("onRune", r)
}
}
func (h *BufPane) VSplitBuf(buf *buffer.Buffer) *BufPane {
e := NewBufPaneFromBuf(buf)
e.splitID = MainTab().GetNode(h.splitID).VSplit(h.Buf.Settings["splitright"].(bool))
func (h *BufPane) VSplitIndex(buf *buffer.Buffer, right bool) *BufPane {
e := NewBufPaneFromBuf(buf, h.tab)
e.splitID = MainTab().GetNode(h.splitID).VSplit(right)
MainTab().Panes = append(MainTab().Panes, e)
MainTab().Resize()
MainTab().SetActive(len(MainTab().Panes) - 1)
return e
}
func (h *BufPane) HSplitBuf(buf *buffer.Buffer) *BufPane {
e := NewBufPaneFromBuf(buf)
e.splitID = MainTab().GetNode(h.splitID).HSplit(h.Buf.Settings["splitbottom"].(bool))
func (h *BufPane) HSplitIndex(buf *buffer.Buffer, bottom bool) *BufPane {
e := NewBufPaneFromBuf(buf, h.tab)
e.splitID = MainTab().GetNode(h.splitID).HSplit(bottom)
MainTab().Panes = append(MainTab().Panes, e)
MainTab().Resize()
MainTab().SetActive(len(MainTab().Panes) - 1)
return e
}
func (h *BufPane) VSplitBuf(buf *buffer.Buffer) *BufPane {
return h.VSplitIndex(buf, h.Buf.Settings["splitright"].(bool))
}
func (h *BufPane) HSplitBuf(buf *buffer.Buffer) *BufPane {
return h.HSplitIndex(buf, h.Buf.Settings["splitbottom"].(bool))
}
func (h *BufPane) Close() {
h.Buf.Close()
}
@@ -442,99 +531,111 @@ func (h *BufPane) SetActive(b bool) {
// BufKeyActions contains the list of all possible key actions the bufhandler could execute
var BufKeyActions = map[string]BufKeyAction{
"CursorUp": (*BufPane).CursorUp,
"CursorDown": (*BufPane).CursorDown,
"CursorPageUp": (*BufPane).CursorPageUp,
"CursorPageDown": (*BufPane).CursorPageDown,
"CursorLeft": (*BufPane).CursorLeft,
"CursorRight": (*BufPane).CursorRight,
"CursorStart": (*BufPane).CursorStart,
"CursorEnd": (*BufPane).CursorEnd,
"SelectToStart": (*BufPane).SelectToStart,
"SelectToEnd": (*BufPane).SelectToEnd,
"SelectUp": (*BufPane).SelectUp,
"SelectDown": (*BufPane).SelectDown,
"SelectLeft": (*BufPane).SelectLeft,
"SelectRight": (*BufPane).SelectRight,
"WordRight": (*BufPane).WordRight,
"WordLeft": (*BufPane).WordLeft,
"SelectWordRight": (*BufPane).SelectWordRight,
"SelectWordLeft": (*BufPane).SelectWordLeft,
"DeleteWordRight": (*BufPane).DeleteWordRight,
"DeleteWordLeft": (*BufPane).DeleteWordLeft,
"SelectLine": (*BufPane).SelectLine,
"SelectToStartOfLine": (*BufPane).SelectToStartOfLine,
"SelectToEndOfLine": (*BufPane).SelectToEndOfLine,
"ParagraphPrevious": (*BufPane).ParagraphPrevious,
"ParagraphNext": (*BufPane).ParagraphNext,
"InsertNewline": (*BufPane).InsertNewline,
"Backspace": (*BufPane).Backspace,
"Delete": (*BufPane).Delete,
"InsertTab": (*BufPane).InsertTab,
"Save": (*BufPane).Save,
"SaveAll": (*BufPane).SaveAll,
"SaveAs": (*BufPane).SaveAs,
"Find": (*BufPane).Find,
"FindNext": (*BufPane).FindNext,
"FindPrevious": (*BufPane).FindPrevious,
"Center": (*BufPane).Center,
"Undo": (*BufPane).Undo,
"Redo": (*BufPane).Redo,
"Copy": (*BufPane).Copy,
"Cut": (*BufPane).Cut,
"CutLine": (*BufPane).CutLine,
"DuplicateLine": (*BufPane).DuplicateLine,
"DeleteLine": (*BufPane).DeleteLine,
"MoveLinesUp": (*BufPane).MoveLinesUp,
"MoveLinesDown": (*BufPane).MoveLinesDown,
"IndentSelection": (*BufPane).IndentSelection,
"OutdentSelection": (*BufPane).OutdentSelection,
"Autocomplete": (*BufPane).Autocomplete,
"OutdentLine": (*BufPane).OutdentLine,
"Paste": (*BufPane).Paste,
"PastePrimary": (*BufPane).PastePrimary,
"SelectAll": (*BufPane).SelectAll,
"OpenFile": (*BufPane).OpenFile,
"Start": (*BufPane).Start,
"End": (*BufPane).End,
"PageUp": (*BufPane).PageUp,
"PageDown": (*BufPane).PageDown,
"SelectPageUp": (*BufPane).SelectPageUp,
"SelectPageDown": (*BufPane).SelectPageDown,
"HalfPageUp": (*BufPane).HalfPageUp,
"HalfPageDown": (*BufPane).HalfPageDown,
"StartOfLine": (*BufPane).StartOfLine,
"EndOfLine": (*BufPane).EndOfLine,
"ToggleHelp": (*BufPane).ToggleHelp,
"ToggleKeyMenu": (*BufPane).ToggleKeyMenu,
"ToggleRuler": (*BufPane).ToggleRuler,
"ClearStatus": (*BufPane).ClearStatus,
"ShellMode": (*BufPane).ShellMode,
"CommandMode": (*BufPane).CommandMode,
"ToggleOverwriteMode": (*BufPane).ToggleOverwriteMode,
"Escape": (*BufPane).Escape,
"Quit": (*BufPane).Quit,
"QuitAll": (*BufPane).QuitAll,
"AddTab": (*BufPane).AddTab,
"PreviousTab": (*BufPane).PreviousTab,
"NextTab": (*BufPane).NextTab,
"NextSplit": (*BufPane).NextSplit,
"PreviousSplit": (*BufPane).PreviousSplit,
"Unsplit": (*BufPane).Unsplit,
"VSplit": (*BufPane).VSplitAction,
"HSplit": (*BufPane).HSplitAction,
"ToggleMacro": (*BufPane).ToggleMacro,
"PlayMacro": (*BufPane).PlayMacro,
"Suspend": (*BufPane).Suspend,
"ScrollUp": (*BufPane).ScrollUpAction,
"ScrollDown": (*BufPane).ScrollDownAction,
"SpawnMultiCursor": (*BufPane).SpawnMultiCursor,
"SpawnMultiCursorSelect": (*BufPane).SpawnMultiCursorSelect,
"RemoveMultiCursor": (*BufPane).RemoveMultiCursor,
"RemoveAllMultiCursors": (*BufPane).RemoveAllMultiCursors,
"SkipMultiCursor": (*BufPane).SkipMultiCursor,
"JumpToMatchingBrace": (*BufPane).JumpToMatchingBrace,
"None": (*BufPane).None,
"CursorUp": (*BufPane).CursorUp,
"CursorDown": (*BufPane).CursorDown,
"CursorPageUp": (*BufPane).CursorPageUp,
"CursorPageDown": (*BufPane).CursorPageDown,
"CursorLeft": (*BufPane).CursorLeft,
"CursorRight": (*BufPane).CursorRight,
"CursorStart": (*BufPane).CursorStart,
"CursorEnd": (*BufPane).CursorEnd,
"SelectToStart": (*BufPane).SelectToStart,
"SelectToEnd": (*BufPane).SelectToEnd,
"SelectUp": (*BufPane).SelectUp,
"SelectDown": (*BufPane).SelectDown,
"SelectLeft": (*BufPane).SelectLeft,
"SelectRight": (*BufPane).SelectRight,
"WordRight": (*BufPane).WordRight,
"WordLeft": (*BufPane).WordLeft,
"SelectWordRight": (*BufPane).SelectWordRight,
"SelectWordLeft": (*BufPane).SelectWordLeft,
"DeleteWordRight": (*BufPane).DeleteWordRight,
"DeleteWordLeft": (*BufPane).DeleteWordLeft,
"SelectLine": (*BufPane).SelectLine,
"SelectToStartOfLine": (*BufPane).SelectToStartOfLine,
"SelectToStartOfText": (*BufPane).SelectToStartOfText,
"SelectToStartOfTextToggle": (*BufPane).SelectToStartOfTextToggle,
"SelectToEndOfLine": (*BufPane).SelectToEndOfLine,
"ParagraphPrevious": (*BufPane).ParagraphPrevious,
"ParagraphNext": (*BufPane).ParagraphNext,
"InsertNewline": (*BufPane).InsertNewline,
"Backspace": (*BufPane).Backspace,
"Delete": (*BufPane).Delete,
"InsertTab": (*BufPane).InsertTab,
"Save": (*BufPane).Save,
"SaveAll": (*BufPane).SaveAll,
"SaveAs": (*BufPane).SaveAs,
"Find": (*BufPane).Find,
"FindLiteral": (*BufPane).FindLiteral,
"FindNext": (*BufPane).FindNext,
"FindPrevious": (*BufPane).FindPrevious,
"Center": (*BufPane).Center,
"Undo": (*BufPane).Undo,
"Redo": (*BufPane).Redo,
"Copy": (*BufPane).Copy,
"CopyLine": (*BufPane).CopyLine,
"Cut": (*BufPane).Cut,
"CutLine": (*BufPane).CutLine,
"DuplicateLine": (*BufPane).DuplicateLine,
"DeleteLine": (*BufPane).DeleteLine,
"MoveLinesUp": (*BufPane).MoveLinesUp,
"MoveLinesDown": (*BufPane).MoveLinesDown,
"IndentSelection": (*BufPane).IndentSelection,
"OutdentSelection": (*BufPane).OutdentSelection,
"Autocomplete": (*BufPane).Autocomplete,
"CycleAutocompleteBack": (*BufPane).CycleAutocompleteBack,
"OutdentLine": (*BufPane).OutdentLine,
"IndentLine": (*BufPane).IndentLine,
"Paste": (*BufPane).Paste,
"PastePrimary": (*BufPane).PastePrimary,
"SelectAll": (*BufPane).SelectAll,
"OpenFile": (*BufPane).OpenFile,
"Start": (*BufPane).Start,
"End": (*BufPane).End,
"PageUp": (*BufPane).PageUp,
"PageDown": (*BufPane).PageDown,
"SelectPageUp": (*BufPane).SelectPageUp,
"SelectPageDown": (*BufPane).SelectPageDown,
"HalfPageUp": (*BufPane).HalfPageUp,
"HalfPageDown": (*BufPane).HalfPageDown,
"StartOfText": (*BufPane).StartOfText,
"StartOfTextToggle": (*BufPane).StartOfTextToggle,
"StartOfLine": (*BufPane).StartOfLine,
"EndOfLine": (*BufPane).EndOfLine,
"ToggleHelp": (*BufPane).ToggleHelp,
"ToggleKeyMenu": (*BufPane).ToggleKeyMenu,
"ToggleDiffGutter": (*BufPane).ToggleDiffGutter,
"ToggleRuler": (*BufPane).ToggleRuler,
"ClearStatus": (*BufPane).ClearStatus,
"ShellMode": (*BufPane).ShellMode,
"CommandMode": (*BufPane).CommandMode,
"ToggleOverwriteMode": (*BufPane).ToggleOverwriteMode,
"Escape": (*BufPane).Escape,
"Quit": (*BufPane).Quit,
"QuitAll": (*BufPane).QuitAll,
"AddTab": (*BufPane).AddTab,
"PreviousTab": (*BufPane).PreviousTab,
"NextTab": (*BufPane).NextTab,
"NextSplit": (*BufPane).NextSplit,
"PreviousSplit": (*BufPane).PreviousSplit,
"Unsplit": (*BufPane).Unsplit,
"VSplit": (*BufPane).VSplitAction,
"HSplit": (*BufPane).HSplitAction,
"ToggleMacro": (*BufPane).ToggleMacro,
"PlayMacro": (*BufPane).PlayMacro,
"Suspend": (*BufPane).Suspend,
"ScrollUp": (*BufPane).ScrollUpAction,
"ScrollDown": (*BufPane).ScrollDownAction,
"SpawnMultiCursor": (*BufPane).SpawnMultiCursor,
"SpawnMultiCursorUp": (*BufPane).SpawnMultiCursorUp,
"SpawnMultiCursorDown": (*BufPane).SpawnMultiCursorDown,
"SpawnMultiCursorSelect": (*BufPane).SpawnMultiCursorSelect,
"RemoveMultiCursor": (*BufPane).RemoveMultiCursor,
"RemoveAllMultiCursors": (*BufPane).RemoveAllMultiCursors,
"SkipMultiCursor": (*BufPane).SkipMultiCursor,
"JumpToMatchingBrace": (*BufPane).JumpToMatchingBrace,
"JumpLine": (*BufPane).JumpLine,
"None": (*BufPane).None,
// This was changed to InsertNewline but I don't want to break backwards compatibility
"InsertEnter": (*BufPane).InsertNewline,
@@ -551,51 +652,57 @@ var BufMouseActions = map[string]BufMouseAction{
// Generally actions that modify global editor state like quitting or
// saving should not be included in this list
var MultiActions = map[string]bool{
"CursorUp": true,
"CursorDown": true,
"CursorPageUp": true,
"CursorPageDown": true,
"CursorLeft": true,
"CursorRight": true,
"CursorStart": true,
"CursorEnd": true,
"SelectToStart": true,
"SelectToEnd": true,
"SelectUp": true,
"SelectDown": true,
"SelectLeft": true,
"SelectRight": true,
"WordRight": true,
"WordLeft": true,
"SelectWordRight": true,
"SelectWordLeft": true,
"DeleteWordRight": true,
"DeleteWordLeft": true,
"SelectLine": true,
"SelectToStartOfLine": true,
"SelectToEndOfLine": true,
"ParagraphPrevious": true,
"ParagraphNext": true,
"InsertNewline": true,
"Backspace": true,
"Delete": true,
"InsertTab": true,
"FindNext": true,
"FindPrevious": true,
"Cut": true,
"CutLine": true,
"DuplicateLine": true,
"DeleteLine": true,
"MoveLinesUp": true,
"MoveLinesDown": true,
"IndentSelection": true,
"OutdentSelection": true,
"OutdentLine": true,
"Paste": true,
"PastePrimary": true,
"SelectPageUp": true,
"SelectPageDown": true,
"StartOfLine": true,
"EndOfLine": true,
"JumpToMatchingBrace": true,
"CursorUp": true,
"CursorDown": true,
"CursorPageUp": true,
"CursorPageDown": true,
"CursorLeft": true,
"CursorRight": true,
"CursorStart": true,
"CursorEnd": true,
"SelectToStart": true,
"SelectToEnd": true,
"SelectUp": true,
"SelectDown": true,
"SelectLeft": true,
"SelectRight": true,
"WordRight": true,
"WordLeft": true,
"SelectWordRight": true,
"SelectWordLeft": true,
"DeleteWordRight": true,
"DeleteWordLeft": true,
"SelectLine": true,
"SelectToStartOfLine": true,
"SelectToStartOfText": true,
"SelectToStartOfTextToggle": true,
"SelectToEndOfLine": true,
"ParagraphPrevious": true,
"ParagraphNext": true,
"InsertNewline": true,
"Backspace": true,
"Delete": true,
"InsertTab": true,
"FindNext": true,
"FindPrevious": true,
"CopyLine": true,
"Cut": true,
"CutLine": true,
"DuplicateLine": true,
"DeleteLine": true,
"MoveLinesUp": true,
"MoveLinesDown": true,
"IndentSelection": true,
"OutdentSelection": true,
"OutdentLine": true,
"IndentLine": true,
"Paste": true,
"PastePrimary": true,
"SelectPageUp": true,
"SelectPageDown": true,
"StartOfLine": true,
"StartOfText": true,
"StartOfTextToggle": true,
"EndOfLine": true,
"JumpToMatchingBrace": true,
}

View File

@@ -10,18 +10,13 @@ import (
"regexp"
"strconv"
"strings"
"unicode/utf8"
luar "layeh.com/gopher-luar"
lua "github.com/yuin/gopher-lua"
"github.com/zyedidia/micro/internal/buffer"
"github.com/zyedidia/micro/internal/config"
ulua "github.com/zyedidia/micro/internal/lua"
"github.com/zyedidia/micro/internal/screen"
"github.com/zyedidia/micro/internal/shell"
"github.com/zyedidia/micro/internal/util"
"github.com/zyedidia/micro/pkg/shellwords"
shellquote "github.com/kballard/go-shellquote"
"github.com/zyedidia/micro/v2/internal/buffer"
"github.com/zyedidia/micro/v2/internal/config"
"github.com/zyedidia/micro/v2/internal/screen"
"github.com/zyedidia/micro/v2/internal/shell"
"github.com/zyedidia/micro/v2/internal/util"
)
// A Command contains information about how to execute a command
@@ -35,65 +30,46 @@ var commands map[string]Command
func InitCommands() {
commands = map[string]Command{
"set": Command{(*BufPane).SetCmd, OptionValueComplete},
"reset": Command{(*BufPane).ResetCmd, OptionValueComplete},
"setlocal": Command{(*BufPane).SetLocalCmd, OptionValueComplete},
"show": Command{(*BufPane).ShowCmd, OptionComplete},
"showkey": Command{(*BufPane).ShowKeyCmd, nil},
"run": Command{(*BufPane).RunCmd, nil},
"bind": Command{(*BufPane).BindCmd, nil},
"unbind": Command{(*BufPane).UnbindCmd, nil},
"quit": Command{(*BufPane).QuitCmd, nil},
"goto": Command{(*BufPane).GotoCmd, nil},
"save": Command{(*BufPane).SaveCmd, nil},
"replace": Command{(*BufPane).ReplaceCmd, nil},
"replaceall": Command{(*BufPane).ReplaceAllCmd, nil},
"vsplit": Command{(*BufPane).VSplitCmd, buffer.FileComplete},
"hsplit": Command{(*BufPane).HSplitCmd, buffer.FileComplete},
"tab": Command{(*BufPane).NewTabCmd, buffer.FileComplete},
"help": Command{(*BufPane).HelpCmd, HelpComplete},
"eval": Command{(*BufPane).EvalCmd, nil},
"log": Command{(*BufPane).ToggleLogCmd, nil},
"plugin": Command{(*BufPane).PluginCmd, PluginComplete},
"reload": Command{(*BufPane).ReloadCmd, nil},
"reopen": Command{(*BufPane).ReopenCmd, nil},
"cd": Command{(*BufPane).CdCmd, buffer.FileComplete},
"pwd": Command{(*BufPane).PwdCmd, nil},
"open": Command{(*BufPane).OpenCmd, buffer.FileComplete},
"tabswitch": Command{(*BufPane).TabSwitchCmd, nil},
"term": Command{(*BufPane).TermCmd, nil},
"memusage": Command{(*BufPane).MemUsageCmd, nil},
"retab": Command{(*BufPane).RetabCmd, nil},
"raw": Command{(*BufPane).RawCmd, nil},
"textfilter": Command{(*BufPane).TextFilterCmd, nil},
"set": {(*BufPane).SetCmd, OptionValueComplete},
"reset": {(*BufPane).ResetCmd, OptionValueComplete},
"setlocal": {(*BufPane).SetLocalCmd, OptionValueComplete},
"show": {(*BufPane).ShowCmd, OptionComplete},
"showkey": {(*BufPane).ShowKeyCmd, nil},
"run": {(*BufPane).RunCmd, nil},
"bind": {(*BufPane).BindCmd, nil},
"unbind": {(*BufPane).UnbindCmd, nil},
"quit": {(*BufPane).QuitCmd, nil},
"goto": {(*BufPane).GotoCmd, nil},
"save": {(*BufPane).SaveCmd, nil},
"replace": {(*BufPane).ReplaceCmd, nil},
"replaceall": {(*BufPane).ReplaceAllCmd, nil},
"vsplit": {(*BufPane).VSplitCmd, buffer.FileComplete},
"hsplit": {(*BufPane).HSplitCmd, buffer.FileComplete},
"tab": {(*BufPane).NewTabCmd, buffer.FileComplete},
"help": {(*BufPane).HelpCmd, HelpComplete},
"eval": {(*BufPane).EvalCmd, nil},
"log": {(*BufPane).ToggleLogCmd, nil},
"plugin": {(*BufPane).PluginCmd, PluginComplete},
"reload": {(*BufPane).ReloadCmd, nil},
"reopen": {(*BufPane).ReopenCmd, nil},
"cd": {(*BufPane).CdCmd, buffer.FileComplete},
"pwd": {(*BufPane).PwdCmd, nil},
"open": {(*BufPane).OpenCmd, buffer.FileComplete},
"tabmove": {(*BufPane).TabMoveCmd, nil},
"tabswitch": {(*BufPane).TabSwitchCmd, nil},
"term": {(*BufPane).TermCmd, nil},
"memusage": {(*BufPane).MemUsageCmd, nil},
"retab": {(*BufPane).RetabCmd, nil},
"raw": {(*BufPane).RawCmd, nil},
"textfilter": {(*BufPane).TextFilterCmd, nil},
}
}
// MakeCommand is a function to easily create new commands
// This can be called by plugins in Lua so that plugins can define their own commands
func LuaMakeCommand(name, function string, completer buffer.Completer) {
action := LuaFunctionCommand(function)
commands[name] = Command{action, completer}
}
// LuaFunctionCommand returns a normal function
// so that a command can be bound to a lua function
func LuaFunctionCommand(fn string) func(*BufPane, []string) {
luaFn := strings.Split(fn, ".")
if len(luaFn) <= 1 {
return nil
}
plName, plFn := luaFn[0], luaFn[1]
pl := config.FindPlugin(plName)
if pl == nil {
return nil
}
return func(bp *BufPane, args []string) {
luaArgs := []lua.LValue{luar.New(ulua.L, bp), luar.New(ulua.L, args)}
_, err := pl.Call(plFn, luaArgs...)
if err != nil {
screen.TermMessage(err)
}
func MakeCommand(name string, action func(bp *BufPane, args []string), completer buffer.Completer) {
if action != nil {
commands[name] = Command{action, completer}
}
}
@@ -120,106 +96,20 @@ func CommandAction(cmd string) BufKeyAction {
}
}
var PluginCmds = []string{"list", "info", "version"}
var PluginCmds = []string{"install", "remove", "update", "available", "list", "search"}
// PluginCmd installs, removes, updates, lists, or searches for given plugins
func (h *BufPane) PluginCmd(args []string) {
if len(args) <= 0 {
InfoBar.Error("Not enough arguments, see 'help commands'")
if len(args) < 1 {
InfoBar.Error("Not enough arguments")
return
}
valid := true
switch args[0] {
case "list":
for _, pl := range config.Plugins {
var en string
if pl.IsEnabled() {
en = "enabled"
} else {
en = "disabled"
}
WriteLog(fmt.Sprintf("%s: %s", pl.Name, en))
if pl.Default {
WriteLog(" (default)\n")
} else {
WriteLog("\n")
}
}
WriteLog("Default plugins come pre-installed with micro.")
case "version":
if len(args) <= 1 {
InfoBar.Error("No plugin provided to give info for")
return
}
found := false
for _, pl := range config.Plugins {
if pl.Name == args[1] {
found = true
if pl.Info == nil {
InfoBar.Message("Sorry no version for", pl.Name)
return
}
WriteLog("Version: " + pl.Info.Vstr + "\n")
}
}
if !found {
InfoBar.Message(args[1], "is not installed")
}
case "info":
if len(args) <= 1 {
InfoBar.Error("No plugin provided to give info for")
return
}
found := false
for _, pl := range config.Plugins {
if pl.Name == args[1] {
found = true
if pl.Info == nil {
InfoBar.Message("Sorry no info for ", pl.Name)
return
}
var buffer bytes.Buffer
buffer.WriteString("Name: ")
buffer.WriteString(pl.Info.Name)
buffer.WriteString("\n")
buffer.WriteString("Description: ")
buffer.WriteString(pl.Info.Desc)
buffer.WriteString("\n")
buffer.WriteString("Website: ")
buffer.WriteString(pl.Info.Site)
buffer.WriteString("\n")
buffer.WriteString("Installation link: ")
buffer.WriteString(pl.Info.Install)
buffer.WriteString("\n")
buffer.WriteString("Version: ")
buffer.WriteString(pl.Info.Vstr)
buffer.WriteString("\n")
buffer.WriteString("Requirements:")
buffer.WriteString("\n")
for _, r := range pl.Info.Require {
buffer.WriteString(" - ")
buffer.WriteString(r)
buffer.WriteString("\n")
}
WriteLog(buffer.String())
}
}
if !found {
InfoBar.Message(args[1], "is not installed")
return
}
default:
InfoBar.Error("Not a valid plugin command")
return
if h.Buf.Type != buffer.BTLog {
h.OpenLogBuf()
}
if valid && h.Buf.Type != buffer.BTLog {
OpenLogBuf(h)
}
config.PluginCommand(buffer.LogBuf, args[0], args[1:])
}
// RetabCmd changes all spaces to tabs or all tabs to spaces
@@ -233,14 +123,14 @@ func (h *BufPane) RetabCmd(args []string) {
func (h *BufPane) RawCmd(args []string) {
width, height := screen.Screen.Size()
iOffset := config.GetInfoBarOffset()
tp := NewTabFromPane(0, 0, width, height-iOffset, NewRawPane())
tp := NewTabFromPane(0, 0, width, height-iOffset, NewRawPane(nil))
Tabs.AddTab(tp)
Tabs.SetActive(len(Tabs.List) - 1)
}
// TextFilterCmd filters the selection through the command.
// Selection goes to the command input.
// On successfull run command output replaces the current selection.
// On successful run command output replaces the current selection.
func (h *BufPane) TextFilterCmd(args []string) {
if len(args) == 0 {
InfoBar.Error("usage: textfilter arguments")
@@ -265,6 +155,56 @@ func (h *BufPane) TextFilterCmd(args []string) {
h.Buf.Insert(h.Cursor.Loc, bout.String())
}
// TabMoveCmd moves the current tab to a given index (starts at 1). The
// displaced tabs are moved up.
func (h *BufPane) TabMoveCmd(args []string) {
if len(args) <= 0 {
InfoBar.Error("Not enough arguments: provide an index, starting at 1")
return
}
if len(args[0]) <= 0 {
InfoBar.Error("Invalid argument: empty string")
return
}
num, err := strconv.Atoi(args[0])
if err != nil {
InfoBar.Error("Invalid argument: ", err)
return
}
// Preserve sign for relative move, if one exists
var shiftDirection byte
if strings.Contains("-+", string([]byte{args[0][0]})) {
shiftDirection = args[0][0]
}
// Relative positions -> absolute positions
idxFrom := Tabs.Active()
idxTo := 0
offset := util.Abs(num)
if shiftDirection == '-' {
idxTo = idxFrom - offset
} else if shiftDirection == '+' {
idxTo = idxFrom + offset
} else {
idxTo = offset - 1
}
// Restrain position to within the valid range
idxTo = util.Clamp(idxTo, 0, len(Tabs.List)-1)
activeTab := Tabs.List[idxFrom]
Tabs.RemoveTab(activeTab.ID())
Tabs.List = append(Tabs.List, nil)
copy(Tabs.List[idxTo+1:], Tabs.List[idxTo:])
Tabs.List[idxTo] = activeTab
Tabs.UpdateNames()
Tabs.SetActive(idxTo)
// InfoBar.Message(fmt.Sprintf("Moved tab from slot %d to %d", idxFrom+1, idxTo+1))
}
// TabSwitchCmd switches to a given tab either by name or by number
func (h *BufPane) TabSwitchCmd(args []string) {
if len(args) > 0 {
@@ -344,11 +284,14 @@ func (h *BufPane) OpenCmd(args []string) {
if len(args) > 0 {
filename := args[0]
// the filename might or might not be quoted, so unquote first then join the strings.
args, err := shellwords.Split(filename)
args, err := shellquote.Split(filename)
if err != nil {
InfoBar.Error("Error parsing args ", err)
return
}
if len(args) == 0 {
return
}
filename = strings.Join(args, " ")
open := func() {
@@ -379,7 +322,7 @@ func (h *BufPane) OpenCmd(args []string) {
// ToggleLogCmd toggles the log view
func (h *BufPane) ToggleLogCmd(args []string) {
if h.Buf.Type != buffer.BTLog {
OpenLogBuf(h)
h.OpenLogBuf()
} else {
h.Quit()
}
@@ -396,7 +339,10 @@ func ReloadConfig() {
if err != nil {
screen.TermMessage(err)
}
config.InitGlobalSettings()
err = config.InitGlobalSettings()
if err != nil {
screen.TermMessage(err)
}
InitBindings()
InitCommands()
@@ -497,6 +443,7 @@ func (h *BufPane) HSplitCmd(args []string) {
// EvalCmd evaluates a lua expression
func (h *BufPane) EvalCmd(args []string) {
InfoBar.Error("Eval unsupported")
}
// NewTabCmd opens the given file in a new tab
@@ -523,37 +470,56 @@ func (h *BufPane) NewTabCmd(args []string) {
}
func SetGlobalOptionNative(option string, nativeValue interface{}) error {
config.GlobalSettings[option] = nativeValue
local := false
for _, s := range config.LocalSettings {
if s == option {
local = true
break
}
}
if option == "colorscheme" {
// LoadSyntaxFiles()
config.InitColorscheme()
for _, b := range buffer.OpenBuffers {
b.UpdateRules()
}
} else if option == "infobar" || option == "keymenu" {
Tabs.Resize()
} else if option == "mouse" {
if !nativeValue.(bool) {
screen.Screen.DisableMouse()
if !local {
config.GlobalSettings[option] = nativeValue
config.ModifiedSettings[option] = true
if option == "colorscheme" {
// LoadSyntaxFiles()
config.InitColorscheme()
for _, b := range buffer.OpenBuffers {
b.UpdateRules()
}
} else if option == "infobar" || option == "keymenu" {
Tabs.Resize()
} else if option == "mouse" {
if !nativeValue.(bool) {
screen.Screen.DisableMouse()
} else {
screen.Screen.EnableMouse()
}
} else if option == "autosave" {
if nativeValue.(float64) > 0 {
config.SetAutoTime(int(nativeValue.(float64)))
config.StartAutoSave()
} else {
config.SetAutoTime(0)
}
} else if option == "paste" {
screen.Screen.SetPaste(nativeValue.(bool))
} else {
screen.Screen.EnableMouse()
}
} else if option == "autosave" {
if nativeValue.(float64) > 0 {
config.SetAutoTime(int(nativeValue.(float64)))
config.StartAutoSave()
} else {
config.SetAutoTime(0)
}
} else {
for _, pl := range config.Plugins {
if option == pl.Name {
if nativeValue.(bool) && !pl.Loaded {
pl.Load()
pl.Call("init")
} else if !nativeValue.(bool) && pl.Loaded {
pl.Call("deinit")
for _, pl := range config.Plugins {
if option == pl.Name {
if nativeValue.(bool) && !pl.Loaded {
pl.Load()
_, err := pl.Call("init")
if err != nil && err != config.ErrNoSuchFunction {
screen.TermMessage(err)
}
} else if !nativeValue.(bool) && pl.Loaded {
_, err := pl.Call("deinit")
if err != nil && err != config.ErrNoSuchFunction {
screen.TermMessage(err)
}
}
}
}
}
@@ -563,7 +529,7 @@ func SetGlobalOptionNative(option string, nativeValue interface{}) error {
b.SetOptionNative(option, nativeValue)
}
return config.WriteSettings(config.ConfigDir + "/settings.json")
return config.WriteSettings(filepath.Join(config.ConfigDir, "settings.json"))
}
func SetGlobalOption(option, value string) error {
@@ -691,7 +657,7 @@ func (h *BufPane) BindCmd(args []string) {
// UnbindCmd binds a key to its default action
func (h *BufPane) UnbindCmd(args []string) {
if len(args) < 1 {
InfoBar.Error("Not enough arguements")
InfoBar.Error("Not enough arguments")
return
}
@@ -703,7 +669,7 @@ func (h *BufPane) UnbindCmd(args []string) {
// RunCmd runs a shell command in the background
func (h *BufPane) RunCmd(args []string) {
runf, err := shell.RunBackgroundShell(shellwords.Join(args...))
runf, err := shell.RunBackgroundShell(shellquote.Join(args...))
if err != nil {
InfoBar.Error(err)
} else {
@@ -739,8 +705,11 @@ func (h *BufPane) GotoCmd(args []string) {
InfoBar.Error(err)
return
}
if line < 0 {
line = h.Buf.LinesNum() + 1 + line
}
line = util.Clamp(line-1, 0, h.Buf.LinesNum()-1)
col = util.Clamp(col-1, 0, utf8.RuneCount(h.Buf.LineBytes(line)))
col = util.Clamp(col-1, 0, util.CharacterCount(h.Buf.LineBytes(line)))
h.Cursor.GotoLoc(buffer.Loc{col, line})
} else {
line, err := strconv.Atoi(args[0])
@@ -748,6 +717,9 @@ func (h *BufPane) GotoCmd(args []string) {
InfoBar.Error(err)
return
}
if line < 0 {
line = h.Buf.LinesNum() + 1 + line
}
line = util.Clamp(line-1, 0, h.Buf.LinesNum()-1)
h.Cursor.GotoLoc(buffer.Loc{0, line})
}
@@ -821,19 +793,19 @@ func (h *BufPane) ReplaceCmd(args []string) {
nreplaced := 0
start := h.Buf.Start()
end := h.Buf.End()
if h.Cursor.HasSelection() {
selection := h.Cursor.HasSelection()
if selection {
start = h.Cursor.CurSelection[0]
end = h.Cursor.CurSelection[1]
}
if all {
nreplaced = h.Buf.ReplaceRegex(start, end, regex, replace)
nreplaced, _ = h.Buf.ReplaceRegex(start, end, regex, replace)
} else {
inRange := func(l buffer.Loc) bool {
return l.GreaterEqual(start) && l.LessThan(end)
return l.GreaterEqual(start) && l.LessEqual(end)
}
searchLoc := start
searching := true
searchLoc := h.Cursor.Loc
var doReplacement func()
doReplacement = func() {
locs, found, err := h.Buf.FindNext(search, start, end, searchLoc, true, !noRegex)
@@ -844,44 +816,58 @@ func (h *BufPane) ReplaceCmd(args []string) {
if !found || !inRange(locs[0]) || !inRange(locs[1]) {
h.Cursor.ResetSelection()
h.Buf.RelocateCursors()
return
}
h.Cursor.SetSelectionStart(locs[0])
h.Cursor.SetSelectionEnd(locs[1])
h.Cursor.GotoLoc(locs[0])
h.Relocate()
InfoBar.YNPrompt("Perform replacement (y,n,esc)", func(yes, canceled bool) {
if !canceled && yes {
h.Buf.Replace(locs[0], locs[1], replaceStr)
_, nrunes := h.Buf.ReplaceRegex(locs[0], locs[1], regex, replace)
searchLoc = locs[0]
searchLoc.X += utf8.RuneCount(replace)
searchLoc.X += nrunes + locs[0].Diff(locs[1], h.Buf)
if end.Y == locs[1].Y {
end = end.Move(nrunes, h.Buf)
}
h.Cursor.Loc = searchLoc
nreplaced++
} else if !canceled && !yes {
searchLoc = locs[0]
searchLoc.X += utf8.RuneCount(replace)
searchLoc.X += util.CharacterCount(replace)
} else if canceled {
h.Cursor.ResetSelection()
h.Buf.RelocateCursors()
return
}
if searching {
doReplacement()
}
doReplacement()
})
}
doReplacement()
}
h.Buf.RelocateCursors()
h.Relocate()
var s string
if nreplaced > 1 {
InfoBar.Message("Replaced ", nreplaced, " occurrences of ", search)
s = fmt.Sprintf("Replaced %d occurrences of %s", nreplaced, search)
} else if nreplaced == 1 {
InfoBar.Message("Replaced ", nreplaced, " occurrence of ", search)
s = fmt.Sprintf("Replaced 1 occurrence of %s", search)
} else {
InfoBar.Message("Nothing matched ", search)
s = fmt.Sprintf("Nothing matched %s", search)
}
if selection {
s += " in selection"
}
InfoBar.Message(s)
}
// ReplaceAllCmd replaces search term all at once
@@ -892,7 +878,12 @@ func (h *BufPane) ReplaceAllCmd(args []string) {
// TermCmd opens a terminal in the current view
func (h *BufPane) TermCmd(args []string) {
ps := MainTab().Panes
ps := h.tab.Panes
if !TermEmuSupported {
InfoBar.Error("Terminal emulator not supported on this system")
return
}
if len(args) == 0 {
sh := os.Getenv("SHELL")
@@ -905,7 +896,11 @@ func (h *BufPane) TermCmd(args []string) {
term := func(i int, newtab bool) {
t := new(shell.Terminal)
t.Start(args, false, true, "", nil)
err := t.Start(args, false, true, nil, nil)
if err != nil {
InfoBar.Error(err)
return
}
id := h.ID()
if newtab {
@@ -917,7 +912,12 @@ func (h *BufPane) TermCmd(args []string) {
}
v := h.GetView()
MainTab().Panes[i] = NewTermPane(v.X, v.Y, v.Width, v.Height, t, id)
tp, err := NewTermPane(v.X, v.Y, v.Width, v.Height, t, id, MainTab())
if err != nil {
InfoBar.Error(err)
return
}
MainTab().Panes[i] = tp
MainTab().SetActive(i)
}
@@ -949,12 +949,16 @@ func (h *BufPane) TermCmd(args []string) {
// HandleCommand handles input from the user
func (h *BufPane) HandleCommand(input string) {
args, err := shellwords.Split(input)
args, err := shellquote.Split(input)
if err != nil {
InfoBar.Error("Error parsing args ", err)
return
}
if len(args) == 0 {
return
}
inputCmd := args[0]
if _, ok := commands[inputCmd]; !ok {

View File

@@ -0,0 +1,108 @@
package action
// DefaultBindings returns a map containing micro's default keybindings
func DefaultBindings() map[string]string {
return map[string]string{
"Up": "CursorUp",
"Down": "CursorDown",
"Right": "CursorRight",
"Left": "CursorLeft",
"ShiftUp": "SelectUp",
"ShiftDown": "SelectDown",
"ShiftLeft": "SelectLeft",
"ShiftRight": "SelectRight",
"AltLeft": "WordLeft",
"AltRight": "WordRight",
"AltUp": "MoveLinesUp",
"AltDown": "MoveLinesDown",
"AltShiftRight": "SelectWordRight",
"AltShiftLeft": "SelectWordLeft",
"CtrlLeft": "StartOfTextToggle",
"CtrlRight": "EndOfLine",
"CtrlShiftLeft": "SelectToStartOfTextToggle",
"ShiftHome": "SelectToStartOfTextToggle",
"CtrlShiftRight": "SelectToEndOfLine",
"ShiftEnd": "SelectToEndOfLine",
"CtrlUp": "CursorStart",
"CtrlDown": "CursorEnd",
"CtrlShiftUp": "SelectToStart",
"CtrlShiftDown": "SelectToEnd",
"Alt-{": "ParagraphPrevious",
"Alt-}": "ParagraphNext",
"Enter": "InsertNewline",
"CtrlH": "Backspace",
"Backspace": "Backspace",
"OldBackspace": "Backspace",
"Alt-CtrlH": "DeleteWordLeft",
"Alt-Backspace": "DeleteWordLeft",
"Tab": "Autocomplete|IndentSelection|InsertTab",
"Backtab": "CycleAutocompleteBack|OutdentSelection|OutdentLine",
"Ctrl-o": "OpenFile",
"Ctrl-s": "Save",
"Ctrl-f": "Find",
"Ctrl-n": "FindNext",
"Ctrl-p": "FindPrevious",
"Ctrl-z": "Undo",
"Ctrl-y": "Redo",
"Ctrl-c": "CopyLine|Copy",
"Ctrl-x": "Cut",
"Ctrl-k": "CutLine",
"Ctrl-d": "DuplicateLine",
"Ctrl-v": "Paste",
"Ctrl-a": "SelectAll",
"Ctrl-t": "AddTab",
"Alt-,": "PreviousTab",
"Alt-.": "NextTab",
"Home": "StartOfTextToggle",
"End": "EndOfLine",
"CtrlHome": "CursorStart",
"CtrlEnd": "CursorEnd",
"PageUp": "CursorPageUp",
"PageDown": "CursorPageDown",
"CtrlPageUp": "PreviousTab",
"CtrlPageDown": "NextTab",
"Ctrl-g": "ToggleHelp",
"Alt-g": "ToggleKeyMenu",
"Ctrl-r": "ToggleRuler",
"Ctrl-l": "command-edit:goto ",
"Delete": "Delete",
"Ctrl-b": "ShellMode",
"Ctrl-q": "Quit",
"Ctrl-e": "CommandMode",
"Ctrl-w": "NextSplit",
"Ctrl-u": "ToggleMacro",
"Ctrl-j": "PlayMacro",
"Insert": "ToggleOverwriteMode",
// Emacs-style keybindings
"Alt-f": "WordRight",
"Alt-b": "WordLeft",
"Alt-a": "StartOfText",
"Alt-e": "EndOfLine",
// "Alt-p": "CursorUp",
// "Alt-n": "CursorDown",
// Integration with file managers
"F2": "Save",
"F3": "Find",
"F4": "Quit",
"F7": "Find",
"F10": "Quit",
"Esc": "Escape",
// Mouse bindings
"MouseWheelUp": "ScrollUp",
"MouseWheelDown": "ScrollDown",
"MouseLeft": "MousePress",
"MouseMiddle": "PastePrimary",
"Ctrl-MouseLeft": "MouseMultiCursor",
"Alt-n": "SpawnMultiCursor",
"AltShiftUp": "SpawnMultiCursorUp",
"AltShiftDown": "SpawnMultiCursorDown",
"Alt-m": "SpawnMultiCursorSelect",
"Alt-p": "RemoveMultiCursor",
"Alt-c": "RemoveAllMultiCursors",
"Alt-x": "SkipMultiCursor",
}
}

View File

@@ -0,0 +1,110 @@
// +build !darwin
package action
// DefaultBindings returns a map containing micro's default keybindings
func DefaultBindings() map[string]string {
return map[string]string{
"Up": "CursorUp",
"Down": "CursorDown",
"Right": "CursorRight",
"Left": "CursorLeft",
"ShiftUp": "SelectUp",
"ShiftDown": "SelectDown",
"ShiftLeft": "SelectLeft",
"ShiftRight": "SelectRight",
"CtrlLeft": "WordLeft",
"CtrlRight": "WordRight",
"AltUp": "MoveLinesUp",
"AltDown": "MoveLinesDown",
"CtrlShiftRight": "SelectWordRight",
"CtrlShiftLeft": "SelectWordLeft",
"AltLeft": "StartOfTextToggle",
"AltRight": "EndOfLine",
"AltShiftLeft": "SelectToStartOfTextToggle",
"ShiftHome": "SelectToStartOfTextToggle",
"AltShiftRight": "SelectToEndOfLine",
"ShiftEnd": "SelectToEndOfLine",
"CtrlUp": "CursorStart",
"CtrlDown": "CursorEnd",
"CtrlShiftUp": "SelectToStart",
"CtrlShiftDown": "SelectToEnd",
"Alt-{": "ParagraphPrevious",
"Alt-}": "ParagraphNext",
"Enter": "InsertNewline",
"CtrlH": "Backspace",
"Backspace": "Backspace",
"OldBackspace": "Backspace",
"Alt-CtrlH": "DeleteWordLeft",
"Alt-Backspace": "DeleteWordLeft",
"Tab": "Autocomplete|IndentSelection|InsertTab",
"Backtab": "CycleAutocompleteBack|OutdentSelection|OutdentLine",
"Ctrl-o": "OpenFile",
"Ctrl-s": "Save",
"Ctrl-f": "Find",
"Ctrl-n": "FindNext",
"Ctrl-p": "FindPrevious",
"Ctrl-z": "Undo",
"Ctrl-y": "Redo",
"Ctrl-c": "CopyLine|Copy",
"Ctrl-x": "Cut",
"Ctrl-k": "CutLine",
"Ctrl-d": "DuplicateLine",
"Ctrl-v": "Paste",
"Ctrl-a": "SelectAll",
"Ctrl-t": "AddTab",
"Alt-,": "PreviousTab",
"Alt-.": "NextTab",
"Home": "StartOfTextToggle",
"End": "EndOfLine",
"CtrlHome": "CursorStart",
"CtrlEnd": "CursorEnd",
"PageUp": "CursorPageUp",
"PageDown": "CursorPageDown",
"CtrlPageUp": "PreviousTab",
"CtrlPageDown": "NextTab",
"Ctrl-g": "ToggleHelp",
"Alt-g": "ToggleKeyMenu",
"Ctrl-r": "ToggleRuler",
"Ctrl-l": "command-edit:goto ",
"Delete": "Delete",
"Ctrl-b": "ShellMode",
"Ctrl-q": "Quit",
"Ctrl-e": "CommandMode",
"Ctrl-w": "NextSplit",
"Ctrl-u": "ToggleMacro",
"Ctrl-j": "PlayMacro",
"Insert": "ToggleOverwriteMode",
// Emacs-style keybindings
"Alt-f": "WordRight",
"Alt-b": "WordLeft",
"Alt-a": "StartOfText",
"Alt-e": "EndOfLine",
// "Alt-p": "CursorUp",
// "Alt-n": "CursorDown",
// Integration with file managers
"F2": "Save",
"F3": "Find",
"F4": "Quit",
"F7": "Find",
"F10": "Quit",
"Esc": "Escape",
// Mouse bindings
"MouseWheelUp": "ScrollUp",
"MouseWheelDown": "ScrollDown",
"MouseLeft": "MousePress",
"MouseMiddle": "PastePrimary",
"Ctrl-MouseLeft": "MouseMultiCursor",
"Alt-n": "SpawnMultiCursor",
"Alt-m": "SpawnMultiCursorSelect",
"AltShiftUp": "SpawnMultiCursorUp",
"AltShiftDown": "SpawnMultiCursorDown",
"Alt-p": "RemoveMultiCursor",
"Alt-c": "RemoveAllMultiCursors",
"Alt-x": "SkipMultiCursor",
}
}

View File

@@ -1,19 +1,22 @@
package action
import "github.com/zyedidia/micro/internal/buffer"
import "github.com/zyedidia/micro/v2/internal/buffer"
var InfoBar *InfoPane
var LogBufPane *BufPane
// InitGlobals initializes the log buffer and the info bar
func InitGlobals() {
InfoBar = NewInfoBar()
buffer.LogBuf = buffer.NewBufferFromString("", "Log", buffer.BTLog)
}
// GetInfoBar returns the infobar pane
func GetInfoBar() *InfoPane {
return InfoBar
}
// WriteLog writes a string to the log buffer
func WriteLog(s string) {
buffer.WriteLog(s)
if LogBufPane != nil {
@@ -28,7 +31,10 @@ func WriteLog(s string) {
}
}
func OpenLogBuf(h *BufPane) {
// OpenLogBuf opens the log buffer from the current bufpane
// If the current bufpane is a log buffer nothing happens,
// otherwise the log buffer is opened in a horizontal split
func (h *BufPane) OpenLogBuf() {
LogBufPane = h.HSplitBuf(buffer.LogBuf)
LogBufPane.CursorEnd()

View File

@@ -5,9 +5,9 @@ import (
"sort"
"strings"
"github.com/zyedidia/micro/internal/buffer"
"github.com/zyedidia/micro/internal/config"
"github.com/zyedidia/micro/internal/util"
"github.com/zyedidia/micro/v2/internal/buffer"
"github.com/zyedidia/micro/v2/internal/config"
"github.com/zyedidia/micro/v2/internal/util"
)
// This file is meant (for now) for autocompletion in command mode, not
@@ -197,7 +197,7 @@ func OptionValueComplete(b *buffer.Buffer) ([]string, []string) {
return completions, suggestions
}
// OptionComplete autocompletes options
// PluginCmdComplete autocompletes the plugin command
func PluginCmdComplete(b *buffer.Buffer) ([]string, []string) {
c := b.GetActiveCursor()
input, argstart := buffer.GetArg(b)
@@ -253,51 +253,28 @@ func PluginComplete(b *buffer.Buffer) ([]string, []string) {
return completions, suggestions
}
// // MakeCompletion registers a function from a plugin for autocomplete commands
// func MakeCompletion(function string) Completion {
// pluginCompletions = append(pluginCompletions, LuaFunctionComplete(function))
// return Completion(-len(pluginCompletions))
// }
// PluginNameComplete completes with the names of loaded plugins
// func PluginNameComplete(b *buffer.Buffer) ([]string, []string) {
// c := b.GetActiveCursor()
// input, argstart := buffer.GetArg(b)
//
// // PluginComplete autocompletes from plugin function
// func PluginComplete(complete Completion, input string) (chosen string, suggestions []string) {
// idx := int(-complete) - 1
//
// if len(pluginCompletions) <= idx {
// return "", nil
// }
// suggestions = pluginCompletions[idx](input)
//
// if len(suggestions) == 1 {
// chosen = suggestions[0]
// }
// return
// }
//
// // PluginCmdComplete completes with possible choices for the `> plugin` command
// func PluginCmdComplete(input string) (chosen string, suggestions []string) {
// for _, cmd := range []string{"install", "remove", "search", "update", "list"} {
// if strings.HasPrefix(cmd, input) {
// suggestions = append(suggestions, cmd)
// }
// }
//
// if len(suggestions) == 1 {
// chosen = suggestions[0]
// }
// return chosen, suggestions
// }
//
// // PluginnameComplete completes with the names of loaded plugins
// func PluginNameComplete(input string) (chosen string, suggestions []string) {
// for _, pp := range GetAllPluginPackages() {
// var suggestions []string
// for _, pp := range config.GetAllPluginPackages(nil) {
// if strings.HasPrefix(pp.Name, input) {
// suggestions = append(suggestions, pp.Name)
// }
// }
//
// if len(suggestions) == 1 {
// chosen = suggestions[0]
// sort.Strings(suggestions)
// completions := make([]string, len(suggestions))
// for i := range suggestions {
// completions[i] = util.SliceEndStr(suggestions[i], c.X-argstart)
// }
// return chosen, suggestions
// return completions, suggestions
// }
// // MakeCompletion registers a function from a plugin for autocomplete commands
// func MakeCompletion(function string) Completion {
// pluginCompletions = append(pluginCompletions, LuaFunctionComplete(function))
// return Completion(-len(pluginCompletions))
// }

View File

@@ -4,9 +4,10 @@ import (
"bytes"
"strings"
"github.com/zyedidia/micro/internal/display"
"github.com/zyedidia/micro/internal/info"
"github.com/zyedidia/micro/internal/util"
"github.com/zyedidia/micro/v2/internal/buffer"
"github.com/zyedidia/micro/v2/internal/display"
"github.com/zyedidia/micro/v2/internal/info"
"github.com/zyedidia/micro/v2/internal/util"
"github.com/zyedidia/tcell"
)
@@ -17,10 +18,10 @@ type InfoPane struct {
*info.InfoBuf
}
func NewInfoPane(ib *info.InfoBuf, w display.BWindow) *InfoPane {
func NewInfoPane(ib *info.InfoBuf, w display.BWindow, tab *Tab) *InfoPane {
ip := new(InfoPane)
ip.InfoBuf = ib
ip.BufPane = NewBufPane(ib.Buffer, w)
ip.BufPane = NewBufPane(ib.Buffer, w, tab)
return ip
}
@@ -28,7 +29,7 @@ func NewInfoPane(ib *info.InfoBuf, w display.BWindow) *InfoPane {
func NewInfoBar() *InfoPane {
ib := info.NewBuffer()
w := display.NewInfoWindow(ib)
return NewInfoPane(ib, w)
return NewInfoPane(ib, w, nil)
}
func (h *InfoPane) Close() {
@@ -68,11 +69,12 @@ func (h *InfoPane) HandleEvent(event tcell.Event) {
h.EventCallback(resp)
}
}
case *tcell.EventMouse:
default:
h.BufPane.HandleEvent(event)
}
}
// DoKeyEvent executes a key event for the command bar, doing any overridden actions
func (h *InfoPane) DoKeyEvent(e KeyEvent) bool {
done := false
if action, ok := BufKeyBindings[e]; ok {
@@ -85,7 +87,7 @@ func (h *InfoPane) DoKeyEvent(e KeyEvent) bool {
for s, a := range InfoOverrides {
// TODO this is a hack and really we should have support
// for having binding overrides for different buffers
if strings.Contains(estr, s) {
if strings.HasPrefix(estr, s) {
done = true
a(h)
break
@@ -122,6 +124,7 @@ var InfoNones = []string{
"HalfPageDown",
"ToggleHelp",
"ToggleKeyMenu",
"ToggleDiffGutter",
"ToggleRuler",
"JumpLine",
"ClearStatus",
@@ -147,14 +150,13 @@ var InfoNones = []string{
"SkipMultiCursor",
}
// InfoOverrides is the list of actions which have been overriden
// InfoOverrides is the list of actions which have been overridden
// by the infohandler
var InfoOverrides = map[string]InfoKeyAction{
"CursorUp": (*InfoPane).CursorUp,
"CursorDown": (*InfoPane).CursorDown,
"InsertNewline": (*InfoPane).InsertNewline,
"Autocomplete": (*InfoPane).Autocomplete,
"OutdentLine": (*InfoPane).CycleBack,
"Escape": (*InfoPane).Escape,
"Quit": (*InfoPane).Quit,
"QuitAll": (*InfoPane).QuitAll,
@@ -185,21 +187,17 @@ func (h *InfoPane) Autocomplete() {
args := bytes.Split(l, []byte{' '})
cmd := string(args[0])
if len(args) == 1 {
b.Autocomplete(CommandComplete)
} else {
if action, ok := commands[cmd]; ok {
if h.PromptType == "Command" {
if len(args) == 1 {
b.Autocomplete(CommandComplete)
} else if action, ok := commands[cmd]; ok {
if action.completer != nil {
b.Autocomplete(action.completer)
}
}
}
}
// CycleBack cycles back in the autocomplete suggestion list
func (h *InfoPane) CycleBack() {
if h.Buf.HasSuggestions {
h.Buf.CycleAutocomplete(false)
} else {
// by default use filename autocompletion
b.Autocomplete(buffer.FileComplete)
}
}

View File

@@ -1,7 +1,7 @@
package action
import (
"github.com/zyedidia/micro/internal/display"
"github.com/zyedidia/micro/v2/internal/display"
)
type Pane interface {
@@ -11,4 +11,6 @@ type Pane interface {
SetID(i uint64)
Name() string
Close()
SetTab(t *Tab)
Tab() *Tab
}

View File

@@ -4,8 +4,8 @@ import (
"fmt"
"reflect"
"github.com/zyedidia/micro/internal/buffer"
"github.com/zyedidia/micro/internal/display"
"github.com/zyedidia/micro/v2/internal/buffer"
"github.com/zyedidia/micro/v2/internal/display"
"github.com/zyedidia/tcell"
)
@@ -13,17 +13,17 @@ type RawPane struct {
*BufPane
}
func NewRawPaneFromWin(b *buffer.Buffer, win display.BWindow) *RawPane {
func NewRawPaneFromWin(b *buffer.Buffer, win display.BWindow, tab *Tab) *RawPane {
rh := new(RawPane)
rh.BufPane = NewBufPane(b, win)
rh.BufPane = NewBufPane(b, win, tab)
return rh
}
func NewRawPane() *RawPane {
func NewRawPane(tab *Tab) *RawPane {
b := buffer.NewBufferFromString("", "", buffer.BTRaw)
w := display.NewBufWindow(0, 0, 0, 0, b)
return NewRawPaneFromWin(b, w)
return NewRawPaneFromWin(b, w, tab)
}
func (h *RawPane) HandleEvent(event tcell.Event) {
@@ -35,6 +35,13 @@ func (h *RawPane) HandleEvent(event tcell.Event) {
}
h.Buf.Insert(h.Cursor.Loc, reflect.TypeOf(event).String()[7:])
switch e := event.(type) {
case *tcell.EventKey:
h.Buf.Insert(h.Cursor.Loc, fmt.Sprintf(": %s", e.Name()))
}
h.Buf.Insert(h.Cursor.Loc, fmt.Sprintf(": %q\n", event.EscSeq()))
h.Relocate()
}

View File

@@ -1,11 +1,11 @@
package action
import (
"github.com/zyedidia/micro/internal/buffer"
"github.com/zyedidia/micro/internal/config"
"github.com/zyedidia/micro/internal/display"
"github.com/zyedidia/micro/internal/screen"
"github.com/zyedidia/micro/internal/views"
"github.com/zyedidia/micro/v2/internal/buffer"
"github.com/zyedidia/micro/v2/internal/config"
"github.com/zyedidia/micro/v2/internal/display"
"github.com/zyedidia/micro/v2/internal/screen"
"github.com/zyedidia/micro/v2/internal/views"
"github.com/zyedidia/tcell"
)
@@ -91,6 +91,7 @@ func (t *TabList) Resize() {
t.List[0].Node.Resize(w, h-iOffset)
t.List[0].Resize()
}
t.TabWindow.Resize(w, h)
}
// HandleEvent checks for a resize event or a mouse event on the tab bar
@@ -103,9 +104,22 @@ func (t *TabList) HandleEvent(event tcell.Event) {
mx, my := e.Position()
switch e.Buttons() {
case tcell.Button1:
ind := t.LocFromVisual(buffer.Loc{mx, my})
if ind != -1 {
t.SetActive(ind)
if my == t.Y && mx == 0 {
t.Scroll(-4)
return
} else if my == t.Y && mx == t.Width-1 {
t.Scroll(4)
return
}
if len(t.List) > 1 {
ind := t.LocFromVisual(buffer.Loc{mx, my})
if ind != -1 {
t.SetActive(ind)
return
}
if my == 0 {
return
}
}
case tcell.WheelUp:
if my == t.Y {
@@ -152,6 +166,8 @@ type Tab struct {
active int
resizing *views.Node // node currently being resized
// captures whether the mouse is released
release bool
}
// NewTabFromBuffer creates a new tab from the given buffer
@@ -160,7 +176,7 @@ func NewTabFromBuffer(x, y, width, height int, b *buffer.Buffer) *Tab {
t.Node = views.NewRoot(x, y, width, height)
t.UIWindow = display.NewUIWindow(t.Node)
e := NewBufPaneFromBuf(b)
e := NewBufPaneFromBuf(b, t)
e.SetID(t.ID())
t.Panes = append(t.Panes, e)
@@ -171,7 +187,7 @@ func NewTabFromPane(x, y, width, height int, pane Pane) *Tab {
t := new(Tab)
t.Node = views.NewRoot(x, y, width, height)
t.UIWindow = display.NewUIWindow(t.Node)
pane.SetTab(t)
pane.SetID(t.ID())
t.Panes = append(t.Panes, pane)
@@ -189,7 +205,8 @@ func (t *Tab) HandleEvent(event tcell.Event) {
mx, my := e.Position()
switch e.Buttons() {
case tcell.Button1:
resizeID := t.GetMouseSplitID(buffer.Loc{mx, my})
wasReleased := t.release
t.release = false
if t.resizing != nil {
var size int
if t.resizing.Kind == views.STVert {
@@ -202,21 +219,25 @@ func (t *Tab) HandleEvent(event tcell.Event) {
return
}
if resizeID != 0 {
t.resizing = t.GetNode(uint64(resizeID))
return
}
if wasReleased {
resizeID := t.GetMouseSplitID(buffer.Loc{mx, my})
if resizeID != 0 {
t.resizing = t.GetNode(uint64(resizeID))
return
}
for i, p := range t.Panes {
v := p.GetView()
inpane := mx >= v.X && mx < v.X+v.Width && my >= v.Y && my < v.Y+v.Height
if inpane {
t.SetActive(i)
break
for i, p := range t.Panes {
v := p.GetView()
inpane := mx >= v.X && mx < v.X+v.Width && my >= v.Y && my < v.Y+v.Height
if inpane {
t.SetActive(i)
break
}
}
}
case tcell.ButtonNone:
t.resizing = nil
t.release = true
default:
for _, p := range t.Panes {
v := p.GetView()
@@ -277,6 +298,10 @@ func (t *Tab) Resize() {
}
// CurPane returns the currently active pane
func (t *Tab) CurPane() Pane {
return t.Panes[t.active]
func (t *Tab) CurPane() *BufPane {
p, ok := t.Panes[t.active].(*BufPane)
if !ok {
return nil
}
return p
}

View File

@@ -3,27 +3,39 @@
package action
import (
"github.com/zyedidia/micro/internal/shell"
"github.com/zyedidia/micro/pkg/shellwords"
shellquote "github.com/kballard/go-shellquote"
"github.com/zyedidia/micro/v2/internal/shell"
)
// TermEmuSupported is a constant that marks if the terminal emulator is supported
const TermEmuSupported = true
func RunTermEmulator(h *BufPane, input string, wait bool, getOutput bool, callback string, userargs []interface{}) error {
args, err := shellwords.Split(input)
// RunTermEmulator starts a terminal emulator from a bufpane with the given input (command)
// if wait is true it will wait for the user to exit by pressing enter once the executable has terminated
// if getOutput is true it will redirect the stdout of the process to a pipe which will be passed to the
// callback which is a function that takes a string and a list of optional user arguments
func RunTermEmulator(h *BufPane, input string, wait bool, getOutput bool, callback func(out string, userargs []interface{}), userargs []interface{}) error {
args, err := shellquote.Split(input)
if err != nil {
return err
}
if len(args) == 0 {
return nil
}
t := new(shell.Terminal)
t.Start(args, getOutput, wait, callback, userargs)
id := h.ID()
h.AddTab()
id = MainTab().Panes[0].ID()
id := MainTab().Panes[0].ID()
v := h.GetView()
MainTab().Panes[0] = NewTermPane(v.X, v.Y, v.Width, v.Height, t, id)
tp, err := NewTermPane(v.X, v.Y, v.Width, v.Height, t, id, MainTab())
if err != nil {
return err
}
MainTab().Panes[0] = tp
MainTab().SetActive(0)
return nil

View File

@@ -4,8 +4,10 @@ package action
import "errors"
// TermEmuSupported is a constant that marks if the terminal emulator is supported
const TermEmuSupported = false
func RunTermEmulator(input string, wait bool, getOutput bool, callback string, userargs []interface{}) error {
// RunTermEmulator returns an error for unsupported systems (non-unix systems
func RunTermEmulator(input string, wait bool, getOutput bool, callback func(out string, userargs []interface{}), userargs []interface{}) error {
return errors.New("Unsupported operating system")
}

View File

@@ -1,12 +1,13 @@
package action
import (
"errors"
"runtime"
"github.com/zyedidia/clipboard"
"github.com/zyedidia/micro/internal/display"
"github.com/zyedidia/micro/internal/screen"
"github.com/zyedidia/micro/internal/shell"
"github.com/zyedidia/micro/v2/internal/display"
"github.com/zyedidia/micro/v2/internal/screen"
"github.com/zyedidia/micro/v2/internal/shell"
"github.com/zyedidia/tcell"
"github.com/zyedidia/terminal"
)
@@ -17,15 +18,21 @@ type TermPane struct {
mouseReleased bool
id uint64
tab *Tab
}
func NewTermPane(x, y, w, h int, t *shell.Terminal, id uint64) *TermPane {
func NewTermPane(x, y, w, h int, t *shell.Terminal, id uint64, tab *Tab) (*TermPane, error) {
if !TermEmuSupported {
return nil, errors.New("Terminal emulator is not supported on this system")
}
th := new(TermPane)
th.Terminal = t
th.id = id
th.mouseReleased = true
th.Window = display.NewTermWindow(x, y, w, h, t)
return th
th.tab = tab
return th, nil
}
func (t *TermPane) ID() uint64 {
@@ -36,6 +43,14 @@ func (t *TermPane) SetID(i uint64) {
t.id = i
}
func (t *TermPane) SetTab(tab *Tab) {
t.tab = tab
}
func (t *TermPane) Tab() *Tab {
return t.tab
}
func (t *TermPane) Close() {}
func (t *TermPane) Quit() {
@@ -80,8 +95,12 @@ func (t *TermPane) HandleEvent(event tcell.Event) {
} else if t.Status != shell.TTDone {
t.WriteString(event.EscSeq())
}
} else if _, ok := event.(*tcell.EventPaste); ok {
if t.Status != shell.TTDone {
t.WriteString(event.EscSeq())
}
} else if e, ok := event.(*tcell.EventMouse); e != nil && (!ok || t.State.Mode(terminal.ModeMouseMask)) {
t.WriteString(event.EscSeq())
// t.WriteString(event.EscSeq())
} else if e != nil {
x, y := e.Position()
v := t.GetView()

View File

@@ -6,9 +6,8 @@ import (
"os"
"sort"
"strings"
"unicode/utf8"
"github.com/zyedidia/micro/internal/util"
"github.com/zyedidia/micro/v2/internal/util"
)
// A Completer is a function that takes a buffer and returns info
@@ -54,7 +53,7 @@ func (b *Buffer) CycleAutocomplete(forward bool) {
start := c.Loc
end := c.Loc
if prevSuggestion < len(b.Suggestions) && prevSuggestion >= 0 {
start = end.Move(-utf8.RuneCountInString(b.Completions[prevSuggestion]), b)
start = end.Move(-util.CharacterCountInString(b.Completions[prevSuggestion]), b)
} else {
// end = start.Move(1, b)
}
@@ -82,7 +81,7 @@ func GetWord(b *Buffer) ([]byte, int) {
args := bytes.FieldsFunc(l, util.IsNonAlphaNumeric)
input := args[len(args)-1]
return input, c.X - utf8.RuneCount(input)
return input, c.X - util.CharacterCount(input)
}
// GetArg gets the most recent word (separated by ' ' only)
@@ -98,7 +97,7 @@ func GetArg(b *Buffer) (string, int) {
if i == len(args)-1 {
break
}
argstart += utf8.RuneCount(a) + 1
argstart += util.CharacterCount(a) + 1
}
return input, argstart
@@ -162,7 +161,7 @@ func BufferComplete(b *Buffer) ([]string, []string) {
return []string{}, []string{}
}
inputLen := utf8.RuneCount(input)
inputLen := util.CharacterCount(input)
suggestionsSet := make(map[string]struct{})
@@ -171,7 +170,7 @@ func BufferComplete(b *Buffer) ([]string, []string) {
l := b.LineBytes(i)
words := bytes.FieldsFunc(l, util.IsNonAlphaNumeric)
for _, w := range words {
if bytes.HasPrefix(w, input) && utf8.RuneCount(w) > inputLen {
if bytes.HasPrefix(w, input) && util.CharacterCount(w) > inputLen {
strw := string(w)
if _, ok := suggestionsSet[strw]; !ok {
suggestionsSet[strw] = struct{}{}
@@ -184,7 +183,7 @@ func BufferComplete(b *Buffer) ([]string, []string) {
l := b.LineBytes(i)
words := bytes.FieldsFunc(l, util.IsNonAlphaNumeric)
for _, w := range words {
if bytes.HasPrefix(w, input) && utf8.RuneCount(w) > inputLen {
if bytes.HasPrefix(w, input) && util.CharacterCount(w) > inputLen {
strw := string(w)
if _, ok := suggestionsSet[strw]; !ok {
suggestionsSet[strw] = struct{}{}

View File

@@ -3,13 +3,13 @@ package buffer
import (
"fmt"
"io"
"log"
"os"
"path/filepath"
"time"
"github.com/zyedidia/micro/internal/config"
"github.com/zyedidia/micro/internal/screen"
"github.com/zyedidia/micro/internal/util"
"github.com/zyedidia/micro/v2/internal/config"
"github.com/zyedidia/micro/v2/internal/screen"
"github.com/zyedidia/micro/v2/internal/util"
"golang.org/x/text/encoding"
)
@@ -17,7 +17,9 @@ const backupMsg = `A backup was detected for this file. This likely means that m
crashed while editing this file, or another instance of micro is currently
editing this file.
The backup was created on %s.
The backup was created on %s, and the file is
%s
* 'recover' will apply the backup as unsaved changes to the current buffer.
When the buffer is closed, the backup will be removed.
@@ -26,33 +28,53 @@ The backup was created on %s.
Options: [r]ecover, [i]gnore: `
var backupRequestChan chan *Buffer
func backupThread() {
for {
time.Sleep(time.Second * 8)
for len(backupRequestChan) > 0 {
b := <-backupRequestChan
b.Backup()
}
}
}
func init() {
backupRequestChan = make(chan *Buffer, 10)
go backupThread()
}
func (b *Buffer) RequestBackup() {
if !b.requestedBackup {
select {
case backupRequestChan <- b:
default:
// channel is full
}
b.requestedBackup = true
}
}
// Backup saves the current buffer to ConfigDir/backups
func (b *Buffer) Backup(checkTime bool) error {
if !b.Settings["backup"].(bool) || b.Path == "" {
func (b *Buffer) Backup() error {
if !b.Settings["backup"].(bool) || b.Path == "" || b.Type != BTDefault {
return nil
}
if checkTime {
sub := time.Now().Sub(b.lastbackup)
if sub < time.Duration(backup_time)*time.Millisecond {
log.Println("Backup event but not enough time has passed", sub)
return nil
}
backupdir, err := util.ReplaceHome(b.Settings["backupdir"].(string))
if len(backupdir) == 0 || err != nil {
backupdir = filepath.Join(config.ConfigDir, "backups")
}
b.lastbackup = time.Now()
backupdir := config.ConfigDir + "/backups/"
if _, err := os.Stat(backupdir); os.IsNotExist(err) {
os.Mkdir(backupdir, os.ModePerm)
log.Println("Creating backup dir")
}
name := backupdir + util.EscapePath(b.AbsPath)
name := filepath.Join(backupdir, util.EscapePath(b.AbsPath))
log.Println("Backing up to", name)
err := overwriteFile(name, encoding.Nop, func(file io.Writer) (e error) {
err = overwriteFile(name, encoding.Nop, func(file io.Writer) (e error) {
if len(b.lines) == 0 {
return
}
@@ -74,31 +96,33 @@ func (b *Buffer) Backup(checkTime bool) error {
}
}
return
})
}, false)
b.requestedBackup = false
return err
}
// RemoveBackup removes any backup file associated with this buffer
func (b *Buffer) RemoveBackup() {
if !b.Settings["backup"].(bool) || b.Path == "" {
if !b.Settings["backup"].(bool) || b.Settings["permbackup"].(bool) || b.Path == "" || b.Type != BTDefault {
return
}
f := config.ConfigDir + "/backups/" + util.EscapePath(b.AbsPath)
f := filepath.Join(config.ConfigDir, "backups", util.EscapePath(b.AbsPath))
os.Remove(f)
}
// ApplyBackup applies the corresponding backup file to this buffer (if one exists)
// Returns true if a backup was applied
func (b *Buffer) ApplyBackup(fsize int64) bool {
if b.Settings["backup"].(bool) && len(b.Path) > 0 {
backupfile := config.ConfigDir + "/backups/" + util.EscapePath(b.AbsPath)
if b.Settings["backup"].(bool) && !b.Settings["permbackup"].(bool) && len(b.Path) > 0 && b.Type == BTDefault {
backupfile := filepath.Join(config.ConfigDir, "backups", util.EscapePath(b.AbsPath))
if info, err := os.Stat(backupfile); err == nil {
backup, err := os.Open(backupfile)
if err == nil {
defer backup.Close()
t := info.ModTime()
msg := fmt.Sprintf(backupMsg, t.Format("Mon Jan _2 at 15:04, 2006"))
msg := fmt.Sprintf(backupMsg, t.Format("Mon Jan _2 at 15:04, 2006"), util.EscapePath(b.AbsPath))
choice := screen.TermPrompt(msg, []string{"r", "i", "recover", "ignore"}, true)
if choice%2 == 0 {

View File

@@ -1,35 +1,42 @@
package buffer
import (
"bufio"
"bytes"
"crypto/md5"
"errors"
"fmt"
"io"
"io/ioutil"
"os"
"path"
"path/filepath"
"strconv"
"strings"
"sync"
"time"
"unicode/utf8"
luar "layeh.com/gopher-luar"
"github.com/zyedidia/micro/internal/config"
ulua "github.com/zyedidia/micro/internal/lua"
"github.com/zyedidia/micro/internal/screen"
. "github.com/zyedidia/micro/internal/util"
"github.com/zyedidia/micro/pkg/highlight"
dmp "github.com/sergi/go-diff/diffmatchpatch"
"github.com/zyedidia/micro/v2/internal/config"
ulua "github.com/zyedidia/micro/v2/internal/lua"
"github.com/zyedidia/micro/v2/internal/screen"
"github.com/zyedidia/micro/v2/internal/util"
"github.com/zyedidia/micro/v2/pkg/highlight"
"golang.org/x/text/encoding/htmlindex"
"golang.org/x/text/encoding/unicode"
"golang.org/x/text/transform"
)
const backup_time = 8000
const backupTime = 8000
var (
// OpenBuffers is a list of the currently open buffers
OpenBuffers []*Buffer
LogBuf *Buffer
// LogBuf is a reference to the log buffer which can be opened with the
// `> log` command
LogBuf *Buffer
)
// The BufType defines what kind of buffer this is
@@ -41,16 +48,29 @@ type BufType struct {
}
var (
// BTDefault is a default buffer
BTDefault = BufType{0, false, false, true}
BTHelp = BufType{1, true, true, true}
BTLog = BufType{2, true, true, false}
// BTHelp is a help buffer
BTHelp = BufType{1, true, true, true}
// BTLog is a log buffer
BTLog = BufType{2, true, true, false}
// BTScratch is a buffer that cannot be saved (for scratch work)
BTScratch = BufType{3, false, true, false}
BTRaw = BufType{4, false, true, false}
BTInfo = BufType{5, false, true, false}
// BTRaw is is a buffer that shows raw terminal events
BTRaw = BufType{4, false, true, false}
// BTInfo is a buffer for inputting information
BTInfo = BufType{5, false, true, false}
// BTStdout is a buffer that only writes to stdout
// when closed
BTStdout = BufType{6, false, true, true}
// ErrFileTooLarge is returned when the file is too large to hash
// (fastdirty is automatically enabled)
ErrFileTooLarge = errors.New("File is too large to hash")
)
// SharedBuffer is a struct containing info that is shared among buffers
// that have the same file open
type SharedBuffer struct {
*LineArray
// Stores the last modification time of the file the buffer is pointing to
@@ -58,23 +78,101 @@ type SharedBuffer struct {
// Type of the buffer (e.g. help, raw, scratch etc..)
Type BufType
// Path to the file on disk
Path string
// Absolute path to the file on disk
AbsPath string
// Name of the buffer on the status line
name string
toStdout bool
// Settings customized by the user
Settings map[string]interface{}
Suggestions []string
Completions []string
CurSuggestion int
Messages []*Message
updateDiffTimer *time.Timer
diffBase []byte
diffBaseLineCount int
diffLock sync.RWMutex
diff map[int]DiffStatus
requestedBackup bool
// ReloadDisabled allows the user to disable reloads if they
// are viewing a file that is constantly changing
ReloadDisabled bool
isModified bool
// Whether or not suggestions can be autocompleted must be shared because
// it changes based on how the buffer has changed
HasSuggestions bool
// The Highlighter struct actually performs the highlighting
Highlighter *highlight.Highlighter
// SyntaxDef represents the syntax highlighting definition being used
// This stores the highlighting rules and filetype detection info
SyntaxDef *highlight.Def
ModifiedThisFrame bool
// Hash of the original buffer -- empty if fastdirty is on
origHash [md5.Size]byte
}
func (b *SharedBuffer) insert(pos Loc, value []byte) {
b.isModified = true
b.HasSuggestions = false
b.LineArray.insert(pos, value)
inslines := bytes.Count(value, []byte{'\n'})
b.MarkModified(pos.Y, pos.Y+inslines)
}
func (b *SharedBuffer) remove(start, end Loc) []byte {
b.isModified = true
b.HasSuggestions = false
defer b.MarkModified(start.Y, end.Y)
return b.LineArray.remove(start, end)
}
// MarkModified marks the buffer as modified for this frame
// and performs rehighlighting if syntax highlighting is enabled
func (b *SharedBuffer) MarkModified(start, end int) {
b.ModifiedThisFrame = true
if !b.Settings["syntax"].(bool) || b.SyntaxDef == nil {
return
}
start = util.Clamp(start, 0, len(b.lines)-1)
end = util.Clamp(end, 0, len(b.lines)-1)
l := -1
for i := start; i <= end; i++ {
l = util.Max(b.Highlighter.ReHighlightStates(b, i), l)
}
b.Highlighter.HighlightMatches(b, start, l)
}
// DisableReload disables future reloads of this sharedbuffer
func (b *SharedBuffer) DisableReload() {
b.ReloadDisabled = true
}
const (
DSUnchanged = 0
DSAdded = 1
DSModified = 2
DSDeletedAbove = 3
)
type DiffStatus byte
// Buffer stores the main information about a currently open file including
// the actual text (in a LineArray), the undo/redo stack (in an EventHandler)
// all the cursors, the syntax highlighting info, the settings for the buffer
@@ -89,42 +187,25 @@ type Buffer struct {
cursors []*Cursor
curCursor int
StartCursor Loc
// Path to the file on disk
Path string
// Absolute path to the file on disk
AbsPath string
// Name of the buffer on the status line
name string
SyntaxDef *highlight.Def
Highlighter *highlight.Highlighter
// Hash of the original buffer -- empty if fastdirty is on
origHash [md5.Size]byte
// Settings customized by the user
Settings map[string]interface{}
Suggestions []string
Completions []string
CurSuggestion int
Messages []*Message
// counts the number of edits
// resets every backup_time edits
lastbackup time.Time
}
// NewBufferFromFile opens a new buffer using the given path
// It will also automatically handle `~`, and line/column with filename:l:c
// It will return an empty buffer if the path does not exist
// and an error if the file is a directory
func NewBufferFromFile(path string, btype BufType) (*Buffer, error) {
// NewBufferFromFileAtLoc opens a new buffer with a given cursor location
// If cursorLoc is {-1, -1} the location does not overwrite what the cursor location
// would otherwise be (start of file, or saved cursor position if `savecursor` is
// enabled)
func NewBufferFromFileAtLoc(path string, btype BufType, cursorLoc Loc) (*Buffer, error) {
var err error
filename, cursorPos := GetPathAndCursorPosition(path)
filename, err = ReplaceHome(filename)
filename := path
if config.GetGlobalOption("parsecursor").(bool) && cursorLoc.X == -1 && cursorLoc.Y == -1 {
var cursorPos []string
filename, cursorPos = util.GetPathAndCursorPosition(filename)
cursorLoc, err = ParseCursorLocation(cursorPos)
if err != nil {
cursorLoc = Loc{-1, -1}
}
}
filename, err = util.ReplaceHome(filename)
if err != nil {
return nil, err
}
@@ -133,27 +214,35 @@ func NewBufferFromFile(path string, btype BufType) (*Buffer, error) {
fileInfo, _ := os.Stat(filename)
if err == nil && fileInfo.IsDir() {
return nil, errors.New(filename + " is a directory")
return nil, errors.New("Error: " + filename + " is a directory and cannot be opened")
}
defer file.Close()
cursorLoc, cursorerr := ParseCursorLocation(cursorPos)
if cursorerr != nil {
cursorLoc = Loc{-1, -1}
}
var buf *Buffer
if err != nil {
// File does not exist -- create an empty buffer with that name
buf = NewBufferFromString("", filename, btype)
} else {
buf = NewBuffer(file, FSize(file), filename, cursorLoc, btype)
buf = NewBuffer(file, util.FSize(file), filename, cursorLoc, btype)
}
return buf, nil
}
// NewBufferFromFile opens a new buffer using the given path
// It will also automatically handle `~`, and line/column with filename:l:c
// It will return an empty buffer if the path does not exist
// and an error if the file is a directory
func NewBufferFromFile(path string, btype BufType) (*Buffer, error) {
return NewBufferFromFileAtLoc(path, btype, Loc{-1, -1})
}
// NewBufferFromStringAtLoc creates a new buffer containing the given string with a cursor loc
func NewBufferFromStringAtLoc(text, path string, btype BufType, cursorLoc Loc) *Buffer {
return NewBuffer(strings.NewReader(text), int64(len(text)), path, cursorLoc, btype)
}
// NewBufferFromString creates a new buffer containing the given string
func NewBufferFromString(text, path string, btype BufType) *Buffer {
return NewBuffer(strings.NewReader(text), int64(len(text)), path, Loc{-1, -1}, btype)
@@ -169,22 +258,6 @@ func NewBuffer(r io.Reader, size int64, path string, startcursor Loc, btype BufT
b := new(Buffer)
b.Settings = config.DefaultCommonSettings()
for k, v := range config.GlobalSettings {
if _, ok := b.Settings[k]; ok {
b.Settings[k] = v
}
}
config.InitLocalSettings(b.Settings, path)
enc, err := htmlindex.Get(b.Settings["encoding"].(string))
if err != nil {
enc = unicode.UTF8
b.Settings["encoding"] = "utf-8"
}
reader := transform.NewReader(r, enc.NewDecoder())
found := false
if len(path) > 0 {
for _, buf := range OpenBuffers {
@@ -196,28 +269,59 @@ func NewBuffer(r io.Reader, size int64, path string, startcursor Loc, btype BufT
}
}
b.Path = path
b.AbsPath = absPath
hasBackup := false
if !found {
b.SharedBuffer = new(SharedBuffer)
b.Type = btype
hasBackup := b.ApplyBackup(size)
b.AbsPath = absPath
b.Path = path
b.Settings = config.DefaultCommonSettings()
for k, v := range config.GlobalSettings {
if _, ok := config.DefaultGlobalOnlySettings[k]; !ok {
// make sure setting is not global-only
b.Settings[k] = v
}
}
config.InitLocalSettings(b.Settings, path)
enc, err := htmlindex.Get(b.Settings["encoding"].(string))
if err != nil {
enc = unicode.UTF8
b.Settings["encoding"] = "utf-8"
}
hasBackup = b.ApplyBackup(size)
if !hasBackup {
b.LineArray = NewLineArray(uint64(size), FFAuto, reader)
reader := bufio.NewReader(transform.NewReader(r, enc.NewDecoder()))
var ff FileFormat = FFAuto
if size == 0 {
// for empty files, use the fileformat setting instead of
// autodetection
switch b.Settings["fileformat"] {
case "unix":
ff = FFUnix
case "dos":
ff = FFDos
}
}
b.LineArray = NewLineArray(uint64(size), ff, reader)
}
b.EventHandler = NewEventHandler(b.SharedBuffer, b.cursors)
// The last time this file was modified
b.UpdateModTime()
}
if b.Settings["readonly"].(bool) {
if b.Settings["readonly"].(bool) && b.Type == BTDefault {
b.Type.Readonly = true
}
// The last time this file was modified
b.ModTime, _ = GetModTime(b.Path)
switch b.Endings {
case FFUnix:
b.Settings["fileformat"] = "unix"
@@ -226,10 +330,11 @@ func NewBuffer(r io.Reader, size int64, path string, startcursor Loc, btype BufT
}
b.UpdateRules()
// init local settings again now that we know the filetype
config.InitLocalSettings(b.Settings, b.Path)
if _, err := os.Stat(config.ConfigDir + "/buffers/"); os.IsNotExist(err) {
os.Mkdir(config.ConfigDir+"/buffers/", os.ModePerm)
if _, err := os.Stat(filepath.Join(config.ConfigDir, "buffers")); os.IsNotExist(err) {
os.Mkdir(filepath.Join(config.ConfigDir, "buffers"), os.ModePerm)
}
if startcursor.X != -1 && startcursor.Y != -1 {
@@ -246,16 +351,18 @@ func NewBuffer(r io.Reader, size int64, path string, startcursor Loc, btype BufT
b.AddCursor(NewCursor(b, b.StartCursor))
b.GetActiveCursor().Relocate()
if !b.Settings["fastdirty"].(bool) {
if !b.Settings["fastdirty"].(bool) && !found {
if size > LargeFileThreshold {
// If the file is larger than LargeFileThreshold fastdirty needs to be on
b.Settings["fastdirty"] = true
} else {
} else if !hasBackup {
// since applying a backup does not save the applied backup to disk, we should
// not calculate the original hash based on the backup data
calcHash(b, &b.origHash)
}
}
err = config.RunPluginFn("onBufferOpen", luar.New(ulua.L, b))
err := config.RunPluginFn("onBufferOpen", luar.New(ulua.L, b))
if err != nil {
screen.TermMessage(err)
}
@@ -285,18 +392,26 @@ func (b *Buffer) Fini() {
b.Serialize()
}
b.RemoveBackup()
if b.Type == BTStdout {
fmt.Fprint(util.Stdout, string(b.Bytes()))
}
}
// GetName returns the name that should be displayed in the statusline
// for this buffer
func (b *Buffer) GetName() string {
if b.name == "" {
name := b.name
if name == "" {
if b.Path == "" {
return "No name"
}
return b.Path
name = b.Path
}
return b.name
if b.Settings["basename"].(bool) {
return path.Base(name)
}
return name
}
//SetName changes the name for this buffer
@@ -304,23 +419,25 @@ func (b *Buffer) SetName(s string) {
b.name = s
}
// Insert inserts the given string of text at the start location
func (b *Buffer) Insert(start Loc, text string) {
if !b.Type.Readonly {
b.EventHandler.cursors = b.cursors
b.EventHandler.active = b.curCursor
b.EventHandler.Insert(start, text)
go b.Backup(true)
b.RequestBackup()
}
}
// Remove removes the characters between the start and end locations
func (b *Buffer) Remove(start, end Loc) {
if !b.Type.Readonly {
b.EventHandler.cursors = b.cursors
b.EventHandler.active = b.curCursor
b.EventHandler.Remove(start, end)
go b.Backup(true)
b.RequestBackup()
}
}
@@ -329,6 +446,22 @@ func (b *Buffer) FileType() string {
return b.Settings["filetype"].(string)
}
// ExternallyModified returns whether the file being edited has
// been modified by some external process
func (b *Buffer) ExternallyModified() bool {
modTime, err := util.GetModTime(b.Path)
if err == nil {
return modTime != b.ModTime
}
return false
}
// UpdateModTime updates the modtime of this file
func (b *Buffer) UpdateModTime() (err error) {
b.ModTime, err = util.GetModTime(b.Path)
return
}
// ReOpen reloads the current buffer from disk
func (b *Buffer) ReOpen() error {
file, err := os.Open(b.Path)
@@ -341,7 +474,7 @@ func (b *Buffer) ReOpen() error {
return err
}
reader := transform.NewReader(file, enc.NewDecoder())
reader := bufio.NewReader(transform.NewReader(file, enc.NewDecoder()))
data, err := ioutil.ReadAll(reader)
txt := string(data)
@@ -350,12 +483,16 @@ func (b *Buffer) ReOpen() error {
}
b.EventHandler.ApplyDiff(txt)
b.ModTime, err = GetModTime(b.Path)
err = b.UpdateModTime()
if !b.Settings["fastdirty"].(bool) {
calcHash(b, &b.origHash)
}
b.isModified = false
b.RelocateCursors()
return err
}
// RelocateCursors relocates all cursors (makes sure they are in the buffer)
func (b *Buffer) RelocateCursors() {
for _, c := range b.cursors {
c.Relocate()
@@ -368,7 +505,7 @@ func (b *Buffer) RuneAt(loc Loc) rune {
if len(line) > 0 {
i := 0
for len(line) > 0 {
r, size := utf8.DecodeRune(line)
r, _, size := util.DecodeCharacter(line)
line = line[size:]
i++
@@ -397,6 +534,22 @@ func (b *Buffer) Modified() bool {
return buff != b.origHash
}
// Size returns the number of bytes in the current buffer
func (b *Buffer) Size() int {
nb := 0
for i := 0; i < b.LinesNum(); i++ {
nb += len(b.LineBytes(i))
if i != b.LinesNum()-1 {
if b.Endings == FFDos {
nb++ // carriage return
}
nb++ // newline
}
}
return nb
}
// calcHash calculates md5 hash of all lines in the buffer
func calcHash(b *Buffer, out *[md5.Size]byte) error {
h := md5.New()
@@ -437,65 +590,144 @@ func (b *Buffer) UpdateRules() {
if !b.Type.Syntax {
return
}
rehighlight := false
var files []*highlight.File
for _, f := range config.ListRuntimeFiles(config.RTSyntax) {
ft := b.Settings["filetype"].(string)
if ft == "off" {
return
}
syntaxFile := ""
foundDef := false
var header *highlight.Header
// search for the syntax file in the user's custom syntax files
for _, f := range config.ListRealRuntimeFiles(config.RTSyntax) {
data, err := f.Data()
if err != nil {
screen.TermMessage("Error loading syntax file " + f.Name() + ": " + err.Error())
} else {
file, err := highlight.ParseFile(data)
if err != nil {
screen.TermMessage("Error loading syntax file " + f.Name() + ": " + err.Error())
continue
}
ftdetect, err := highlight.ParseFtDetect(file)
if err != nil {
screen.TermMessage("Error loading syntax file " + f.Name() + ": " + err.Error())
continue
}
continue
}
ft := b.Settings["filetype"].(string)
if (ft == "unknown" || ft == "") && !rehighlight {
if highlight.MatchFiletype(ftdetect, b.Path, b.lines[0].data) {
header := new(highlight.Header)
header.FileType = file.FileType
header.FtDetect = ftdetect
b.SyntaxDef, err = highlight.ParseDef(file, header)
if err != nil {
screen.TermMessage("Error loading syntax file " + f.Name() + ": " + err.Error())
continue
}
rehighlight = true
}
} else {
if file.FileType == ft && !rehighlight {
header := new(highlight.Header)
header.FileType = file.FileType
header.FtDetect = ftdetect
b.SyntaxDef, err = highlight.ParseDef(file, header)
if err != nil {
screen.TermMessage("Error loading syntax file " + f.Name() + ": " + err.Error())
continue
}
rehighlight = true
}
header, err = highlight.MakeHeaderYaml(data)
file, err := highlight.ParseFile(data)
if err != nil {
screen.TermMessage("Error parsing syntax file " + f.Name() + ": " + err.Error())
continue
}
if ((ft == "unknown" || ft == "") && highlight.MatchFiletype(header.FtDetect, b.Path, b.lines[0].data)) || header.FileType == ft {
syndef, err := highlight.ParseDef(file, header)
if err != nil {
screen.TermMessage("Error parsing syntax file " + f.Name() + ": " + err.Error())
continue
}
files = append(files, file)
b.SyntaxDef = syndef
syntaxFile = f.Name()
foundDef = true
break
}
}
if b.SyntaxDef != nil {
// search in the default syntax files
for _, f := range config.ListRuntimeFiles(config.RTSyntaxHeader) {
data, err := f.Data()
if err != nil {
screen.TermMessage("Error loading syntax header file " + f.Name() + ": " + err.Error())
continue
}
header, err = highlight.MakeHeader(data)
if err != nil {
screen.TermMessage("Error reading syntax header file", f.Name(), err)
continue
}
if ft == "unknown" || ft == "" {
if highlight.MatchFiletype(header.FtDetect, b.Path, b.lines[0].data) {
syntaxFile = f.Name()
break
}
} else if header.FileType == ft {
syntaxFile = f.Name()
break
}
}
if syntaxFile != "" && !foundDef {
// we found a syntax file using a syntax header file
for _, f := range config.ListRuntimeFiles(config.RTSyntax) {
if f.Name() == syntaxFile {
data, err := f.Data()
if err != nil {
screen.TermMessage("Error loading syntax file " + f.Name() + ": " + err.Error())
continue
}
file, err := highlight.ParseFile(data)
if err != nil {
screen.TermMessage("Error parsing syntax file " + f.Name() + ": " + err.Error())
continue
}
syndef, err := highlight.ParseDef(file, header)
if err != nil {
screen.TermMessage("Error parsing syntax file " + f.Name() + ": " + err.Error())
continue
}
b.SyntaxDef = syndef
break
}
}
}
if b.SyntaxDef != nil && highlight.HasIncludes(b.SyntaxDef) {
includes := highlight.GetIncludes(b.SyntaxDef)
var files []*highlight.File
for _, f := range config.ListRuntimeFiles(config.RTSyntax) {
data, err := f.Data()
if err != nil {
screen.TermMessage("Error parsing syntax file " + f.Name() + ": " + err.Error())
continue
}
header, err := highlight.MakeHeaderYaml(data)
if err != nil {
screen.TermMessage("Error parsing syntax file " + f.Name() + ": " + err.Error())
continue
}
for _, i := range includes {
if header.FileType == i {
file, err := highlight.ParseFile(data)
if err != nil {
screen.TermMessage("Error parsing syntax file " + f.Name() + ": " + err.Error())
continue
}
files = append(files, file)
break
}
}
if len(files) >= len(includes) {
break
}
}
highlight.ResolveIncludes(b.SyntaxDef, files)
}
if b.Highlighter == nil || rehighlight {
if b.Highlighter == nil || syntaxFile != "" {
if b.SyntaxDef != nil {
b.Settings["filetype"] = b.SyntaxDef.FileType
b.Highlighter = highlight.NewHighlighter(b.SyntaxDef)
if b.Settings["syntax"].(bool) {
}
} else {
b.SyntaxDef = &highlight.EmptyDef
}
if b.SyntaxDef != nil {
b.Highlighter = highlight.NewHighlighter(b.SyntaxDef)
if b.Settings["syntax"].(bool) {
go func() {
b.Highlighter.HighlightStates(b)
}
b.Highlighter.HighlightMatches(b, 0, b.End().Y)
screen.Redraw()
}()
}
}
}
@@ -512,7 +744,7 @@ func (b *Buffer) ClearMatches() {
// depending on the settings)
func (b *Buffer) IndentString(tabsize int) string {
if b.Settings["tabstospaces"].(bool) {
return Spaces(tabsize)
return util.Spaces(tabsize)
}
return "\t"
}
@@ -600,7 +832,7 @@ func (b *Buffer) RemoveCursor(i int) {
copy(b.cursors[i:], b.cursors[i+1:])
b.cursors[len(b.cursors)-1] = nil
b.cursors = b.cursors[:len(b.cursors)-1]
b.curCursor = Clamp(b.curCursor, 0, len(b.cursors)-1)
b.curCursor = util.Clamp(b.curCursor, 0, len(b.cursors)-1)
b.UpdateCursors()
}
@@ -622,19 +854,18 @@ func (b *Buffer) MoveLinesUp(start int, end int) {
}
l := string(b.LineBytes(start - 1))
if end == len(b.lines) {
b.Insert(
b.insert(
Loc{
utf8.RuneCount(b.lines[end-1].data),
util.CharacterCount(b.lines[end-1].data),
end - 1,
},
"\n"+l,
)
} else {
b.Insert(
Loc{0, end},
l+"\n",
[]byte{'\n'},
)
}
b.Insert(
Loc{0, end},
l+"\n",
)
b.Remove(
Loc{0, start - 1},
Loc{0, start},
@@ -643,7 +874,7 @@ func (b *Buffer) MoveLinesUp(start int, end int) {
// MoveLinesDown moves the range of lines down one row
func (b *Buffer) MoveLinesDown(start int, end int) {
if start < 0 || start >= end || end >= len(b.lines)-1 {
if start < 0 || start >= end || end >= len(b.lines) {
return
}
l := string(b.LineBytes(end))
@@ -671,7 +902,7 @@ var BracePairs = [][2]rune{
// returns the location of the matching brace
// if the boolean returned is true then the original matching brace is one character left
// of the starting location
func (b *Buffer) FindMatchingBrace(braceType [2]rune, start Loc) (Loc, bool) {
func (b *Buffer) FindMatchingBrace(braceType [2]rune, start Loc) (Loc, bool, bool) {
curLine := []rune(string(b.LineBytes(start.Y)))
startChar := ' '
if start.X >= 0 && start.X < len(curLine) {
@@ -701,9 +932,9 @@ func (b *Buffer) FindMatchingBrace(braceType [2]rune, start Loc) (Loc, bool) {
i--
if i == 0 {
if startChar == braceType[0] {
return Loc{x, y}, false
return Loc{x, y}, false, true
}
return Loc{x, y}, true
return Loc{x, y}, true, true
}
}
}
@@ -725,9 +956,9 @@ func (b *Buffer) FindMatchingBrace(braceType [2]rune, start Loc) (Loc, bool) {
i--
if i == 0 {
if leftChar == braceType[1] {
return Loc{x, y}, true
return Loc{x, y}, true, true
}
return Loc{x, y}, false
return Loc{x, y}, false, true
}
} else if r == braceType[1] {
i++
@@ -735,19 +966,19 @@ func (b *Buffer) FindMatchingBrace(braceType [2]rune, start Loc) (Loc, bool) {
}
}
}
return start, true
return start, true, false
}
// Retab changes all tabs to spaces or vice versa
func (b *Buffer) Retab() {
toSpaces := b.Settings["tabstospaces"].(bool)
tabsize := IntOpt(b.Settings["tabsize"])
tabsize := util.IntOpt(b.Settings["tabsize"])
dirty := false
for i := 0; i < b.LinesNum(); i++ {
l := b.LineBytes(i)
ws := GetLeadingWhitespace(l)
ws := util.GetLeadingWhitespace(l)
if len(ws) != 0 {
if toSpaces {
ws = bytes.Replace(ws, []byte{'\t'}, bytes.Repeat([]byte{' '}, tabsize), -1)
@@ -758,6 +989,7 @@ func (b *Buffer) Retab() {
l = bytes.TrimLeft(l, " \t")
b.lines[i].data = append(ws, l...)
b.MarkModified(i, i)
dirty = true
}
@@ -776,12 +1008,12 @@ func ParseCursorLocation(cursorPositions []string) (Loc, error) {
}
startpos.Y, err = strconv.Atoi(cursorPositions[0])
startpos.Y -= 1
startpos.Y--
if err == nil {
if len(cursorPositions) > 1 {
startpos.X, err = strconv.Atoi(cursorPositions[1])
if startpos.X > 0 {
startpos.X -= 1
startpos.X--
}
}
}
@@ -789,10 +1021,117 @@ func ParseCursorLocation(cursorPositions []string) (Loc, error) {
return startpos, err
}
// Line returns the string representation of the given line number
func (b *Buffer) Line(i int) string {
return string(b.LineBytes(i))
}
func (b *Buffer) Write(bytes []byte) (n int, err error) {
b.EventHandler.InsertBytes(b.End(), bytes)
return len(bytes), nil
}
func (b *Buffer) updateDiffSync() {
b.diffLock.Lock()
defer b.diffLock.Unlock()
b.diff = make(map[int]DiffStatus)
if b.diffBase == nil {
return
}
differ := dmp.New()
baseRunes, bufferRunes, _ := differ.DiffLinesToRunes(string(b.diffBase), string(b.Bytes()))
diffs := differ.DiffMainRunes(baseRunes, bufferRunes, false)
lineN := 0
for _, diff := range diffs {
lineCount := len([]rune(diff.Text))
switch diff.Type {
case dmp.DiffEqual:
lineN += lineCount
case dmp.DiffInsert:
var status DiffStatus
if b.diff[lineN] == DSDeletedAbove {
status = DSModified
} else {
status = DSAdded
}
for i := 0; i < lineCount; i++ {
b.diff[lineN] = status
lineN++
}
case dmp.DiffDelete:
b.diff[lineN] = DSDeletedAbove
}
}
}
// UpdateDiff computes the diff between the diff base and the buffer content.
// The update may be performed synchronously or asynchronously.
// UpdateDiff calls the supplied callback when the update is complete.
// The argument passed to the callback is set to true if and only if
// the update was performed synchronously.
// If an asynchronous update is already pending when UpdateDiff is called,
// UpdateDiff does not schedule another update, in which case the callback
// is not called.
func (b *Buffer) UpdateDiff(callback func(bool)) {
if b.updateDiffTimer != nil {
return
}
lineCount := b.LinesNum()
if b.diffBaseLineCount > lineCount {
lineCount = b.diffBaseLineCount
}
if lineCount < 1000 {
b.updateDiffSync()
callback(true)
} else if lineCount < 30000 {
b.updateDiffTimer = time.AfterFunc(500*time.Millisecond, func() {
b.updateDiffTimer = nil
b.updateDiffSync()
callback(false)
})
} else {
// Don't compute diffs for very large files
b.diffLock.Lock()
b.diff = make(map[int]DiffStatus)
b.diffLock.Unlock()
callback(true)
}
}
// SetDiffBase sets the text that is used as the base for diffing the buffer content
func (b *Buffer) SetDiffBase(diffBase []byte) {
b.diffBase = diffBase
if diffBase == nil {
b.diffBaseLineCount = 0
} else {
b.diffBaseLineCount = strings.Count(string(diffBase), "\n")
}
b.UpdateDiff(func(synchronous bool) {
screen.Redraw()
})
}
// DiffStatus returns the diff status for a line in the buffer
func (b *Buffer) DiffStatus(lineN int) DiffStatus {
b.diffLock.RLock()
defer b.diffLock.RUnlock()
// Note that the zero value for DiffStatus is equal to DSUnchanged
return b.diff[lineN]
}
// WriteLog writes a string to the log buffer
func WriteLog(s string) {
LogBuf.EventHandler.Insert(LogBuf.End(), s)
}
// GetLogBuf returns the log buffer
func GetLogBuf() *Buffer {
return LogBuf
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,322 @@
package buffer
import (
"math/rand"
"strings"
"testing"
"github.com/stretchr/testify/assert"
lua "github.com/yuin/gopher-lua"
"github.com/zyedidia/micro/v2/internal/config"
ulua "github.com/zyedidia/micro/v2/internal/lua"
"github.com/zyedidia/micro/v2/internal/util"
)
type operation struct {
start Loc
end Loc
text []string
}
func init() {
ulua.L = lua.NewState()
config.InitGlobalSettings()
config.GlobalSettings["backup"] = false
config.GlobalSettings["fastdirty"] = true
}
func check(t *testing.T, before []string, operations []operation, after []string) {
assert := assert.New(t)
b := NewBufferFromString(strings.Join(before, "\n"), "", BTDefault)
assert.NotEqual("", b.GetName())
assert.Equal(false, b.ExternallyModified())
assert.Equal(false, b.Modified())
assert.Equal(1, b.NumCursors())
checkText := func(lines []string) {
assert.Equal([]byte(strings.Join(lines, "\n")), b.Bytes())
assert.Equal(len(lines), b.LinesNum())
for i, s := range lines {
assert.Equal(s, b.Line(i))
assert.Equal([]byte(s), b.LineBytes(i))
}
}
checkText(before)
var cursors []*Cursor
for _, op := range operations {
cursor := NewCursor(b, op.start)
cursor.SetSelectionStart(op.start)
cursor.SetSelectionEnd(op.end)
b.AddCursor(cursor)
cursors = append(cursors, cursor)
}
assert.Equal(1+len(operations), b.NumCursors())
for i, op := range operations {
cursor := cursors[i]
b.SetCurCursor(cursor.Num)
cursor.DeleteSelection()
b.Insert(cursor.Loc, strings.Join(op.text, "\n"))
}
checkText(after)
// must have exactly two events per operation (delete and insert)
for range operations {
b.UndoOneEvent()
b.UndoOneEvent()
}
checkText(before)
for i, op := range operations {
cursor := cursors[i]
if op.start == op.end {
assert.Equal(op.start, cursor.Loc)
} else {
assert.Equal(op.start, cursor.CurSelection[0])
assert.Equal(op.end, cursor.CurSelection[1])
}
}
for range operations {
b.RedoOneEvent()
b.RedoOneEvent()
}
checkText(after)
b.Close()
}
const maxLineLength = 200
var alphabet = []rune(" abcdeäم📚")
func randomString(length int) string {
runes := make([]rune, length)
for i := range runes {
runes[i] = alphabet[rand.Intn(len(alphabet))]
}
return string(runes)
}
func randomText(nLines int) string {
lines := make([]string, nLines)
for i := range lines {
lines[i] = randomString(rand.Intn(maxLineLength + 1))
}
return strings.Join(lines, "\n")
}
func benchCreateAndClose(testingB *testing.B, nLines int) {
rand.Seed(int64(nLines))
text := randomText(nLines)
testingB.ResetTimer()
for i := 0; i < testingB.N; i++ {
b := NewBufferFromString(text, "", BTDefault)
b.Close()
}
}
func benchRead(testingB *testing.B, nLines int) {
rand.Seed(int64(nLines))
b := NewBufferFromString(randomText(nLines), "", BTDefault)
testingB.ResetTimer()
for i := 0; i < testingB.N; i++ {
b.Bytes()
for j := 0; j < b.LinesNum(); j++ {
b.Line(j)
b.LineBytes(j)
}
}
testingB.StopTimer()
b.Close()
}
func benchEdit(testingB *testing.B, nLines, nCursors int) {
rand.Seed(int64(nLines + nCursors))
b := NewBufferFromString(randomText(nLines), "", BTDefault)
regionSize := nLines / nCursors
operations := make([]operation, nCursors)
for i := range operations {
startLine := (i * regionSize) + rand.Intn(regionSize-5)
startColumn := rand.Intn(util.CharacterCountInString(b.Line(startLine)) + 1)
endLine := startLine + 1 + rand.Intn(5)
endColumn := rand.Intn(util.CharacterCountInString(b.Line(endLine)) + 1)
operations[i] = operation{
start: Loc{startColumn, startLine},
end: Loc{endColumn, endLine},
text: []string{randomText(2 + rand.Intn(4))},
}
}
testingB.ResetTimer()
for i := 0; i < testingB.N; i++ {
b.SetCursors([]*Cursor{})
var cursors []*Cursor
for _, op := range operations {
cursor := NewCursor(b, op.start)
cursor.SetSelectionStart(op.start)
cursor.SetSelectionEnd(op.end)
b.AddCursor(cursor)
cursors = append(cursors, cursor)
}
for j, op := range operations {
cursor := cursors[j]
b.SetCurCursor(cursor.Num)
cursor.DeleteSelection()
b.Insert(cursor.Loc, op.text[0])
}
for b.UndoStack.Peek() != nil {
b.UndoOneEvent()
}
}
testingB.StopTimer()
b.Close()
}
func BenchmarkCreateAndClose10Lines(b *testing.B) {
benchCreateAndClose(b, 10)
}
func BenchmarkCreateAndClose100Lines(b *testing.B) {
benchCreateAndClose(b, 100)
}
func BenchmarkCreateAndClose1000Lines(b *testing.B) {
benchCreateAndClose(b, 1000)
}
func BenchmarkCreateAndClose10000Lines(b *testing.B) {
benchCreateAndClose(b, 10000)
}
func BenchmarkCreateAndClose100000Lines(b *testing.B) {
benchCreateAndClose(b, 100000)
}
func BenchmarkCreateAndClose1000000Lines(b *testing.B) {
benchCreateAndClose(b, 1000000)
}
func BenchmarkRead10Lines(b *testing.B) {
benchRead(b, 10)
}
func BenchmarkRead100Lines(b *testing.B) {
benchRead(b, 100)
}
func BenchmarkRead1000Lines(b *testing.B) {
benchRead(b, 1000)
}
func BenchmarkRead10000Lines(b *testing.B) {
benchRead(b, 10000)
}
func BenchmarkRead100000Lines(b *testing.B) {
benchRead(b, 100000)
}
func BenchmarkRead1000000Lines(b *testing.B) {
benchRead(b, 1000000)
}
func BenchmarkEdit10Lines1Cursor(b *testing.B) {
benchEdit(b, 10, 1)
}
func BenchmarkEdit100Lines1Cursor(b *testing.B) {
benchEdit(b, 100, 1)
}
func BenchmarkEdit100Lines10Cursors(b *testing.B) {
benchEdit(b, 100, 10)
}
func BenchmarkEdit1000Lines1Cursor(b *testing.B) {
benchEdit(b, 1000, 1)
}
func BenchmarkEdit1000Lines10Cursors(b *testing.B) {
benchEdit(b, 1000, 10)
}
func BenchmarkEdit1000Lines100Cursors(b *testing.B) {
benchEdit(b, 1000, 100)
}
func BenchmarkEdit10000Lines1Cursor(b *testing.B) {
benchEdit(b, 10000, 1)
}
func BenchmarkEdit10000Lines10Cursors(b *testing.B) {
benchEdit(b, 10000, 10)
}
func BenchmarkEdit10000Lines100Cursors(b *testing.B) {
benchEdit(b, 10000, 100)
}
func BenchmarkEdit10000Lines1000Cursors(b *testing.B) {
benchEdit(b, 10000, 1000)
}
func BenchmarkEdit100000Lines1Cursor(b *testing.B) {
benchEdit(b, 100000, 1)
}
func BenchmarkEdit100000Lines10Cursors(b *testing.B) {
benchEdit(b, 100000, 10)
}
func BenchmarkEdit100000Lines100Cursors(b *testing.B) {
benchEdit(b, 100000, 100)
}
func BenchmarkEdit100000Lines1000Cursors(b *testing.B) {
benchEdit(b, 100000, 1000)
}
func BenchmarkEdit1000000Lines1Cursor(b *testing.B) {
benchEdit(b, 1000000, 1)
}
func BenchmarkEdit1000000Lines10Cursors(b *testing.B) {
benchEdit(b, 1000000, 10)
}
func BenchmarkEdit1000000Lines100Cursors(b *testing.B) {
benchEdit(b, 1000000, 100)
}
func BenchmarkEdit1000000Lines1000Cursors(b *testing.B) {
benchEdit(b, 1000000, 1000)
}

View File

@@ -1,15 +1,13 @@
package buffer
import (
"unicode/utf8"
"github.com/zyedidia/clipboard"
"github.com/zyedidia/micro/internal/util"
"github.com/zyedidia/micro/v2/internal/util"
)
// InBounds returns whether the given location is a valid character position in the given buffer
func InBounds(pos Loc, buf *Buffer) bool {
if pos.Y < 0 || pos.Y >= len(buf.lines) || pos.X < 0 || pos.X > utf8.RuneCount(buf.LineBytes(pos.Y)) {
if pos.Y < 0 || pos.Y >= len(buf.lines) || pos.X < 0 || pos.X > util.CharacterCount(buf.LineBytes(pos.Y)) {
return false
}
@@ -76,9 +74,6 @@ func (c *Cursor) GetVisualX() int {
bytes := c.buf.LineBytes(c.Y)
tabsize := int(c.buf.Settings["tabsize"].(float64))
if c.X > utf8.RuneCount(bytes) {
c.X = utf8.RuneCount(bytes) - 1
}
return util.StringWidth(bytes, c.X, tabsize)
}
@@ -102,16 +97,29 @@ func (c *Cursor) Start() {
func (c *Cursor) StartOfText() {
c.Start()
for util.IsWhitespace(c.RuneUnder(c.X)) {
if c.X == utf8.RuneCount(c.buf.LineBytes(c.Y)) {
if c.X == util.CharacterCount(c.buf.LineBytes(c.Y)) {
break
}
c.Right()
}
}
// IsStartOfText returns whether the cursor is at the first
// non-whitespace rune of the line it is on
func (c *Cursor) IsStartOfText() bool {
x := 0
for util.IsWhitespace(c.RuneUnder(x)) {
if x == util.CharacterCount(c.buf.LineBytes(c.Y)) {
break
}
x++
}
return c.X == x
}
// End moves the cursor to the end of the line it is on
func (c *Cursor) End() {
c.X = utf8.RuneCount(c.buf.LineBytes(c.Y))
c.X = util.CharacterCount(c.buf.LineBytes(c.Y))
c.LastVisualX = c.GetVisualX()
}
@@ -229,8 +237,14 @@ func (c *Cursor) UpN(amount int) {
bytes := c.buf.LineBytes(proposedY)
c.X = c.GetCharPosInLine(bytes, c.LastVisualX)
if c.X > utf8.RuneCount(bytes) || (amount < 0 && proposedY == c.Y) {
c.X = utf8.RuneCount(bytes)
if c.X > util.CharacterCount(bytes) || (amount < 0 && proposedY == c.Y) {
c.X = util.CharacterCount(bytes)
c.StoreVisualX()
}
if c.X < 0 || (amount > 0 && proposedY == c.Y) {
c.X = 0
c.StoreVisualX()
}
c.Y = proposedY
@@ -272,7 +286,7 @@ func (c *Cursor) Right() {
if c.Loc == c.buf.End() {
return
}
if c.X < utf8.RuneCount(c.buf.LineBytes(c.Y)) {
if c.X < util.CharacterCount(c.buf.LineBytes(c.Y)) {
c.X++
} else {
c.Down()
@@ -293,8 +307,8 @@ func (c *Cursor) Relocate() {
if c.X < 0 {
c.X = 0
} else if c.X > utf8.RuneCount(c.buf.LineBytes(c.Y)) {
c.X = utf8.RuneCount(c.buf.LineBytes(c.Y))
} else if c.X > util.CharacterCount(c.buf.LineBytes(c.Y)) {
c.X = util.CharacterCount(c.buf.LineBytes(c.Y))
}
}
@@ -320,7 +334,7 @@ func (c *Cursor) SelectWord() {
c.SetSelectionStart(Loc{backward, c.Y})
c.OrigSelection[0] = c.CurSelection[0]
lineLen := utf8.RuneCount(c.buf.LineBytes(c.Y)) - 1
lineLen := util.CharacterCount(c.buf.LineBytes(c.Y)) - 1
for forward < lineLen && util.IsWordChar(c.RuneUnder(forward+1)) {
forward++
}
@@ -352,7 +366,7 @@ func (c *Cursor) AddWordToSelection() {
if c.Loc.GreaterThan(c.OrigSelection[1]) {
forward := c.X
lineLen := utf8.RuneCount(c.buf.LineBytes(c.Y)) - 1
lineLen := util.CharacterCount(c.buf.LineBytes(c.Y)) - 1
for forward < lineLen && util.IsWordChar(c.RuneUnder(forward+1)) {
forward++
}
@@ -379,7 +393,7 @@ func (c *Cursor) SelectTo(loc Loc) {
// WordRight moves the cursor one word to the right
func (c *Cursor) WordRight() {
for util.IsWhitespace(c.RuneUnder(c.X)) {
if c.X == utf8.RuneCount(c.buf.LineBytes(c.Y)) {
if c.X == util.CharacterCount(c.buf.LineBytes(c.Y)) {
c.Right()
return
}
@@ -387,7 +401,7 @@ func (c *Cursor) WordRight() {
}
c.Right()
for util.IsWordChar(c.RuneUnder(c.X)) {
if c.X == utf8.RuneCount(c.buf.LineBytes(c.Y)) {
if c.X == util.CharacterCount(c.buf.LineBytes(c.Y)) {
return
}
c.Right()
@@ -416,14 +430,14 @@ func (c *Cursor) WordLeft() {
// RuneUnder returns the rune under the given x position
func (c *Cursor) RuneUnder(x int) rune {
line := c.buf.LineBytes(c.Y)
if len(line) == 0 || x >= utf8.RuneCount(line) {
if len(line) == 0 || x >= util.CharacterCount(line) {
return '\n'
} else if x < 0 {
x = 0
}
i := 0
for len(line) > 0 {
r, size := utf8.DecodeRune(line)
r, _, size := util.DecodeCharacter(line)
line = line[size:]
if i == x {

View File

@@ -1,10 +1,15 @@
package buffer
import (
"bytes"
"time"
"unicode/utf8"
dmp "github.com/sergi/go-diff/diffmatchpatch"
"github.com/zyedidia/micro/v2/internal/config"
ulua "github.com/zyedidia/micro/v2/internal/lua"
"github.com/zyedidia/micro/v2/internal/screen"
"github.com/zyedidia/micro/v2/internal/util"
luar "layeh.com/gopher-luar"
)
const (
@@ -17,7 +22,7 @@ const (
// TextEventReplace represents a replace event
TextEventReplace = 0
undoThreshold = 500 // If two events are less than n milliseconds apart, undo both of them
undoThreshold = 1000 // If two events are less than n milliseconds apart, undo both of them
)
// TextEvent holds data for a manipulation on some text that can be undone
@@ -36,6 +41,73 @@ type Delta struct {
End Loc
}
// DoTextEvent runs a text event
func (eh *EventHandler) DoTextEvent(t *TextEvent, useUndo bool) {
oldl := eh.buf.LinesNum()
if useUndo {
eh.Execute(t)
} else {
ExecuteTextEvent(t, eh.buf)
}
if len(t.Deltas) != 1 {
return
}
text := t.Deltas[0].Text
start := t.Deltas[0].Start
lastnl := -1
var endX int
var textX int
if t.EventType == TextEventInsert {
linecount := eh.buf.LinesNum() - oldl
textcount := util.CharacterCount(text)
lastnl = bytes.LastIndex(text, []byte{'\n'})
if lastnl >= 0 {
endX = util.CharacterCount(text[lastnl+1:])
textX = endX
} else {
endX = start.X + textcount
textX = textcount
}
t.Deltas[0].End = clamp(Loc{endX, start.Y + linecount}, eh.buf.LineArray)
}
end := t.Deltas[0].End
for _, c := range eh.cursors {
move := func(loc Loc) Loc {
if t.EventType == TextEventInsert {
if start.Y != loc.Y && loc.GreaterThan(start) {
loc.Y += end.Y - start.Y
} else if loc.Y == start.Y && loc.GreaterEqual(start) {
loc.Y += end.Y - start.Y
if lastnl >= 0 {
loc.X += textX - start.X
} else {
loc.X += textX
}
}
return loc
} else {
if loc.Y != end.Y && loc.GreaterThan(end) {
loc.Y -= end.Y - start.Y
} else if loc.Y == end.Y && loc.GreaterEqual(end) {
loc = loc.MoveLA(-DiffLA(start, end, eh.buf.LineArray), eh.buf.LineArray)
}
return loc
}
}
c.Loc = move(c.Loc)
c.CurSelection[0] = move(c.CurSelection[0])
c.CurSelection[1] = move(c.CurSelection[1])
c.OrigSelection[0] = move(c.OrigSelection[0])
c.OrigSelection[1] = move(c.OrigSelection[1])
c.Relocate()
c.LastVisualX = c.GetVisualX()
}
}
// ExecuteTextEvent runs a text event
func ExecuteTextEvent(t *TextEvent, buf *SharedBuffer) {
if t.EventType == TextEventInsert {
@@ -51,7 +123,7 @@ func ExecuteTextEvent(t *TextEvent, buf *SharedBuffer) {
t.Deltas[i].Text = buf.remove(d.Start, d.End)
buf.insert(d.Start, d.Text)
t.Deltas[i].Start = d.Start
t.Deltas[i].End = Loc{d.Start.X + utf8.RuneCount(d.Text), d.Start.Y}
t.Deltas[i].End = Loc{d.Start.X + util.CharacterCount(d.Text), d.Start.Y}
}
for i, j := 0, len(t.Deltas)-1; i < j; i, j = i+1, j-1 {
t.Deltas[i], t.Deltas[j] = t.Deltas[j], t.Deltas[i]
@@ -60,9 +132,9 @@ func ExecuteTextEvent(t *TextEvent, buf *SharedBuffer) {
}
// UndoTextEvent undoes a text event
func UndoTextEvent(t *TextEvent, buf *SharedBuffer) {
func (eh *EventHandler) UndoTextEvent(t *TextEvent) {
t.EventType = -t.EventType
ExecuteTextEvent(t, buf)
eh.DoTextEvent(t, false)
}
// EventHandler executes text manipulations and allows undoing and redoing
@@ -94,12 +166,12 @@ func (eh *EventHandler) ApplyDiff(new string) {
loc := eh.buf.Start()
for _, d := range diff {
if d.Type == dmp.DiffDelete {
eh.Remove(loc, loc.MoveLA(utf8.RuneCountInString(d.Text), eh.buf.LineArray))
eh.Remove(loc, loc.MoveLA(util.CharacterCountInString(d.Text), eh.buf.LineArray))
} else {
if d.Type == dmp.DiffInsert {
eh.Insert(loc, d.Text)
}
loc = loc.MoveLA(utf8.RuneCountInString(d.Text), eh.buf.LineArray)
loc = loc.MoveLA(util.CharacterCountInString(d.Text), eh.buf.LineArray)
}
}
}
@@ -107,60 +179,38 @@ func (eh *EventHandler) ApplyDiff(new string) {
// Insert creates an insert text event and executes it
func (eh *EventHandler) Insert(start Loc, textStr string) {
text := []byte(textStr)
eh.InsertBytes(start, text)
}
// InsertBytes creates an insert text event and executes it
func (eh *EventHandler) InsertBytes(start Loc, text []byte) {
if len(text) == 0 {
return
}
start = clamp(start, eh.buf.LineArray)
e := &TextEvent{
C: *eh.cursors[eh.active],
EventType: TextEventInsert,
Deltas: []Delta{{text, start, Loc{0, 0}}},
Time: time.Now(),
}
eh.Execute(e)
e.Deltas[0].End = start.MoveLA(utf8.RuneCount(text), eh.buf.LineArray)
end := e.Deltas[0].End
for _, c := range eh.cursors {
move := func(loc Loc) Loc {
if start.Y != end.Y && loc.GreaterThan(start) {
loc.Y += end.Y - start.Y
} else if loc.Y == start.Y && loc.GreaterEqual(start) {
loc = loc.MoveLA(utf8.RuneCount(text), eh.buf.LineArray)
}
return loc
}
c.Loc = move(c.Loc)
c.CurSelection[0] = move(c.CurSelection[0])
c.CurSelection[1] = move(c.CurSelection[1])
c.OrigSelection[0] = move(c.OrigSelection[0])
c.OrigSelection[1] = move(c.OrigSelection[1])
c.LastVisualX = c.GetVisualX()
}
eh.DoTextEvent(e, true)
}
// Remove creates a remove text event and executes it
func (eh *EventHandler) Remove(start, end Loc) {
if start == end {
return
}
start = clamp(start, eh.buf.LineArray)
end = clamp(end, eh.buf.LineArray)
e := &TextEvent{
C: *eh.cursors[eh.active],
EventType: TextEventRemove,
Deltas: []Delta{{[]byte{}, start, end}},
Time: time.Now(),
}
eh.Execute(e)
for _, c := range eh.cursors {
move := func(loc Loc) Loc {
if start.Y != end.Y && loc.GreaterThan(end) {
loc.Y -= end.Y - start.Y
} else if loc.Y == end.Y && loc.GreaterEqual(end) {
loc = loc.MoveLA(-DiffLA(start, end, eh.buf.LineArray), eh.buf.LineArray)
}
return loc
}
c.Loc = move(c.Loc)
c.CurSelection[0] = move(c.CurSelection[0])
c.CurSelection[1] = move(c.CurSelection[1])
c.OrigSelection[0] = move(c.OrigSelection[0])
c.OrigSelection[1] = move(c.OrigSelection[1])
c.LastVisualX = c.GetVisualX()
}
eh.DoTextEvent(e, true)
}
// MultipleReplace creates an multiple insertions executes them
@@ -187,16 +237,14 @@ func (eh *EventHandler) Execute(t *TextEvent) {
}
eh.UndoStack.Push(t)
// TODO: Call plugins on text events
// for pl := range loadedPlugins {
// ret, err := Call(pl+".onBeforeTextEvent", t)
// if err != nil && !strings.HasPrefix(err.Error(), "function does not exist") {
// screen.TermMessage(err)
// }
// if val, ok := ret.(lua.LBool); ok && val == lua.LFalse {
// return
// }
// }
b, err := config.RunPluginFnBool("onBeforeTextEvent", luar.New(ulua.L, eh.buf), luar.New(ulua.L, t))
if err != nil {
screen.TermMessage(err)
}
if !b {
return
}
ExecuteTextEvent(t, eh.buf)
}
@@ -209,8 +257,7 @@ func (eh *EventHandler) Undo() {
}
startTime := t.Time.UnixNano() / int64(time.Millisecond)
eh.UndoOneEvent()
endTime := startTime - (startTime % undoThreshold)
for {
t = eh.UndoStack.Peek()
@@ -218,10 +265,9 @@ func (eh *EventHandler) Undo() {
return
}
if startTime-(t.Time.UnixNano()/int64(time.Millisecond)) > undoThreshold {
if t.Time.UnixNano()/int64(time.Millisecond) < endTime {
return
}
startTime = t.Time.UnixNano() / int64(time.Millisecond)
eh.UndoOneEvent()
}
@@ -235,10 +281,9 @@ func (eh *EventHandler) UndoOneEvent() {
if t == nil {
return
}
// Undo it
// Modifies the text event
UndoTextEvent(t, eh.buf)
eh.UndoTextEvent(t)
// Set the cursor in the right place
teCursor := t.C
@@ -261,8 +306,7 @@ func (eh *EventHandler) Redo() {
}
startTime := t.Time.UnixNano() / int64(time.Millisecond)
eh.RedoOneEvent()
endTime := startTime - (startTime % undoThreshold) + undoThreshold
for {
t = eh.RedoStack.Peek()
@@ -270,7 +314,7 @@ func (eh *EventHandler) Redo() {
return
}
if (t.Time.UnixNano()/int64(time.Millisecond))-startTime > undoThreshold {
if t.Time.UnixNano()/int64(time.Millisecond) > endTime {
return
}
@@ -285,9 +329,6 @@ func (eh *EventHandler) RedoOneEvent() {
return
}
// Modifies the text event
UndoTextEvent(t, eh.buf)
teCursor := t.C
if teCursor.Num >= 0 && teCursor.Num < len(eh.cursors) {
t.C = *eh.cursors[teCursor.Num]
@@ -296,5 +337,8 @@ func (eh *EventHandler) RedoOneEvent() {
teCursor.Num = -1
}
// Modifies the text event
eh.UndoTextEvent(t)
eh.UndoStack.Push(t)
}

View File

@@ -2,10 +2,12 @@ package buffer
import (
"bufio"
"bytes"
"io"
"unicode/utf8"
"sync"
"github.com/zyedidia/micro/pkg/highlight"
"github.com/zyedidia/micro/v2/internal/util"
"github.com/zyedidia/micro/v2/pkg/highlight"
)
// Finds the byte index of the nth rune in a byte slice
@@ -17,7 +19,7 @@ func runeToByteIndex(n int, txt []byte) int {
count := 0
i := 0
for len(txt) > 0 {
_, size := utf8.DecodeRune(txt)
_, _, size := util.DecodeCharacter(txt)
txt = txt[size:]
count += size
@@ -38,6 +40,7 @@ type Line struct {
state highlight.State
match highlight.LineMatch
rehighlight bool
lock sync.Mutex
}
const (
@@ -84,6 +87,8 @@ func NewLineArray(size uint64, endings FileFormat, reader io.Reader) *LineArray
br := bufio.NewReader(reader)
var loaded int
la.Endings = endings
n := 0
for {
data, err := br.ReadBytes('\n')
@@ -124,12 +129,22 @@ func NewLineArray(size uint64, endings FileFormat, reader io.Reader) *LineArray
if err != nil {
if err == io.EOF {
la.lines = Append(la.lines, Line{data[:], nil, nil, false})
la.lines = Append(la.lines, Line{
data: data[:],
state: nil,
match: nil,
rehighlight: false,
})
}
// Last line was read
break
} else {
la.lines = Append(la.lines, Line{data[:dlen-1], nil, nil, false})
la.lines = Append(la.lines, Line{
data: data[:dlen-1],
state: nil,
match: nil,
rehighlight: false,
})
}
n++
}
@@ -140,34 +155,51 @@ func NewLineArray(size uint64, endings FileFormat, reader io.Reader) *LineArray
// Bytes returns the string that should be written to disk when
// the line array is saved
func (la *LineArray) Bytes() []byte {
str := make([]byte, 0, la.initsize+1000) // initsize should provide a good estimate
b := new(bytes.Buffer)
// initsize should provide a good estimate
b.Grow(int(la.initsize + 4096))
for i, l := range la.lines {
str = append(str, l.data...)
b.Write(l.data)
if i != len(la.lines)-1 {
if la.Endings == FFDos {
str = append(str, '\r')
b.WriteByte('\r')
}
str = append(str, '\n')
b.WriteByte('\n')
}
}
return str
return b.Bytes()
}
// newlineBelow adds a newline below the given line number
func (la *LineArray) newlineBelow(y int) {
la.lines = append(la.lines, Line{[]byte{' '}, nil, nil, false})
la.lines = append(la.lines, Line{
data: []byte{' '},
state: nil,
match: nil,
rehighlight: false,
})
copy(la.lines[y+2:], la.lines[y+1:])
la.lines[y+1] = Line{[]byte{}, la.lines[y].state, nil, false}
la.lines[y+1] = Line{
data: []byte{},
state: la.lines[y].state,
match: nil,
rehighlight: false,
}
}
// Inserts a byte array at a given location
func (la *LineArray) insert(pos Loc, value []byte) {
x, y := runeToByteIndex(pos.X, la.lines[pos.Y].data), pos.Y
for i := 0; i < len(value); i++ {
if value[i] == '\n' {
if value[i] == '\n' || (value[i] == '\r' && i < len(value)-1 && value[i+1] == '\n') {
la.split(Loc{x, y})
x = 0
y++
if value[i] == '\r' {
i++
}
continue
}
la.insertByte(Loc{x, y}, value[i])
@@ -208,9 +240,7 @@ func (la *LineArray) remove(start, end Loc) []byte {
if start.Y == end.Y {
la.lines[start.Y].data = append(la.lines[start.Y].data[:startX], la.lines[start.Y].data[endX:]...)
} else {
for i := start.Y + 1; i <= end.Y-1; i++ {
la.deleteLine(start.Y + 1)
}
la.deleteLines(start.Y+1, end.Y-1)
la.deleteToEnd(Loc{startX, start.Y})
la.deleteFromStart(Loc{endX - 1, start.Y + 1})
la.joinLines(start.Y, start.Y+1)
@@ -233,6 +263,10 @@ func (la *LineArray) deleteLine(y int) {
la.lines = la.lines[:y+copy(la.lines[y:], la.lines[y+1:])]
}
func (la *LineArray) deleteLines(y1, y2 int) {
la.lines = la.lines[:y1+copy(la.lines[y1:], la.lines[y2+1:])]
}
// DeleteByte deletes the byte at a position
func (la *LineArray) deleteByte(pos Loc) {
la.lines[pos.Y].data = la.lines[pos.Y].data[:pos.X+copy(la.lines[pos.Y].data[pos.X:], la.lines[pos.Y].data[pos.X+1:])]
@@ -272,7 +306,7 @@ func (la *LineArray) Start() Loc {
// End returns the location of the last character in the buffer
func (la *LineArray) End() Loc {
numlines := len(la.lines)
return Loc{utf8.RuneCount(la.lines[numlines-1].data), numlines - 1}
return Loc{util.CharacterCount(la.lines[numlines-1].data), numlines - 1}
}
// LineBytes returns line n as an array of bytes
@@ -285,28 +319,40 @@ func (la *LineArray) LineBytes(n int) []byte {
// State gets the highlight state for the given line number
func (la *LineArray) State(lineN int) highlight.State {
la.lines[lineN].lock.Lock()
defer la.lines[lineN].lock.Unlock()
return la.lines[lineN].state
}
// SetState sets the highlight state at the given line number
func (la *LineArray) SetState(lineN int, s highlight.State) {
la.lines[lineN].lock.Lock()
defer la.lines[lineN].lock.Unlock()
la.lines[lineN].state = s
}
// SetMatch sets the match at the given line number
func (la *LineArray) SetMatch(lineN int, m highlight.LineMatch) {
la.lines[lineN].lock.Lock()
defer la.lines[lineN].lock.Unlock()
la.lines[lineN].match = m
}
// Match retrieves the match for the given line number
func (la *LineArray) Match(lineN int) highlight.LineMatch {
la.lines[lineN].lock.Lock()
defer la.lines[lineN].lock.Unlock()
return la.lines[lineN].match
}
func (la *LineArray) Rehighlight(lineN int) bool {
la.lines[lineN].lock.Lock()
defer la.lines[lineN].lock.Unlock()
return la.lines[lineN].rehighlight
}
func (la *LineArray) SetRehighlight(lineN int, on bool) {
la.lines[lineN].lock.Lock()
defer la.lines[lineN].lock.Unlock()
la.lines[lineN].rehighlight = on
}

View File

@@ -46,14 +46,15 @@ func TestInsert(t *testing.T) {
assert.Equal(t, []byte("Uppen Sevarne staþe, foobar sel þar him þuhte,"), sub1)
la.insert(Loc{25, 2}, []byte("ಮಣ್ಣಾಗಿ"))
la.insert(Loc{25, 2}, []byte("H̼̥̯͇͙̕͘͞e̸̦̞̠̣̰͙̼̥̦̼̖̬͕͕̰̯̫͇̕ĺ̜̠̩̯̯͙̼̭̠͕̮̞͜l̶͓̫̞̮͈͞ͅo̸͔͙̳̠͈̮̼̳͙̥̲͜͠"))
sub2 := la.Substr(Loc{0, 2}, Loc{60, 2})
assert.Equal(t, []byte("He wonede at Ernleȝe at æಮಣ್ಣಾಗಿðelen are chirechen,"), sub2)
assert.Equal(t, []byte("He wonede at Ernleȝe at æH̼̥̯͇͙̕͘͞e̸̦̞̠̣̰͙̼̥̦̼̖̬͕͕̰̯̫͇̕ĺ̜̠̩̯̯͙̼̭̠͕̮̞͜l̶͓̫̞̮͈͞ͅo̸͔͙̳̠͈̮̼̳͙̥̲͜͠ðelen are chirechen,"), sub2)
}
func TestRemove(t *testing.T) {
la.remove(Loc{20, 3}, Loc{27, 3})
la.remove(Loc{25, 2}, Loc{32, 2})
la.remove(Loc{25, 2}, Loc{30, 2})
bytes := la.Bytes()
assert.Equal(t, unicode_txt, string(bytes))

View File

@@ -1,9 +1,7 @@
package buffer
import (
"unicode/utf8"
"github.com/zyedidia/micro/internal/util"
"github.com/zyedidia/micro/v2/internal/util"
)
// Loc stores a location
@@ -68,9 +66,9 @@ func DiffLA(a, b Loc, buf *LineArray) int {
loc := 0
for i := a.Y + 1; i < b.Y; i++ {
// + 1 for the newline
loc += utf8.RuneCount(buf.LineBytes(i)) + 1
loc += util.CharacterCount(buf.LineBytes(i)) + 1
}
loc += utf8.RuneCount(buf.LineBytes(a.Y)) - a.X + b.X + 1
loc += util.CharacterCount(buf.LineBytes(a.Y)) - a.X + b.X + 1
return loc
}
@@ -80,7 +78,7 @@ func (l Loc) right(buf *LineArray) Loc {
return Loc{l.X + 1, l.Y}
}
var res Loc
if l.X < utf8.RuneCount(buf.LineBytes(l.Y)) {
if l.X < util.CharacterCount(buf.LineBytes(l.Y)) {
res = Loc{l.X + 1, l.Y}
} else {
res = Loc{0, l.Y + 1}
@@ -97,12 +95,12 @@ func (l Loc) left(buf *LineArray) Loc {
if l.X > 0 {
res = Loc{l.X - 1, l.Y}
} else {
res = Loc{utf8.RuneCount(buf.LineBytes(l.Y - 1)), l.Y - 1}
res = Loc{util.CharacterCount(buf.LineBytes(l.Y - 1)), l.Y - 1}
}
return res
}
// Move moves the cursor n characters to the left or right
// MoveLA moves the cursor n characters to the left or right
// It moves the cursor left if n is negative
func (l Loc) MoveLA(n int, buf *LineArray) Loc {
if n > 0 {
@@ -117,9 +115,12 @@ func (l Loc) MoveLA(n int, buf *LineArray) Loc {
return l
}
func (l Loc) Diff(a, b Loc, buf *Buffer) int {
return DiffLA(a, b, buf.LineArray)
// Diff returns the difference between two locs
func (l Loc) Diff(b Loc, buf *Buffer) int {
return DiffLA(l, b, buf.LineArray)
}
// Move moves a loc n characters
func (l Loc) Move(n int, buf *Buffer) Loc {
return l.MoveLA(n, buf.LineArray)
}
@@ -135,3 +136,13 @@ func ByteOffset(pos Loc, buf *Buffer) int {
loc += len(buf.Line(y)[:x])
return loc
}
// clamps a loc within a buffer
func clamp(pos Loc, la *LineArray) Loc {
if pos.GreaterEqual(la.End()) {
return la.End()
} else if pos.LessThan(la.Start()) {
return la.Start()
}
return pos
}

View File

@@ -1,7 +1,7 @@
package buffer
import (
"github.com/zyedidia/micro/internal/config"
"github.com/zyedidia/micro/v2/internal/config"
"github.com/zyedidia/tcell"
)
@@ -13,13 +13,19 @@ const (
MTError
)
// Message represents the information for a gutter message
type Message struct {
Msg string
// The Msg iteslf
Msg string
// Start and End locations for the message
Start, End Loc
Kind MsgType
Owner string
// The Kind stores the message type
Kind MsgType
// The Owner of the message
Owner string
}
// NewMessage creates a new gutter message
func NewMessage(owner string, msg string, start, end Loc, kind MsgType) *Message {
return &Message{
Msg: msg,
@@ -30,6 +36,7 @@ func NewMessage(owner string, msg string, start, end Loc, kind MsgType) *Message
}
}
// NewMessageAtLine creates a new gutter message at a given line
func NewMessageAtLine(owner string, msg string, line int, kind MsgType) *Message {
start := Loc{-1, line - 1}
end := start

View File

@@ -1,6 +1,7 @@
package buffer
import (
"bufio"
"bytes"
"errors"
"io"
@@ -8,12 +9,12 @@ import (
"os/exec"
"os/signal"
"path/filepath"
"runtime"
"unicode"
"unicode/utf8"
"github.com/zyedidia/micro/internal/config"
"github.com/zyedidia/micro/internal/screen"
. "github.com/zyedidia/micro/internal/util"
"github.com/zyedidia/micro/v2/internal/config"
"github.com/zyedidia/micro/v2/internal/screen"
"github.com/zyedidia/micro/v2/internal/util"
"golang.org/x/text/encoding"
"golang.org/x/text/encoding/htmlindex"
"golang.org/x/text/transform"
@@ -26,70 +27,43 @@ const LargeFileThreshold = 50000
// overwriteFile opens the given file for writing, truncating if one exists, and then calls
// the supplied function with the file as io.Writer object, also making sure the file is
// closed afterwards.
func overwriteFile(name string, enc encoding.Encoding, fn func(io.Writer) error) (err error) {
var file *os.File
func overwriteFile(name string, enc encoding.Encoding, fn func(io.Writer) error, withSudo bool) (err error) {
var writeCloser io.WriteCloser
if file, err = os.OpenFile(name, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644); err != nil {
return
}
if withSudo {
cmd := exec.Command(config.GlobalSettings["sucmd"].(string), "dd", "bs=4k", "of="+name)
defer func() {
if e := file.Close(); e != nil && err == nil {
err = e
if writeCloser, err = cmd.StdinPipe(); err != nil {
return
}
}()
w := transform.NewWriter(file, enc.NewEncoder())
// w := bufio.NewWriter(file)
if err = fn(w); err != nil {
return
}
// err = w.Flush()
return
}
// overwriteFileAsRoot executes dd as root and then calls the supplied function
// with dd's standard input as an io.Writer object. Dd opens the given file for writing,
// truncating it if it exists, and writes what it receives on its standard input to the file.
func overwriteFileAsRoot(name string, enc encoding.Encoding, fn func(io.Writer) error) (err error) {
cmd := exec.Command(config.GlobalSettings["sucmd"].(string), "dd", "status=none", "bs=4K", "of="+name)
var stdin io.WriteCloser
screenb := screen.TempFini()
// This is a trap for Ctrl-C so that it doesn't kill micro
// Instead we trap Ctrl-C to kill the program we're running
c := make(chan os.Signal, 1)
signal.Notify(c, os.Interrupt)
go func() {
for range c {
c := make(chan os.Signal, 1)
signal.Notify(c, os.Interrupt)
go func() {
<-c
cmd.Process.Kill()
}
}()
}()
if stdin, err = cmd.StdinPipe(); err != nil {
defer func() {
screenb := screen.TempFini()
if e := cmd.Run(); e != nil && err == nil {
err = e
}
screen.TempStart(screenb)
}()
} else if writeCloser, err = os.OpenFile(name, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644); err != nil {
return
}
if err = cmd.Start(); err != nil {
return
w := bufio.NewWriter(transform.NewWriter(writeCloser, enc.NewEncoder()))
err = fn(w)
w.Flush()
if e := writeCloser.Close(); e != nil && err == nil {
err = e
}
e := fn(stdin)
if err = stdin.Close(); err != nil {
return
}
if err = cmd.Wait(); err != nil {
return
}
screen.TempStart(screenb)
return e
return
}
// Save saves the buffer to its default path
@@ -118,13 +92,15 @@ func (b *Buffer) saveToFile(filename string, withSudo bool) error {
if b.Type.Scratch {
return errors.New("Cannot save scratch buffer")
}
if withSudo && runtime.GOOS == "windows" {
return errors.New("Save with sudo not supported on Windows")
}
b.UpdateRules()
if b.Settings["rmtrailingws"].(bool) {
for i, l := range b.lines {
leftover := utf8.RuneCount(bytes.TrimRightFunc(l.data, unicode.IsSpace))
leftover := util.CharacterCount(bytes.TrimRightFunc(l.data, unicode.IsSpace))
linelen := utf8.RuneCount(l.data)
linelen := util.CharacterCount(l.data)
b.Remove(Loc{leftover, i}, Loc{linelen, i})
}
@@ -133,19 +109,19 @@ func (b *Buffer) saveToFile(filename string, withSudo bool) error {
if b.Settings["eofnewline"].(bool) {
end := b.End()
if b.RuneAt(Loc{end.X - 1, end.Y}) != '\n' {
b.Insert(end, "\n")
if b.RuneAt(Loc{end.X, end.Y}) != '\n' {
b.insert(end, []byte{'\n'})
}
}
// Update the last time this file was updated after saving
defer func() {
b.ModTime, _ = GetModTime(filename)
b.ModTime, _ = util.GetModTime(filename)
err = b.Serialize()
}()
// Removes any tilde and replaces with the absolute path to home
absFilename, _ := ReplaceHome(filename)
absFilename, _ := util.ReplaceHome(filename)
// Get the leading path to the file | "." is returned if there's no leading path provided
if dirname := filepath.Dir(absFilename); dirname != "." {
@@ -201,13 +177,7 @@ func (b *Buffer) saveToFile(filename string, withSudo bool) error {
return
}
if withSudo {
err = overwriteFileAsRoot(absFilename, enc, fwriter)
} else {
err = overwriteFile(absFilename, enc, fwriter)
}
if err != nil {
if err = overwriteFile(absFilename, enc, fwriter, withSudo); err != nil {
return err
}
@@ -224,5 +194,6 @@ func (b *Buffer) saveToFile(filename string, withSudo bool) error {
absPath, _ := filepath.Abs(filename)
b.AbsPath = absPath
b.isModified = false
b.UpdateRules()
return err
}

View File

@@ -2,12 +2,18 @@ package buffer
import (
"regexp"
"unicode/utf8"
"github.com/zyedidia/micro/internal/util"
"github.com/zyedidia/micro/v2/internal/util"
)
func (b *Buffer) findDown(r *regexp.Regexp, start, end Loc) ([2]Loc, bool) {
lastcn := util.CharacterCount(b.LineBytes(b.LinesNum() - 1))
if start.Y > b.LinesNum()-1 {
start.X = lastcn - 1
}
if end.Y > b.LinesNum()-1 {
end.X = lastcn
}
start.Y = util.Clamp(start.Y, 0, b.LinesNum()-1)
end.Y = util.Clamp(end.Y, 0, b.LinesNum()-1)
@@ -20,19 +26,19 @@ func (b *Buffer) findDown(r *regexp.Regexp, start, end Loc) ([2]Loc, bool) {
charpos := 0
if i == start.Y && start.Y == end.Y {
nchars := utf8.RuneCount(l)
nchars := util.CharacterCount(l)
start.X = util.Clamp(start.X, 0, nchars)
end.X = util.Clamp(end.X, 0, nchars)
l = util.SliceStart(l, end.X)
l = util.SliceEnd(l, start.X)
charpos = start.X
} else if i == start.Y {
nchars := utf8.RuneCount(l)
nchars := util.CharacterCount(l)
start.X = util.Clamp(start.X, 0, nchars)
l = util.SliceEnd(l, start.X)
charpos = start.X
} else if i == end.Y {
nchars := utf8.RuneCount(l)
nchars := util.CharacterCount(l)
end.X = util.Clamp(end.X, 0, nchars)
l = util.SliceStart(l, end.X)
}
@@ -49,6 +55,13 @@ func (b *Buffer) findDown(r *regexp.Regexp, start, end Loc) ([2]Loc, bool) {
}
func (b *Buffer) findUp(r *regexp.Regexp, start, end Loc) ([2]Loc, bool) {
lastcn := util.CharacterCount(b.LineBytes(b.LinesNum() - 1))
if start.Y > b.LinesNum()-1 {
start.X = lastcn - 1
}
if end.Y > b.LinesNum()-1 {
end.X = lastcn
}
start.Y = util.Clamp(start.Y, 0, b.LinesNum()-1)
end.Y = util.Clamp(end.Y, 0, b.LinesNum()-1)
@@ -61,19 +74,19 @@ func (b *Buffer) findUp(r *regexp.Regexp, start, end Loc) ([2]Loc, bool) {
charpos := 0
if i == start.Y && start.Y == end.Y {
nchars := utf8.RuneCount(l)
nchars := util.CharacterCount(l)
start.X = util.Clamp(start.X, 0, nchars)
end.X = util.Clamp(end.X, 0, nchars)
l = util.SliceStart(l, end.X)
l = util.SliceEnd(l, start.X)
charpos = start.X
} else if i == start.Y {
nchars := utf8.RuneCount(l)
nchars := util.CharacterCount(l)
start.X = util.Clamp(start.X, 0, nchars)
l = util.SliceEnd(l, start.X)
charpos = start.X
} else if i == end.Y {
nchars := utf8.RuneCount(l)
nchars := util.CharacterCount(l)
end.X = util.Clamp(end.X, 0, nchars)
l = util.SliceStart(l, end.X)
}
@@ -115,29 +128,32 @@ func (b *Buffer) FindNext(s string, start, end, from Loc, down bool, useRegex bo
return [2]Loc{}, false, err
}
found := false
var found bool
var l [2]Loc
if down {
l, found = b.findDown(r, from, end)
if !found {
l, found = b.findDown(r, start, from)
l, found = b.findDown(r, start, end)
}
} else {
l, found = b.findUp(r, from, start)
if !found {
l, found = b.findUp(r, end, from)
l, found = b.findUp(r, end, start)
}
}
return l, found, nil
}
// ReplaceRegex replaces all occurrences of 'search' with 'replace' in the given area
// and returns the number of replacements made
func (b *Buffer) ReplaceRegex(start, end Loc, search *regexp.Regexp, replace []byte) int {
// and returns the number of replacements made and the number of runes
// added or removed on the last line of the range
func (b *Buffer) ReplaceRegex(start, end Loc, search *regexp.Regexp, replace []byte) (int, int) {
if start.GreaterThan(end) {
start, end = end, start
}
netrunes := 0
found := 0
var deltas []Delta
for i := start.Y; i <= end.Y; i++ {
@@ -155,16 +171,23 @@ func (b *Buffer) ReplaceRegex(start, end Loc, search *regexp.Regexp, replace []b
l = util.SliceStart(l, end.X)
}
newText := search.ReplaceAllFunc(l, func(in []byte) []byte {
result := []byte{}
for _, submatches := range search.FindAllSubmatchIndex(in, -1) {
result = search.Expand(result, replace, in, submatches)
}
found++
return replace
if i == end.Y {
netrunes += util.CharacterCount(result) - util.CharacterCount(in)
}
return result
})
from := Loc{charpos, i}
to := Loc{charpos + utf8.RuneCount(l), i}
to := Loc{charpos + util.CharacterCount(l), i}
deltas = append(deltas, Delta{newText, from, to})
}
b.MultipleReplace(deltas)
return found
return found, netrunes
}

View File

@@ -5,12 +5,13 @@ import (
"errors"
"io"
"os"
"path/filepath"
"time"
"golang.org/x/text/encoding"
"github.com/zyedidia/micro/internal/config"
. "github.com/zyedidia/micro/internal/util"
"github.com/zyedidia/micro/v2/internal/config"
"github.com/zyedidia/micro/v2/internal/util"
)
// The SerializedBuffer holds the types that get serialized when a buffer is saved
@@ -30,7 +31,7 @@ func (b *Buffer) Serialize() error {
return nil
}
name := config.ConfigDir + "/buffers/" + EscapePath(b.AbsPath)
name := filepath.Join(config.ConfigDir, "buffers", util.EscapePath(b.AbsPath))
return overwriteFile(name, encoding.Nop, func(file io.Writer) error {
err := gob.NewEncoder(file).Encode(SerializedBuffer{
@@ -39,23 +40,24 @@ func (b *Buffer) Serialize() error {
b.ModTime,
})
return err
})
}, false)
}
// Unserialize loads the buffer info from config.ConfigDir/buffers
func (b *Buffer) Unserialize() error {
// If either savecursor or saveundo is turned on, we need to load the serialized information
// from ~/.config/micro/buffers
if b.Path == "" {
return nil
}
file, err := os.Open(config.ConfigDir + "/buffers/" + EscapePath(b.AbsPath))
file, err := os.Open(filepath.Join(config.ConfigDir, "buffers", util.EscapePath(b.AbsPath)))
defer file.Close()
if err == nil {
var buffer SerializedBuffer
decoder := gob.NewDecoder(file)
err = decoder.Decode(&buffer)
if err != nil {
return errors.New(err.Error() + "\nYou may want to remove the files in ~/.config/micro/buffers (these files store the information for the 'saveundo' and 'savecursor' options) if this problem persists.")
return errors.New(err.Error() + "\nYou may want to remove the files in ~/.config/micro/buffers (these files\nstore the information for the 'saveundo' and 'savecursor' options) if\nthis problem persists.\nThis may be caused by upgrading to version 2.0, and removing the 'buffers'\ndirectory will reset the cursor and undo history and solve the problem.")
}
if b.Settings["savecursor"].(bool) {
b.StartCursor = buffer.Cursor

View File

@@ -1,8 +1,8 @@
package buffer
import (
"github.com/zyedidia/micro/internal/config"
"github.com/zyedidia/micro/internal/screen"
"github.com/zyedidia/micro/v2/internal/config"
"github.com/zyedidia/micro/v2/internal/screen"
)
func (b *Buffer) SetOptionNative(option string, nativeValue interface{}) error {
@@ -35,7 +35,7 @@ func (b *Buffer) SetOptionNative(option string, nativeValue interface{}) error {
}
} else if option == "encoding" {
b.isModified = true
} else if option == "readonly" {
} else if option == "readonly" && b.Type.Kind == BTDefault.Kind {
b.Type.Readonly = nativeValue.(bool)
}

View File

@@ -151,6 +151,9 @@ func StringToStyle(str string) tcell.Style {
if strings.Contains(str, "bold") {
style = style.Bold(true)
}
if strings.Contains(str, "italic") {
style = style.Italic(true)
}
if strings.Contains(str, "reverse") {
style = style.Reverse(true)
}

View File

@@ -3,6 +3,7 @@ package config
import (
"errors"
"os"
"path/filepath"
homedir "github.com/mitchellh/go-homedir"
)
@@ -14,16 +15,22 @@ var ConfigDir string
func InitConfigDir(flagConfigDir string) error {
var e error
xdgHome := os.Getenv("XDG_CONFIG_HOME")
if xdgHome == "" {
// The user has not set $XDG_CONFIG_HOME so we should act like it was set to ~/.config
home, err := homedir.Dir()
if err != nil {
return errors.New("Error finding your home directory\nCan't load config files")
microHome := os.Getenv("MICRO_CONFIG_HOME")
if microHome == "" {
// The user has not set $MICRO_CONFIG_HOME so we'll try $XDG_CONFIG_HOME
xdgHome := os.Getenv("XDG_CONFIG_HOME")
if xdgHome == "" {
// The user has not set $XDG_CONFIG_HOME so we should act like it was set to ~/.config
home, err := homedir.Dir()
if err != nil {
return errors.New("Error finding your home directory\nCan't load config files: " + err.Error())
}
xdgHome = filepath.Join(home, ".config")
}
xdgHome = home + "/.config"
microHome = filepath.Join(xdgHome, "micro")
}
ConfigDir = xdgHome + "/micro"
ConfigDir = microHome
if len(flagConfigDir) > 0 {
if _, err := os.Stat(flagConfigDir); os.IsNotExist(err) {
@@ -34,20 +41,11 @@ func InitConfigDir(flagConfigDir string) error {
}
}
if _, err := os.Stat(xdgHome); os.IsNotExist(err) {
// If the xdgHome doesn't exist we should create it
err = os.Mkdir(xdgHome, os.ModePerm)
if err != nil {
return errors.New("Error creating XDG_CONFIG_HOME directory: " + err.Error())
}
}
if _, err := os.Stat(ConfigDir); os.IsNotExist(err) {
// If the micro specific config directory doesn't exist we should create that too
err = os.Mkdir(ConfigDir, os.ModePerm)
if err != nil {
return errors.New("Error creating configuration directory: " + err.Error())
}
// Create micro config home directory if it does not exist
// This creates parent directories and does nothing if it already exists
err := os.MkdirAll(ConfigDir, os.ModePerm)
if err != nil {
return errors.New("Error creating configuration directory: " + err.Error())
}
return e

View File

@@ -2,11 +2,13 @@ package config
import (
"errors"
"log"
lua "github.com/yuin/gopher-lua"
ulua "github.com/zyedidia/micro/internal/lua"
ulua "github.com/zyedidia/micro/v2/internal/lua"
)
// ErrNoSuchFunction is returned when Call is executed on a function that does not exist
var ErrNoSuchFunction = errors.New("No such function exists")
// LoadAllPlugins loads all detected plugins (in runtime/plugins and ConfigDir/plugins)
@@ -55,16 +57,16 @@ func RunPluginFnBool(fn string, args ...lua.LValue) (bool, error) {
reterr = errors.New("Plugin " + p.Name + ": " + err.Error())
continue
}
if v, ok := val.(lua.LBool); !ok {
reterr = errors.New(p.Name + "." + fn + " should return a boolean")
} else {
if v, ok := val.(lua.LBool); ok {
retbool = retbool && bool(v)
}
}
return retbool, reterr
}
// Plugin stores information about the source files/info for a plugin
type Plugin struct {
DirName string // name of plugin folder
Name string // name of plugin
Info *PluginInfo // json file containing info
Srcs []RuntimeFile // lua files
@@ -72,6 +74,7 @@ type Plugin struct {
Default bool // pre-installed plugin
}
// IsEnabled returns if a plugin is enabled
func (p *Plugin) IsEnabled() bool {
if v, ok := GlobalSettings[p.Name]; ok {
return v.(bool) && p.Loaded
@@ -79,13 +82,15 @@ func (p *Plugin) IsEnabled() bool {
return true
}
// Plugins is a list of all detected plugins (enabled or disabled)
var Plugins []*Plugin
// Load creates an option for the plugin and runs all source files
func (p *Plugin) Load() error {
if v, ok := GlobalSettings[p.Name]; ok && !v.(bool) {
return nil
}
for _, f := range p.Srcs {
if v, ok := GlobalSettings[p.Name]; ok && !v.(bool) {
return nil
}
dat, err := f.Data()
if err != nil {
return err
@@ -94,14 +99,19 @@ func (p *Plugin) Load() error {
if err != nil {
return err
}
p.Loaded = true
RegisterGlobalOption(p.Name, true)
}
p.Loaded = true
RegisterGlobalOption(p.Name, true)
return nil
}
// Call calls a given function in this plugin
func (p *Plugin) Call(fn string, args ...lua.LValue) (lua.LValue, error) {
plug := ulua.L.GetGlobal(p.Name)
if plug == lua.LNil {
log.Println("Plugin does not exist:", p.Name, "at", p.DirName, ":", p)
return nil, nil
}
luafn := ulua.L.GetField(plug, fn)
if luafn == lua.LNil {
return nil, ErrNoSuchFunction
@@ -119,7 +129,23 @@ func (p *Plugin) Call(fn string, args ...lua.LValue) (lua.LValue, error) {
return ret, nil
}
// FindPlugin returns the plugin with the given name
func FindPlugin(name string) *Plugin {
var pl *Plugin
for _, p := range Plugins {
if !p.IsEnabled() {
continue
}
if p.Name == name {
pl = p
break
}
}
return pl
}
// FindAnyPlugin does not require the plugin to be enabled
func FindAnyPlugin(name string) *Plugin {
var pl *Plugin
for _, p := range Plugins {
if p.Name == name {

View File

@@ -0,0 +1,712 @@
package config
import (
"archive/zip"
"bytes"
"fmt"
"io"
"io/ioutil"
"net/http"
"os"
"path/filepath"
"sort"
"strings"
"sync"
"github.com/blang/semver"
lua "github.com/yuin/gopher-lua"
"github.com/zyedidia/json5"
ulua "github.com/zyedidia/micro/v2/internal/lua"
"github.com/zyedidia/micro/v2/internal/util"
)
var (
allPluginPackages PluginPackages
)
// CorePluginName is a plugin dependency name for the micro core.
const CorePluginName = "micro"
// PluginChannel contains an url to a json list of PluginRepository
type PluginChannel string
// PluginChannels is a slice of PluginChannel
type PluginChannels []PluginChannel
// PluginRepository contains an url to json file containing PluginPackages
type PluginRepository string
// PluginPackage contains the meta-data of a plugin and all available versions
type PluginPackage struct {
Name string
Description string
Author string
Tags []string
Versions PluginVersions
}
// PluginPackages is a list of PluginPackage instances.
type PluginPackages []*PluginPackage
// PluginVersion descripes a version of a PluginPackage. Containing a version, download url and also dependencies.
type PluginVersion struct {
pack *PluginPackage
Version semver.Version
Url string
Require PluginDependencies
}
func (pv *PluginVersion) Pack() *PluginPackage {
return pv.pack
}
// PluginVersions is a slice of PluginVersion
type PluginVersions []*PluginVersion
// PluginDependency descripes a dependency to another plugin or micro itself.
type PluginDependency struct {
Name string
Range semver.Range
}
// PluginDependencies is a slice of PluginDependency
type PluginDependencies []*PluginDependency
func (pp *PluginPackage) String() string {
buf := new(bytes.Buffer)
buf.WriteString("Plugin: ")
buf.WriteString(pp.Name)
buf.WriteRune('\n')
if pp.Author != "" {
buf.WriteString("Author: ")
buf.WriteString(pp.Author)
buf.WriteRune('\n')
}
if pp.Description != "" {
buf.WriteRune('\n')
buf.WriteString(pp.Description)
}
return buf.String()
}
func fetchAllSources(count int, fetcher func(i int) PluginPackages) PluginPackages {
wgQuery := new(sync.WaitGroup)
wgQuery.Add(count)
results := make(chan PluginPackages)
wgDone := new(sync.WaitGroup)
wgDone.Add(1)
var packages PluginPackages
for i := 0; i < count; i++ {
go func(i int) {
results <- fetcher(i)
wgQuery.Done()
}(i)
}
go func() {
packages = make(PluginPackages, 0)
for res := range results {
packages = append(packages, res...)
}
wgDone.Done()
}()
wgQuery.Wait()
close(results)
wgDone.Wait()
return packages
}
// Fetch retrieves all available PluginPackages from the given channels
func (pc PluginChannels) Fetch(out io.Writer) PluginPackages {
return fetchAllSources(len(pc), func(i int) PluginPackages {
return pc[i].Fetch(out)
})
}
// Fetch retrieves all available PluginPackages from the given channel
func (pc PluginChannel) Fetch(out io.Writer) PluginPackages {
resp, err := http.Get(string(pc))
if err != nil {
fmt.Fprintln(out, "Failed to query plugin channel:\n", err)
return PluginPackages{}
}
defer resp.Body.Close()
decoder := json5.NewDecoder(resp.Body)
var repositories []PluginRepository
if err := decoder.Decode(&repositories); err != nil {
fmt.Fprintln(out, "Failed to decode channel data:\n", err)
return PluginPackages{}
}
return fetchAllSources(len(repositories), func(i int) PluginPackages {
return repositories[i].Fetch(out)
})
}
// Fetch retrieves all available PluginPackages from the given repository
func (pr PluginRepository) Fetch(out io.Writer) PluginPackages {
resp, err := http.Get(string(pr))
if err != nil {
fmt.Fprintln(out, "Failed to query plugin repository:\n", err)
return PluginPackages{}
}
defer resp.Body.Close()
decoder := json5.NewDecoder(resp.Body)
var plugins PluginPackages
if err := decoder.Decode(&plugins); err != nil {
fmt.Fprintln(out, "Failed to decode repository data:\n", err)
return PluginPackages{}
}
if len(plugins) > 0 {
return PluginPackages{plugins[0]}
}
return nil
// return plugins
}
// UnmarshalJSON unmarshals raw json to a PluginVersion
func (pv *PluginVersion) UnmarshalJSON(data []byte) error {
var values struct {
Version semver.Version
Url string
Require map[string]string
}
if err := json5.Unmarshal(data, &values); err != nil {
return err
}
pv.Version = values.Version
pv.Url = values.Url
pv.Require = make(PluginDependencies, 0)
for k, v := range values.Require {
// don't add the dependency if it's the core and
// we have a unknown version number.
// in that case just accept that dependency (which equals to not adding it.)
if k != CorePluginName || !isUnknownCoreVersion() {
if vRange, err := semver.ParseRange(v); err == nil {
pv.Require = append(pv.Require, &PluginDependency{k, vRange})
}
}
}
return nil
}
// UnmarshalJSON unmarshals raw json to a PluginPackage
func (pp *PluginPackage) UnmarshalJSON(data []byte) error {
var values struct {
Name string
Description string
Author string
Tags []string
Versions PluginVersions
}
if err := json5.Unmarshal(data, &values); err != nil {
return err
}
pp.Name = values.Name
pp.Description = values.Description
pp.Author = values.Author
pp.Tags = values.Tags
pp.Versions = values.Versions
for _, v := range pp.Versions {
v.pack = pp
}
return nil
}
// GetAllPluginPackages gets all PluginPackages which may be available.
func GetAllPluginPackages(out io.Writer) PluginPackages {
if allPluginPackages == nil {
getOption := func(name string) []string {
data := GetGlobalOption(name)
if strs, ok := data.([]string); ok {
return strs
}
if ifs, ok := data.([]interface{}); ok {
result := make([]string, len(ifs))
for i, urlIf := range ifs {
if url, ok := urlIf.(string); ok {
result[i] = url
} else {
return nil
}
}
return result
}
return nil
}
channels := PluginChannels{}
for _, url := range getOption("pluginchannels") {
channels = append(channels, PluginChannel(url))
}
repos := []PluginRepository{}
for _, url := range getOption("pluginrepos") {
repos = append(repos, PluginRepository(url))
}
allPluginPackages = fetchAllSources(len(repos)+1, func(i int) PluginPackages {
if i == 0 {
return channels.Fetch(out)
}
return repos[i-1].Fetch(out)
})
}
return allPluginPackages
}
func (pv PluginVersions) find(ppName string) *PluginVersion {
for _, v := range pv {
if v.pack.Name == ppName {
return v
}
}
return nil
}
// Len returns the number of pluginversions in this slice
func (pv PluginVersions) Len() int {
return len(pv)
}
// Swap two entries of the slice
func (pv PluginVersions) Swap(i, j int) {
pv[i], pv[j] = pv[j], pv[i]
}
// Less returns true if the version at position i is greater then the version at position j (used for sorting)
func (pv PluginVersions) Less(i, j int) bool {
return pv[i].Version.GT(pv[j].Version)
}
// Match returns true if the package matches a given search text
func (pp PluginPackage) Match(text string) bool {
text = strings.ToLower(text)
for _, t := range pp.Tags {
if strings.ToLower(t) == text {
return true
}
}
if strings.Contains(strings.ToLower(pp.Name), text) {
return true
}
if strings.Contains(strings.ToLower(pp.Description), text) {
return true
}
return false
}
// IsInstallable returns true if the package can be installed.
func (pp PluginPackage) IsInstallable(out io.Writer) error {
_, err := GetAllPluginPackages(out).Resolve(GetInstalledVersions(true), PluginDependencies{
&PluginDependency{
Name: pp.Name,
Range: semver.Range(func(v semver.Version) bool { return true }),
}})
return err
}
// SearchPlugin retrieves a list of all PluginPackages which match the given search text and
// could be or are already installed
func SearchPlugin(out io.Writer, texts []string) (plugins PluginPackages) {
plugins = make(PluginPackages, 0)
pluginLoop:
for _, pp := range GetAllPluginPackages(out) {
for _, text := range texts {
if !pp.Match(text) {
continue pluginLoop
}
}
if err := pp.IsInstallable(out); err == nil {
plugins = append(plugins, pp)
}
}
return
}
func isUnknownCoreVersion() bool {
_, err := semver.ParseTolerant(util.Version)
return err != nil
}
func newStaticPluginVersion(name, version string) *PluginVersion {
vers, err := semver.ParseTolerant(version)
if err != nil {
if vers, err = semver.ParseTolerant("0.0.0-" + version); err != nil {
vers = semver.MustParse("0.0.0-unknown")
}
}
pl := &PluginPackage{
Name: name,
}
pv := &PluginVersion{
pack: pl,
Version: vers,
}
pl.Versions = PluginVersions{pv}
return pv
}
// GetInstalledVersions returns a list of all currently installed plugins including an entry for
// micro itself. This can be used to resolve dependencies.
func GetInstalledVersions(withCore bool) PluginVersions {
result := PluginVersions{}
if withCore {
result = append(result, newStaticPluginVersion(CorePluginName, util.Version))
}
for _, p := range Plugins {
if !p.IsEnabled() {
continue
}
version := GetInstalledPluginVersion(p.Name)
if pv := newStaticPluginVersion(p.Name, version); pv != nil {
result = append(result, pv)
}
}
return result
}
// GetInstalledPluginVersion returns the string of the exported VERSION variable of a loaded plugin
func GetInstalledPluginVersion(name string) string {
plugin := ulua.L.GetGlobal(name)
if plugin != lua.LNil {
version := ulua.L.GetField(plugin, "VERSION")
if str, ok := version.(lua.LString); ok {
return string(str)
}
}
return ""
}
// DownloadAndInstall downloads and installs the given plugin and version
func (pv *PluginVersion) DownloadAndInstall(out io.Writer) error {
fmt.Fprintf(out, "Downloading %q (%s) from %q\n", pv.pack.Name, pv.Version, pv.Url)
resp, err := http.Get(pv.Url)
if err != nil {
return err
}
defer resp.Body.Close()
data, err := ioutil.ReadAll(resp.Body)
if err != nil {
return err
}
zipbuf := bytes.NewReader(data)
z, err := zip.NewReader(zipbuf, zipbuf.Size())
if err != nil {
return err
}
targetDir := filepath.Join(ConfigDir, "plug", pv.pack.Name)
dirPerm := os.FileMode(0755)
if err = os.MkdirAll(targetDir, dirPerm); err != nil {
return err
}
// Check if all files in zip are in the same directory.
// this might be the case if the plugin zip contains the whole plugin dir
// instead of its content.
var prefix string
allPrefixed := false
for i, f := range z.File {
parts := strings.Split(f.Name, "/")
if i == 0 {
prefix = parts[0]
} else if parts[0] != prefix {
allPrefixed = false
break
} else {
// switch to true since we have at least a second file
allPrefixed = true
}
}
// Install files and directory's
for _, f := range z.File {
parts := strings.Split(f.Name, "/")
if allPrefixed {
parts = parts[1:]
}
targetName := filepath.Join(targetDir, filepath.Join(parts...))
if f.FileInfo().IsDir() {
if err := os.MkdirAll(targetName, dirPerm); err != nil {
return err
}
} else {
basepath := filepath.Dir(targetName)
if err := os.MkdirAll(basepath, dirPerm); err != nil {
return err
}
content, err := f.Open()
if err != nil {
return err
}
defer content.Close()
target, err := os.Create(targetName)
if err != nil {
return err
}
defer target.Close()
if _, err = io.Copy(target, content); err != nil {
return err
}
}
}
return nil
}
func (pl PluginPackages) Get(name string) *PluginPackage {
for _, p := range pl {
if p.Name == name {
return p
}
}
return nil
}
func (pl PluginPackages) GetAllVersions(name string) PluginVersions {
result := make(PluginVersions, 0)
p := pl.Get(name)
if p != nil {
for _, v := range p.Versions {
result = append(result, v)
}
}
return result
}
func (req PluginDependencies) Join(other PluginDependencies) PluginDependencies {
m := make(map[string]*PluginDependency)
for _, r := range req {
m[r.Name] = r
}
for _, o := range other {
cur, ok := m[o.Name]
if ok {
m[o.Name] = &PluginDependency{
o.Name,
o.Range.AND(cur.Range),
}
} else {
m[o.Name] = o
}
}
result := make(PluginDependencies, 0, len(m))
for _, v := range m {
result = append(result, v)
}
return result
}
// Resolve resolves dependencies between different plugins
func (all PluginPackages) Resolve(selectedVersions PluginVersions, open PluginDependencies) (PluginVersions, error) {
if len(open) == 0 {
return selectedVersions, nil
}
currentRequirement, stillOpen := open[0], open[1:]
if currentRequirement != nil {
if selVersion := selectedVersions.find(currentRequirement.Name); selVersion != nil {
if currentRequirement.Range(selVersion.Version) {
return all.Resolve(selectedVersions, stillOpen)
}
return nil, fmt.Errorf("unable to find a matching version for \"%s\"", currentRequirement.Name)
}
availableVersions := all.GetAllVersions(currentRequirement.Name)
sort.Sort(availableVersions)
for _, version := range availableVersions {
if currentRequirement.Range(version.Version) {
resolved, err := all.Resolve(append(selectedVersions, version), stillOpen.Join(version.Require))
if err == nil {
return resolved, nil
}
}
}
return nil, fmt.Errorf("unable to find a matching version for \"%s\"", currentRequirement.Name)
}
return selectedVersions, nil
}
func (pv PluginVersions) install(out io.Writer) {
anyInstalled := false
currentlyInstalled := GetInstalledVersions(true)
for _, sel := range pv {
if sel.pack.Name != CorePluginName {
shouldInstall := true
if pv := currentlyInstalled.find(sel.pack.Name); pv != nil {
if pv.Version.NE(sel.Version) {
fmt.Fprintln(out, "Uninstalling", sel.pack.Name)
UninstallPlugin(out, sel.pack.Name)
} else {
shouldInstall = false
}
}
if shouldInstall {
if err := sel.DownloadAndInstall(out); err != nil {
fmt.Fprintln(out, err)
return
}
anyInstalled = true
}
}
}
if anyInstalled {
fmt.Fprintln(out, "One or more plugins installed.")
} else {
fmt.Fprintln(out, "Nothing to install / update")
}
}
// UninstallPlugin deletes the plugin folder of the given plugin
func UninstallPlugin(out io.Writer, name string) {
for _, p := range Plugins {
if !p.IsEnabled() {
continue
}
if p.Name == name {
p.Loaded = false
if err := os.RemoveAll(filepath.Join(ConfigDir, "plug", p.DirName)); err != nil {
fmt.Fprintln(out, err)
return
}
break
}
}
}
// Install installs the plugin
func (pl PluginPackage) Install(out io.Writer) {
selected, err := GetAllPluginPackages(out).Resolve(GetInstalledVersions(true), PluginDependencies{
&PluginDependency{
Name: pl.Name,
Range: semver.Range(func(v semver.Version) bool { return true }),
}})
if err != nil {
fmt.Fprintln(out, err)
return
}
selected.install(out)
}
// UpdatePlugins updates the given plugins
func UpdatePlugins(out io.Writer, plugins []string) {
// if no plugins are specified, update all installed plugins.
if len(plugins) == 0 {
for _, p := range Plugins {
if !p.IsEnabled() {
continue
}
plugins = append(plugins, p.Name)
}
}
fmt.Fprintln(out, "Checking for plugin updates")
microVersion := PluginVersions{
newStaticPluginVersion(CorePluginName, util.Version),
}
var updates = make(PluginDependencies, 0)
for _, name := range plugins {
pv := GetInstalledPluginVersion(name)
r, err := semver.ParseRange(">=" + pv) // Try to get newer versions.
if err == nil {
updates = append(updates, &PluginDependency{
Name: name,
Range: r,
})
}
}
selected, err := GetAllPluginPackages(out).Resolve(microVersion, updates)
if err != nil {
fmt.Fprintln(out, err)
return
}
selected.install(out)
}
func PluginCommand(out io.Writer, cmd string, args []string) {
switch cmd {
case "install":
installedVersions := GetInstalledVersions(false)
for _, plugin := range args {
pp := GetAllPluginPackages(out).Get(plugin)
if pp == nil {
fmt.Fprintln(out, "Unknown plugin \""+plugin+"\"")
} else if err := pp.IsInstallable(out); err != nil {
fmt.Fprintln(out, "Error installing ", plugin, ": ", err)
} else {
for _, installed := range installedVersions {
if pp.Name == installed.Pack().Name {
if pp.Versions[0].Version.Compare(installed.Version) == 1 {
fmt.Fprintln(out, pp.Name, " is already installed but out-of-date: use 'plugin update ", pp.Name, "' to update")
} else {
fmt.Fprintln(out, pp.Name, " is already installed")
}
}
}
pp.Install(out)
}
}
case "remove":
removed := ""
for _, plugin := range args {
// check if the plugin exists.
for _, p := range Plugins {
if p.Name == plugin && p.Default {
fmt.Fprintln(out, "Default plugins cannot be removed, but can be disabled via settings.")
continue
}
if p.Name == plugin {
UninstallPlugin(out, plugin)
removed += plugin + " "
continue
}
}
}
if removed != "" {
fmt.Fprintln(out, "Removed ", removed)
} else {
fmt.Fprintln(out, "No plugins removed")
}
case "update":
UpdatePlugins(out, args)
case "list":
plugins := GetInstalledVersions(false)
fmt.Fprintln(out, "The following plugins are currently installed:")
for _, p := range plugins {
fmt.Fprintf(out, "%s (%s)\n", p.Pack().Name, p.Version)
}
case "search":
plugins := SearchPlugin(out, args)
fmt.Fprintln(out, len(plugins), " plugins found")
for _, p := range plugins {
fmt.Fprintln(out, "----------------")
fmt.Fprintln(out, p.String())
}
fmt.Fprintln(out, "----------------")
case "available":
packages := GetAllPluginPackages(out)
fmt.Fprintln(out, "Available Plugins:")
for _, pkg := range packages {
fmt.Fprintln(out, pkg.Name)
}
default:
fmt.Fprintln(out, "Invalid plugin command")
}
}

View File

@@ -0,0 +1,56 @@
package config
import (
"testing"
"github.com/blang/semver"
"github.com/zyedidia/json5"
)
func TestDependencyResolving(t *testing.T) {
js := `
[{
"Name": "Foo",
"Versions": [{ "Version": "1.0.0" }, { "Version": "1.5.0" },{ "Version": "2.0.0" }]
}, {
"Name": "Bar",
"Versions": [{ "Version": "1.0.0", "Require": {"Foo": ">1.0.0 <2.0.0"} }]
}, {
"Name": "Unresolvable",
"Versions": [{ "Version": "1.0.0", "Require": {"Foo": "<=1.0.0", "Bar": ">0.0.0"} }]
}]
`
var all PluginPackages
err := json5.Unmarshal([]byte(js), &all)
if err != nil {
t.Error(err)
}
selected, err := all.Resolve(PluginVersions{}, PluginDependencies{
&PluginDependency{"Bar", semver.MustParseRange(">=1.0.0")},
})
check := func(name, version string) {
v := selected.find(name)
expected := semver.MustParse(version)
if v == nil {
t.Errorf("Failed to resolve %s", name)
} else if expected.NE(v.Version) {
t.Errorf("%s resolved in wrong version %v", name, v)
}
}
if err != nil {
t.Error(err)
} else {
check("Foo", "1.5.0")
check("Bar", "1.0.0")
}
selected, err = all.Resolve(PluginVersions{}, PluginDependencies{
&PluginDependency{"Unresolvable", semver.MustParseRange(">0.0.0")},
})
if err == nil {
t.Error("Unresolvable package resolved:", selected)
}
}

View File

@@ -7,12 +7,9 @@ import (
)
var (
ErrMissingName = errors.New("Missing or empty name field")
ErrMissingDesc = errors.New("Missing or empty description field")
ErrMissingSite = errors.New("Missing or empty website field")
ErrMissingInstall = errors.New("Missing or empty install field")
ErrMissingVstr = errors.New("Missing or empty versions field")
ErrMissingRequire = errors.New("Missing or empty require field")
ErrMissingName = errors.New("Missing or empty name field")
ErrMissingDesc = errors.New("Missing or empty description field")
ErrMissingSite = errors.New("Missing or empty website field")
)
// PluginInfo contains all the needed info about a plugin
@@ -27,19 +24,16 @@ var (
// Vstr: version
// Require: list of dependencies and requirements
type PluginInfo struct {
Name string `json:"name"`
Desc string `json:"description"`
Site string `json:"website"`
Install string `json:"install"`
Vstr string `json:"version"`
Require []string `json:"require"`
Name string `json:"Name"`
Desc string `json:"Description"`
Site string `json:"Website"`
}
// NewPluginInfo parses a JSON input into a valid PluginInfo struct
// Returns an error if there are any missing fields or any invalid fields
// There are no optional fields in a plugin info json file
func NewPluginInfo(data []byte) (*PluginInfo, error) {
var info PluginInfo
var info []PluginInfo
dec := json.NewDecoder(bytes.NewReader(data))
// dec.DisallowUnknownFields() // Force errors
@@ -48,19 +42,5 @@ func NewPluginInfo(data []byte) (*PluginInfo, error) {
return nil, err
}
// if len(info.Name) == 0 {
// return nil, ErrMissingName
// } else if len(info.Desc) == 0 {
// return nil, ErrMissingDesc
// } else if len(info.Site) == 0 {
// return nil, ErrMissingSite
// } else if len(info.Install) == 0 {
// return nil, ErrMissingInstall
// } else if len(info.Vstr) == 0 {
// return nil, ErrMissingVstr
// } else if len(info.Require) == 0 {
// return nil, ErrMissingRequire
// }
return &info, nil
return &info[0], nil
}

View File

@@ -1,6 +1,7 @@
package config
import (
"errors"
"io/ioutil"
"log"
"os"
@@ -11,14 +12,18 @@ import (
)
const (
RTColorscheme = 0
RTSyntax = 1
RTHelp = 2
RTPlugin = 3
NumTypes = 4 // How many filetypes are there
RTColorscheme = 0
RTSyntax = 1
RTHelp = 2
RTPlugin = 3
RTSyntaxHeader = 4
)
type RTFiletype byte
var (
NumTypes = 5 // How many filetypes are there
)
type RTFiletype int
// RuntimeFile allows the program to read runtime data like colorschemes or syntax files
type RuntimeFile interface {
@@ -29,7 +34,21 @@ type RuntimeFile interface {
}
// allFiles contains all available files, mapped by filetype
var allFiles [NumTypes][]RuntimeFile
var allFiles [][]RuntimeFile
var realFiles [][]RuntimeFile
func init() {
allFiles = make([][]RuntimeFile, NumTypes)
realFiles = make([][]RuntimeFile, NumTypes)
}
// NewRTFiletype creates a new RTFiletype
func NewRTFiletype() int {
NumTypes++
allFiles = append(allFiles, []RuntimeFile{})
realFiles = append(realFiles, []RuntimeFile{})
return NumTypes - 1
}
// some file on filesystem
type realFile string
@@ -83,6 +102,12 @@ func AddRuntimeFile(fileType RTFiletype, file RuntimeFile) {
allFiles[fileType] = append(allFiles[fileType], file)
}
// AddRealRuntimeFile registers a file for the given filetype
func AddRealRuntimeFile(fileType RTFiletype, file RuntimeFile) {
allFiles[fileType] = append(allFiles[fileType], file)
realFiles[fileType] = append(realFiles[fileType], file)
}
// AddRuntimeFilesFromDirectory registers each file from the given directory for
// the filetype which matches the file-pattern
func AddRuntimeFilesFromDirectory(fileType RTFiletype, directory, pattern string) {
@@ -90,7 +115,7 @@ func AddRuntimeFilesFromDirectory(fileType RTFiletype, directory, pattern string
for _, f := range files {
if ok, _ := filepath.Match(pattern, f.Name()); !f.IsDir() && ok {
fullPath := filepath.Join(directory, f.Name())
AddRuntimeFile(fileType, realFile(fullPath))
AddRealRuntimeFile(fileType, realFile(fullPath))
}
}
}
@@ -125,6 +150,12 @@ func ListRuntimeFiles(fileType RTFiletype) []RuntimeFile {
return allFiles[fileType]
}
// ListRealRuntimeFiles lists all real runtime files (on disk) for a filetype
// these runtime files will be ones defined by the user and loaded from the config directory
func ListRealRuntimeFiles(fileType RTFiletype) []RuntimeFile {
return realFiles[fileType]
}
// InitRuntimeFiles initializes all assets file and the config directory
func InitRuntimeFiles() {
add := func(fileType RTFiletype, dir, pattern string) {
@@ -134,12 +165,14 @@ func InitRuntimeFiles() {
add(RTColorscheme, "colorschemes", "*.micro")
add(RTSyntax, "syntax", "*.yaml")
add(RTSyntaxHeader, "syntax", "*.hdr")
add(RTHelp, "help", "*.md")
initlua := filepath.Join(ConfigDir, "init.lua")
if _, err := os.Stat(initlua); !os.IsNotExist(err) {
p := new(Plugin)
p.Name = "initlua"
p.DirName = "initlua"
p.Srcs = append(p.Srcs, realFile(initlua))
Plugins = append(Plugins, p)
}
@@ -155,21 +188,25 @@ func InitRuntimeFiles() {
srcs, _ := ioutil.ReadDir(filepath.Join(plugdir, d.Name()))
p := new(Plugin)
p.Name = d.Name()
p.DirName = d.Name()
for _, f := range srcs {
if strings.HasSuffix(f.Name(), ".lua") {
p.Srcs = append(p.Srcs, realFile(filepath.Join(plugdir, d.Name(), f.Name())))
} else if f.Name() == "info.json" {
data, err := ioutil.ReadFile(filepath.Join(plugdir, d.Name(), "info.json"))
} else if strings.HasSuffix(f.Name(), ".json") {
data, err := ioutil.ReadFile(filepath.Join(plugdir, d.Name(), f.Name()))
if err != nil {
continue
}
p.Info, err = NewPluginInfo(data)
if err != nil {
continue
}
p.Info, _ = NewPluginInfo(data)
p.Name = p.Info.Name
}
}
if !isID(p.Name) {
log.Println("Invalid plugin name", p.Name)
if !isID(p.Name) || len(p.Srcs) <= 0 {
log.Println(p.Name, "is not a plugin")
continue
}
Plugins = append(Plugins, p)
@@ -182,21 +219,25 @@ func InitRuntimeFiles() {
if srcs, err := AssetDir(filepath.Join(plugdir, d)); err == nil {
p := new(Plugin)
p.Name = d
p.DirName = d
p.Default = true
for _, f := range srcs {
if strings.HasSuffix(f, ".lua") {
p.Srcs = append(p.Srcs, assetFile(filepath.Join(plugdir, d, f)))
} else if f == "info.json" {
data, err := Asset(filepath.Join(plugdir, d, "info.json"))
} else if strings.HasSuffix(f, ".json") {
data, err := Asset(filepath.Join(plugdir, d, f))
if err != nil {
continue
}
p.Info, err = NewPluginInfo(data)
if err != nil {
continue
}
p.Info, _ = NewPluginInfo(data)
p.Name = p.Info.Name
}
}
if !isID(p.Name) {
log.Println("Invalid plugin name", p.Name)
if !isID(p.Name) || len(p.Srcs) <= 0 {
log.Println(p.Name, "is not a plugin")
continue
}
Plugins = append(Plugins, p)
@@ -226,28 +267,40 @@ func PluginListRuntimeFiles(fileType RTFiletype) []string {
}
// PluginAddRuntimeFile adds a file to the runtime files for a plugin
func PluginAddRuntimeFile(plugin string, filetype RTFiletype, filePath string) {
fullpath := filepath.Join(ConfigDir, "plug", plugin, filePath)
func PluginAddRuntimeFile(plugin string, filetype RTFiletype, filePath string) error {
pl := FindPlugin(plugin)
if pl == nil {
return errors.New("Plugin " + plugin + " does not exist")
}
pldir := pl.DirName
fullpath := filepath.Join(ConfigDir, "plug", pldir, filePath)
if _, err := os.Stat(fullpath); err == nil {
AddRuntimeFile(filetype, realFile(fullpath))
AddRealRuntimeFile(filetype, realFile(fullpath))
} else {
fullpath = path.Join("runtime", "plugins", plugin, filePath)
fullpath = path.Join("runtime", "plugins", pldir, filePath)
AddRuntimeFile(filetype, assetFile(fullpath))
}
return nil
}
// PluginAddRuntimeFilesFromDirectory adds files from a directory to the runtime files for a plugin
func PluginAddRuntimeFilesFromDirectory(plugin string, filetype RTFiletype, directory, pattern string) {
fullpath := filepath.Join(ConfigDir, "plug", plugin, directory)
func PluginAddRuntimeFilesFromDirectory(plugin string, filetype RTFiletype, directory, pattern string) error {
pl := FindPlugin(plugin)
if pl == nil {
return errors.New("Plugin " + plugin + " does not exist")
}
pldir := pl.DirName
fullpath := filepath.Join(ConfigDir, "plug", pldir, directory)
if _, err := os.Stat(fullpath); err == nil {
AddRuntimeFilesFromDirectory(filetype, fullpath, pattern)
} else {
fullpath = path.Join("runtime", "plugins", plugin, directory)
fullpath = path.Join("runtime", "plugins", pldir, directory)
AddRuntimeFilesFromAssets(filetype, fullpath, pattern)
}
return nil
}
// PluginAddRuntimeFileFromMemory adds a file to the runtime files for a plugin from a given string
func PluginAddRuntimeFileFromMemory(plugin string, filetype RTFiletype, filename, data string) {
AddRuntimeFile(filetype, memoryFile{filename, []byte(data)})
func PluginAddRuntimeFileFromMemory(filetype RTFiletype, filename, data string) {
AddRealRuntimeFile(filetype, memoryFile{filename, []byte(data)})
}

File diff suppressed because one or more lines are too long

View File

@@ -3,15 +3,17 @@ package config
import (
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"reflect"
"strconv"
"strings"
"github.com/flynn/json5"
"github.com/zyedidia/glob"
"github.com/zyedidia/micro/internal/util"
"github.com/zyedidia/json5"
"github.com/zyedidia/micro/v2/internal/util"
"golang.org/x/text/encoding/htmlindex"
)
@@ -25,10 +27,16 @@ var (
GlobalSettings map[string]interface{}
// This is the raw parsed json
parsedSettings map[string]interface{}
parsedSettings map[string]interface{}
settingsParseError bool
// ModifiedSettings is a map of settings which should be written to disk
// because they have been modified by the user in this session
ModifiedSettings map[string]bool
)
func init() {
ModifiedSettings = make(map[string]bool)
parsedSettings = make(map[string]interface{})
}
@@ -45,33 +53,64 @@ var optionValidators = map[string]optionValidator{
}
func ReadSettings() error {
filename := ConfigDir + "/settings.json"
filename := filepath.Join(ConfigDir, "settings.json")
if _, e := os.Stat(filename); e == nil {
input, err := ioutil.ReadFile(filename)
if err != nil {
settingsParseError = true
return errors.New("Error reading settings.json file: " + err.Error())
}
if !strings.HasPrefix(string(input), "null") {
// Unmarshal the input into the parsed map
err = json5.Unmarshal(input, &parsedSettings)
if err != nil {
settingsParseError = true
return errors.New("Error reading settings.json: " + err.Error())
}
// check if autosave is a boolean and convert it to float if so
if v, ok := parsedSettings["autosave"]; ok {
s, ok := v.(bool)
if ok {
if s {
parsedSettings["autosave"] = 8.0
} else {
parsedSettings["autosave"] = 0.0
}
}
}
}
}
return nil
}
func verifySetting(option string, value reflect.Type, def reflect.Type) bool {
var interfaceArr []interface{}
switch option {
case "pluginrepos", "pluginchannels":
return value.AssignableTo(reflect.TypeOf(interfaceArr))
default:
return def.AssignableTo(value)
}
}
// InitGlobalSettings initializes the options map and sets all options to their default values
// Must be called after ReadSettings
func InitGlobalSettings() {
func InitGlobalSettings() error {
var err error
GlobalSettings = DefaultGlobalSettings()
for k, v := range parsedSettings {
if !strings.HasPrefix(reflect.TypeOf(v).String(), "map") {
if _, ok := GlobalSettings[k]; ok && !verifySetting(k, reflect.TypeOf(v), reflect.TypeOf(GlobalSettings[k])) {
err = errors.New(fmt.Sprintf("Global Error: setting '%s' has incorrect type (%s), using default value: %v (%s)", k, reflect.TypeOf(v), GlobalSettings[k], reflect.TypeOf(GlobalSettings[k])))
continue
}
GlobalSettings[k] = v
}
}
return err
}
// InitLocalSettings scans the json in settings.json and sets the options locally based
@@ -84,6 +123,10 @@ func InitLocalSettings(settings map[string]interface{}, path string) error {
if strings.HasPrefix(k, "ft:") {
if settings["filetype"].(string) == k[3:] {
for k1, v1 := range v.(map[string]interface{}) {
if _, ok := settings[k1]; ok && !verifySetting(k1, reflect.TypeOf(v1), reflect.TypeOf(settings[k1])) {
parseError = errors.New(fmt.Sprintf("Error: setting '%s' has incorrect type (%s), using default value: %v (%s)", k, reflect.TypeOf(v1), settings[k1], reflect.TypeOf(settings[k1])))
continue
}
settings[k1] = v1
}
}
@@ -96,6 +139,10 @@ func InitLocalSettings(settings map[string]interface{}, path string) error {
if g.MatchString(path) {
for k1, v1 := range v.(map[string]interface{}) {
if _, ok := settings[k1]; ok && !verifySetting(k1, reflect.TypeOf(v1), reflect.TypeOf(settings[k1])) {
parseError = errors.New(fmt.Sprintf("Error: setting '%s' has incorrect type (%s), using default value: %v (%s)", k, reflect.TypeOf(v1), settings[k1], reflect.TypeOf(settings[k1])))
continue
}
settings[k1] = v1
}
}
@@ -107,10 +154,35 @@ func InitLocalSettings(settings map[string]interface{}, path string) error {
// WriteSettings writes the settings to the specified filename as JSON
func WriteSettings(filename string) error {
if settingsParseError {
// Don't write settings if there was a parse error
// because this will delete the settings.json if it
// is invalid. Instead we should allow the user to fix
// it manually.
return nil
}
var err error
if _, e := os.Stat(ConfigDir); e == nil {
defaults := DefaultGlobalSettings()
// remove any options froms parsedSettings that have since been marked as default
for k, v := range parsedSettings {
if !strings.HasPrefix(reflect.TypeOf(v).String(), "map") {
cur, okcur := GlobalSettings[k]
if def, ok := defaults[k]; ok && okcur && reflect.DeepEqual(cur, def) {
delete(parsedSettings, k)
}
}
}
// add any options to parsedSettings that have since been marked as non-default
for k, v := range GlobalSettings {
parsedSettings[k] = v
if def, ok := defaults[k]; !ok || !reflect.DeepEqual(v, def) {
if _, wr := ModifiedSettings[k]; wr {
parsedSettings[k] = v
}
}
}
txt, _ := json.MarshalIndent(parsedSettings, "", " ")
@@ -119,31 +191,60 @@ func WriteSettings(filename string) error {
return err
}
// RegisterCommonOption creates a new option. This is meant to be called by plugins to add options.
func RegisterCommonOption(name string, defaultvalue interface{}) error {
if v, ok := GlobalSettings[name]; !ok {
// OverwriteSettings writes the current settings to settings.json and
// resets any user configuration of local settings present in settings.json
func OverwriteSettings(filename string) error {
settings := make(map[string]interface{})
var err error
if _, e := os.Stat(ConfigDir); e == nil {
defaults := DefaultGlobalSettings()
for k, v := range GlobalSettings {
if def, ok := defaults[k]; !ok || !reflect.DeepEqual(v, def) {
if _, wr := ModifiedSettings[k]; wr {
settings[k] = v
}
}
}
txt, _ := json.MarshalIndent(settings, "", " ")
err = ioutil.WriteFile(filename, append(txt, '\n'), 0644)
}
return err
}
// RegisterCommonOptionPlug creates a new option (called pl.name). This is meant to be called by plugins to add options.
func RegisterCommonOptionPlug(pl string, name string, defaultvalue interface{}) error {
name = pl + "." + name
if _, ok := GlobalSettings[name]; !ok {
defaultCommonSettings[name] = defaultvalue
GlobalSettings[name] = defaultvalue
err := WriteSettings(ConfigDir + "/settings.json")
err := WriteSettings(filepath.Join(ConfigDir, "settings.json"))
if err != nil {
return errors.New("Error writing settings.json file: " + err.Error())
}
} else {
defaultCommonSettings[name] = v
defaultCommonSettings[name] = defaultvalue
}
return nil
}
// RegisterGlobalOptionPlug creates a new global-only option (named pl.name)
func RegisterGlobalOptionPlug(pl string, name string, defaultvalue interface{}) error {
return RegisterGlobalOption(pl+"."+name, defaultvalue)
}
// RegisterGlobalOption creates a new global-only option
func RegisterGlobalOption(name string, defaultvalue interface{}) error {
if v, ok := GlobalSettings[name]; !ok {
defaultGlobalSettings[name] = defaultvalue
DefaultGlobalOnlySettings[name] = defaultvalue
GlobalSettings[name] = defaultvalue
err := WriteSettings(ConfigDir + "/settings.json")
err := WriteSettings(filepath.Join(ConfigDir, "settings.json"))
if err != nil {
return errors.New("Error writing settings.json file: " + err.Error())
}
} else {
defaultGlobalSettings[name] = v
DefaultGlobalOnlySettings[name] = v
}
return nil
}
@@ -155,13 +256,16 @@ func GetGlobalOption(name string) interface{} {
var defaultCommonSettings = map[string]interface{}{
"autoindent": true,
"autosu": false,
"backup": true,
"backupdir": "",
"basename": false,
"colorcolumn": float64(0),
"cursorline": true,
"diffgutter": false,
"encoding": "utf-8",
"eofnewline": false,
"fastdirty": true,
"eofnewline": true,
"fastdirty": false,
"fileformat": "unix",
"filetype": "unknown",
"ignorecase": false,
@@ -169,9 +273,11 @@ var defaultCommonSettings = map[string]interface{}{
"keepautoindent": false,
"matchbrace": true,
"mkparents": false,
"permbackup": false,
"readonly": false,
"rmtrailingws": false,
"ruler": true,
"relativeruler": false,
"savecursor": false,
"saveundo": false,
"scrollbar": false,
@@ -181,8 +287,8 @@ var defaultCommonSettings = map[string]interface{}{
"softwrap": false,
"splitbottom": true,
"splitright": true,
"statusformatl": "$(filename) $(modified)($(line),$(col)) | ft:$(opt:filetype) | $(opt:fileformat) | $(opt:encoding)",
"statusformatr": "$(bind:ToggleKeyMenu): show bindings, $(bind:ToggleHelp): toggle help",
"statusformatl": "$(filename) $(modified)($(line),$(col)) $(status.paste)| ft:$(opt:filetype) | $(opt:fileformat) | $(opt:encoding)",
"statusformatr": "$(bind:ToggleKeyMenu): bindings, $(bind:ToggleHelp): help",
"statusline": true,
"syntax": true,
"tabmovement": false,
@@ -212,15 +318,29 @@ func DefaultCommonSettings() map[string]interface{} {
return commonsettings
}
var defaultGlobalSettings = map[string]interface{}{
"autosave": float64(0),
"colorscheme": "default",
"infobar": true,
"keymenu": false,
"mouse": true,
"savehistory": true,
"sucmd": "sudo",
"termtitle": false,
// a list of settings that should only be globally modified and their
// default values
var DefaultGlobalOnlySettings = map[string]interface{}{
"autosave": float64(0),
"colorscheme": "default",
"divchars": "|-",
"divreverse": true,
"infobar": true,
"keymenu": false,
"mouse": true,
"parsecursor": false,
"paste": false,
"savehistory": true,
"sucmd": "sudo",
"pluginchannels": []string{"https://raw.githubusercontent.com/micro-editor/plugin-channel/master/channel.json"},
"pluginrepos": []string{},
"xterm": false,
}
// a list of settings that should never be globally modified
var LocalSettings = []string{
"filetype",
"readonly",
}
// DefaultGlobalSettings returns the default global settings for micro
@@ -230,7 +350,7 @@ func DefaultGlobalSettings() map[string]interface{} {
for k, v := range defaultCommonSettings {
globalsettings[k] = v
}
for k, v := range defaultGlobalSettings {
for k, v := range DefaultGlobalOnlySettings {
globalsettings[k] = v
}
return globalsettings
@@ -243,12 +363,13 @@ func DefaultAllSettings() map[string]interface{} {
for k, v := range defaultCommonSettings {
allsettings[k] = v
}
for k, v := range defaultGlobalSettings {
for k, v := range DefaultGlobalOnlySettings {
allsettings[k] = v
}
return allsettings
}
// GetNativeValue parses and validates a value for a given option
func GetNativeValue(option string, realValue interface{}, value string) (interface{}, error) {
var native interface{}
kind := reflect.TypeOf(realValue).Kind()

View File

@@ -1,15 +1,13 @@
package display
import (
"log"
"strconv"
"unicode/utf8"
runewidth "github.com/mattn/go-runewidth"
"github.com/zyedidia/micro/internal/buffer"
"github.com/zyedidia/micro/internal/config"
"github.com/zyedidia/micro/internal/screen"
"github.com/zyedidia/micro/internal/util"
"github.com/zyedidia/micro/v2/internal/buffer"
"github.com/zyedidia/micro/v2/internal/config"
"github.com/zyedidia/micro/v2/internal/screen"
"github.com/zyedidia/micro/v2/internal/util"
"github.com/zyedidia/tcell"
)
@@ -45,12 +43,12 @@ func (w *BufWindow) SetBuffer(b *buffer.Buffer) {
w.Buf = b
}
func (v *View) GetView() *View {
return v
func (w *BufWindow) GetView() *View {
return w.View
}
func (v *View) SetView(view *View) {
v = view
func (w *BufWindow) SetView(view *View) {
w.View = view
}
func (w *BufWindow) Resize(width, height int) {
@@ -74,9 +72,9 @@ func (w *BufWindow) getStartInfo(n, lineN int) ([]byte, int, int, *tcell.Style)
curStyle := config.DefStyle
var s *tcell.Style
for len(b) > 0 {
r, size := utf8.DecodeRune(b)
r, _, size := util.DecodeCharacter(b)
curStyle, found := w.getStyle(curStyle, bloc, r)
curStyle, found := w.getStyle(curStyle, bloc)
if found {
s = &curStyle
}
@@ -103,7 +101,7 @@ func (w *BufWindow) getStartInfo(n, lineN int) ([]byte, int, int, *tcell.Style)
func (w *BufWindow) Clear() {
for y := 0; y < w.Height; y++ {
for x := 0; x < w.Width; x++ {
screen.Screen.SetContent(w.X+x, w.Y+y, ' ', nil, config.DefStyle)
screen.SetContent(w.X+x, w.Y+y, ' ', nil, config.DefStyle)
}
}
}
@@ -121,9 +119,8 @@ func (w *BufWindow) Bottomline() int {
return h
}
l := w.LocFromVisual(buffer.Loc{0, w.Height})
l := w.LocFromVisual(buffer.Loc{0, w.Y + w.Height})
log.Println("Bottom line:", l.Y)
return l.Y
}
@@ -134,14 +131,10 @@ func (w *BufWindow) Relocate() bool {
b := w.Buf
// how many buffer lines are in the view
height := w.Bottomline() + 1 - w.StartLine
log.Printf("Height: %d, w.Height: %d\n", height, w.Height)
h := w.Height
if w.drawStatus {
h--
}
if b.LinesNum() <= h {
height = w.Height
}
ret := false
activeC := w.Buf.GetActiveCursor()
cy := activeC.Y
@@ -191,6 +184,11 @@ func (w *BufWindow) LocFromVisual(svloc buffer.Loc) buffer.Loc {
bufHeight--
}
bufWidth := w.Width
if w.Buf.Settings["scrollbar"].(bool) && w.Buf.LinesNum() > w.Height {
bufWidth--
}
// We need to know the string length of the largest line number
// so we can pad appropriately when displaying line numbers
maxLineNumLength := len(strconv.Itoa(b.LinesNum()))
@@ -210,6 +208,9 @@ func (w *BufWindow) LocFromVisual(svloc buffer.Loc) buffer.Loc {
if hasMessage {
vloc.X += 2
}
if b.Settings["diffgutter"].(bool) {
vloc.X++
}
if b.Settings["ruler"].(bool) {
vloc.X += maxLineNumLength + 1
}
@@ -235,7 +236,7 @@ func (w *BufWindow) LocFromVisual(svloc buffer.Loc) buffer.Loc {
return bloc
}
r, size := utf8.DecodeRune(line)
r, _, size := util.DecodeCharacter(line)
draw()
width := 0
@@ -262,7 +263,7 @@ func (w *BufWindow) LocFromVisual(svloc buffer.Loc) buffer.Loc {
totalwidth += width
// If we reach the end of the window then we either stop or we wrap for softwrap
if vloc.X >= w.Width {
if vloc.X >= bufWidth {
if !softwrap {
break
} else {
@@ -270,9 +271,7 @@ func (w *BufWindow) LocFromVisual(svloc buffer.Loc) buffer.Loc {
if vloc.Y >= bufHeight {
break
}
vloc.X = 0
// This will draw an empty line number because the current line is wrapped
vloc.X += maxLineNumLength + 1
vloc.X = w.gutterOffset
}
}
}
@@ -301,38 +300,73 @@ func (w *BufWindow) drawGutter(vloc *buffer.Loc, bloc *buffer.Loc) {
break
}
}
screen.Screen.SetContent(w.X+vloc.X, w.Y+vloc.Y, char, nil, s)
screen.SetContent(w.X+vloc.X, w.Y+vloc.Y, char, nil, s)
vloc.X++
screen.Screen.SetContent(w.X+vloc.X, w.Y+vloc.Y, char, nil, s)
screen.SetContent(w.X+vloc.X, w.Y+vloc.Y, char, nil, s)
vloc.X++
}
func (w *BufWindow) drawDiffGutter(backgroundStyle tcell.Style, softwrapped bool, vloc *buffer.Loc, bloc *buffer.Loc) {
symbol := ' '
styleName := ""
switch w.Buf.DiffStatus(bloc.Y) {
case buffer.DSAdded:
symbol = '\u258C' // Left half block
styleName = "diff-added"
case buffer.DSModified:
symbol = '\u258C' // Left half block
styleName = "diff-modified"
case buffer.DSDeletedAbove:
if !softwrapped {
symbol = '\u2594' // Upper one eighth block
styleName = "diff-deleted"
}
}
style := backgroundStyle
if s, ok := config.Colorscheme[styleName]; ok {
foreground, _, _ := s.Decompose()
style = style.Foreground(foreground)
}
screen.SetContent(w.X+vloc.X, w.Y+vloc.Y, symbol, nil, style)
vloc.X++
}
func (w *BufWindow) drawLineNum(lineNumStyle tcell.Style, softwrapped bool, maxLineNumLength int, vloc *buffer.Loc, bloc *buffer.Loc) {
lineNum := strconv.Itoa(bloc.Y + 1)
cursorLine := w.Buf.GetActiveCursor().Loc.Y
var lineInt int
if w.Buf.Settings["relativeruler"] == false || cursorLine == bloc.Y {
lineInt = bloc.Y + 1
} else {
lineInt = bloc.Y - cursorLine
}
lineNum := strconv.Itoa(util.Abs(lineInt))
// Write the spaces before the line number if necessary
for i := 0; i < maxLineNumLength-len(lineNum); i++ {
screen.Screen.SetContent(w.X+vloc.X, w.Y+vloc.Y, ' ', nil, lineNumStyle)
screen.SetContent(w.X+vloc.X, w.Y+vloc.Y, ' ', nil, lineNumStyle)
vloc.X++
}
// Write the actual line number
for _, ch := range lineNum {
if softwrapped {
screen.Screen.SetContent(w.X+vloc.X, w.Y+vloc.Y, ' ', nil, lineNumStyle)
screen.SetContent(w.X+vloc.X, w.Y+vloc.Y, ' ', nil, lineNumStyle)
} else {
screen.Screen.SetContent(w.X+vloc.X, w.Y+vloc.Y, ch, nil, lineNumStyle)
screen.SetContent(w.X+vloc.X, w.Y+vloc.Y, ch, nil, lineNumStyle)
}
vloc.X++
}
// Write the extra space
screen.Screen.SetContent(w.X+vloc.X, w.Y+vloc.Y, ' ', nil, lineNumStyle)
screen.SetContent(w.X+vloc.X, w.Y+vloc.Y, ' ', nil, lineNumStyle)
vloc.X++
}
// getStyle returns the highlight style for the given character position
// If there is no change to the current highlight style it just returns that
func (w *BufWindow) getStyle(style tcell.Style, bloc buffer.Loc, r rune) (tcell.Style, bool) {
func (w *BufWindow) getStyle(style tcell.Style, bloc buffer.Loc) (tcell.Style, bool) {
if group, ok := w.Buf.Match(bloc.Y)[bloc.X]; ok {
s := config.GetColor(group.String())
return s, true
@@ -343,10 +377,9 @@ func (w *BufWindow) getStyle(style tcell.Style, bloc buffer.Loc, r rune) (tcell.
func (w *BufWindow) showCursor(x, y int, main bool) {
if w.active {
if main {
screen.Screen.ShowCursor(x, y)
screen.ShowCursor(x, y)
} else {
r, _, _, _ := screen.Screen.GetContent(x, y)
screen.Screen.SetContent(x, y, r, nil, config.DefStyle.Reverse(true))
screen.ShowFakeCursorMulti(x, y)
}
}
}
@@ -355,6 +388,10 @@ func (w *BufWindow) showCursor(x, y int, main bool) {
func (w *BufWindow) displayBuffer() {
b := w.Buf
if w.Height <= 0 || w.Width <= 0 {
return
}
hasMessage := len(b.Messages) > 0
bufHeight := w.Height
if w.drawStatus {
@@ -366,18 +403,22 @@ func (w *BufWindow) displayBuffer() {
bufWidth--
}
if b.Settings["syntax"].(bool) && b.SyntaxDef != nil {
for _, c := range b.GetCursors() {
// rehighlight starting from where the cursor is
start := c.Y
if start > 0 && b.Rehighlight(start-1) {
b.Highlighter.ReHighlightLine(b, start-1)
b.SetRehighlight(start-1, false)
}
b.Highlighter.ReHighlightStates(b, start)
b.Highlighter.HighlightMatches(b, w.StartLine, w.StartLine+bufHeight)
if b.ModifiedThisFrame {
if b.Settings["diffgutter"].(bool) {
b.UpdateDiff(func(synchronous bool) {
// If the diff was updated asynchronously, the outer call to
// displayBuffer might already be completed and we need to
// schedule a redraw in order to display the new diff.
// Note that this cannot lead to an infinite recursion
// because the modifications were cleared above so there won't
// be another call to UpdateDiff when displayBuffer is called
// during the redraw.
if !synchronous {
screen.Redraw()
}
})
}
b.ModifiedThisFrame = false
}
var matchingBraces []buffer.Loc
@@ -394,12 +435,14 @@ func (w *BufWindow) displayBuffer() {
r := c.RuneUnder(curX)
rl := c.RuneUnder(curX - 1)
if r == bp[0] || r == bp[1] || rl == bp[0] || rl == bp[1] {
mb, left := b.FindMatchingBrace(bp, curLoc)
matchingBraces = append(matchingBraces, mb)
if !left {
matchingBraces = append(matchingBraces, curLoc)
} else {
matchingBraces = append(matchingBraces, curLoc.Move(-1, b))
mb, left, found := b.FindMatchingBrace(bp, curLoc)
if found {
matchingBraces = append(matchingBraces, mb)
if !left {
matchingBraces = append(matchingBraces, curLoc)
} else {
matchingBraces = append(matchingBraces, curLoc.Move(-1, b))
}
}
}
}
@@ -412,7 +455,11 @@ func (w *BufWindow) displayBuffer() {
}
curNumStyle := config.DefStyle
if style, ok := config.Colorscheme["current-line-number"]; ok {
curNumStyle = style
if !b.Settings["cursorline"].(bool) {
curNumStyle = lineNumStyle
} else {
curNumStyle = style
}
}
// We need to know the string length of the largest line number
@@ -436,18 +483,28 @@ func (w *BufWindow) displayBuffer() {
for vloc.Y = 0; vloc.Y < bufHeight; vloc.Y++ {
vloc.X = 0
currentLine := false
for _, c := range cursors {
if bloc.Y == c.Y && w.active {
currentLine = true
break
}
}
s := lineNumStyle
if currentLine {
s = curNumStyle
}
if hasMessage {
w.drawGutter(&vloc, &bloc)
}
if b.Settings["diffgutter"].(bool) {
w.drawDiffGutter(s, false, &vloc, &bloc)
}
if b.Settings["ruler"].(bool) {
s := lineNumStyle
for _, c := range cursors {
if bloc.Y == c.Y && w.active {
s = curNumStyle
break
}
}
w.drawLineNum(s, false, maxLineNumLength, &vloc, &bloc)
}
@@ -459,8 +516,15 @@ func (w *BufWindow) displayBuffer() {
}
bloc.X = bslice
draw := func(r rune, style tcell.Style, showcursor bool) {
draw := func(r rune, combc []rune, style tcell.Style, showcursor bool) {
if nColsBeforeStart <= 0 {
_, origBg, _ := style.Decompose()
_, defBg, _ := config.DefStyle.Decompose()
// syntax highlighting with non-default background takes precedence
// over cursor-line and color-column
dontOverrideBackground := origBg != defBg
for _, c := range cursors {
if c.HasSelection() &&
(bloc.GreaterEqual(c.CurSelection[0]) && bloc.LessThan(c.CurSelection[1]) ||
@@ -473,7 +537,7 @@ func (w *BufWindow) displayBuffer() {
}
}
if b.Settings["cursorline"].(bool) && w.active &&
if b.Settings["cursorline"].(bool) && w.active && !dontOverrideBackground &&
!c.HasSelection() && c.Y == bloc.Y {
if s, ok := config.Colorscheme["cursor-line"]; ok {
fg, _, _ := s.Decompose()
@@ -505,7 +569,7 @@ func (w *BufWindow) displayBuffer() {
}
if s, ok := config.Colorscheme["color-column"]; ok {
if colorcolumn != 0 && vloc.X-w.gutterOffset == colorcolumn {
if colorcolumn != 0 && vloc.X-w.gutterOffset+w.StartCol == colorcolumn && !dontOverrideBackground {
fg, _, _ := s.Decompose()
style = style.Background(fg)
}
@@ -517,7 +581,7 @@ func (w *BufWindow) displayBuffer() {
}
}
screen.Screen.SetContent(w.X+vloc.X, w.Y+vloc.Y, r, nil, style)
screen.SetContent(w.X+vloc.X, w.Y+vloc.Y, r, combc, style)
if showcursor {
for _, c := range cursors {
@@ -533,10 +597,11 @@ func (w *BufWindow) displayBuffer() {
totalwidth := w.StartCol - nColsBeforeStart
for len(line) > 0 {
r, size := utf8.DecodeRune(line)
curStyle, _ = w.getStyle(curStyle, bloc, r)
r, combc, size := util.DecodeCharacter(line)
draw(r, curStyle, true)
curStyle, _ = w.getStyle(curStyle, bloc)
draw(r, combc, curStyle, true)
width := 0
@@ -553,7 +618,7 @@ func (w *BufWindow) displayBuffer() {
// Draw any extra characters either spaces for tabs or @ for incomplete wide runes
if width > 1 {
for i := 1; i < width; i++ {
draw(char, curStyle, false)
draw(char, nil, curStyle, false)
}
}
bloc.X++
@@ -571,8 +636,17 @@ func (w *BufWindow) displayBuffer() {
break
}
vloc.X = 0
if hasMessage {
w.drawGutter(&vloc, &bloc)
}
if b.Settings["diffgutter"].(bool) {
w.drawDiffGutter(lineNumStyle, true, &vloc, &bloc)
}
// This will draw an empty line number because the current line is wrapped
w.drawLineNum(lineNumStyle, true, maxLineNumLength, &vloc, &bloc)
if b.Settings["ruler"].(bool) {
w.drawLineNum(lineNumStyle, true, maxLineNumLength, &vloc, &bloc)
}
}
}
}
@@ -590,22 +664,19 @@ func (w *BufWindow) displayBuffer() {
for i := vloc.X; i < bufWidth; i++ {
curStyle := style
if s, ok := config.Colorscheme["color-column"]; ok {
if colorcolumn != 0 && i-w.gutterOffset == colorcolumn {
if colorcolumn != 0 && i-w.gutterOffset+w.StartCol == colorcolumn {
fg, _, _ := s.Decompose()
curStyle = style.Background(fg)
}
}
screen.Screen.SetContent(i+w.X, vloc.Y+w.Y, ' ', nil, curStyle)
screen.SetContent(i+w.X, vloc.Y+w.Y, ' ', nil, curStyle)
}
for _, c := range cursors {
if c.X == bloc.X && c.Y == bloc.Y && !c.HasSelection() {
w.showCursor(w.X+vloc.X, w.Y+vloc.Y, c.Num == 0)
}
if vloc.X != bufWidth {
// Display newline within a selection
draw(' ', nil, config.DefStyle, true)
}
draw(' ', curStyle, false)
bloc.X = w.StartCol
bloc.Y++
if bloc.Y >= b.LinesNum() {
@@ -626,8 +697,27 @@ func (w *BufWindow) displayStatusLine() {
w.sline.Display()
} else if w.Y+w.Height != infoY {
w.drawStatus = true
divchars := config.GetGlobalOption("divchars").(string)
if util.CharacterCountInString(divchars) != 2 {
divchars = "|-"
}
_, _, size := util.DecodeCharacterInString(divchars)
divchar, combc, _ := util.DecodeCharacterInString(divchars[size:])
dividerStyle := config.DefStyle
if style, ok := config.Colorscheme["divider"]; ok {
dividerStyle = style
}
divreverse := config.GetGlobalOption("divreverse").(bool)
if divreverse {
dividerStyle = dividerStyle.Reverse(true)
}
for x := w.X; x < w.X+w.Width; x++ {
screen.Screen.SetContent(x, w.Y+w.Height-1, '-', nil, config.DefStyle.Reverse(true))
screen.SetContent(x, w.Y+w.Height-1, divchar, combc, dividerStyle)
}
} else {
w.drawStatus = false
@@ -647,7 +737,7 @@ func (w *BufWindow) displayScrollBar() {
}
barstart := w.Y + int(float64(w.StartLine)/float64(w.Buf.LinesNum())*float64(w.Height))
for y := barstart; y < util.Min(barstart+barsize, w.Y+bufHeight); y++ {
screen.Screen.SetContent(scrollX, y, '|', nil, config.DefStyle.Reverse(true))
screen.SetContent(scrollX, y, '|', nil, config.DefStyle.Reverse(true))
}
}
}

View File

@@ -1,20 +1,20 @@
package display
import (
"unicode/utf8"
runewidth "github.com/mattn/go-runewidth"
"github.com/zyedidia/micro/internal/buffer"
"github.com/zyedidia/micro/internal/config"
"github.com/zyedidia/micro/internal/info"
"github.com/zyedidia/micro/internal/screen"
"github.com/zyedidia/micro/internal/util"
"github.com/zyedidia/micro/v2/internal/buffer"
"github.com/zyedidia/micro/v2/internal/config"
"github.com/zyedidia/micro/v2/internal/info"
"github.com/zyedidia/micro/v2/internal/screen"
"github.com/zyedidia/micro/v2/internal/util"
"github.com/zyedidia/tcell"
)
type InfoWindow struct {
*info.InfoBuf
*View
hscroll int
}
func (i *InfoWindow) errStyle() tcell.Style {
@@ -68,13 +68,13 @@ func (i *InfoWindow) IsActive() bool { return true }
func (i *InfoWindow) LocFromVisual(vloc buffer.Loc) buffer.Loc {
c := i.Buffer.GetActiveCursor()
l := i.Buffer.LineBytes(0)
n := utf8.RuneCountInString(i.Msg)
n := util.CharacterCountInString(i.Msg)
return buffer.Loc{c.GetCharPosInLine(l, vloc.X-n), 0}
}
func (i *InfoWindow) Clear() {
for x := 0; x < i.Width; x++ {
screen.Screen.SetContent(x, i.Y, ' ', nil, i.defStyle())
screen.SetContent(x, i.Y, ' ', nil, i.defStyle())
}
}
@@ -84,13 +84,13 @@ func (i *InfoWindow) displayBuffer() {
activeC := b.GetActiveCursor()
blocX := 0
vlocX := utf8.RuneCountInString(i.Msg)
vlocX := util.CharacterCountInString(i.Msg)
tabsize := 4
line, nColsBeforeStart, bslice := util.SliceVisualEnd(line, blocX, tabsize)
blocX = bslice
draw := func(r rune, style tcell.Style) {
draw := func(r rune, combc []rune, style tcell.Style) {
if nColsBeforeStart <= 0 {
bloc := buffer.Loc{X: blocX, Y: 0}
if activeC.HasSelection() &&
@@ -110,8 +110,9 @@ func (i *InfoWindow) displayBuffer() {
c := r
if j > 0 {
c = ' '
combc = nil
}
screen.Screen.SetContent(vlocX, i.Y, c, nil, style)
screen.SetContent(vlocX, i.Y, c, combc, style)
}
vlocX++
}
@@ -120,13 +121,11 @@ func (i *InfoWindow) displayBuffer() {
totalwidth := blocX - nColsBeforeStart
for len(line) > 0 {
if activeC.X == blocX {
screen.Screen.ShowCursor(vlocX, i.Y)
}
curVX := vlocX
curBX := blocX
r, combc, size := util.DecodeCharacter(line)
r, size := utf8.DecodeRune(line)
draw(r, i.defStyle())
draw(r, combc, i.defStyle())
width := 0
@@ -146,16 +145,19 @@ func (i *InfoWindow) displayBuffer() {
// Draw any extra characters either spaces for tabs or @ for incomplete wide runes
if width > 1 {
for j := 1; j < width; j++ {
draw(char, i.defStyle())
draw(char, nil, i.defStyle())
}
}
if activeC.X == curBX {
screen.ShowCursor(curVX, i.Y)
}
totalwidth += width
if vlocX >= i.Width {
break
}
}
if activeC.X == blocX {
screen.Screen.ShowCursor(vlocX, i.Y)
screen.ShowCursor(vlocX, i.Y)
}
}
@@ -167,21 +169,52 @@ func (i *InfoWindow) displayKeyMenu() {
for y := 0; y < len(keydisplay); y++ {
for x := 0; x < i.Width; x++ {
if x < len(keydisplay[y]) {
screen.Screen.SetContent(x, i.Y-len(keydisplay)+y, rune(keydisplay[y][x]), nil, i.defStyle())
screen.SetContent(x, i.Y-len(keydisplay)+y, rune(keydisplay[y][x]), nil, i.defStyle())
} else {
screen.Screen.SetContent(x, i.Y-len(keydisplay)+y, ' ', nil, i.defStyle())
screen.SetContent(x, i.Y-len(keydisplay)+y, ' ', nil, i.defStyle())
}
}
}
}
func (i *InfoWindow) Display() {
func (i *InfoWindow) totalSize() int {
sum := 0
for _, n := range i.Suggestions {
sum += runewidth.StringWidth(n) + 1
}
return sum
}
func (i *InfoWindow) scrollToSuggestion() {
x := 0
if config.GetGlobalOption("keymenu").(bool) {
i.displayKeyMenu()
s := i.totalSize()
for j, n := range i.Suggestions {
c := util.CharacterCountInString(n)
if j == i.CurSuggestion {
if x+c >= i.hscroll+i.Width {
i.hscroll = util.Clamp(x+c+1-i.Width, 0, s-i.Width)
} else if x < i.hscroll {
i.hscroll = util.Clamp(x-1, 0, s-i.Width)
}
break
}
x += c + 1
}
if s-i.Width <= 0 {
i.hscroll = 0
}
}
func (i *InfoWindow) Display() {
if i.HasPrompt || config.GlobalSettings["infobar"].(bool) {
i.Clear()
x := 0
if config.GetGlobalOption("keymenu").(bool) {
i.displayKeyMenu()
}
if !i.HasPrompt && !i.HasMessage && !i.HasError {
return
}
@@ -194,7 +227,7 @@ func (i *InfoWindow) Display() {
display := i.Msg
for _, c := range display {
screen.Screen.SetContent(x, i.Y, c, nil, style)
screen.SetContent(x, i.Y, c, nil, style)
x += runewidth.RuneWidth(c)
}
@@ -204,6 +237,11 @@ func (i *InfoWindow) Display() {
}
if i.HasSuggestions && len(i.Suggestions) > 1 {
i.scrollToSuggestion()
x := -i.hscroll
done := false
statusLineStyle := config.DefStyle.Reverse(true)
if style, ok := config.Colorscheme["statusline"]; ok {
statusLineStyle = style
@@ -212,29 +250,43 @@ func (i *InfoWindow) Display() {
if config.GetGlobalOption("keymenu").(bool) {
keymenuOffset = len(keydisplay)
}
x := 0
draw := func(r rune, s tcell.Style) {
y := i.Y - keymenuOffset - 1
rw := runewidth.RuneWidth(r)
for j := 0; j < rw; j++ {
c := r
if j > 0 {
c = ' '
}
if x == i.Width-1 && !done {
screen.SetContent(i.Width-1, y, '>', nil, s)
x++
break
} else if x == 0 && i.hscroll > 0 {
screen.SetContent(0, y, '<', nil, s)
} else if x >= 0 && x < i.Width {
screen.SetContent(x, y, c, nil, s)
}
x++
}
}
for j, s := range i.Suggestions {
style := statusLineStyle
if i.CurSuggestion == j {
style = style.Reverse(true)
}
for _, r := range s {
screen.Screen.SetContent(x, i.Y-keymenuOffset-1, r, nil, style)
x++
if x >= i.Width {
return
}
}
screen.Screen.SetContent(x, i.Y-keymenuOffset-1, ' ', nil, statusLineStyle)
x++
if x >= i.Width {
return
draw(r, style)
// screen.SetContent(x, i.Y-keymenuOffset-1, r, nil, style)
}
draw(' ', statusLineStyle)
}
for x < i.Width {
screen.Screen.SetContent(x, i.Y-keymenuOffset-1, ' ', nil, statusLineStyle)
x++
draw(' ', statusLineStyle)
}
}
}

View File

@@ -3,21 +3,19 @@ package display
import (
"bytes"
"fmt"
"path"
"regexp"
"strconv"
"strings"
"unicode/utf8"
luar "layeh.com/gopher-luar"
runewidth "github.com/mattn/go-runewidth"
lua "github.com/yuin/gopher-lua"
"github.com/zyedidia/micro/internal/buffer"
"github.com/zyedidia/micro/internal/config"
ulua "github.com/zyedidia/micro/internal/lua"
"github.com/zyedidia/micro/internal/screen"
"github.com/zyedidia/micro/internal/util"
"github.com/zyedidia/micro/v2/internal/buffer"
"github.com/zyedidia/micro/v2/internal/config"
ulua "github.com/zyedidia/micro/v2/internal/lua"
"github.com/zyedidia/micro/v2/internal/screen"
"github.com/zyedidia/micro/v2/internal/util"
)
// StatusLine represents the information line at the bottom
@@ -32,9 +30,6 @@ type StatusLine struct {
var statusInfo = map[string]func(*buffer.Buffer) string{
"filename": func(b *buffer.Buffer) string {
if b.Settings["basename"].(bool) {
return path.Base(b.GetName())
}
return b.GetName()
},
"line": func(b *buffer.Buffer) string {
@@ -47,11 +42,14 @@ var statusInfo = map[string]func(*buffer.Buffer) string{
if b.Modified() {
return "+ "
}
if b.Type.Readonly {
return "[ro] "
}
return ""
},
}
func SetStatusInfoFnLua(s string, fn string) {
func SetStatusInfoFnLua(fn string) {
luaFn := strings.Split(fn, ".")
if len(luaFn) <= 1 {
return
@@ -61,7 +59,7 @@ func SetStatusInfoFnLua(s string, fn string) {
if pl == nil {
return
}
statusInfo[s] = func(b *buffer.Buffer) string {
statusInfo[fn] = func(b *buffer.Buffer) string {
if pl == nil || !pl.IsEnabled() {
return ""
}
@@ -118,13 +116,13 @@ func (s *StatusLine) Display() {
style = style.Reverse(true)
}
for _, r := range sug {
screen.Screen.SetContent(x, y-keymenuOffset, r, nil, style)
screen.SetContent(x, y-keymenuOffset, r, nil, style)
x++
if x >= s.win.Width {
return
}
}
screen.Screen.SetContent(x, y-keymenuOffset, ' ', nil, statusLineStyle)
screen.SetContent(x, y-keymenuOffset, ' ', nil, statusLineStyle)
x++
if x >= s.win.Width {
return
@@ -132,7 +130,7 @@ func (s *StatusLine) Display() {
}
for x < s.win.Width {
screen.Screen.SetContent(x, y-keymenuOffset, ' ', nil, statusLineStyle)
screen.SetContent(x, y-keymenuOffset, ' ', nil, statusLineStyle)
x++
}
return
@@ -169,37 +167,39 @@ func (s *StatusLine) Display() {
statusLineStyle = style
}
leftLen := util.StringWidth(leftText, utf8.RuneCount(leftText), 1)
rightLen := util.StringWidth(rightText, utf8.RuneCount(rightText), 1)
leftLen := util.StringWidth(leftText, util.CharacterCount(leftText), 1)
rightLen := util.StringWidth(rightText, util.CharacterCount(rightText), 1)
winX := s.win.X
for x := 0; x < s.win.Width; x++ {
if x < leftLen {
r, size := utf8.DecodeRune(leftText)
r, combc, size := util.DecodeCharacter(leftText)
leftText = leftText[size:]
rw := runewidth.RuneWidth(r)
for j := 0; j < rw; j++ {
c := r
if j > 0 {
c = ' '
combc = nil
x++
}
screen.Screen.SetContent(winX+x, y, c, nil, statusLineStyle)
screen.SetContent(winX+x, y, c, combc, statusLineStyle)
}
} else if x >= s.win.Width-rightLen && x < rightLen+s.win.Width-rightLen {
r, size := utf8.DecodeRune(rightText)
r, combc, size := util.DecodeCharacter(rightText)
rightText = rightText[size:]
rw := runewidth.RuneWidth(r)
for j := 0; j < rw; j++ {
c := r
if j > 0 {
c = ' '
combc = nil
x++
}
screen.Screen.SetContent(winX+x, y, c, nil, statusLineStyle)
screen.SetContent(winX+x, y, c, combc, statusLineStyle)
}
} else {
screen.Screen.SetContent(winX+x, y, ' ', nil, statusLineStyle)
screen.SetContent(winX+x, y, ' ', nil, statusLineStyle)
}
}
}

View File

@@ -1,42 +1,44 @@
package display
import (
"unicode/utf8"
runewidth "github.com/mattn/go-runewidth"
"github.com/zyedidia/micro/internal/buffer"
"github.com/zyedidia/micro/internal/config"
"github.com/zyedidia/micro/internal/screen"
"github.com/zyedidia/micro/internal/util"
"github.com/zyedidia/micro/v2/internal/buffer"
"github.com/zyedidia/micro/v2/internal/config"
"github.com/zyedidia/micro/v2/internal/screen"
"github.com/zyedidia/micro/v2/internal/util"
)
type TabWindow struct {
Names []string
active int
Y int
width int
Width int
hscroll int
}
func NewTabWindow(w int, y int) *TabWindow {
tw := new(TabWindow)
tw.width = w
tw.Width = w
tw.Y = y
return tw
}
func (w *TabWindow) Resize(width, height int) {
w.Width = width
}
func (w *TabWindow) LocFromVisual(vloc buffer.Loc) int {
x := -w.hscroll
for i, n := range w.Names {
x++
s := utf8.RuneCountInString(n)
s := util.CharacterCountInString(n)
if vloc.Y == w.Y && vloc.X < x+s {
return i
}
x += s
x += 3
if x >= w.width {
if x >= w.Width {
break
}
}
@@ -45,13 +47,18 @@ func (w *TabWindow) LocFromVisual(vloc buffer.Loc) int {
func (w *TabWindow) Scroll(amt int) {
w.hscroll += amt
w.hscroll = util.Clamp(w.hscroll, 0, w.TotalSize()-w.width)
s := w.TotalSize()
w.hscroll = util.Clamp(w.hscroll, 0, s-w.Width)
if s-w.Width <= 0 {
w.hscroll = 0
}
}
func (w *TabWindow) TotalSize() int {
sum := 2
for _, n := range w.Names {
sum += utf8.RuneCountInString(n) + 4
sum += runewidth.StringWidth(n) + 4
}
return sum - 4
}
@@ -64,24 +71,34 @@ func (w *TabWindow) SetActive(a int) {
w.active = a
x := 2
s := w.TotalSize()
for i, n := range w.Names {
c := utf8.RuneCountInString(n)
c := util.CharacterCountInString(n)
if i == a {
if x+c >= w.hscroll+w.width {
w.hscroll = util.Clamp(x+c+1-w.width, 0, s-w.width)
if x+c >= w.hscroll+w.Width {
w.hscroll = util.Clamp(x+c+1-w.Width, 0, s-w.Width)
} else if x < w.hscroll {
w.hscroll = util.Clamp(x-4, 0, s-w.width)
w.hscroll = util.Clamp(x-4, 0, s-w.Width)
}
break
}
x += c + 4
}
if s-w.Width <= 0 {
w.hscroll = 0
}
}
func (w *TabWindow) Display() {
x := -w.hscroll
done := false
tabBarStyle := config.DefStyle.Reverse(true)
if style, ok := config.Colorscheme["tabbar"]; ok {
tabBarStyle = style
}
draw := func(r rune, n int) {
for i := 0; i < n; i++ {
rw := runewidth.RuneWidth(r)
@@ -90,14 +107,14 @@ func (w *TabWindow) Display() {
if j > 0 {
c = ' '
}
if x == w.width-1 && !done {
screen.Screen.SetContent(w.width-1, w.Y, '>', nil, config.DefStyle.Reverse(true))
if x == w.Width-1 && !done {
screen.SetContent(w.Width-1, w.Y, '>', nil, tabBarStyle)
x++
break
} else if x == 0 && w.hscroll > 0 {
screen.Screen.SetContent(0, w.Y, '<', nil, config.DefStyle.Reverse(true))
} else if x >= 0 && x < w.width {
screen.Screen.SetContent(x, w.Y, c, nil, config.DefStyle.Reverse(true))
screen.SetContent(0, w.Y, '<', nil, tabBarStyle)
} else if x >= 0 && x < w.Width {
screen.SetContent(x, w.Y, c, nil, tabBarStyle)
}
x++
}
@@ -122,12 +139,12 @@ func (w *TabWindow) Display() {
} else {
draw(' ', 3)
}
if x >= w.width {
if x >= w.Width {
break
}
}
if x < w.width {
draw(' ', w.width-x)
if x < w.Width {
draw(' ', w.Width-x)
}
}

View File

@@ -1,12 +1,11 @@
package display
import (
"unicode/utf8"
"github.com/zyedidia/micro/internal/buffer"
"github.com/zyedidia/micro/internal/config"
"github.com/zyedidia/micro/internal/screen"
"github.com/zyedidia/micro/internal/shell"
"github.com/zyedidia/micro/v2/internal/buffer"
"github.com/zyedidia/micro/v2/internal/config"
"github.com/zyedidia/micro/v2/internal/screen"
"github.com/zyedidia/micro/v2/internal/shell"
"github.com/zyedidia/micro/v2/internal/util"
"github.com/zyedidia/tcell"
"github.com/zyedidia/terminal"
)
@@ -51,7 +50,7 @@ func (w *TermWindow) LocFromVisual(vloc buffer.Loc) buffer.Loc {
func (w *TermWindow) Clear() {
for y := 0; y < w.Height; y++ {
for x := 0; x < w.Width; x++ {
screen.Screen.SetContent(w.X+x, w.Y+y, ' ', nil, config.DefStyle)
screen.SetContent(w.X+x, w.Y+y, ' ', nil, config.DefStyle)
}
}
}
@@ -88,7 +87,7 @@ func (w *TermWindow) Display() {
st = st.Reverse(true)
}
screen.Screen.SetContent(w.X+x, w.Y+y, c, nil, st)
screen.SetContent(w.X+x, w.Y+y, c, nil, st)
}
}
if config.GetGlobalOption("statusline").(bool) {
@@ -98,19 +97,19 @@ func (w *TermWindow) Display() {
}
text := []byte(w.Name())
textLen := utf8.RuneCount(text)
textLen := util.CharacterCount(text)
for x := 0; x < w.Width; x++ {
if x < textLen {
r, size := utf8.DecodeRune(text)
r, combc, size := util.DecodeCharacter(text)
text = text[size:]
screen.Screen.SetContent(w.X+x, w.Y+w.Height, r, nil, statusLineStyle)
screen.SetContent(w.X+x, w.Y+w.Height, r, combc, statusLineStyle)
} else {
screen.Screen.SetContent(w.X+x, w.Y+w.Height, ' ', nil, statusLineStyle)
screen.SetContent(w.X+x, w.Y+w.Height, ' ', nil, statusLineStyle)
}
}
}
if w.State.CursorVisible() && w.active {
curx, cury := w.State.Cursor()
screen.Screen.ShowCursor(curx+w.X, cury+w.Y)
screen.ShowCursor(curx+w.X, cury+w.Y)
}
}

View File

@@ -1,10 +1,11 @@
package display
import (
"github.com/zyedidia/micro/internal/buffer"
"github.com/zyedidia/micro/internal/config"
"github.com/zyedidia/micro/internal/screen"
"github.com/zyedidia/micro/internal/views"
"github.com/zyedidia/micro/v2/internal/buffer"
"github.com/zyedidia/micro/v2/internal/config"
"github.com/zyedidia/micro/v2/internal/screen"
"github.com/zyedidia/micro/v2/internal/util"
"github.com/zyedidia/micro/v2/internal/views"
)
type UIWindow struct {
@@ -24,11 +25,23 @@ func (w *UIWindow) drawNode(n *views.Node) {
dividerStyle = style
}
divchars := config.GetGlobalOption("divchars").(string)
if util.CharacterCountInString(divchars) != 2 {
divchars = "|-"
}
divchar, combc, _ := util.DecodeCharacterInString(divchars)
divreverse := config.GetGlobalOption("divreverse").(bool)
if divreverse {
dividerStyle = dividerStyle.Reverse(true)
}
for i, c := range cs {
if c.IsLeaf() && c.Kind == views.STVert {
if i != len(cs)-1 {
for h := 0; h < c.H; h++ {
screen.Screen.SetContent(c.X+c.W, c.Y+h, '|', nil, dividerStyle.Reverse(true))
screen.SetContent(c.X+c.W, c.Y+h, divchar, combc, dividerStyle)
}
}
} else {

View File

@@ -1,7 +1,7 @@
package display
import (
"github.com/zyedidia/micro/internal/buffer"
"github.com/zyedidia/micro/v2/internal/buffer"
)
type View struct {

View File

@@ -3,8 +3,9 @@ package info
import (
"encoding/gob"
"os"
"path/filepath"
"github.com/zyedidia/micro/internal/config"
"github.com/zyedidia/micro/v2/internal/config"
)
// LoadHistory attempts to load user history from configDir/buffers/history
@@ -12,7 +13,7 @@ import (
// The savehistory option must be on
func (i *InfoBuf) LoadHistory() {
if config.GetGlobalOption("savehistory").(bool) {
file, err := os.Open(config.ConfigDir + "/buffers/history")
file, err := os.Open(filepath.Join(config.ConfigDir, "buffers", "history"))
defer file.Close()
var decodedMap map[string][]string
if err == nil {
@@ -46,7 +47,7 @@ func (i *InfoBuf) SaveHistory() {
}
}
file, err := os.Create(config.ConfigDir + "/buffers/history")
file, err := os.Create(filepath.Join(config.ConfigDir, "buffers", "history"))
defer file.Close()
if err == nil {
encoder := gob.NewEncoder(file)

View File

@@ -2,14 +2,8 @@ package info
import (
"fmt"
"strings"
"github.com/zyedidia/micro/internal/buffer"
luar "layeh.com/gopher-luar"
"github.com/zyedidia/micro/internal/config"
ulua "github.com/zyedidia/micro/internal/lua"
"github.com/zyedidia/micro/internal/screen"
"github.com/zyedidia/micro/v2/internal/buffer"
)
// The InfoBuf displays messages and other info at the bottom of the screen.
@@ -134,63 +128,6 @@ func (i *InfoBuf) YNPrompt(prompt string, donecb func(bool, bool)) {
i.YNCallback = donecb
}
// PlugPrompt provides a plugin interface for calling "Prompt" with the appropriate Lua callbacks
func (i *InfoBuf) PlugPrompt(prompt string, msg string, ptype string, eventcb string, donecb string) {
eventLuaFn := strings.Split(eventcb, ".")
doneLuaFn := strings.Split(donecb, ".")
var luaEventcb func(string)
var luaDonecb func(string, bool)
if len(eventLuaFn) == 2 {
plName, plFn := doneLuaFn[0], doneLuaFn[1]
pl := config.FindPlugin(plName)
if pl != nil {
luaEventcb = func(resp string) {
_, err := pl.Call(plFn, luar.New(ulua.L, resp))
if err != nil && err != config.ErrNoSuchFunction {
screen.TermMessage(err)
}
}
}
}
if len(doneLuaFn) == 2 {
plName, plFn := doneLuaFn[0], doneLuaFn[1]
pl := config.FindPlugin(plName)
if pl != nil {
luaDonecb = func(resp string, canceled bool) {
_, err := pl.Call(plFn, luar.New(ulua.L, resp), luar.New(ulua.L, canceled))
if err != nil && err != config.ErrNoSuchFunction {
screen.TermMessage(err)
}
}
}
}
i.Prompt(prompt, msg, ptype, luaEventcb, luaDonecb)
}
// PlugYNPrompt provides a plugin interface for calling "YNPrompt" with the appropriate Lua callbacks
func (i *InfoBuf) PlugYNPrompt(prompt string, donecb string) {
doneLuaFn := strings.Split(donecb, ".")
var luaDonecb func(bool, bool)
if len(doneLuaFn) == 2 {
plName, plFn := doneLuaFn[0], doneLuaFn[1]
pl := config.FindPlugin(plName)
if pl != nil {
luaDonecb = func(resp bool, canceled bool) {
_, err := pl.Call(plFn, luar.New(ulua.L, resp), luar.New(ulua.L, canceled))
if err != nil && err != config.ErrNoSuchFunction {
screen.TermMessage(err)
}
}
}
}
i.YNPrompt(prompt, luaDonecb)
}
// DonePrompt finishes the current prompt and indicates whether or not it was canceled
func (i *InfoBuf) DonePrompt(canceled bool) {
hadYN := i.HasYN
@@ -209,7 +146,7 @@ func (i *InfoBuf) DonePrompt(canceled bool) {
h := i.History[i.PromptType]
h[len(h)-1] = resp
}
i.PromptCallback = nil
// i.PromptCallback = nil
}
i.Replace(i.Start(), i.End(), "")
}

View File

@@ -18,6 +18,7 @@ import (
"time"
"unicode/utf8"
humanize "github.com/dustin/go-humanize"
lua "github.com/yuin/gopher-lua"
luar "layeh.com/gopher-luar"
)
@@ -69,6 +70,8 @@ func Import(pkg string) *lua.LTable {
return importTime()
case "unicode/utf8", "utf8":
return importUtf8()
case "humanize":
return importHumanize()
default:
return nil
}
@@ -556,3 +559,14 @@ func importUtf8() *lua.LTable {
return pkg
}
func importHumanize() *lua.LTable {
pkg := L.NewTable()
L.SetField(pkg, "Bytes", luar.New(L, humanize.Bytes))
L.SetField(pkg, "Ordinal", luar.New(L, humanize.Ordinal))
L.SetField(pkg, "Ftoa", luar.New(L, humanize.Ftoa))
L.SetField(pkg, "FtoaWithDigits", luar.New(L, humanize.FtoaWithDigits))
return pkg
}

View File

@@ -1,12 +1,13 @@
package screen
import (
"fmt"
"errors"
"os"
"sync"
"unicode"
"github.com/zyedidia/micro/internal/config"
"github.com/zyedidia/micro/pkg/terminfo"
"github.com/zyedidia/micro/v2/internal/config"
"github.com/zyedidia/micro/v2/internal/util"
"github.com/zyedidia/tcell"
)
@@ -17,19 +18,96 @@ import (
// screen. TODO: maybe we should worry about polling and drawing at the
// same time too.
var Screen tcell.Screen
var lock sync.Mutex
var DrawChan chan bool
// The lock is necessary since the screen is polled on a separate thread
var lock sync.Mutex
// drawChan is a channel that will cause the screen to redraw when
// written to even if no event user event has occurred
var drawChan chan bool
// Lock locks the screen lock
func Lock() {
lock.Lock()
}
// Unlock unlocks the screen lock
func Unlock() {
lock.Unlock()
}
// Redraw schedules a redraw with the draw channel
func Redraw() {
DrawChan <- true
select {
case drawChan <- true:
default:
// channel is full
}
}
// DrawChan returns the draw channel
func DrawChan() chan bool {
return drawChan
}
type screenCell struct {
x, y int
r rune
combc []rune
style tcell.Style
}
var lastCursor screenCell
// ShowFakeCursor displays a cursor at the given position by modifying the
// style of the given column instead of actually using the terminal cursor
// This can be useful in certain terminals such as the windows console where
// modifying the cursor location is slow and frequent modifications cause flashing
// This keeps track of the most recent fake cursor location and resets it when
// a new fake cursor location is specified
func ShowFakeCursor(x, y int) {
r, combc, style, _ := Screen.GetContent(x, y)
Screen.SetContent(lastCursor.x, lastCursor.y, lastCursor.r, lastCursor.combc, lastCursor.style)
Screen.SetContent(x, y, r, combc, config.DefStyle.Reverse(true))
lastCursor.x, lastCursor.y = x, y
lastCursor.r = r
lastCursor.combc = combc
lastCursor.style = style
}
// ShowFakeCursorMulti is the same as ShowFakeCursor except it does not
// reset previous locations of the cursor
// Fake cursors are also necessary to display multiple cursors
func ShowFakeCursorMulti(x, y int) {
r, _, _, _ := Screen.GetContent(x, y)
Screen.SetContent(x, y, r, nil, config.DefStyle.Reverse(true))
}
// ShowCursor puts the cursor at the given location using a fake cursor
// if enabled or using the terminal cursor otherwise
// By default only the windows console will use a fake cursor
func ShowCursor(x, y int) {
if util.FakeCursor {
ShowFakeCursor(x, y)
} else {
Screen.ShowCursor(x, y)
}
}
// SetContent sets a cell at a point on the screen and makes sure that it is
// synced with the last cursor location
func SetContent(x, y int, mainc rune, combc []rune, style tcell.Style) {
if !unicode.IsPrint(mainc) {
mainc = '<27>'
}
Screen.SetContent(x, y, mainc, combc, style)
if util.FakeCursor && lastCursor.x == x && lastCursor.y == y {
lastCursor.r = mainc
lastCursor.style = style
lastCursor.combc = combc
}
}
// TempFini shuts the screen down temporarily
@@ -53,52 +131,34 @@ func TempStart(screenWasNil bool) {
}
// Init creates and initializes the tcell screen
func Init() {
DrawChan = make(chan bool, 8)
func Init() error {
drawChan = make(chan bool, 8)
// Should we enable true color?
truecolor := os.Getenv("MICRO_TRUECOLOR") == "1"
tcelldb := os.Getenv("TCELLDB")
os.Setenv("TCELLDB", config.ConfigDir+"/.tcelldb")
if !truecolor {
os.Setenv("TCELL_TRUECOLOR", "disable")
}
// In order to enable true color, we have to set the TERM to `xterm-truecolor` when
// initializing tcell, but after that, we can set the TERM back to whatever it was
oldTerm := os.Getenv("TERM")
if truecolor {
os.Setenv("TERM", "xterm-truecolor")
var oldTerm string
if config.GetGlobalOption("xterm").(bool) {
oldTerm = os.Getenv("TERM")
os.Setenv("TERM", "xterm-256color")
}
// Initilize tcell
var err error
Screen, err = tcell.NewScreen()
if err != nil {
if err == tcell.ErrTermNotFound {
err = terminfo.WriteDB(config.ConfigDir + "/.tcelldb")
if err != nil {
fmt.Println(err)
fmt.Println("Fatal: Micro could not create terminal database file", config.ConfigDir+"/.tcelldb")
os.Exit(1)
}
Screen, err = tcell.NewScreen()
if err != nil {
fmt.Println(err)
fmt.Println("Fatal: Micro could not initialize a Screen.")
os.Exit(1)
}
} else {
fmt.Println(err)
fmt.Println("Fatal: Micro could not initialize a Screen.")
os.Exit(1)
}
return err
}
if err = Screen.Init(); err != nil {
fmt.Println(err)
os.Exit(1)
return err
}
// Now we can put the TERM back to what it was before
if truecolor {
// restore TERM
if config.GetGlobalOption("xterm").(bool) {
os.Setenv("TERM", oldTerm)
}
@@ -106,5 +166,29 @@ func Init() {
Screen.EnableMouse()
}
os.Setenv("TCELLDB", tcelldb)
return nil
}
// InitSimScreen initializes a simulation screen for testing purposes
func InitSimScreen() (tcell.SimulationScreen, error) {
drawChan = make(chan bool, 8)
// Initilize tcell
var err error
s := tcell.NewSimulationScreen("")
if s == nil {
return nil, errors.New("Failed to get a simulation screen")
}
if err = s.Init(); err != nil {
return nil, err
}
s.SetSize(80, 24)
Screen = s
if config.GetGlobalOption("mouse").(bool) {
Screen.EnableMouse()
}
return s, nil
}

View File

@@ -4,14 +4,6 @@ import (
"bytes"
"io"
"os/exec"
"strings"
luar "layeh.com/gopher-luar"
lua "github.com/yuin/gopher-lua"
"github.com/zyedidia/micro/internal/config"
ulua "github.com/zyedidia/micro/internal/lua"
"github.com/zyedidia/micro/internal/screen"
)
var Jobs chan JobFunction
@@ -32,7 +24,7 @@ func init() {
// JobFunction is a representation of a job (this data structure is what is loaded
// into the jobs channel)
type JobFunction struct {
Function func(string, ...interface{})
Function func(string, []interface{})
Output string
Args []interface{}
}
@@ -41,7 +33,7 @@ type JobFunction struct {
type CallbackFile struct {
io.Writer
callback func(string, ...interface{})
callback func(string, []interface{})
args []interface{}
}
@@ -55,23 +47,23 @@ func (f *CallbackFile) Write(data []byte) (int, error) {
// JobStart starts a shell command in the background with the given callbacks
// It returns an *exec.Cmd as the job id
func JobStart(cmd string, onStdout, onStderr, onExit string, userargs ...interface{}) *exec.Cmd {
func JobStart(cmd string, onStdout, onStderr, onExit func(string, []interface{}), userargs ...interface{}) *exec.Cmd {
return JobSpawn("sh", []string{"-c", cmd}, onStdout, onStderr, onExit, userargs...)
}
// JobSpawn starts a process with args in the background with the given callbacks
// It returns an *exec.Cmd as the job id
func JobSpawn(cmdName string, cmdArgs []string, onStdout, onStderr, onExit string, userargs ...interface{}) *exec.Cmd {
func JobSpawn(cmdName string, cmdArgs []string, onStdout, onStderr, onExit func(string, []interface{}), userargs ...interface{}) *exec.Cmd {
// Set up everything correctly if the functions have been provided
proc := exec.Command(cmdName, cmdArgs...)
var outbuf bytes.Buffer
if onStdout != "" {
proc.Stdout = &CallbackFile{&outbuf, luaFunctionJob(onStdout), userargs}
if onStdout != nil {
proc.Stdout = &CallbackFile{&outbuf, onStdout, userargs}
} else {
proc.Stdout = &outbuf
}
if onStderr != "" {
proc.Stderr = &CallbackFile{&outbuf, luaFunctionJob(onStderr), userargs}
if onStderr != nil {
proc.Stderr = &CallbackFile{&outbuf, onStderr, userargs}
} else {
proc.Stderr = &outbuf
}
@@ -79,7 +71,7 @@ func JobSpawn(cmdName string, cmdArgs []string, onStdout, onStderr, onExit strin
go func() {
// Run the process in the background and create the onExit callback
proc.Run()
jobFunc := JobFunction{luaFunctionJob(onExit), string(outbuf.Bytes()), userargs}
jobFunc := JobFunction{onExit, string(outbuf.Bytes()), userargs}
Jobs <- jobFunc
}()
@@ -100,25 +92,3 @@ func JobSend(cmd *exec.Cmd, data string) {
stdin.Write([]byte(data))
}
// luaFunctionJob returns a function that will call the given lua function
// structured as a job call i.e. the job output and arguments are provided
// to the lua function
func luaFunctionJob(fn string) func(string, ...interface{}) {
luaFn := strings.Split(fn, ".")
if len(luaFn) <= 1 {
return nil
}
plName, plFn := luaFn[0], luaFn[1]
pl := config.FindPlugin(plName)
if pl == nil {
return nil
}
return func(output string, args ...interface{}) {
luaArgs := []lua.LValue{luar.New(ulua.L, output), luar.New(ulua.L, args)}
_, err := pl.Call(plFn, luaArgs...)
if err != nil && err != config.ErrNoSuchFunction {
screen.TermMessage(err)
}
}
}

View File

@@ -2,6 +2,7 @@ package shell
import (
"bytes"
"errors"
"fmt"
"io"
"os"
@@ -9,8 +10,8 @@ import (
"os/signal"
"strings"
"github.com/zyedidia/micro/internal/screen"
"github.com/zyedidia/micro/pkg/shellwords"
shellquote "github.com/kballard/go-shellquote"
"github.com/zyedidia/micro/v2/internal/screen"
)
// ExecCommand executes a command using exec
@@ -32,10 +33,13 @@ func ExecCommand(name string, arg ...string) (string, error) {
// RunCommand executes a shell command and returns the output/error
func RunCommand(input string) (string, error) {
args, err := shellwords.Split(input)
args, err := shellquote.Split(input)
if err != nil {
return "", err
}
if len(args) == 0 {
return "", errors.New("No arguments")
}
inputCmd := args[0]
return ExecCommand(inputCmd, args[1:]...)
@@ -45,10 +49,13 @@ func RunCommand(input string) (string, error) {
// It returns a function which will run the command and returns a string
// message result
func RunBackgroundShell(input string) (func() string, error) {
args, err := shellwords.Split(input)
args, err := shellquote.Split(input)
if err != nil {
return nil, err
}
if len(args) == 0 {
return nil, errors.New("No arguments")
}
inputCmd := args[0]
return func() string {
output, err := RunCommand(input)
@@ -68,10 +75,13 @@ func RunBackgroundShell(input string) (func() string, error) {
// RunInteractiveShell runs a shellcommand interactively
func RunInteractiveShell(input string, wait bool, getOutput bool) (string, error) {
args, err := shellwords.Split(input)
args, err := shellquote.Split(input)
if err != nil {
return "", err
}
if len(args) == 0 {
return "", errors.New("No arguments")
}
inputCmd := args[0]
// Shut down the screen because we're going to interact directly with the shell

View File

@@ -2,19 +2,12 @@ package shell
import (
"bytes"
"fmt"
"os"
"os/exec"
"strconv"
"strings"
lua "github.com/yuin/gopher-lua"
"github.com/zyedidia/micro/internal/buffer"
"github.com/zyedidia/micro/internal/config"
ulua "github.com/zyedidia/micro/internal/lua"
"github.com/zyedidia/micro/internal/screen"
"github.com/zyedidia/micro/v2/internal/buffer"
"github.com/zyedidia/micro/v2/internal/screen"
"github.com/zyedidia/terminal"
luar "layeh.com/gopher-luar"
)
type TermType int
@@ -76,7 +69,7 @@ func (t *Terminal) GetSelection(width int) string {
}
// Start begins a new command in this terminal with a given view
func (t *Terminal) Start(execCmd []string, getOutput bool, wait bool, callback string, userargs []interface{}) error {
func (t *Terminal) Start(execCmd []string, getOutput bool, wait bool, callback func(out string, userargs []interface{}), userargs []interface{}) error {
if len(execCmd) <= 0 {
return nil
}
@@ -95,27 +88,16 @@ func (t *Terminal) Start(execCmd []string, getOutput bool, wait bool, callback s
t.Status = TTRunning
t.title = execCmd[0] + ":" + strconv.Itoa(cmd.Process.Pid)
t.wait = wait
luaFn := strings.Split(callback, ".")
if len(luaFn) >= 2 {
plName, plFn := luaFn[0], luaFn[1]
pl := config.FindPlugin(plName)
if pl != nil {
t.callback = func(out string) {
luaArgs := []lua.LValue{luar.New(ulua.L, out), luar.New(ulua.L, userargs)}
_, err := pl.Call(plFn, luaArgs...)
if err != nil {
screen.TermMessage(err)
}
}
}
t.callback = func(out string) {
callback(out, userargs)
}
go func() {
for {
err := Term.Parse()
if err != nil {
fmt.Fprintln(os.Stderr, "[Press enter to close]")
Term.Write([]byte("Press enter to close"))
screen.Redraw()
break
}
screen.Redraw()

View File

@@ -1,13 +1,11 @@
package util
import (
"unicode/utf8"
)
// LuaRuneAt is a helper function for lua plugins to return the rune
// at an index within a string
func LuaRuneAt(str string, runeidx int) string {
i := 0
for len(str) > 0 {
r, size := utf8.DecodeRuneInString(str)
r, _, size := DecodeCharacterInString(str)
str = str[size:]
@@ -20,10 +18,11 @@ func LuaRuneAt(str string, runeidx int) string {
return ""
}
// LuaGetLeadingWhitespace returns the leading whitespace of a string (used by lua plugins)
func LuaGetLeadingWhitespace(s string) string {
ws := []byte{}
for len(s) > 0 {
r, size := utf8.DecodeRuneInString(s)
r, _, size := DecodeCharacterInString(s)
if r == ' ' || r == '\t' {
ws = append(ws, byte(r))
} else {
@@ -35,7 +34,8 @@ func LuaGetLeadingWhitespace(s string) string {
return string(ws)
}
// LuaIsWordChar returns true if the first rune in a string is a word character
func LuaIsWordChar(s string) bool {
r, _ := utf8.DecodeRuneInString(s)
r, _, _ := DecodeCharacterInString(s)
return IsWordChar(r)
}

View File

@@ -16,14 +16,12 @@ func GetMemStats() string {
return fmt.Sprintf("Alloc: %s, Sys: %s, GC: %d, PauseTotalNs: %dns", humanize.Bytes(memstats.Alloc), humanize.Bytes(memstats.Sys), memstats.NumGC, memstats.PauseTotalNs)
}
var start time.Time
func Tic(s string) {
func Tic(s string) time.Time {
log.Println("START:", s)
start = time.Now()
return time.Now()
}
func Toc() {
func Toc(start time.Time) {
end := time.Now()
log.Println("END: ElapsedTime in seconds:", end.Sub(start))
}

96
internal/util/unicode.go Normal file
View File

@@ -0,0 +1,96 @@
package util
import (
"unicode"
"unicode/utf8"
)
// Unicode is annoying. A "code point" (rune in Go-speak) may need up to
// 4 bytes to represent it. In general, a code point will represent a
// complete character, but this is not always the case. A character with
// accents may be made up of multiple code points (the code point for the
// original character, and additional code points for each accent/marking).
// The functions below are meant to help deal with these additional "combining"
// code points. In underlying operations (search, replace, etc...), micro will
// treat a character with combining code points as just the original code point.
// For rendering, micro will display the combining characters. It's not perfect
// but it's pretty good.
var minMark = rune(unicode.Mark.R16[0].Lo)
func isMark(r rune) bool {
// Fast path
if r < minMark {
return false
}
return unicode.In(r, unicode.Mark)
}
// DecodeCharacter returns the next character from an array of bytes
// A character is a rune along with any accompanying combining runes
func DecodeCharacter(b []byte) (rune, []rune, int) {
r, size := utf8.DecodeRune(b)
b = b[size:]
c, s := utf8.DecodeRune(b)
var combc []rune
for isMark(c) {
combc = append(combc, c)
size += s
b = b[s:]
c, s = utf8.DecodeRune(b)
}
return r, combc, size
}
// DecodeCharacterInString returns the next character from a string
// A character is a rune along with any accompanying combining runes
func DecodeCharacterInString(str string) (rune, []rune, int) {
r, size := utf8.DecodeRuneInString(str)
str = str[size:]
c, s := utf8.DecodeRuneInString(str)
var combc []rune
for isMark(c) {
combc = append(combc, c)
size += s
str = str[s:]
c, s = utf8.DecodeRuneInString(str)
}
return r, combc, size
}
// CharacterCount returns the number of characters in a byte array
// Similar to utf8.RuneCount but for unicode characters
func CharacterCount(b []byte) int {
s := 0
for len(b) > 0 {
r, size := utf8.DecodeRune(b)
if !isMark(r) {
s++
}
b = b[size:]
}
return s
}
// CharacterCount returns the number of characters in a string
// Similar to utf8.RuneCountInString but for unicode characters
func CharacterCountInString(str string) int {
s := 0
for _, r := range str {
if !isMark(r) {
s++
}
}
return s
}

View File

@@ -1,17 +1,18 @@
package util
import (
"bytes"
"errors"
"fmt"
"os"
"os/user"
"path/filepath"
"regexp"
"runtime"
"strconv"
"strings"
"time"
"unicode"
"unicode/utf8"
"github.com/blang/semver"
runewidth "github.com/mattn/go-runewidth"
@@ -29,7 +30,13 @@ var (
// CompileDate is the date this binary was compiled on
CompileDate = "Unknown"
// Debug logging
Debug = "ON"
Debug = "OFF"
// FakeCursor is used to disable the terminal cursor and have micro
// draw its own (enabled for windows consoles where the cursor is slow)
FakeCursor = false
// Stdout is a buffer that is written to stdout when micro closes
Stdout *bytes.Buffer
)
func init() {
@@ -38,6 +45,11 @@ func init() {
if err != nil {
fmt.Println("Invalid version: ", Version, err)
}
if runtime.GOOS == "windows" {
FakeCursor = true
}
Stdout = new(bytes.Buffer)
}
// SliceEnd returns a byte slice where the index is a rune index
@@ -51,7 +63,7 @@ func SliceEnd(slc []byte, index int) []byte {
return slc[totalSize:]
}
_, size := utf8.DecodeRune(slc[totalSize:])
_, _, size := DecodeCharacter(slc[totalSize:])
totalSize += size
i++
}
@@ -69,7 +81,7 @@ func SliceEndStr(str string, index int) string {
return str[totalSize:]
}
_, size := utf8.DecodeRuneInString(str[totalSize:])
_, _, size := DecodeCharacterInString(str[totalSize:])
totalSize += size
i++
}
@@ -88,7 +100,7 @@ func SliceStart(slc []byte, index int) []byte {
return slc[:totalSize]
}
_, size := utf8.DecodeRune(slc[totalSize:])
_, _, size := DecodeCharacter(slc[totalSize:])
totalSize += size
i++
}
@@ -106,7 +118,7 @@ func SliceStartStr(str string, index int) string {
return str[:totalSize]
}
_, size := utf8.DecodeRuneInString(str[totalSize:])
_, _, size := DecodeCharacterInString(str[totalSize:])
totalSize += size
i++
}
@@ -122,7 +134,7 @@ func SliceVisualEnd(b []byte, n, tabsize int) ([]byte, int, int) {
width := 0
i := 0
for len(b) > 0 {
r, size := utf8.DecodeRune(b)
r, _, size := DecodeCharacter(b)
w := 0
switch r {
@@ -159,7 +171,7 @@ func StringWidth(b []byte, n, tabsize int) int {
i := 0
width := 0
for len(b) > 0 {
r, size := utf8.DecodeRune(b)
r, _, size := DecodeCharacter(b)
b = b[size:]
switch r {
@@ -252,7 +264,7 @@ func IsBytesWhitespace(b []byte) bool {
// RunePos returns the rune index of a given byte index
// Make sure the byte index is not between code points
func RunePos(b []byte, i int) int {
return utf8.RuneCount(b[:i])
return CharacterCount(b[:i])
}
// MakeRelative will attempt to make a relative path between path and base
@@ -267,7 +279,6 @@ func MakeRelative(path, base string) (string, error) {
return path, nil
}
// TODO: consider changing because of snap segfault
// ReplaceHome takes a path as input and replaces ~ at the start of the path with the user's
// home directory. Does nothing if the path does not start with '~'.
func ReplaceHome(path string) (string, error) {
@@ -332,7 +343,7 @@ func EscapePath(path string) string {
func GetLeadingWhitespace(b []byte) []byte {
ws := []byte{}
for len(b) > 0 {
r, size := utf8.DecodeRune(b)
r, _, size := DecodeCharacter(b)
if r == ' ' || r == '\t' {
ws = append(ws, byte(r))
} else {
@@ -358,7 +369,7 @@ func GetCharPosInLine(b []byte, visualPos int, tabsize int) int {
i := 0 // char pos
width := 0 // string visual width
for len(b) > 0 {
r, size := utf8.DecodeRune(b)
r, _, size := DecodeCharacter(b)
b = b[size:]
switch r {
@@ -404,9 +415,18 @@ func Clamp(val, min, max int) int {
}
func IsNonAlphaNumeric(c rune) bool {
return !unicode.IsLetter(c) && !unicode.IsNumber(c)
return !unicode.IsLetter(c) && !unicode.IsNumber(c) && c != '_'
}
func IsAutocomplete(c rune) bool {
return c == '.' || !IsNonAlphaNumeric(c)
}
func ParseSpecial(s string) string {
return strings.Replace(s, "\\t", "\t", -1)
}
// String converts a byte array to a string (for lua plugins)
func String(s []byte) string {
return string(s)
}

View File

@@ -456,9 +456,9 @@ func (n *Node) unsplit(i int, h bool) {
// Unsplit deletes this split and resizes everything
// else accordingly
func (n *Node) Unsplit() {
if !n.IsLeaf() {
return
func (n *Node) Unsplit() bool {
if !n.IsLeaf() || n.parent == nil {
return false
}
ind := 0
for i, c := range n.parent.children {
@@ -473,8 +473,9 @@ func (n *Node) Unsplit() {
}
if n.parent.IsLeaf() {
n.parent.Unsplit()
return n.parent.Unsplit()
}
return true
}
// String returns the string form of the node and all children (used for debugging)

View File

@@ -6,7 +6,7 @@ import "regexp"
// to determine the filetype of the file
// It will return the corresponding syntax definition for the filetype
func MatchFiletype(ftdetect [2]*regexp.Regexp, filename string, firstLine []byte) bool {
if ftdetect[0].MatchString(filename) {
if ftdetect[0] != nil && ftdetect[0].MatchString(filename) {
return true
}

View File

@@ -3,7 +3,6 @@ package highlight
import (
"regexp"
"strings"
"unicode/utf8"
)
func sliceStart(slc []byte, index int) []byte {
@@ -15,7 +14,7 @@ func sliceStart(slc []byte, index int) []byte {
return slc[totalSize:]
}
_, size := utf8.DecodeRune(slc[totalSize:])
_, _, size := DecodeCharacter(slc[totalSize:])
totalSize += size
i++
}
@@ -32,7 +31,7 @@ func sliceEnd(slc []byte, index int) []byte {
return slc[:totalSize]
}
_, size := utf8.DecodeRune(slc[totalSize:])
_, _, size := DecodeCharacter(slc[totalSize:])
totalSize += size
i++
}
@@ -47,9 +46,9 @@ func runePos(p int, str []byte) int {
return 0
}
if p >= len(str) {
return utf8.RuneCount(str)
return CharacterCount(str)
}
return utf8.RuneCount(str[:p])
return CharacterCount(str[:p])
}
func combineLineMatch(src, dst LineMatch) LineMatch {
@@ -68,6 +67,8 @@ func combineLineMatch(src, dst LineMatch) LineMatch {
// A State represents the region at the end of a line
type State *region
var EmptyDef = Def{nil, &rules{}}
// LineStates is an interface for a buffer-like object which can also store the states and matches for every line
type LineStates interface {
LineBytes(n int) []byte
@@ -110,7 +111,7 @@ func findIndex(regex *regexp.Regexp, skip *regexp.Regexp, str []byte, canMatchSt
var strbytes []byte
if skip != nil {
strbytes = skip.ReplaceAllFunc(str, func(match []byte) []byte {
res := make([]byte, utf8.RuneCount(match))
res := make([]byte, CharacterCount(match))
return res
})
} else {
@@ -146,7 +147,7 @@ func findAllIndex(regex *regexp.Regexp, str []byte, canMatchStart, canMatchEnd b
}
func (h *Highlighter) highlightRegion(highlights LineMatch, start int, canMatchEnd bool, lineNum int, line []byte, curRegion *region, statesOnly bool) LineMatch {
lineLen := utf8.RuneCount(line)
lineLen := CharacterCount(line)
if start == 0 {
if !statesOnly {
if _, ok := highlights[0]; !ok {
@@ -176,7 +177,7 @@ func (h *Highlighter) highlightRegion(highlights LineMatch, start int, canMatchE
return highlights
}
if lineLen == 0 || statesOnly {
if lineLen == 0 {
if canMatchEnd {
h.lastRegion = curRegion
}
@@ -197,28 +198,32 @@ func (h *Highlighter) highlightRegion(highlights LineMatch, start int, canMatchE
}
}
if firstLoc[0] != lineLen {
highlights[start+firstLoc[0]] = firstRegion.limitGroup
if !statesOnly {
highlights[start+firstLoc[0]] = firstRegion.limitGroup
}
h.highlightRegion(highlights, start, false, lineNum, sliceEnd(line, firstLoc[0]), curRegion, statesOnly)
h.highlightRegion(highlights, start+firstLoc[1], canMatchEnd, lineNum, sliceStart(line, firstLoc[1]), firstRegion, statesOnly)
return highlights
}
fullHighlights := make([]Group, lineLen)
for i := 0; i < len(fullHighlights); i++ {
fullHighlights[i] = curRegion.group
}
if !statesOnly {
fullHighlights := make([]Group, lineLen)
for i := 0; i < len(fullHighlights); i++ {
fullHighlights[i] = curRegion.group
}
for _, p := range curRegion.rules.patterns {
matches := findAllIndex(p.regex, line, start == 0, canMatchEnd)
for _, m := range matches {
for i := m[0]; i < m[1]; i++ {
fullHighlights[i] = p.group
for _, p := range curRegion.rules.patterns {
matches := findAllIndex(p.regex, line, start == 0, canMatchEnd)
for _, m := range matches {
for i := m[0]; i < m[1]; i++ {
fullHighlights[i] = p.group
}
}
}
}
for i, h := range fullHighlights {
if i == 0 || h != fullHighlights[i-1] {
highlights[start+i] = h
for i, h := range fullHighlights {
if i == 0 || h != fullHighlights[i-1] {
highlights[start+i] = h
}
}
}
@@ -230,7 +235,7 @@ func (h *Highlighter) highlightRegion(highlights LineMatch, start int, canMatchE
}
func (h *Highlighter) highlightEmptyRegion(highlights LineMatch, start int, canMatchEnd bool, lineNum int, line []byte, statesOnly bool) LineMatch {
lineLen := utf8.RuneCount(line)
lineLen := CharacterCount(line)
if lineLen == 0 {
if canMatchEnd {
h.lastRegion = nil
@@ -330,11 +335,11 @@ func (h *Highlighter) HighlightStates(input LineStates) {
}
}
// HighlightMatches sets the matches for each line in between startline and endline
// HighlightMatches sets the matches for each line from startline to endline
// It sets all other matches in the buffer to nil to conserve memory
// This assumes that all the states are set correctly
func (h *Highlighter) HighlightMatches(input LineStates, startline, endline int) {
for i := startline; i < endline; i++ {
for i := startline; i <= endline; i++ {
if i >= input.LinesNum() {
break
}
@@ -354,8 +359,9 @@ func (h *Highlighter) HighlightMatches(input LineStates, startline, endline int)
}
// ReHighlightStates will scan down from `startline` and set the appropriate end of line state
// for each line until it comes across the same state in two consecutive lines
func (h *Highlighter) ReHighlightStates(input LineStates, startline int) {
// for each line until it comes across a line whose state does not change
// returns the number of the final line
func (h *Highlighter) ReHighlightStates(input LineStates, startline int) int {
// lines := input.LineData()
h.lastRegion = nil
@@ -378,9 +384,11 @@ func (h *Highlighter) ReHighlightStates(input LineStates, startline int) {
input.SetState(i, curState)
if curState == lastState {
break
return i
}
}
return input.LinesNum() - 1
}
// ReHighlightLine will rehighlight the state and match for a single line

View File

@@ -1,6 +1,7 @@
package highlight
import (
"bytes"
"errors"
"fmt"
"regexp"
@@ -41,6 +42,14 @@ type Header struct {
FtDetect [2]*regexp.Regexp
}
type HeaderYaml struct {
FileType string `yaml:"filetype"`
Detect struct {
FNameRgx string `yaml:"filename"`
HeaderRgx string `yaml:"header"`
} `yaml:"detect"`
}
type File struct {
FileType string
@@ -82,52 +91,59 @@ func init() {
Groups = make(map[string]Group)
}
func ParseFtDetect(file *File) (r [2]*regexp.Regexp, err error) {
defer func() {
if r := recover(); r != nil {
var ok bool
err, ok = r.(error)
if !ok {
err = fmt.Errorf("pkg: %v", r)
}
}
}()
// MakeHeader takes a header (.hdr file) file and parses the header
// Header files make parsing more efficient when you only want to compute
// on the headers of syntax files
// A yaml file might take ~400us to parse while a header file only takes ~20us
func MakeHeader(data []byte) (*Header, error) {
lines := bytes.Split(data, []byte{'\n'})
if len(lines) < 3 {
return nil, errors.New("Header file has incorrect format")
}
header := new(Header)
var err error
header.FileType = string(lines[0])
fnameRgx := string(lines[1])
headerRgx := string(lines[2])
rules := file.yamlSrc
loaded := 0
for k, v := range rules {
if k == "detect" {
ftdetect := v.(map[interface{}]interface{})
if len(ftdetect) >= 1 {
syntax, err := regexp.Compile(ftdetect["filename"].(string))
if err != nil {
return r, err
}
r[0] = syntax
}
if len(ftdetect) >= 2 {
header, err := regexp.Compile(ftdetect["header"].(string))
if err != nil {
return r, err
}
r[1] = header
}
loaded++
}
if loaded >= 2 {
break
}
if fnameRgx != "" {
header.FtDetect[0], err = regexp.Compile(fnameRgx)
}
if headerRgx != "" {
header.FtDetect[1], err = regexp.Compile(headerRgx)
}
if loaded == 0 {
return r, errors.New("No detect regexes found")
if err != nil {
return nil, err
}
return r, err
return header, nil
}
// MakeHeaderYaml takes a yaml spec for a syntax file and parses the
// header
func MakeHeaderYaml(data []byte) (*Header, error) {
var hdrYaml HeaderYaml
err := yaml.Unmarshal(data, &hdrYaml)
if err != nil {
return nil, err
}
header := new(Header)
header.FileType = hdrYaml.FileType
if hdrYaml.Detect.FNameRgx != "" {
header.FtDetect[0], err = regexp.Compile(hdrYaml.Detect.FNameRgx)
}
if hdrYaml.Detect.HeaderRgx != "" {
header.FtDetect[1], err = regexp.Compile(hdrYaml.Detect.HeaderRgx)
}
if err != nil {
return nil, err
}
return header, nil
}
func ParseFile(input []byte) (f *File, err error) {
@@ -196,6 +212,40 @@ func ParseDef(f *File, header *Header) (s *Def, err error) {
return s, err
}
// HasIncludes returns whether this syntax def has any include statements
func HasIncludes(d *Def) bool {
hasIncludes := len(d.rules.includes) > 0
for _, r := range d.rules.regions {
hasIncludes = hasIncludes || hasIncludesInRegion(r)
}
return hasIncludes
}
func hasIncludesInRegion(region *region) bool {
hasIncludes := len(region.rules.includes) > 0
for _, r := range region.rules.regions {
hasIncludes = hasIncludes || hasIncludesInRegion(r)
}
return hasIncludes
}
// GetIncludes returns a list of filetypes that are included by this syntax def
func GetIncludes(d *Def) []string {
includes := d.rules.includes
for _, r := range d.rules.regions {
includes = append(includes, getIncludesInRegion(r)...)
}
return includes
}
func getIncludesInRegion(region *region) []string {
includes := region.rules.includes
for _, r := range region.rules.regions {
includes = append(includes, getIncludesInRegion(r)...)
}
return includes
}
// ResolveIncludes will sort out the rules for including other filetypes
// You should call this after parsing all the Defs
func ResolveIncludes(def *Def, files []*File) {

85
pkg/highlight/unicode.go Normal file
View File

@@ -0,0 +1,85 @@
package highlight
import (
"unicode"
"unicode/utf8"
)
var minMark = rune(unicode.Mark.R16[0].Lo)
func isMark(r rune) bool {
// Fast path
if r < minMark {
return false
}
return unicode.In(r, unicode.Mark)
}
// DecodeCharacter returns the next character from an array of bytes
// A character is a rune along with any accompanying combining runes
func DecodeCharacter(b []byte) (rune, []rune, int) {
r, size := utf8.DecodeRune(b)
b = b[size:]
c, s := utf8.DecodeRune(b)
var combc []rune
for isMark(c) {
combc = append(combc, c)
size += s
b = b[s:]
c, s = utf8.DecodeRune(b)
}
return r, combc, size
}
// DecodeCharacterInString returns the next character from a string
// A character is a rune along with any accompanying combining runes
func DecodeCharacterInString(str string) (rune, []rune, int) {
r, size := utf8.DecodeRuneInString(str)
str = str[size:]
c, s := utf8.DecodeRuneInString(str)
var combc []rune
for isMark(c) {
combc = append(combc, c)
size += s
str = str[s:]
c, s = utf8.DecodeRuneInString(str)
}
return r, combc, size
}
// CharacterCount returns the number of characters in a byte array
// Similar to utf8.RuneCount but for unicode characters
func CharacterCount(b []byte) int {
s := 0
for len(b) > 0 {
r, size := utf8.DecodeRune(b)
if !isMark(r) {
s++
}
b = b[size:]
}
return s
}
// CharacterCount returns the number of characters in a string
// Similar to utf8.RuneCountInString but for unicode characters
func CharacterCountInString(str string) int {
s := 0
for _, r := range str {
if !isMark(r) {
s++
}
}
return s
}

View File

@@ -1,21 +0,0 @@
The MIT License (MIT)
Copyright (c) 2017 Yasuhiro Matsumoto
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -1,49 +0,0 @@
This is a modified version of `go-shellwords` for the micro editor.
# go-shellwords
[![Coverage Status](https://coveralls.io/repos/mattn/go-shellwords/badge.png?branch=master)](https://coveralls.io/r/mattn/go-shellwords?branch=master)
[![Build Status](https://travis-ci.org/mattn/go-shellwords.svg?branch=master)](https://travis-ci.org/mattn/go-shellwords)
Parse line as shell words.
## Usage
```go
args, err := shellwords.Parse("./foo --bar=baz")
// args should be ["./foo", "--bar=baz"]
```
```go
os.Setenv("FOO", "bar")
p := shellwords.NewParser()
p.ParseEnv = true
args, err := p.Parse("./foo $FOO")
// args should be ["./foo", "bar"]
```
```go
p := shellwords.NewParser()
p.ParseBacktick = true
args, err := p.Parse("./foo `echo $SHELL`")
// args should be ["./foo", "/bin/bash"]
```
```go
shellwords.ParseBacktick = true
p := shellwords.NewParser()
args, err := p.Parse("./foo `echo $SHELL`")
// args should be ["./foo", "/bin/bash"]
```
# Thanks
This is based on cpan module [Parse::CommandLine](https://metacpan.org/pod/Parse::CommandLine).
# License
under the MIT License: http://mattn.mit-license.org/2017
# Author
Yasuhiro Matsumoto (a.k.a mattn)

View File

@@ -1,180 +0,0 @@
package shellwords
import (
"bytes"
"errors"
"os"
"regexp"
)
var envRe = regexp.MustCompile(`\$({[a-zA-Z0-9_]+}|[a-zA-Z0-9_]+)`)
func isSpace(r rune) bool {
switch r {
case ' ', '\t', '\r', '\n':
return true
}
return false
}
func replaceEnv(s string) string {
return envRe.ReplaceAllStringFunc(s, func(s string) string {
s = s[1:]
if s[0] == '{' {
s = s[1 : len(s)-1]
}
return os.Getenv(s)
})
}
type Parser struct {
Position int
}
func NewParser() *Parser {
return &Parser{0}
}
func (p *Parser) Parse(line string) ([]string, error) {
args := []string{}
buf := ""
var escaped, doubleQuoted, singleQuoted, backQuote, dollarQuote bool
backtick := ""
pos := -1
got := false
loop:
for i, r := range line {
if escaped {
buf += string(r)
escaped = false
continue
}
if r == '\\' {
if singleQuoted {
buf += string(r)
} else {
escaped = true
}
continue
}
if isSpace(r) {
if singleQuoted || doubleQuoted || backQuote || dollarQuote {
buf += string(r)
backtick += string(r)
} else if got {
buf = replaceEnv(buf)
args = append(args, buf)
buf = ""
got = false
}
continue
}
switch r {
case '`':
if !singleQuoted && !doubleQuoted && !dollarQuote {
if backQuote {
out, err := shellRun(backtick)
if err != nil {
return nil, err
}
buf = out
}
backtick = ""
backQuote = !backQuote
continue
backtick = ""
backQuote = !backQuote
}
case ')':
if !singleQuoted && !doubleQuoted && !backQuote {
if dollarQuote {
out, err := shellRun(backtick)
if err != nil {
return nil, err
}
buf = out
}
backtick = ""
dollarQuote = !dollarQuote
continue
backtick = ""
dollarQuote = !dollarQuote
}
case '(':
if !singleQuoted && !doubleQuoted && !backQuote {
if !dollarQuote && len(buf) > 0 && buf == "$" {
dollarQuote = true
buf += "("
continue
} else {
return nil, errors.New("invalid command line string")
}
}
case '"':
if !singleQuoted && !dollarQuote {
doubleQuoted = !doubleQuoted
continue
}
case '\'':
if !doubleQuoted && !dollarQuote {
singleQuoted = !singleQuoted
continue
}
case ';', '&', '|', '<', '>':
if !(escaped || singleQuoted || doubleQuoted || backQuote) {
pos = i
break loop
}
}
got = true
buf += string(r)
if backQuote || dollarQuote {
backtick += string(r)
}
}
buf = replaceEnv(buf)
args = append(args, buf)
if escaped || singleQuoted || doubleQuoted || backQuote || dollarQuote {
return nil, errors.New("invalid command line string")
}
p.Position = pos
return args, nil
}
func Split(line string) ([]string, error) {
return NewParser().Parse(line)
}
func Join(args ...string) string {
var buf bytes.Buffer
for i, w := range args {
if i != 0 {
buf.WriteByte(' ')
}
if w == "" {
buf.WriteString("''")
continue
}
for _, b := range w {
switch b {
case ' ', '\t', '\r', '\n':
buf.WriteByte('\\')
buf.WriteString(string(b))
default:
buf.WriteString(string(b))
}
}
}
return buf.String()
}

View File

@@ -1,22 +0,0 @@
// +build !windows
package shellwords
import (
"errors"
"os"
"os/exec"
"strings"
)
func shellRun(line string) (string, error) {
shell := os.Getenv("SHELL")
b, err := exec.Command(shell, "-c", line).Output()
if err != nil {
if eerr, ok := err.(*exec.ExitError); ok {
b = eerr.Stderr
}
return "", errors.New(err.Error() + ":" + string(b))
}
return strings.TrimSpace(string(b)), nil
}

View File

@@ -1,20 +0,0 @@
package shellwords
import (
"errors"
"os"
"os/exec"
"strings"
)
func shellRun(line string) (string, error) {
shell := os.Getenv("COMSPEC")
b, err := exec.Command(shell, "/c", line).Output()
if err != nil {
if eerr, ok := err.(*exec.ExitError); ok {
b = eerr.Stderr
}
return "", errors.New(err.Error() + ":" + string(b))
}
return strings.TrimSpace(string(b)), nil
}

View File

@@ -1,203 +0,0 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View File

@@ -1,6 +0,0 @@
# Terminfo parser
This terminfo parser was written by the authors of [tcell](https://github.com/gdamore/tcell). We are using it here
to compile the terminal database if the terminal entry is not found in set of precompiled terminals.
The source for `mkinfo.go` is adapted from tcell's `mkinfo` tool to be more of a library.

View File

@@ -1,518 +0,0 @@
// Copyright 2017 The TCell Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use file except in compliance with the License.
// You may obtain a copy of the license at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// This command is used to generate suitable configuration files in either
// go syntax or in JSON. It defaults to JSON output on stdout. If no
// term values are specified on the command line, then $TERM is used.
//
// Usage is like this:
//
// mkinfo [-init] [-go file.go] [-json file.json] [-quiet] [-nofatal] [<term>...]
//
// -gzip specifies output should be compressed (json only)
// -go specifies Go output into the named file. Use - for stdout.
// -json specifies JSON output in the named file. Use - for stdout
// -nofatal indicates that errors loading definitions should not be fatal
//
package terminfo
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"io"
"os"
"os/exec"
"regexp"
"strconv"
"strings"
)
type termcap struct {
name string
desc string
aliases []string
bools map[string]bool
nums map[string]int
strs map[string]string
}
func (tc *termcap) getnum(s string) int {
return (tc.nums[s])
}
func (tc *termcap) getflag(s string) bool {
return (tc.bools[s])
}
func (tc *termcap) getstr(s string) string {
return (tc.strs[s])
}
const (
NONE = iota
CTRL
ESC
)
func unescape(s string) string {
// Various escapes are in \x format. Control codes are
// encoded as ^M (carat followed by ASCII equivalent).
// Escapes are: \e, \E - escape
// \0 NULL, \n \l \r \t \b \f \s for equivalent C escape.
buf := &bytes.Buffer{}
esc := NONE
for i := 0; i < len(s); i++ {
c := s[i]
switch esc {
case NONE:
switch c {
case '\\':
esc = ESC
case '^':
esc = CTRL
default:
buf.WriteByte(c)
}
case CTRL:
buf.WriteByte(c - 0x40)
esc = NONE
case ESC:
switch c {
case 'E', 'e':
buf.WriteByte(0x1b)
case '0', '1', '2', '3', '4', '5', '6', '7':
if i+2 < len(s) && s[i+1] >= '0' && s[i+1] <= '7' && s[i+2] >= '0' && s[i+2] <= '7' {
buf.WriteByte(((c - '0') * 64) + ((s[i+1] - '0') * 8) + (s[i+2] - '0'))
i = i + 2
} else if c == '0' {
buf.WriteByte(0)
}
case 'n':
buf.WriteByte('\n')
case 'r':
buf.WriteByte('\r')
case 't':
buf.WriteByte('\t')
case 'b':
buf.WriteByte('\b')
case 'f':
buf.WriteByte('\f')
case 's':
buf.WriteByte(' ')
case 'l':
panic("WTF: weird format: " + s)
default:
buf.WriteByte(c)
}
esc = NONE
}
}
return (buf.String())
}
func (tc *termcap) setupterm(name string) error {
cmd := exec.Command("infocmp", "-1", name)
output := &bytes.Buffer{}
cmd.Stdout = output
tc.strs = make(map[string]string)
tc.bools = make(map[string]bool)
tc.nums = make(map[string]int)
err := cmd.Run()
if err != nil {
return err
}
// Now parse the output.
// We get comment lines (starting with "#"), followed by
// a header line that looks like "<name>|<alias>|...|<desc>"
// then capabilities, one per line, starting with a tab and ending
// with a comma and newline.
lines := strings.Split(output.String(), "\n")
for len(lines) > 0 && strings.HasPrefix(lines[0], "#") {
lines = lines[1:]
}
// Ditch trailing empty last line
if lines[len(lines)-1] == "" {
lines = lines[:len(lines)-1]
}
header := lines[0]
if strings.HasSuffix(header, ",") {
header = header[:len(header)-1]
}
names := strings.Split(header, "|")
tc.name = names[0]
names = names[1:]
if len(names) > 0 {
tc.desc = names[len(names)-1]
names = names[:len(names)-1]
}
tc.aliases = names
for _, val := range lines[1:] {
if (!strings.HasPrefix(val, "\t")) ||
(!strings.HasSuffix(val, ",")) {
return (errors.New("malformed infocmp: " + val))
}
val = val[1:]
val = val[:len(val)-1]
if k := strings.SplitN(val, "=", 2); len(k) == 2 {
tc.strs[k[0]] = unescape(k[1])
} else if k := strings.SplitN(val, "#", 2); len(k) == 2 {
if strings.HasPrefix(k[1], "0x") {
if u, err := strconv.ParseUint(k[1][2:], 16, 0); err != nil {
return (err)
} else {
tc.nums[k[0]] = int(u)
}
} else if u, err := strconv.ParseUint(k[1], 10, 0); err != nil {
return (err)
} else {
tc.nums[k[0]] = int(u)
}
} else {
tc.bools[val] = true
}
}
return nil
}
// This program is used to collect data from the system's terminfo library,
// and write it into Go source code. That is, we maintain our terminfo
// capabilities encoded in the program. It should never need to be run by
// an end user, but developers can use this to add codes for additional
// terminal types.
//
// If a terminal name ending with -truecolor is given, and we cannot find
// one, we will try to fabricate one from either the -256color (if present)
// or the unadorned base name, adding the XTerm specific 24-bit color
// escapes. We believe that all 24-bit capable terminals use the same
// escape sequences, and terminfo has yet to evolve to support this.
func getinfo(name string) (*Terminfo, string, error) {
var tc termcap
addTrueColor := false
if err := tc.setupterm(name); err != nil {
if strings.HasSuffix(name, "-truecolor") {
base := name[:len(name)-len("-truecolor")]
// Probably -256color is closest to what we want
if err = tc.setupterm(base + "-256color"); err != nil {
err = tc.setupterm(base)
}
if err == nil {
addTrueColor = true
}
tc.name = name
}
if err != nil {
return nil, "", err
}
}
t := &Terminfo{}
// If this is an alias record, then just emit the alias
t.Name = tc.name
if t.Name != name {
return t, "", nil
}
t.Aliases = tc.aliases
t.Colors = tc.getnum("colors")
t.Columns = tc.getnum("cols")
t.Lines = tc.getnum("lines")
t.Bell = tc.getstr("bel")
t.Clear = tc.getstr("clear")
t.EnterCA = tc.getstr("smcup")
t.ExitCA = tc.getstr("rmcup")
t.ShowCursor = tc.getstr("cnorm")
t.HideCursor = tc.getstr("civis")
t.AttrOff = tc.getstr("sgr0")
t.Underline = tc.getstr("smul")
t.Bold = tc.getstr("bold")
t.Blink = tc.getstr("blink")
t.Dim = tc.getstr("dim")
t.Reverse = tc.getstr("rev")
t.EnterKeypad = tc.getstr("smkx")
t.ExitKeypad = tc.getstr("rmkx")
t.SetFg = tc.getstr("setaf")
t.SetBg = tc.getstr("setab")
t.SetCursor = tc.getstr("cup")
t.CursorBack1 = tc.getstr("cub1")
t.CursorUp1 = tc.getstr("cuu1")
t.KeyF1 = tc.getstr("kf1")
t.KeyF2 = tc.getstr("kf2")
t.KeyF3 = tc.getstr("kf3")
t.KeyF4 = tc.getstr("kf4")
t.KeyF5 = tc.getstr("kf5")
t.KeyF6 = tc.getstr("kf6")
t.KeyF7 = tc.getstr("kf7")
t.KeyF8 = tc.getstr("kf8")
t.KeyF9 = tc.getstr("kf9")
t.KeyF10 = tc.getstr("kf10")
t.KeyF11 = tc.getstr("kf11")
t.KeyF12 = tc.getstr("kf12")
t.KeyF13 = tc.getstr("kf13")
t.KeyF14 = tc.getstr("kf14")
t.KeyF15 = tc.getstr("kf15")
t.KeyF16 = tc.getstr("kf16")
t.KeyF17 = tc.getstr("kf17")
t.KeyF18 = tc.getstr("kf18")
t.KeyF19 = tc.getstr("kf19")
t.KeyF20 = tc.getstr("kf20")
t.KeyF21 = tc.getstr("kf21")
t.KeyF22 = tc.getstr("kf22")
t.KeyF23 = tc.getstr("kf23")
t.KeyF24 = tc.getstr("kf24")
t.KeyF25 = tc.getstr("kf25")
t.KeyF26 = tc.getstr("kf26")
t.KeyF27 = tc.getstr("kf27")
t.KeyF28 = tc.getstr("kf28")
t.KeyF29 = tc.getstr("kf29")
t.KeyF30 = tc.getstr("kf30")
t.KeyF31 = tc.getstr("kf31")
t.KeyF32 = tc.getstr("kf32")
t.KeyF33 = tc.getstr("kf33")
t.KeyF34 = tc.getstr("kf34")
t.KeyF35 = tc.getstr("kf35")
t.KeyF36 = tc.getstr("kf36")
t.KeyF37 = tc.getstr("kf37")
t.KeyF38 = tc.getstr("kf38")
t.KeyF39 = tc.getstr("kf39")
t.KeyF40 = tc.getstr("kf40")
t.KeyF41 = tc.getstr("kf41")
t.KeyF42 = tc.getstr("kf42")
t.KeyF43 = tc.getstr("kf43")
t.KeyF44 = tc.getstr("kf44")
t.KeyF45 = tc.getstr("kf45")
t.KeyF46 = tc.getstr("kf46")
t.KeyF47 = tc.getstr("kf47")
t.KeyF48 = tc.getstr("kf48")
t.KeyF49 = tc.getstr("kf49")
t.KeyF50 = tc.getstr("kf50")
t.KeyF51 = tc.getstr("kf51")
t.KeyF52 = tc.getstr("kf52")
t.KeyF53 = tc.getstr("kf53")
t.KeyF54 = tc.getstr("kf54")
t.KeyF55 = tc.getstr("kf55")
t.KeyF56 = tc.getstr("kf56")
t.KeyF57 = tc.getstr("kf57")
t.KeyF58 = tc.getstr("kf58")
t.KeyF59 = tc.getstr("kf59")
t.KeyF60 = tc.getstr("kf60")
t.KeyF61 = tc.getstr("kf61")
t.KeyF62 = tc.getstr("kf62")
t.KeyF63 = tc.getstr("kf63")
t.KeyF64 = tc.getstr("kf64")
t.KeyInsert = tc.getstr("kich1")
t.KeyDelete = tc.getstr("kdch1")
t.KeyBackspace = tc.getstr("kbs")
t.KeyHome = tc.getstr("khome")
t.KeyEnd = tc.getstr("kend")
t.KeyUp = tc.getstr("kcuu1")
t.KeyDown = tc.getstr("kcud1")
t.KeyRight = tc.getstr("kcuf1")
t.KeyLeft = tc.getstr("kcub1")
t.KeyPgDn = tc.getstr("knp")
t.KeyPgUp = tc.getstr("kpp")
t.KeyBacktab = tc.getstr("kcbt")
t.KeyExit = tc.getstr("kext")
t.KeyCancel = tc.getstr("kcan")
t.KeyPrint = tc.getstr("kprt")
t.KeyHelp = tc.getstr("khlp")
t.KeyClear = tc.getstr("kclr")
t.AltChars = tc.getstr("acsc")
t.EnterAcs = tc.getstr("smacs")
t.ExitAcs = tc.getstr("rmacs")
t.EnableAcs = tc.getstr("enacs")
t.Mouse = tc.getstr("kmous")
t.KeyShfRight = tc.getstr("kRIT")
t.KeyShfLeft = tc.getstr("kLFT")
t.KeyShfHome = tc.getstr("kHOM")
t.KeyShfEnd = tc.getstr("kEND")
// Terminfo lacks descriptions for a bunch of modified keys,
// but modern XTerm and emulators often have them. Let's add them,
// if the shifted right and left arrows are defined.
if t.KeyShfRight == "\x1b[1;2C" && t.KeyShfLeft == "\x1b[1;2D" {
t.KeyShfUp = "\x1b[1;2A"
t.KeyShfDown = "\x1b[1;2B"
t.KeyMetaUp = "\x1b[1;9A"
t.KeyMetaDown = "\x1b[1;9B"
t.KeyMetaRight = "\x1b[1;9C"
t.KeyMetaLeft = "\x1b[1;9D"
t.KeyAltUp = "\x1b[1;3A"
t.KeyAltDown = "\x1b[1;3B"
t.KeyAltRight = "\x1b[1;3C"
t.KeyAltLeft = "\x1b[1;3D"
t.KeyCtrlUp = "\x1b[1;5A"
t.KeyCtrlDown = "\x1b[1;5B"
t.KeyCtrlRight = "\x1b[1;5C"
t.KeyCtrlLeft = "\x1b[1;5D"
t.KeyAltShfUp = "\x1b[1;4A"
t.KeyAltShfDown = "\x1b[1;4B"
t.KeyAltShfRight = "\x1b[1;4C"
t.KeyAltShfLeft = "\x1b[1;4D"
t.KeyMetaShfUp = "\x1b[1;10A"
t.KeyMetaShfDown = "\x1b[1;10B"
t.KeyMetaShfRight = "\x1b[1;10C"
t.KeyMetaShfLeft = "\x1b[1;10D"
t.KeyCtrlShfUp = "\x1b[1;6A"
t.KeyCtrlShfDown = "\x1b[1;6B"
t.KeyCtrlShfRight = "\x1b[1;6C"
t.KeyCtrlShfLeft = "\x1b[1;6D"
}
// And also for Home and End
if t.KeyShfHome == "\x1b[1;2H" && t.KeyShfEnd == "\x1b[1;2F" {
t.KeyCtrlHome = "\x1b[1;5H"
t.KeyCtrlEnd = "\x1b[1;5F"
t.KeyAltHome = "\x1b[1;9H"
t.KeyAltEnd = "\x1b[1;9F"
t.KeyCtrlShfHome = "\x1b[1;6H"
t.KeyCtrlShfEnd = "\x1b[1;6F"
t.KeyAltShfHome = "\x1b[1;4H"
t.KeyAltShfEnd = "\x1b[1;4F"
t.KeyMetaShfHome = "\x1b[1;10H"
t.KeyMetaShfEnd = "\x1b[1;10F"
}
// And the same thing for rxvt and workalikes (Eterm, aterm, etc.)
// It seems that urxvt at least send ESC as ALT prefix for these,
// although some places seem to indicate a separate ALT key sesquence.
if t.KeyShfRight == "\x1b[c" && t.KeyShfLeft == "\x1b[d" {
t.KeyShfUp = "\x1b[a"
t.KeyShfDown = "\x1b[b"
t.KeyCtrlUp = "\x1b[Oa"
t.KeyCtrlDown = "\x1b[Ob"
t.KeyCtrlRight = "\x1b[Oc"
t.KeyCtrlLeft = "\x1b[Od"
}
if t.KeyShfHome == "\x1b[7$" && t.KeyShfEnd == "\x1b[8$" {
t.KeyCtrlHome = "\x1b[7^"
t.KeyCtrlEnd = "\x1b[8^"
}
// If the kmous entry is present, then we need to record the
// the codes to enter and exit mouse mode. Sadly, this is not
// part of the terminfo databases anywhere that I've found, but
// is an extension. The escape codes are documented in the XTerm
// manual, and all terminals that have kmous are expected to
// use these same codes, unless explicitly configured otherwise
// vi XM. Note that in any event, we only known how to parse either
// x11 or SGR mouse events -- if your terminal doesn't support one
// of these two forms, you maybe out of luck.
t.MouseMode = tc.getstr("XM")
if t.Mouse != "" && t.MouseMode == "" {
// we anticipate that all xterm mouse tracking compatible
// terminals understand mouse tracking (1000), but we hope
// that those that don't understand any-event tracking (1003)
// will at least ignore it. Likewise we hope that terminals
// that don't understand SGR reporting (1006) just ignore it.
t.MouseMode = "%?%p1%{1}%=%t%'h'%Pa%e%'l'%Pa%;" +
"\x1b[?1000%ga%c\x1b[?1002%ga%c\x1b[?1003%ga%c\x1b[?1006%ga%c"
}
// We only support colors in ANSI 8 or 256 color mode.
if t.Colors < 8 || t.SetFg == "" {
t.Colors = 0
}
if t.SetCursor == "" {
return nil, "", errors.New("terminal not cursor addressable")
}
// For padding, we lookup the pad char. If that isn't present,
// and npc is *not* set, then we assume a null byte.
t.PadChar = tc.getstr("pad")
if t.PadChar == "" {
if !tc.getflag("npc") {
t.PadChar = "\u0000"
}
}
// For some terminals we fabricate a -truecolor entry, that may
// not exist in terminfo.
if addTrueColor {
t.SetFgRGB = "\x1b[38;2;%p1%d;%p2%d;%p3%dm"
t.SetBgRGB = "\x1b[48;2;%p1%d;%p2%d;%p3%dm"
t.SetFgBgRGB = "\x1b[38;2;%p1%d;%p2%d;%p3%d;" +
"48;2;%p4%d;%p5%d;%p6%dm"
}
// For terminals that use "standard" SGR sequences, lets combine the
// foreground and background together.
if strings.HasPrefix(t.SetFg, "\x1b[") &&
strings.HasPrefix(t.SetBg, "\x1b[") &&
strings.HasSuffix(t.SetFg, "m") &&
strings.HasSuffix(t.SetBg, "m") {
fg := t.SetFg[:len(t.SetFg)-1]
r := regexp.MustCompile("%p1")
bg := r.ReplaceAllString(t.SetBg[2:], "%p2")
t.SetFgBg = fg + ";" + bg
}
return t, tc.desc, nil
}
func WriteDB(filename string) error {
var e error
js := []byte{}
args := []string{os.Getenv("TERM")}
tdata := make(map[string]*Terminfo)
descs := make(map[string]string)
for _, term := range args {
if t, desc, e := getinfo(term); e != nil {
return e
} else {
tdata[term] = t
descs[term] = desc
}
}
if len(tdata) == 0 {
// No data.
return errors.New("No data")
}
o := os.Stdout
if o, e = os.Create(filename); e != nil {
return e
}
var w io.WriteCloser
w = o
for _, term := range args {
if t := tdata[term]; t != nil {
js, e = json.Marshal(t)
fmt.Fprintln(w, string(js))
}
// arguably if there is more than one term, this
// should be a javascript array, but that's not how
// we load it. We marshal objects one at a time from
// the file.
}
if e != nil {
return e
}
w.Close()
if w != o {
o.Close()
}
return nil
}

View File

@@ -1,838 +0,0 @@
// Copyright 2017 The TCell Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use file except in compliance with the License.
// You may obtain a copy of the license at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package terminfo
import (
"bytes"
"compress/gzip"
"encoding/json"
"errors"
"fmt"
"io"
"os"
"path"
"strconv"
"strings"
"sync"
)
var (
// ErrTermNotFound indicates that a suitable terminal entry could
// not be found. This can result from either not having TERM set,
// or from the TERM failing to support certain minimal functionality,
// in particular absolute cursor addressability (the cup capability)
// is required. For example, legacy "adm3" lacks this capability,
// whereas the slightly newer "adm3a" supports it. This failure
// occurs most often with "dumb".
ErrTermNotFound = errors.New("terminal entry not found")
)
// Terminfo represents a terminfo entry. Note that we use friendly names
// in Go, but when we write out JSON, we use the same names as terminfo.
// The name, aliases and smous, rmous fields do not come from terminfo directly.
type Terminfo struct {
Name string `json:"name"`
Aliases []string `json:"aliases,omitempty"`
Columns int `json:"cols,omitempty"` // cols
Lines int `json:"lines,omitempty"` // lines
Colors int `json:"colors,omitempty"` // colors
Bell string `json:"bell,omitempty"` // bell
Clear string `json:"clear,omitempty"` // clear
EnterCA string `json:"smcup,omitempty"` // smcup
ExitCA string `json:"rmcup,omitempty"` // rmcup
ShowCursor string `json:"cnorm,omitempty"` // cnorm
HideCursor string `json:"civis,omitempty"` // civis
AttrOff string `json:"sgr0,omitempty"` // sgr0
Underline string `json:"smul,omitempty"` // smul
Bold string `json:"bold,omitempty"` // bold
Blink string `json:"blink,omitempty"` // blink
Reverse string `json:"rev,omitempty"` // rev
Dim string `json:"dim,omitempty"` // dim
EnterKeypad string `json:"smkx,omitempty"` // smkx
ExitKeypad string `json:"rmkx,omitempty"` // rmkx
SetFg string `json:"setaf,omitempty"` // setaf
SetBg string `json:"setbg,omitempty"` // setab
SetCursor string `json:"cup,omitempty"` // cup
CursorBack1 string `json:"cub1,omitempty"` // cub1
CursorUp1 string `json:"cuu1,omitempty"` // cuu1
PadChar string `json:"pad,omitempty"` // pad
KeyBackspace string `json:"kbs,omitempty"` // kbs
KeyF1 string `json:"kf1,omitempty"` // kf1
KeyF2 string `json:"kf2,omitempty"` // kf2
KeyF3 string `json:"kf3,omitempty"` // kf3
KeyF4 string `json:"kf4,omitempty"` // kf4
KeyF5 string `json:"kf5,omitempty"` // kf5
KeyF6 string `json:"kf6,omitempty"` // kf6
KeyF7 string `json:"kf7,omitempty"` // kf7
KeyF8 string `json:"kf8,omitempty"` // kf8
KeyF9 string `json:"kf9,omitempty"` // kf9
KeyF10 string `json:"kf10,omitempty"` // kf10
KeyF11 string `json:"kf11,omitempty"` // kf11
KeyF12 string `json:"kf12,omitempty"` // kf12
KeyF13 string `json:"kf13,omitempty"` // kf13
KeyF14 string `json:"kf14,omitempty"` // kf14
KeyF15 string `json:"kf15,omitempty"` // kf15
KeyF16 string `json:"kf16,omitempty"` // kf16
KeyF17 string `json:"kf17,omitempty"` // kf17
KeyF18 string `json:"kf18,omitempty"` // kf18
KeyF19 string `json:"kf19,omitempty"` // kf19
KeyF20 string `json:"kf20,omitempty"` // kf20
KeyF21 string `json:"kf21,omitempty"` // kf21
KeyF22 string `json:"kf22,omitempty"` // kf22
KeyF23 string `json:"kf23,omitempty"` // kf23
KeyF24 string `json:"kf24,omitempty"` // kf24
KeyF25 string `json:"kf25,omitempty"` // kf25
KeyF26 string `json:"kf26,omitempty"` // kf26
KeyF27 string `json:"kf27,omitempty"` // kf27
KeyF28 string `json:"kf28,omitempty"` // kf28
KeyF29 string `json:"kf29,omitempty"` // kf29
KeyF30 string `json:"kf30,omitempty"` // kf30
KeyF31 string `json:"kf31,omitempty"` // kf31
KeyF32 string `json:"kf32,omitempty"` // kf32
KeyF33 string `json:"kf33,omitempty"` // kf33
KeyF34 string `json:"kf34,omitempty"` // kf34
KeyF35 string `json:"kf35,omitempty"` // kf35
KeyF36 string `json:"kf36,omitempty"` // kf36
KeyF37 string `json:"kf37,omitempty"` // kf37
KeyF38 string `json:"kf38,omitempty"` // kf38
KeyF39 string `json:"kf39,omitempty"` // kf39
KeyF40 string `json:"kf40,omitempty"` // kf40
KeyF41 string `json:"kf41,omitempty"` // kf41
KeyF42 string `json:"kf42,omitempty"` // kf42
KeyF43 string `json:"kf43,omitempty"` // kf43
KeyF44 string `json:"kf44,omitempty"` // kf44
KeyF45 string `json:"kf45,omitempty"` // kf45
KeyF46 string `json:"kf46,omitempty"` // kf46
KeyF47 string `json:"kf47,omitempty"` // kf47
KeyF48 string `json:"kf48,omitempty"` // kf48
KeyF49 string `json:"kf49,omitempty"` // kf49
KeyF50 string `json:"kf50,omitempty"` // kf50
KeyF51 string `json:"kf51,omitempty"` // kf51
KeyF52 string `json:"kf52,omitempty"` // kf52
KeyF53 string `json:"kf53,omitempty"` // kf53
KeyF54 string `json:"kf54,omitempty"` // kf54
KeyF55 string `json:"kf55,omitempty"` // kf55
KeyF56 string `json:"kf56,omitempty"` // kf56
KeyF57 string `json:"kf57,omitempty"` // kf57
KeyF58 string `json:"kf58,omitempty"` // kf58
KeyF59 string `json:"kf59,omitempty"` // kf59
KeyF60 string `json:"kf60,omitempty"` // kf60
KeyF61 string `json:"kf61,omitempty"` // kf61
KeyF62 string `json:"kf62,omitempty"` // kf62
KeyF63 string `json:"kf63,omitempty"` // kf63
KeyF64 string `json:"kf64,omitempty"` // kf64
KeyInsert string `json:"kich,omitempty"` // kich1
KeyDelete string `json:"kdch,omitempty"` // kdch1
KeyHome string `json:"khome,omitempty"` // khome
KeyEnd string `json:"kend,omitempty"` // kend
KeyHelp string `json:"khlp,omitempty"` // khlp
KeyPgUp string `json:"kpp,omitempty"` // kpp
KeyPgDn string `json:"knp,omitempty"` // knp
KeyUp string `json:"kcuu1,omitempty"` // kcuu1
KeyDown string `json:"kcud1,omitempty"` // kcud1
KeyLeft string `json:"kcub1,omitempty"` // kcub1
KeyRight string `json:"kcuf1,omitempty"` // kcuf1
KeyBacktab string `json:"kcbt,omitempty"` // kcbt
KeyExit string `json:"kext,omitempty"` // kext
KeyClear string `json:"kclr,omitempty"` // kclr
KeyPrint string `json:"kprt,omitempty"` // kprt
KeyCancel string `json:"kcan,omitempty"` // kcan
Mouse string `json:"kmous,omitempty"` // kmous
MouseMode string `json:"XM,omitempty"` // XM
AltChars string `json:"acsc,omitempty"` // acsc
EnterAcs string `json:"smacs,omitempty"` // smacs
ExitAcs string `json:"rmacs,omitempty"` // rmacs
EnableAcs string `json:"enacs,omitempty"` // enacs
KeyShfRight string `json:"kRIT,omitempty"` // kRIT
KeyShfLeft string `json:"kLFT,omitempty"` // kLFT
KeyShfHome string `json:"kHOM,omitempty"` // kHOM
KeyShfEnd string `json:"kEND,omitempty"` // kEND
// These are non-standard extensions to terminfo. This includes
// true color support, and some additional keys. Its kind of bizarre
// that shifted variants of left and right exist, but not up and down.
// Terminal support for these are going to vary amongst XTerm
// emulations, so don't depend too much on them in your application.
SetFgBg string `json:"_setfgbg,omitempty"` // setfgbg
SetFgBgRGB string `json:"_setfgbgrgb,omitempty"` // setfgbgrgb
SetFgRGB string `json:"_setfrgb,omitempty"` // setfrgb
SetBgRGB string `json:"_setbrgb,omitempty"` // setbrgb
KeyShfUp string `json:"_kscu1,omitempty"` // shift-up
KeyShfDown string `json:"_kscud1,omitempty"` // shift-down
KeyCtrlUp string `json:"_kccu1,omitempty"` // ctrl-up
KeyCtrlDown string `json:"_kccud1,omitempty"` // ctrl-left
KeyCtrlRight string `json:"_kccuf1,omitempty"` // ctrl-right
KeyCtrlLeft string `json:"_kccub1,omitempty"` // ctrl-left
KeyMetaUp string `json:"_kmcu1,omitempty"` // meta-up
KeyMetaDown string `json:"_kmcud1,omitempty"` // meta-left
KeyMetaRight string `json:"_kmcuf1,omitempty"` // meta-right
KeyMetaLeft string `json:"_kmcub1,omitempty"` // meta-left
KeyAltUp string `json:"_kacu1,omitempty"` // alt-up
KeyAltDown string `json:"_kacud1,omitempty"` // alt-left
KeyAltRight string `json:"_kacuf1,omitempty"` // alt-right
KeyAltLeft string `json:"_kacub1,omitempty"` // alt-left
KeyCtrlHome string `json:"_kchome,omitempty"`
KeyCtrlEnd string `json:"_kcend,omitempty"`
KeyMetaHome string `json:"_kmhome,omitempty"`
KeyMetaEnd string `json:"_kmend,omitempty"`
KeyAltHome string `json:"_kahome,omitempty"`
KeyAltEnd string `json:"_kaend,omitempty"`
KeyAltShfUp string `json:"_kascu1,omitempty"`
KeyAltShfDown string `json:"_kascud1,omitempty"`
KeyAltShfLeft string `json:"_kascub1,omitempty"`
KeyAltShfRight string `json:"_kascuf1,omitempty"`
KeyMetaShfUp string `json:"_kmscu1,omitempty"`
KeyMetaShfDown string `json:"_kmscud1,omitempty"`
KeyMetaShfLeft string `json:"_kmscub1,omitempty"`
KeyMetaShfRight string `json:"_kmscuf1,omitempty"`
KeyCtrlShfUp string `json:"_kcscu1,omitempty"`
KeyCtrlShfDown string `json:"_kcscud1,omitempty"`
KeyCtrlShfLeft string `json:"_kcscub1,omitempty"`
KeyCtrlShfRight string `json:"_kcscuf1,omitempty"`
KeyCtrlShfHome string `json:"_kcHOME,omitempty"`
KeyCtrlShfEnd string `json:"_kcEND,omitempty"`
KeyAltShfHome string `json:"_kaHOME,omitempty"`
KeyAltShfEnd string `json:"_kaEND,omitempty"`
KeyMetaShfHome string `json:"_kmHOME,omitempty"`
KeyMetaShfEnd string `json:"_kmEND,omitempty"`
}
type stackElem struct {
s string
i int
isStr bool
isInt bool
}
type stack []stackElem
func (st stack) Push(v string) stack {
e := stackElem{
s: v,
isStr: true,
}
return append(st, e)
}
func (st stack) Pop() (string, stack) {
v := ""
if len(st) > 0 {
e := st[len(st)-1]
st = st[:len(st)-1]
if e.isStr {
v = e.s
} else {
v = strconv.Itoa(e.i)
}
}
return v, st
}
func (st stack) PopInt() (int, stack) {
if len(st) > 0 {
e := st[len(st)-1]
st = st[:len(st)-1]
if e.isInt {
return e.i, st
} else if e.isStr {
i, _ := strconv.Atoi(e.s)
return i, st
}
}
return 0, st
}
func (st stack) PopBool() (bool, stack) {
if len(st) > 0 {
e := st[len(st)-1]
st = st[:len(st)-1]
if e.isStr {
if e.s == "1" {
return true, st
}
return false, st
} else if e.i == 1 {
return true, st
} else {
return false, st
}
}
return false, st
}
func (st stack) PushInt(i int) stack {
e := stackElem{
i: i,
isInt: true,
}
return append(st, e)
}
func (st stack) PushBool(i bool) stack {
if i {
return st.PushInt(1)
}
return st.PushInt(0)
}
func nextch(s string, index int) (byte, int) {
if index < len(s) {
return s[index], index + 1
}
return 0, index
}
// static vars
var svars [26]string
// paramsBuffer handles some persistent state for TParam. Technically we
// could probably dispense with this, but caching buffer arrays gives us
// a nice little performance boost. Furthermore, we know that TParam is
// rarely (never?) called re-entrantly, so we can just reuse the same
// buffers, making it thread-safe by stashing a lock.
type paramsBuffer struct {
out bytes.Buffer
buf bytes.Buffer
lk sync.Mutex
}
// Start initializes the params buffer with the initial string data.
// It also locks the paramsBuffer. The caller must call End() when
// finished.
func (pb *paramsBuffer) Start(s string) {
pb.lk.Lock()
pb.out.Reset()
pb.buf.Reset()
pb.buf.WriteString(s)
}
// End returns the final output from TParam, but it also releases the lock.
func (pb *paramsBuffer) End() string {
s := pb.out.String()
pb.lk.Unlock()
return s
}
// NextCh returns the next input character to the expander.
func (pb *paramsBuffer) NextCh() (byte, error) {
return pb.buf.ReadByte()
}
// PutCh "emits" (rather schedules for output) a single byte character.
func (pb *paramsBuffer) PutCh(ch byte) {
pb.out.WriteByte(ch)
}
// PutString schedules a string for output.
func (pb *paramsBuffer) PutString(s string) {
pb.out.WriteString(s)
}
var pb = &paramsBuffer{}
// TParm takes a terminfo parameterized string, such as setaf or cup, and
// evaluates the string, and returns the result with the parameter
// applied.
func (t *Terminfo) TParm(s string, p ...int) string {
var stk stack
var a, b string
var ai, bi int
var ab bool
var dvars [26]string
var params [9]int
pb.Start(s)
// make sure we always have 9 parameters -- makes it easier
// later to skip checks
for i := 0; i < len(params) && i < len(p); i++ {
params[i] = p[i]
}
nest := 0
for {
ch, err := pb.NextCh()
if err != nil {
break
}
if ch != '%' {
pb.PutCh(ch)
continue
}
ch, err = pb.NextCh()
if err != nil {
// XXX Error
break
}
switch ch {
case '%': // quoted %
pb.PutCh(ch)
case 'i': // increment both parameters (ANSI cup support)
params[0]++
params[1]++
case 'c', 's':
// NB: these, and 'd' below are special cased for
// efficiency. They could be handled by the richer
// format support below, less efficiently.
a, stk = stk.Pop()
pb.PutString(a)
case 'd':
ai, stk = stk.PopInt()
pb.PutString(strconv.Itoa(ai))
case '0', '1', '2', '3', '4', 'x', 'X', 'o', ':':
// This is pretty suboptimal, but this is rarely used.
// None of the mainstream terminals use any of this,
// and it would surprise me if this code is ever
// executed outside of test cases.
f := "%"
if ch == ':' {
ch, _ = pb.NextCh()
}
f += string(ch)
for ch == '+' || ch == '-' || ch == '#' || ch == ' ' {
ch, _ = pb.NextCh()
f += string(ch)
}
for (ch >= '0' && ch <= '9') || ch == '.' {
ch, _ = pb.NextCh()
f += string(ch)
}
switch ch {
case 'd', 'x', 'X', 'o':
ai, stk = stk.PopInt()
pb.PutString(fmt.Sprintf(f, ai))
case 'c', 's':
a, stk = stk.Pop()
pb.PutString(fmt.Sprintf(f, a))
}
case 'p': // push parameter
ch, _ = pb.NextCh()
ai = int(ch - '1')
if ai >= 0 && ai < len(params) {
stk = stk.PushInt(params[ai])
} else {
stk = stk.PushInt(0)
}
case 'P': // pop & store variable
ch, _ = pb.NextCh()
if ch >= 'A' && ch <= 'Z' {
svars[int(ch-'A')], stk = stk.Pop()
} else if ch >= 'a' && ch <= 'z' {
dvars[int(ch-'a')], stk = stk.Pop()
}
case 'g': // recall & push variable
ch, _ = pb.NextCh()
if ch >= 'A' && ch <= 'Z' {
stk = stk.Push(svars[int(ch-'A')])
} else if ch >= 'a' && ch <= 'z' {
stk = stk.Push(dvars[int(ch-'a')])
}
case '\'': // push(char)
ch, _ = pb.NextCh()
pb.NextCh() // must be ' but we don't check
stk = stk.Push(string(ch))
case '{': // push(int)
ai = 0
ch, _ = pb.NextCh()
for ch >= '0' && ch <= '9' {
ai *= 10
ai += int(ch - '0')
ch, _ = pb.NextCh()
}
// ch must be '}' but no verification
stk = stk.PushInt(ai)
case 'l': // push(strlen(pop))
a, stk = stk.Pop()
stk = stk.PushInt(len(a))
case '+':
bi, stk = stk.PopInt()
ai, stk = stk.PopInt()
stk = stk.PushInt(ai + bi)
case '-':
bi, stk = stk.PopInt()
ai, stk = stk.PopInt()
stk = stk.PushInt(ai - bi)
case '*':
bi, stk = stk.PopInt()
ai, stk = stk.PopInt()
stk = stk.PushInt(ai * bi)
case '/':
bi, stk = stk.PopInt()
ai, stk = stk.PopInt()
if bi != 0 {
stk = stk.PushInt(ai / bi)
} else {
stk = stk.PushInt(0)
}
case 'm': // push(pop mod pop)
bi, stk = stk.PopInt()
ai, stk = stk.PopInt()
if bi != 0 {
stk = stk.PushInt(ai % bi)
} else {
stk = stk.PushInt(0)
}
case '&': // AND
bi, stk = stk.PopInt()
ai, stk = stk.PopInt()
stk = stk.PushInt(ai & bi)
case '|': // OR
bi, stk = stk.PopInt()
ai, stk = stk.PopInt()
stk = stk.PushInt(ai | bi)
case '^': // XOR
bi, stk = stk.PopInt()
ai, stk = stk.PopInt()
stk = stk.PushInt(ai ^ bi)
case '~': // bit complement
ai, stk = stk.PopInt()
stk = stk.PushInt(ai ^ -1)
case '!': // logical NOT
ai, stk = stk.PopInt()
stk = stk.PushBool(ai != 0)
case '=': // numeric compare or string compare
b, stk = stk.Pop()
a, stk = stk.Pop()
stk = stk.PushBool(a == b)
case '>': // greater than, numeric
bi, stk = stk.PopInt()
ai, stk = stk.PopInt()
stk = stk.PushBool(ai > bi)
case '<': // less than, numeric
bi, stk = stk.PopInt()
ai, stk = stk.PopInt()
stk = stk.PushBool(ai < bi)
case '?': // start conditional
case 't':
ab, stk = stk.PopBool()
if ab {
// just keep going
break
}
nest = 0
ifloop:
// this loop consumes everything until we hit our else,
// or the end of the conditional
for {
ch, err = pb.NextCh()
if err != nil {
break
}
if ch != '%' {
continue
}
ch, _ = pb.NextCh()
switch ch {
case ';':
if nest == 0 {
break ifloop
}
nest--
case '?':
nest++
case 'e':
if nest == 0 {
break ifloop
}
}
}
case 'e':
// if we got here, it means we didn't use the else
// in the 't' case above, and we should skip until
// the end of the conditional
nest = 0
elloop:
for {
ch, err = pb.NextCh()
if err != nil {
break
}
if ch != '%' {
continue
}
ch, _ = pb.NextCh()
switch ch {
case ';':
if nest == 0 {
break elloop
}
nest--
case '?':
nest++
}
}
case ';': // endif
}
}
return pb.End()
}
// TPuts emits the string to the writer, but expands inline padding
// indications (of the form $<[delay]> where [delay] is msec) to
// a suitable number of padding characters (usually null bytes) based
// upon the supplied baud. At high baud rates, more padding characters
// will be inserted. All Terminfo based strings should be emitted using
// this function.
func (t *Terminfo) TPuts(w io.Writer, s string, baud int) {
for {
beg := strings.Index(s, "$<")
if beg < 0 {
// Most strings don't need padding, which is good news!
io.WriteString(w, s)
return
}
io.WriteString(w, s[:beg])
s = s[beg+2:]
end := strings.Index(s, ">")
if end < 0 {
// unterminated.. just emit bytes unadulterated
io.WriteString(w, "$<"+s)
return
}
val := s[:end]
s = s[end+1:]
padus := 0
unit := 1000
dot := false
loop:
for i := range val {
switch val[i] {
case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
padus *= 10
padus += int(val[i] - '0')
if dot {
unit *= 10
}
case '.':
if !dot {
dot = true
} else {
break loop
}
default:
break loop
}
}
cnt := int(((baud / 8) * padus) / unit)
for cnt > 0 {
io.WriteString(w, t.PadChar)
cnt--
}
}
}
// TGoto returns a string suitable for addressing the cursor at the given
// row and column. The origin 0, 0 is in the upper left corner of the screen.
func (t *Terminfo) TGoto(col, row int) string {
return t.TParm(t.SetCursor, row, col)
}
// TColor returns a string corresponding to the given foreground and background
// colors. Either fg or bg can be set to -1 to elide.
func (t *Terminfo) TColor(fi, bi int) string {
rv := ""
// As a special case, we map bright colors to lower versions if the
// color table only holds 8. For the remaining 240 colors, the user
// is out of luck. Someday we could create a mapping table, but its
// not worth it.
if t.Colors == 8 {
if fi > 7 && fi < 16 {
fi -= 8
}
if bi > 7 && bi < 16 {
bi -= 8
}
}
if t.Colors > fi && fi >= 0 {
rv += t.TParm(t.SetFg, fi)
}
if t.Colors > bi && bi >= 0 {
rv += t.TParm(t.SetBg, bi)
}
return rv
}
var (
dblock sync.Mutex
terminfos = make(map[string]*Terminfo)
aliases = make(map[string]string)
)
// AddTerminfo can be called to register a new Terminfo entry.
func AddTerminfo(t *Terminfo) {
dblock.Lock()
terminfos[t.Name] = t
for _, x := range t.Aliases {
terminfos[x] = t
}
dblock.Unlock()
}
func loadFromFile(fname string, term string) (*Terminfo, error) {
var e error
var f io.ReadCloser
if f, e = os.Open(fname); e != nil {
return nil, e
}
defer f.Close()
if strings.HasSuffix(fname, ".gz") {
if f, e = gzip.NewReader(f); e != nil {
return nil, e
}
}
d := json.NewDecoder(f)
for {
t := &Terminfo{}
if e := d.Decode(t); e != nil {
if e == io.EOF {
return nil, ErrTermNotFound
}
return nil, e
}
if t.SetCursor == "" {
// This must be an alias record, return it.
return t, nil
}
if t.Name == term {
return t, nil
}
for _, a := range t.Aliases {
if a == term {
return t, nil
}
}
}
}
// LookupTerminfo attempts to find a definition for the named $TERM.
// It first looks in the builtin database, which should cover just about
// everyone. If it can't find one there, then it will attempt to read
// one from the JSON file located in either $TCELLDB, $HOME/.tcelldb
// or in this package's source directory as database.json).
func LookupTerminfo(name string) (*Terminfo, error) {
if name == "" {
// else on windows: index out of bounds
// on the name[0] reference below
return nil, ErrTermNotFound
}
dblock.Lock()
t := terminfos[name]
dblock.Unlock()
if t == nil {
var files []string
letter := fmt.Sprintf("%02x", name[0])
gzfile := path.Join(letter, name+".gz")
jsfile := path.Join(letter, name)
// Build up the search path. Old versions of tcell used a
// single database file, whereas the new ones locate them
// in JSON (optionally compressed) files.
//
// The search path looks like:
//
// $TCELLDB/x/xterm.gz
// $TCELLDB/x/xterm
// $TCELLDB
// $HOME/.tcelldb/x/xterm.gz
// $HOME/.tcelldb/x/xterm
// $HOME/.tcelldb
// $GOPATH/terminfo/database/x/xterm.gz
// $GOPATH/terminfo/database/x/xterm
//
if pth := os.Getenv("TCELLDB"); pth != "" {
files = append(files, path.Join(pth, gzfile))
files = append(files, path.Join(pth, jsfile))
files = append(files, pth)
}
if pth := os.Getenv("HOME"); pth != "" {
pth = path.Join(pth, ".tcelldb")
files = append(files, path.Join(pth, gzfile))
files = append(files, path.Join(pth, jsfile))
files = append(files, pth)
}
for _, pth := range strings.Split(os.Getenv("GOPATH"), string(os.PathListSeparator)) {
pth = path.Join(pth, "src", "github.com", "gdamore", "tcell", "terminfo", "database")
files = append(files, path.Join(pth, gzfile))
files = append(files, path.Join(pth, jsfile))
}
for _, fname := range files {
t, _ = loadFromFile(fname, name)
if t != nil {
break
}
}
if t != nil {
if t.Name != name {
// Check for a database loop (no infinite
// recursion).
dblock.Lock()
if aliases[name] != "" {
dblock.Unlock()
return nil, ErrTermNotFound
}
aliases[name] = t.Name
dblock.Unlock()
return LookupTerminfo(t.Name)
}
dblock.Lock()
terminfos[name] = t
dblock.Unlock()
}
}
if t == nil {
return nil, ErrTermNotFound
}
return t, nil
}

View File

@@ -16,6 +16,9 @@ color-link tabbar "#1D1F21,#C5C8C6"
color-link indent-char "#505050,#1D1F21"
color-link line-number "#656866,#232526"
color-link current-line-number "#656866,#1D1F21"
color-link diff-added "#00AF00"
color-link diff-modified "#FFAF00"
color-link diff-deleted "#D70000"
color-link gutter-error "#FF4444,#1D1F21"
color-link gutter-warning "#EEEE77,#1D1F21"
color-link cursor-line "#2D2F31"

View File

@@ -14,11 +14,14 @@ color-link underlined "underline 241,231"
color-link todo "246,231"
color-link statusline "241,254"
color-link tabbar "241,254"
color-link diff-added "34"
color-link diff-modified "214"
color-link diff-deleted "160"
color-link gutter-error "197,231"
color-link gutter-warning "134,231"
color-link line-number "246,254"
color-link cursor-line "254"
color-link color-column "254"
#No extended types (bool in C, &c.) and plain brackets
color-link type.extended "default"
color-link symbol.brackets "default"
color-link type.extended "241,231"
color-link symbol.brackets "241,231"

View File

@@ -33,6 +33,9 @@ color-link statusline "white,blue"
color-link tabbar "white,blue"
color-link current-line-number "red"
color-link current-line-number.scroller "red"
color-link diff-added "green"
color-link diff-modified "yellow"
color-link diff-deleted "red"
color-link gutter-error ",red"
color-link gutter-warning "red"
color-link color-column "cyan"

View File

@@ -1,37 +0,0 @@
#CaptainMcClellan's personal color scheme.
#Paper version
color-link default "black,white"
color-link comment "bold black"
color-link constant "cyan"
color-link constant.bool "bold cyan"
color-link constant.bool.true "bold green"
color-link constant.bool.false "bold red"
color-link constant.string "bold yellow"
color-link constant.string.url "underline blue, white"
color-link constant.number "constant"
color-link constant.specialChar "bold magenta"
color-link identifier "bold red"
color-link identifier.macro "bold red"
color-link identifier.var "bold blue"
color-link identifier.class "bold green"
color-link preproc "bold cyan"
color-link statement "bold yellow"
color-link symbol "red"
color-link symbol.brackets "blue"
color-link type "green"
color-link type.keyword "bold green"
color-link special "magenta"
color-link ignore "default"
color-link error ",brightred"
color-link todo "black,brightyellow"
color-link indent-char ",brightgreen"
color-link line-number "green"
color-link line-number.scrollbar "green"
color-link statusline "white,blue"
color-link tabbar "white,blue"
color-link current-line-number "red"
color-link current-line-number.scroller "red"
color-link gutter-error ",red"
color-link gutter-warning "red"
color-link color-column "cyan"
color-link underlined.url "underline blue, white"

View File

@@ -28,9 +28,12 @@ color-link statusline "#aaaaaa,#8a496b"
color-link tabbar "#aaaaaa,#8a496b"
color-link current-line-number "bold #e34234,#424549"
color-link current-line-number.scroller "red"
color-link diff-added "#00AF00"
color-link diff-modified "#FFAF00"
color-link diff-deleted "#D70000"
color-link gutter-error ",#e34234"
color-link gutter-warning "#e34234"
color-link color-column "#f26522"
color-link constant.bool "bold #55ffff"
color-link constant.bool.true "bold #85ff85"
color-link constant.bool.false "bold #ff8585"
color-link constant.bool.false "bold #ff8585"

View File

@@ -15,12 +15,15 @@ color-link todo "bold #D33682,#242424"
color-link statusline "#242424,#CCCCCC"
color-link tabbar "#242424,#CCCCCC"
color-link indent-char "#4F4F4F,#242424"
color-link line-number "#666666,#242424"
color-link line-number "#666666,#2C2C2C"
color-link current-line-number "#666666,#242424"
color-link diff-added "#00AF00"
color-link diff-modified "#FFAF00"
color-link diff-deleted "#D70000"
color-link gutter-error "#CB4B16,#242424"
color-link gutter-warning "#E6DB74,#242424"
color-link cursor-line "default,#2C2C2C"
color-link color-column "default,#2C2C2C"
color-link cursor-line "#2C2C2C"
color-link color-column "#2C2C2C"
#No extended types; Plain brackets.
color-link type.extended "default"
#color-link symbol.brackets "default"

View File

@@ -17,6 +17,9 @@ color-link tabbar "#282828,#F8F8F2"
color-link indent-char "#505050,#282828"
color-link line-number "#AAAAAA,#323232"
color-link current-line-number "#AAAAAA,#282828"
color-link diff-added "#00AF00"
color-link diff-modified "#FFAF00"
color-link diff-deleted "#D70000"
color-link gutter-error "#CB4B16,#282828"
color-link gutter-warning "#E6DB74,#282828"
color-link cursor-line "#323232"

View File

@@ -0,0 +1,34 @@
color-link color-column "#001e28"
color-link comment "#608b4e,#001e28"
color-link constant.bool "#fd971f,#001e28"
color-link constant "#fd971f,#001e28"
color-link constant.string "#a0f000,#001e28"
color-link constant.string.char "#a0f000,#001e28"
color-link constant.string.url "#a0f000,#001e28"
color-link current-line-number "bold #fd971f,#001e28"
color-link cursor-line "#001923"
color-link default "#ffffff,#001e28"
color-link diff-added "#00c8a0,#001e28"
color-link diff-modified "#fd971f,#001e28"
color-link diff-deleted "#cb4b16,#001e28"
color-link divider "#001e28,#d0d0d0"
color-link error "#cb4b16,#001e28"
color-link gutter-error "#cb4b16,#001e28"
color-link gutter-warning "#fce94f,#001e28"
color-link identifier "#00c8a0,#001e28"
color-link identifier.class "#00c8a0,#001e28"
color-link indent-char "#a0a0a0,#001e28"
color-link line-number "#a0a0a0,#001923"
color-link preproc "bold #5aaae6,#001e28"
color-link special "#a6e22e,#001e28"
color-link statement "bold #5aaae6,#001e28"
color-link statusline "#ffffff,#0078c8"
color-link symbol "#00c8a0,#001e28"
color-link symbol.brackets "#ffffff,#001e28"
color-link symbol.tag "bold #5aaae6,#001e28"
color-link tabbar "#001e28,#ffffff"
color-link todo "#fce94f,#001e28"
color-link type "bold #3cc83c,#001e28"
color-link type.keyword "bold #5aaae6,#001e28"
color-link type.extended "#ffffff,#001e28"
color-link underlined "#608b4e,#001e28"

View File

@@ -0,0 +1,34 @@
color-link color-column "#f0f0f0"
color-link comment "#3f7f5f,#f0f0f0"
color-link constant.bool "#641e00,#f0f0f0"
color-link constant "#641e00,#f0f0f0"
color-link constant.string "#0000ff,#f0f0f0"
color-link constant.string.char "#0000ff,#f0f0f0"
color-link constant.string.url "#0000ff,#f0f0f0"
color-link current-line-number "bold #004080,#f0f0f0"
color-link cursor-line "#e6e6e6"
color-link default "#000000,#f0f0f0"
color-link diff-added "#008040,#f0f0f0"
color-link diff-modified "#641e00,#f0f0f0"
color-link diff-deleted "#500000,#f0f0f0"
color-link divider "#f0f0f0,#004080"
color-link error "#500000,#f0f0f0"
color-link gutter-error "#500000,#f0f0f0"
color-link gutter-warning "#dcc800,#f0f0f0"
color-link identifier "bold #0078a0,#f0f0f0"
color-link identifier.class "bold #0078a0,#f0f0f0"
color-link indent-char "#404040,#f0f0f0"
color-link line-number "#404040,#e6e6e6"
color-link preproc "bold #780050,#f0f0f0"
color-link special "bold #0078a0,#f0f0f0"
color-link statement "bold #780050,#f0f0f0"
color-link statusline "#ffffff,#0078c8"
color-link symbol "bold #0078a0,#f0f0f0"
color-link symbol.brackets "#000000,#f0f0f0"
color-link symbol.tag "bold #780050,#f0f0f0"
color-link tabbar "#f0f0f0,#004080"
color-link todo "#dcc800,#f0f0f0"
color-link type "bold #004080,#f0f0f0"
color-link type.keyword "bold #780050,#f0f0f0"
color-link type.extended "#000000,#f0f0f0"
color-link underlined "#3f7f5f,#f0f0f0"

Some files were not shown because too many files have changed in this diff Show More