From 970aebf3bbcd252125330ecdef658bc4c959d4d8 Mon Sep 17 00:00:00 2001 From: Nicolai Date: Mon, 12 Dec 2016 22:19:03 +0100 Subject: [PATCH 01/89] Show dirs, set focus on new opened tab --- filetree.lua | 77 ++++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 71 insertions(+), 6 deletions(-) diff --git a/filetree.lua b/filetree.lua index e8d1d24..689ac06 100644 --- a/filetree.lua +++ b/filetree.lua @@ -1,33 +1,98 @@ -VERSION = "1.0.0" +VERSION = "1.1.2" treeView = nil +cwd = "." function OpenTree() local origNum = CurView().Num - CurView():VSplitIndex(NewBuffer("Filetree", ""), 0) + CurView():VSplitIndex(NewBuffer("", ""), 0) CurView().Width = 30 CurView().LockWidth = true tabs[curTab+1]:Resize() treeView = CurView() + RefreshTree() + SetFocus(origNum) +end + +function SetFocus(num) + tabs[curTab+1].CurView = num +end - treeView.Buf:Insert(Loc(0, 0), table.concat(scandir("."), "\n")) +function RefreshTree() + treeView.Buf:remove(treeView.Buf:Start(), treeView.Buf:End()) + treeView.Buf:Insert(Loc(0,0), table.concat(scandir(cwd), "\n")) + treeView.Buf.Settings["softwrap"] = false treeView.Buf.Settings["autosave"] = false treeView.Buf.IsModified = false +end + +-- When user press enter +function preInsertNewline(view) + if view == treeView then + local selected = view.Buf:Line(view.Cursor.Loc.Y) + if isDir(selected) then + cwd = cwd .. "/" .. selected + RefreshTree() + else + -- TODO: NewBuffer calls NewBufferFromString + -- ... so manually read file content: + local filename = cwd .. "/" .. selected + local filehandle = io.open(filename, "r") + if not filehandle then + TermMessage("Can't open file:", filename) + return false + end + local filecontent = filehandle:read("*all") + CurView():VSplitIndex(NewBuffer(filecontent, filename), 0) + tabs[curTab+1]:Resize() + SetFocus(CurView().Num) + end + return false + end + return true +end - tabs[curTab+1].CurView = origNum + 1 +--[[ allows for deleting files +function preDelete(view) + if view == treeView then + messenger:YesNoPrompt("Do you want to delete ...?") + return false + end + return true end +]]-- +-- don't prompt to save tree view +function preQuit(view) + if view == treeView then + view.Buf.IsModified = false + end + return true +end function scandir(directory) local i, t, popen = 0, {}, io.popen - local pfile = popen('ls -a "'..directory..'"') + local pfile = popen('ls -aF "'..directory..'"') for filename in pfile:lines() do + if i > 0 then + -- skip "." dir, (but keep "..") + t[i] = filename + end i = i + 1 - t[i] = filename end pfile:close() return t end +function isDir(path) + local pfile = io.popen('ls -adl "' .. cwd .. "/" .. path .. '"') + local status = false + if pfile:read(1) == "d" then + status = true + end + pfile:close() + return status +end + MakeCommand("tree", "filetree.OpenTree", 0) From 44e896726ffb212544b7ff8386bedbce8ec83f26 Mon Sep 17 00:00:00 2001 From: Nicolai Date: Mon, 12 Dec 2016 23:01:09 +0100 Subject: [PATCH 02/89] Better "select file" menu --- filetree.lua | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/filetree.lua b/filetree.lua index 689ac06..035b7b4 100644 --- a/filetree.lua +++ b/filetree.lua @@ -1,4 +1,4 @@ -VERSION = "1.1.2" +VERSION = "1.1.3" treeView = nil cwd = "." @@ -53,6 +53,20 @@ function preInsertNewline(view) return true end +-- don't use build-in view.Cursor:SelectLine() as it will copy to clipboard +function SelectLine(v) + local y = v.Cursor.Loc.Y + v.Cursor.CurSelection[1] = Loc(0, y) + v.Cursor.CurSelection[2] = Loc(30, y) -- TODO: 30 => v.Width +end + +function onCursorDown(view) + if view == treeView then SelectLine(view) end +end +function onCursorUp(view) + if view == treeView then SelectLine(view) end +end + --[[ allows for deleting files function preDelete(view) if view == treeView then From f45a8c3e87271c09b0f263c708552c06bd2ecaca Mon Sep 17 00:00:00 2001 From: Nicolai Date: Mon, 12 Dec 2016 23:09:03 +0100 Subject: [PATCH 03/89] Remove statusline from filetree --- filetree.lua | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/filetree.lua b/filetree.lua index 035b7b4..1df7b0d 100644 --- a/filetree.lua +++ b/filetree.lua @@ -1,4 +1,4 @@ -VERSION = "1.1.3" +VERSION = "1.1.4" treeView = nil cwd = "." @@ -24,6 +24,7 @@ function RefreshTree() treeView.Buf:Insert(Loc(0,0), table.concat(scandir(cwd), "\n")) treeView.Buf.Settings["softwrap"] = false treeView.Buf.Settings["autosave"] = false + treeView.Buf.Settings["statusline"] = false treeView.Buf.IsModified = false end From 7bcf2eb800e50230fcd42829823995a9e4b60c7e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicolai=20S=C3=B8borg?= Date: Mon, 12 Dec 2016 23:23:00 +0100 Subject: [PATCH 04/89] Add some text to README --- README.md | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 2944c05..22c37eb 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,15 @@ # Filetree Plugin -This is still a work in progress... +A simple plugin that allows for easy navigation of a file tree. + +Place this folder in `~/.config/micro/plugins/` and restart micro. + +Now it will be possible to open a navigation panel by running the command `tree` (ctrl + e). + +## Example +![filetree cli](https://i.imgur.com/gO5CnT4.png "Filetree CLI") + +## Known Issues + * Does not work on Windows + * Opening of (huge) files will be slow + From ef19390e27316dbfd2969e906b4a16ba2bb8aadf Mon Sep 17 00:00:00 2001 From: Nicolai Date: Tue, 13 Dec 2016 01:29:51 +0100 Subject: [PATCH 05/89] Support for Windows filesystems (limited) --- README.md | 4 ++-- filetree.lua | 42 ++++++++++++++++++++++++++++++++++-------- 2 files changed, 36 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 22c37eb..9bc080f 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,6 @@ Now it will be possible to open a navigation panel by running the command `tree` ![filetree cli](https://i.imgur.com/gO5CnT4.png "Filetree CLI") ## Known Issues - * Does not work on Windows + * Limited Windows support (can only read files from `C:`) * Opening of (huge) files will be slow - + diff --git a/filetree.lua b/filetree.lua index 1df7b0d..d584e4a 100644 --- a/filetree.lua +++ b/filetree.lua @@ -2,6 +2,9 @@ VERSION = "1.1.4" treeView = nil cwd = "." +if OS == "windows" then + cwd = "./" -- for some reason this works, but "C:" or "%CD%" doesn't +end function OpenTree() local origNum = CurView().Num @@ -38,10 +41,17 @@ function preInsertNewline(view) else -- TODO: NewBuffer calls NewBufferFromString -- ... so manually read file content: - local filename = cwd .. "/" .. selected + local filename + if OS == "windows" then + -- TODO: The weird "cwd = ./" hack above means we need to cut away "." + -- ... and set the drive letter manually: + filename = "C:" .. cwd:sub(2) .. "/" .. selected + else + filename = cwd .. "/" .. selected + end local filehandle = io.open(filename, "r") if not filehandle then - TermMessage("Can't open file:", filename) + messenger:Message("Can't open file:", filename) return false end local filecontent = filehandle:read("*all") @@ -88,21 +98,37 @@ end function scandir(directory) local i, t, popen = 0, {}, io.popen - local pfile = popen('ls -aF "'..directory..'"') - for filename in pfile:lines() do - if i > 0 then - -- skip "." dir, (but keep "..") + local pfile + if OS == "windows" then + pfile = popen('dir /a /b "'..directory..'"') + t[1] = ".." -- add ".." not shown in dir output + i = 2 + for filename in pfile:lines() do t[i] = filename + i = i + 1 + end + else + pfile = popen('ls -aF "'..directory..'"') + for filename in pfile:lines() do + if i > 0 then + -- skip "." dir, (but keep "..") + t[i] = filename + end + i = i + 1 end - i = i + 1 end pfile:close() return t end function isDir(path) - local pfile = io.popen('ls -adl "' .. cwd .. "/" .. path .. '"') local status = false + local pfile + if OS == "windows" then + pfile = io.popen('IF EXIST "'.. cwd .. "/" .. path .. '" ECHO d') + else + pfile = io.popen('ls -adl "' .. cwd .. "/" .. path .. '"') + end if pfile:read(1) == "d" then status = true end From cffd201f22a797a6bb2b9ec6b3c3e331f3a9ce52 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicolai=20S=C3=B8borg?= Date: Tue, 13 Dec 2016 01:32:02 +0100 Subject: [PATCH 06/89] Bump version number --- filetree.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/filetree.lua b/filetree.lua index d584e4a..db52037 100644 --- a/filetree.lua +++ b/filetree.lua @@ -1,4 +1,4 @@ -VERSION = "1.1.4" +VERSION = "1.2.0" treeView = nil cwd = "." From f3a04df9f7ee3b867a18a8d23b3d8f2e4a501541 Mon Sep 17 00:00:00 2001 From: Nicolai Date: Tue, 13 Dec 2016 13:58:31 +0100 Subject: [PATCH 07/89] Open files on right side, optimize RefreshTree() --- filetree.lua | 31 +++++++++++++++---------------- 1 file changed, 15 insertions(+), 16 deletions(-) diff --git a/filetree.lua b/filetree.lua index db52037..ee3c40e 100644 --- a/filetree.lua +++ b/filetree.lua @@ -1,4 +1,4 @@ -VERSION = "1.2.0" +VERSION = "1.2.1" treeView = nil cwd = "." @@ -7,15 +7,17 @@ if OS == "windows" then end function OpenTree() - local origNum = CurView().Num CurView():VSplitIndex(NewBuffer("", ""), 0) - CurView().Width = 30 - CurView().LockWidth = true - tabs[curTab+1]:Resize() - treeView = CurView() + treeView.Width = 20 + treeView.LockWidth = true + treeView.Buf.Settings["ruler"] = false + -- treeView.Buf.Settings["softwrap"] = false + treeView.Buf.Settings["autosave"] = false + treeView.Buf.Settings["statusline"] = false + + tabs[curTab+1]:Resize() RefreshTree() - SetFocus(origNum) end function SetFocus(num) @@ -25,17 +27,15 @@ end function RefreshTree() treeView.Buf:remove(treeView.Buf:Start(), treeView.Buf:End()) treeView.Buf:Insert(Loc(0,0), table.concat(scandir(cwd), "\n")) - treeView.Buf.Settings["softwrap"] = false - treeView.Buf.Settings["autosave"] = false - treeView.Buf.Settings["statusline"] = false - treeView.Buf.IsModified = false end -- When user press enter function preInsertNewline(view) if view == treeView then local selected = view.Buf:Line(view.Cursor.Loc.Y) - if isDir(selected) then + if view.Cursor.Loc.Y == 0 then + return false + elseif isDir(selected) then cwd = cwd .. "/" .. selected RefreshTree() else @@ -49,15 +49,14 @@ function preInsertNewline(view) else filename = cwd .. "/" .. selected end - local filehandle = io.open(filename, "r") + local filehandle = io.open(filename) if not filehandle then messenger:Message("Can't open file:", filename) return false end local filecontent = filehandle:read("*all") - CurView():VSplitIndex(NewBuffer(filecontent, filename), 0) + CurView():VSplitIndex(NewBuffer(filecontent, filename), 1) tabs[curTab+1]:Resize() - SetFocus(CurView().Num) end return false end @@ -68,7 +67,7 @@ end function SelectLine(v) local y = v.Cursor.Loc.Y v.Cursor.CurSelection[1] = Loc(0, y) - v.Cursor.CurSelection[2] = Loc(30, y) -- TODO: 30 => v.Width + v.Cursor.CurSelection[2] = Loc(v.Width, y) end function onCursorDown(view) From e52fb67751abc635c6376ffc35c9e3d21744a3fb Mon Sep 17 00:00:00 2001 From: Nicolai Date: Tue, 13 Dec 2016 14:46:28 +0100 Subject: [PATCH 08/89] More refactor, show path in filetree --- filetree.lua | 46 ++++++++++++++++++++-------------------------- 1 file changed, 20 insertions(+), 26 deletions(-) diff --git a/filetree.lua b/filetree.lua index ee3c40e..6317f38 100644 --- a/filetree.lua +++ b/filetree.lua @@ -1,4 +1,4 @@ -VERSION = "1.2.1" +VERSION = "1.2.2" treeView = nil cwd = "." @@ -20,10 +20,6 @@ function OpenTree() RefreshTree() end -function SetFocus(num) - tabs[curTab+1].CurView = num -end - function RefreshTree() treeView.Buf:remove(treeView.Buf:Start(), treeView.Buf:End()) treeView.Buf:Insert(Loc(0,0), table.concat(scandir(cwd), "\n")) @@ -70,12 +66,17 @@ function SelectLine(v) v.Cursor.CurSelection[2] = Loc(v.Width, y) end -function onCursorDown(view) - if view == treeView then SelectLine(view) end -end -function onCursorUp(view) - if view == treeView then SelectLine(view) end -end +-- disallow selecting topmost line in treeview: +function preCursorUp(view) + if view == treeView then + if view.Cursor.Loc.Y == 1 then + return false +end end end + +-- 'beautiful' file selection: +function onCursorDown(view) if view == treeView then SelectLine(view) end end +function onCursorUp(view) if view == treeView then SelectLine(view) end end + --[[ allows for deleting files function preDelete(view) @@ -96,25 +97,18 @@ function preQuit(view) end function scandir(directory) - local i, t, popen = 0, {}, io.popen + local i, t, popen = 3, {}, io.popen local pfile + t[1] = cwd + t[2] = ".." if OS == "windows" then pfile = popen('dir /a /b "'..directory..'"') - t[1] = ".." -- add ".." not shown in dir output - i = 2 - for filename in pfile:lines() do - t[i] = filename - i = i + 1 - end else - pfile = popen('ls -aF "'..directory..'"') - for filename in pfile:lines() do - if i > 0 then - -- skip "." dir, (but keep "..") - t[i] = filename - end - i = i + 1 - end + pfile = popen('ls -AF "'..directory..'"') + end + for filename in pfile:lines() do + t[i] = filename + i = i + 1 end pfile:close() return t From dc3906b5220bf2c60644f3cd2f84bfedd5a094fc Mon Sep 17 00:00:00 2001 From: Nicolai Date: Tue, 13 Dec 2016 20:21:48 +0100 Subject: [PATCH 09/89] Try to do a better job of Windows path handeling --- filetree.lua | 50 ++++++++++++++++++++------------------------------ 1 file changed, 20 insertions(+), 30 deletions(-) diff --git a/filetree.lua b/filetree.lua index 6317f38..a5eb239 100644 --- a/filetree.lua +++ b/filetree.lua @@ -1,26 +1,24 @@ -VERSION = "1.2.2" +VERSION = "1.2.3" treeView = nil -cwd = "." -if OS == "windows" then - cwd = "./" -- for some reason this works, but "C:" or "%CD%" doesn't -end +cwd = DirectoryName(".") +driveLetter = "C:\\" function OpenTree() CurView():VSplitIndex(NewBuffer("", ""), 0) treeView = CurView() - treeView.Width = 20 + treeView.Width = 30 treeView.LockWidth = true - treeView.Buf.Settings["ruler"] = false - -- treeView.Buf.Settings["softwrap"] = false - treeView.Buf.Settings["autosave"] = false - treeView.Buf.Settings["statusline"] = false - + SetLocalOption("ruler", "false", treeView) + SetLocalOption("softwrap", "true", treeView) + SetLocalOption("autosave", "false", treeView) + SetLocalOption("statusline", "false", treeView) tabs[curTab+1]:Resize() RefreshTree() end function RefreshTree() + --local sep = (OS == "windows" and "\r\n" or "\n") treeView.Buf:remove(treeView.Buf:Start(), treeView.Buf:End()) treeView.Buf:Insert(Loc(0,0), table.concat(scandir(cwd), "\n")) end @@ -30,26 +28,16 @@ function preInsertNewline(view) if view == treeView then local selected = view.Buf:Line(view.Cursor.Loc.Y) if view.Cursor.Loc.Y == 0 then - return false + return false -- topmost line is cwd, so disallowing selecting it elseif isDir(selected) then - cwd = cwd .. "/" .. selected + cwd = JoinPaths(cwd, selected) RefreshTree() else + local filename = JoinPaths(cwd, selected) + if OS == "windows" then filename = driveLetter .. filename end -- TODO: NewBuffer calls NewBufferFromString -- ... so manually read file content: - local filename - if OS == "windows" then - -- TODO: The weird "cwd = ./" hack above means we need to cut away "." - -- ... and set the drive letter manually: - filename = "C:" .. cwd:sub(2) .. "/" .. selected - else - filename = cwd .. "/" .. selected - end - local filehandle = io.open(filename) - if not filehandle then - messenger:Message("Can't open file:", filename) - return false - end + local filehandle = assert(io.open(filename)) local filecontent = filehandle:read("*all") CurView():VSplitIndex(NewBuffer(filecontent, filename), 1) tabs[curTab+1]:Resize() @@ -59,6 +47,7 @@ function preInsertNewline(view) return true end + -- don't use build-in view.Cursor:SelectLine() as it will copy to clipboard function SelectLine(v) local y = v.Cursor.Loc.Y @@ -99,7 +88,7 @@ end function scandir(directory) local i, t, popen = 3, {}, io.popen local pfile - t[1] = cwd + t[1] = cwd -- show path t[2] = ".." if OS == "windows" then pfile = popen('dir /a /b "'..directory..'"') @@ -107,7 +96,7 @@ function scandir(directory) pfile = popen('ls -AF "'..directory..'"') end for filename in pfile:lines() do - t[i] = filename + t[i] = tostring(filename) i = i + 1 end pfile:close() @@ -118,9 +107,10 @@ function isDir(path) local status = false local pfile if OS == "windows" then - pfile = io.popen('IF EXIST "'.. cwd .. "/" .. path .. '" ECHO d') + -- TODO: This should work, but doesn't: github.com/yuin/gopher-lua/issue/90 + pfile = io.popen('IF EXIST "' .. driveLetter .. JoinPaths(cwd, path) .. '\\*" (ECHO d) ELSE (ECHO -)') else - pfile = io.popen('ls -adl "' .. cwd .. "/" .. path .. '"') + pfile = io.popen('ls -adl "' .. JoinPaths(cwd, path) .. '"') end if pfile:read(1) == "d" then status = true From 48dff2c285152411a5f7c40988722baea6af5e49 Mon Sep 17 00:00:00 2001 From: Nicolai Date: Wed, 14 Dec 2016 00:37:46 +0100 Subject: [PATCH 10/89] Fix: Linux filepath shouldn't contain a star --- README.md | 30 ++++++++++++++++++++++++++++-- filetree.lua | 6 +++--- 2 files changed, 31 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 9bc080f..3bfc9b6 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,33 @@ Now it will be possible to open a navigation panel by running the command `tree` ## Example ![filetree cli](https://i.imgur.com/gO5CnT4.png "Filetree CLI") + ## Known Issues - * Limited Windows support (can only read files from `C:`) - * Opening of (huge) files will be slow +* Limited Windows support (can only read files from `C:`) + +* Opening of (huge) files will be slow + + +## Requirements + +To better support all filesystems, this plugin uses `LuaFileSystem`. + +Installing on windows: + +1 Install LuaRocks + + * Get newest ("win32.zip" package here)[https://keplerproject.github.io/luarocks/releases/]. + + * Unpack and run `install.bat /L` + + * Go to the installed LuaRocks dir, e.g. `C:\Program Files (x86)\LuaRocks\` and run `luarocks.bat install luafilesystem` + + * e.g. `"C:\Program Files (x86)\LuaRocks\luarocks.bat" install luafilesystem` + + * If you get compiling errors, then install Visual Studio and open `Developer Command Prompt for Visual Studio` + (https://msdn.microsoft.com/en-us/library/ms229859(v=vs.110).aspx) and run the above command as admin. + + * + +2 TODO diff --git a/filetree.lua b/filetree.lua index a5eb239..09cb94b 100644 --- a/filetree.lua +++ b/filetree.lua @@ -1,4 +1,4 @@ -VERSION = "1.2.3" +VERSION = "1.2.4" treeView = nil cwd = DirectoryName(".") @@ -88,12 +88,12 @@ end function scandir(directory) local i, t, popen = 3, {}, io.popen local pfile - t[1] = cwd -- show path + t[1] = cwd t[2] = ".." if OS == "windows" then pfile = popen('dir /a /b "'..directory..'"') else - pfile = popen('ls -AF "'..directory..'"') + pfile = popen('ls -Ap "'..directory..'"') end for filename in pfile:lines() do t[i] = tostring(filename) From b4a396004cbac8bf89462523bc48f79a4a8af68b Mon Sep 17 00:00:00 2001 From: Nicolai Date: Wed, 14 Dec 2016 00:55:42 +0100 Subject: [PATCH 11/89] Fix wrong readme ... lfs might not be an option :-1: --- README.md | 31 +++++-------------------------- 1 file changed, 5 insertions(+), 26 deletions(-) diff --git a/README.md b/README.md index 3bfc9b6..e0ff7d4 100644 --- a/README.md +++ b/README.md @@ -6,36 +6,15 @@ Place this folder in `~/.config/micro/plugins/` and restart micro. Now it will be possible to open a navigation panel by running the command `tree` (ctrl + e). + ## Example -![filetree cli](https://i.imgur.com/gO5CnT4.png "Filetree CLI") + +![filetree cli](https://i.imgur.com/YdBtZx1.png "Filetree CLI") ## Known Issues -* Limited Windows support (can only read files from `C:`) +* Very limited Windows support (also; can only read files from `C:`) + See github.com/yuin/gopher-lua/issue/90 * Opening of (huge) files will be slow - - -## Requirements - -To better support all filesystems, this plugin uses `LuaFileSystem`. - -Installing on windows: - -1 Install LuaRocks - - * Get newest ("win32.zip" package here)[https://keplerproject.github.io/luarocks/releases/]. - - * Unpack and run `install.bat /L` - - * Go to the installed LuaRocks dir, e.g. `C:\Program Files (x86)\LuaRocks\` and run `luarocks.bat install luafilesystem` - - * e.g. `"C:\Program Files (x86)\LuaRocks\luarocks.bat" install luafilesystem` - - * If you get compiling errors, then install Visual Studio and open `Developer Command Prompt for Visual Studio` - (https://msdn.microsoft.com/en-us/library/ms229859(v=vs.110).aspx) and run the above command as admin. - - * - -2 TODO From 0d3f484c64f8adc314c24045cc3d10cf78f2f207 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicolai=20S=C3=B8borg?= Date: Thu, 15 Dec 2016 00:35:07 +0100 Subject: [PATCH 12/89] Fix windows "isDir" path detection --- filetree.lua | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/filetree.lua b/filetree.lua index 09cb94b..b4a5f81 100644 --- a/filetree.lua +++ b/filetree.lua @@ -1,4 +1,4 @@ -VERSION = "1.2.4" +VERSION = "1.2.5" treeView = nil cwd = DirectoryName(".") @@ -107,8 +107,7 @@ function isDir(path) local status = false local pfile if OS == "windows" then - -- TODO: This should work, but doesn't: github.com/yuin/gopher-lua/issue/90 - pfile = io.popen('IF EXIST "' .. driveLetter .. JoinPaths(cwd, path) .. '\\*" (ECHO d) ELSE (ECHO -)') + pfile = io.popen('IF EXIST ' .. driveLetter .. JoinPaths(cwd, path) .. '/* (ECHO d) ELSE (ECHO -)') else pfile = io.popen('ls -adl "' .. JoinPaths(cwd, path) .. '"') end From 8a537916ae8d752bfabfad6c3d4b0ef914fea209 Mon Sep 17 00:00:00 2001 From: Nicolai Date: Sun, 18 Dec 2016 00:50:58 +0100 Subject: [PATCH 13/89] Fix: slow opening of large files + some refactoring --- README.md | 4 +--- filetree.lua | 21 +++++++++------------ 2 files changed, 10 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index e0ff7d4..36cada8 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,5 @@ Now it will be possible to open a navigation panel by running the command `tree` ## Known Issues -* Very limited Windows support (also; can only read files from `C:`) - See github.com/yuin/gopher-lua/issue/90 +* Limited Windows support (also; can only read files from `C:`) -* Opening of (huge) files will be slow diff --git a/filetree.lua b/filetree.lua index b4a5f81..8276b72 100644 --- a/filetree.lua +++ b/filetree.lua @@ -1,8 +1,9 @@ -VERSION = "1.2.5" +VERSION = "1.2.6" treeView = nil cwd = DirectoryName(".") driveLetter = "C:\\" +isWin = (OS == "windows") function OpenTree() CurView():VSplitIndex(NewBuffer("", ""), 0) @@ -18,7 +19,6 @@ function OpenTree() end function RefreshTree() - --local sep = (OS == "windows" and "\r\n" or "\n") treeView.Buf:remove(treeView.Buf:Start(), treeView.Buf:End()) treeView.Buf:Insert(Loc(0,0), table.concat(scandir(cwd), "\n")) end @@ -34,12 +34,9 @@ function preInsertNewline(view) RefreshTree() else local filename = JoinPaths(cwd, selected) - if OS == "windows" then filename = driveLetter .. filename end - -- TODO: NewBuffer calls NewBufferFromString - -- ... so manually read file content: - local filehandle = assert(io.open(filename)) - local filecontent = filehandle:read("*all") - CurView():VSplitIndex(NewBuffer(filecontent, filename), 1) + if isWin then filename = driveLetter .. filename end + CurView():VSplitIndex(NewBuffer("", filename), 1) + CurView():ReOpen() tabs[curTab+1]:Resize() end return false @@ -88,15 +85,15 @@ end function scandir(directory) local i, t, popen = 3, {}, io.popen local pfile - t[1] = cwd + t[1] = (isWin and driveLetter or "") .. cwd t[2] = ".." - if OS == "windows" then + if isWin then pfile = popen('dir /a /b "'..directory..'"') else pfile = popen('ls -Ap "'..directory..'"') end for filename in pfile:lines() do - t[i] = tostring(filename) + t[i] = filename i = i + 1 end pfile:close() @@ -106,7 +103,7 @@ end function isDir(path) local status = false local pfile - if OS == "windows" then + if isWin then pfile = io.popen('IF EXIST ' .. driveLetter .. JoinPaths(cwd, path) .. '/* (ECHO d) ELSE (ECHO -)') else pfile = io.popen('ls -adl "' .. JoinPaths(cwd, path) .. '"') From 98449eb8468fa5fafbbee749e74cc96bc782ab6f Mon Sep 17 00:00:00 2001 From: Nicolai Date: Mon, 26 Dec 2016 21:01:30 +0100 Subject: [PATCH 14/89] Better filetree styling --- README.md | 2 +- filetree.lua | 7 ++++--- syntax.micro | 3 +++ 3 files changed, 8 insertions(+), 4 deletions(-) create mode 100644 syntax.micro diff --git a/README.md b/README.md index 36cada8..bbeb37d 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ Now it will be possible to open a navigation panel by running the command `tree` ## Example -![filetree cli](https://i.imgur.com/YdBtZx1.png "Filetree CLI") +![filetree cli](https://i.imgur.com/MBou7Hb.png "Filetree CLI") ## Known Issues diff --git a/filetree.lua b/filetree.lua index bcf961a..9b25ebe 100644 --- a/filetree.lua +++ b/filetree.lua @@ -1,4 +1,4 @@ -VERSION = "1.2.6" +VERSION = "1.2.7" treeView = nil cwd = DirectoryName(".") @@ -20,13 +20,13 @@ end function RefreshTree() treeView.Buf:remove(treeView.Buf:Start(), treeView.Buf:End()) - treeView.Buf:Insert(Loc(0,0), table.concat(scandir(cwd), "\n")) + treeView.Buf:Insert(Loc(0,0), table.concat(scandir(cwd), "\n ")) end -- When user press enter function preInsertNewline(view) if view == treeView then - local selected = view.Buf:Line(view.Cursor.Loc.Y) + local selected = (view.Buf:Line(view.Cursor.Loc.Y)):sub(2) if view.Cursor.Loc.Y == 0 then return false -- topmost line is cwd, so disallowing selecting it elseif isDir(selected) then @@ -115,3 +115,4 @@ function isDir(path) end MakeCommand("tree", "filetree.OpenTree", 0) +AddRuntimeFile("filetree", "syntax", "syntax.micro") diff --git a/syntax.micro b/syntax.micro new file mode 100644 index 0000000..12c0a0c --- /dev/null +++ b/syntax.micro @@ -0,0 +1,3 @@ +syntax "filetree-plugin" "^$" + +color special "\.\.|/" From 27c66343547e52d6eebdf22382a4b7cef1799619 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicolai=20S=C3=B8borg?= Date: Mon, 30 Jan 2017 21:21:40 +0100 Subject: [PATCH 15/89] Add deletion option, bump to version 1.3.0, refactor --- filetree.lua | 79 ++++++++++++++++++++++++++++++++-------------------- 1 file changed, 49 insertions(+), 30 deletions(-) diff --git a/filetree.lua b/filetree.lua index 9b25ebe..097b9d2 100644 --- a/filetree.lua +++ b/filetree.lua @@ -1,4 +1,4 @@ -VERSION = "1.2.7" +VERSION = "1.3.0" treeView = nil cwd = DirectoryName(".") @@ -13,7 +13,8 @@ function OpenTree() SetLocalOption("ruler", "false", treeView) SetLocalOption("softwrap", "true", treeView) SetLocalOption("autosave", "false", treeView) - SetLocalOption("statusline", "false", treeView) + SetLocalOption("statusline", "false", treeView) + --SetLocalOption("readonly", "true", treeView) tabs[curTab+1]:Resize() RefreshTree() end @@ -23,10 +24,15 @@ function RefreshTree() treeView.Buf:Insert(Loc(0,0), table.concat(scandir(cwd), "\n ")) end +-- returns currently selected line in treeView +function getSelection() + return (treeView.Buf:Line(treeView.Cursor.Loc.Y)):sub(2) +end + -- When user press enter function preInsertNewline(view) if view == treeView then - local selected = (view.Buf:Line(view.Cursor.Loc.Y)):sub(2) + local selected = getSelection() if view.Cursor.Loc.Y == 0 then return false -- topmost line is cwd, so disallowing selecting it elseif isDir(selected) then @@ -44,13 +50,6 @@ function preInsertNewline(view) return true end --- don't use build-in view.Cursor:SelectLine() as it will copy to clipboard -function SelectLine(v) - local y = v.Cursor.Loc.Y - v.Cursor.CurSelection[1] = Loc(0, y) - v.Cursor.CurSelection[2] = Loc(v.Width, y) -end - -- disallow selecting topmost line in treeview: function preCursorUp(view) if view == treeView then @@ -58,20 +57,42 @@ function preCursorUp(view) return false end end end --- 'beautiful' file selection: -function onCursorDown(view) if view == treeView then SelectLine(view) end end -function onCursorUp(view) if view == treeView then SelectLine(view) end end +-- don't use build-in view.Cursor:SelectLine() as it will copy to clipboard (in old versions of Micro) +function SelectLineInTree(v) + if v == treeView then + local y = v.Cursor.Loc.Y + v.Cursor.CurSelection[1] = Loc(0, y) + v.Cursor.CurSelection[2] = Loc(v.Width, y) + end +end +-- 'beautiful' file selection: +function onCursorDown(view) SelectLineInTree(view) end +function onCursorUp(view) SelectLineInTree(view) end ---[[ allows for deleting files +-- allows for deleting files function preDelete(view) if view == treeView then - messenger:YesNoPrompt("Do you want to delete ...?") + local selected = getSelection() + if selected == ".." then return false end + local question, command + if isDir(selected) then + question = "Do you want to delete dir '"..selected.."' ?" + command = isWin and "del /S /Q" or "rm -r" + else + question = "Do you want to delete file '"..selected.."' ?" + command = isWin and "del" or "rm -I" + end + command = command .. " " .. (isWin and driveLetter or "") .. JoinPaths(cwd, selected) + local yes, cancel = messenger:YesNoPrompt(question) + if not cancel and yes then + io.popen(command .. " " .. selected):close() + RefreshTree() + end return false end return true end -]]-- -- don't prompt to save tree view function preQuit(view) @@ -82,36 +103,34 @@ function preQuit(view) end function scandir(directory) - local i, t, popen = 3, {}, io.popen - local pfile + local i, t, proc = 3, {}, nil t[1] = (isWin and driveLetter or "") .. cwd t[2] = ".." if isWin then - pfile = popen('dir /a /b "'..directory..'"') + proc = io.popen('dir /a /b "'..directory..'"') else - pfile = popen('ls -Ap "'..directory..'"') + proc = io.popen('ls -Ap "'..directory..'"') end - for filename in pfile:lines() do + for filename in proc:lines() do t[i] = filename i = i + 1 end - pfile:close() + proc:close() return t end function isDir(path) - local status = false - local pfile + local dir, proc = false, nil if isWin then - pfile = io.popen('IF EXIST ' .. driveLetter .. JoinPaths(cwd, path) .. '/* (ECHO d) ELSE (ECHO -)') + proc = io.popen('IF EXIST ' .. driveLetter .. JoinPaths(cwd, path) .. '/* (ECHO d) ELSE (ECHO -)') else - pfile = io.popen('ls -adl "' .. JoinPaths(cwd, path) .. '"') + proc = io.popen('ls -adl "' .. JoinPaths(cwd, path) .. '"') end - if pfile:read(1) == "d" then - status = true + if proc:read(1) == "d" then + dir = true end - pfile:close() - return status + proc:close() + return dir end MakeCommand("tree", "filetree.OpenTree", 0) From 982ecbebe86b427c6d5e955e40f1a74cd915aa8d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicolai=20S=C3=B8borg?= Date: Mon, 30 Jan 2017 21:23:35 +0100 Subject: [PATCH 16/89] Add license and repo.json --- LICENSE | 21 +++++++++++++++++++++ repo.json | 14 ++++++++++++++ 2 files changed, 35 insertions(+) create mode 100644 LICENSE create mode 100644 repo.json diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..ae6fb2f --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2017 Nicolai Søborg + +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. diff --git a/repo.json b/repo.json new file mode 100644 index 0000000..f3eb738 --- /dev/null +++ b/repo.json @@ -0,0 +1,14 @@ +[{ + "Name": "filetree", + "Description": "File manager for Micro", + "Tags": ["filetree", "file", "manager"], + "Versions": [ + { + "Version": "1.3.0", + "Url": "https://github.com/NicolaiSoeborg/filetree-plugin/archive/v1.3.0.zip", + "Require": { + "micro": ">=1.0.0" + } + } + ] +}] From d3c8c54231e36ee769eff834415fa248cb476e4d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicolai=20S=C3=B8borg?= Date: Wed, 15 Feb 2017 00:59:32 +0100 Subject: [PATCH 17/89] Fix error when deleting files. --- README.md | 9 +++++++-- filetree.lua | 5 +++-- repo.json | 4 ++-- 3 files changed, 12 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index bbeb37d..b1fda5e 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,8 @@ A simple plugin that allows for easy navigation of a file tree. -Place this folder in `~/.config/micro/plugins/` and restart micro. +Place this folder in `~/.config/micro/plugins/` and restart micro: +> git clone https://github.com/NicolaiSoeborg/filetree-plugin.git ~/.config/micro/plugins/filetree Now it will be possible to open a navigation panel by running the command `tree` (ctrl + e). @@ -11,8 +12,12 @@ Now it will be possible to open a navigation panel by running the command `tree` ![filetree cli](https://i.imgur.com/MBou7Hb.png "Filetree CLI") +## Issues -## Known Issues +Please use the issue tracker to fill issues or feature requests! + + +### Known Issues * Limited Windows support (also; can only read files from `C:`) diff --git a/filetree.lua b/filetree.lua index 097b9d2..82e7b2c 100644 --- a/filetree.lua +++ b/filetree.lua @@ -1,4 +1,4 @@ -VERSION = "1.3.0" +VERSION = "1.3.1" treeView = nil cwd = DirectoryName(".") @@ -84,9 +84,10 @@ function preDelete(view) command = isWin and "del" or "rm -I" end command = command .. " " .. (isWin and driveLetter or "") .. JoinPaths(cwd, selected) + local yes, cancel = messenger:YesNoPrompt(question) if not cancel and yes then - io.popen(command .. " " .. selected):close() + os.execute(command) RefreshTree() end return false diff --git a/repo.json b/repo.json index f3eb738..648ad3f 100644 --- a/repo.json +++ b/repo.json @@ -4,8 +4,8 @@ "Tags": ["filetree", "file", "manager"], "Versions": [ { - "Version": "1.3.0", - "Url": "https://github.com/NicolaiSoeborg/filetree-plugin/archive/v1.3.0.zip", + "Version": "1.3.1", + "Url": "https://github.com/NicolaiSoeborg/filetree-plugin/archive/v1.3.1.zip", "Require": { "micro": ">=1.0.0" } From 2542bae6dfb33ae5eba76c309334cb888046238e Mon Sep 17 00:00:00 2001 From: Nicolai Date: Sun, 19 Feb 2017 23:29:19 +0100 Subject: [PATCH 18/89] Remove messenger prompt on deletion --- README.md | 9 +++++++-- filetree.lua | 33 ++++++++++++++++++--------------- 2 files changed, 25 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index b1fda5e..90115d2 100644 --- a/README.md +++ b/README.md @@ -5,8 +5,13 @@ A simple plugin that allows for easy navigation of a file tree. Place this folder in `~/.config/micro/plugins/` and restart micro: > git clone https://github.com/NicolaiSoeborg/filetree-plugin.git ~/.config/micro/plugins/filetree -Now it will be possible to open a navigation panel by running the command `tree` (ctrl + e). - +Now it will be possible to open a navigation panel by running +the command `tree` (ctrl + e) or creating a keybinding like so: +``` +{ + "Ctrl-E": "filetree.OpenTree" +} +``` ## Example diff --git a/filetree.lua b/filetree.lua index 82e7b2c..f85ce2e 100644 --- a/filetree.lua +++ b/filetree.lua @@ -1,4 +1,4 @@ -VERSION = "1.3.1" +VERSION = "1.3.2" treeView = nil cwd = DirectoryName(".") @@ -14,14 +14,14 @@ function OpenTree() SetLocalOption("softwrap", "true", treeView) SetLocalOption("autosave", "false", treeView) SetLocalOption("statusline", "false", treeView) - --SetLocalOption("readonly", "true", treeView) + SetLocalOption("readonly", "true", treeView) tabs[curTab+1]:Resize() - RefreshTree() + refreshTree() end -function RefreshTree() +function refreshTree() treeView.Buf:remove(treeView.Buf:Start(), treeView.Buf:End()) - treeView.Buf:Insert(Loc(0,0), table.concat(scandir(cwd), "\n ")) + treeView.Buf:Insert(Loc(0,0), table.concat(scanDir(cwd), "\n ")) end -- returns currently selected line in treeView @@ -37,7 +37,7 @@ function preInsertNewline(view) return false -- topmost line is cwd, so disallowing selecting it elseif isDir(selected) then cwd = JoinPaths(cwd, selected) - RefreshTree() + refreshTree() else local filename = JoinPaths(cwd, selected) if isWin then filename = driveLetter .. filename end @@ -58,7 +58,7 @@ function preCursorUp(view) end end end -- don't use build-in view.Cursor:SelectLine() as it will copy to clipboard (in old versions of Micro) -function SelectLineInTree(v) +function selectLineInTree(v) if v == treeView then local y = v.Cursor.Loc.Y v.Cursor.CurSelection[1] = Loc(0, y) @@ -67,29 +67,32 @@ function SelectLineInTree(v) end -- 'beautiful' file selection: -function onCursorDown(view) SelectLineInTree(view) end -function onCursorUp(view) SelectLineInTree(view) end +function onCursorDown(view) selectLineInTree(view) end +function onCursorUp(view) selectLineInTree(view) end -- allows for deleting files function preDelete(view) if view == treeView then local selected = getSelection() if selected == ".." then return false end - local question, command + local type, command if isDir(selected) then - question = "Do you want to delete dir '"..selected.."' ?" + type = "dir" command = isWin and "del /S /Q" or "rm -r" else - question = "Do you want to delete file '"..selected.."' ?" + type = "file" command = isWin and "del" or "rm -I" end command = command .. " " .. (isWin and driveLetter or "") .. JoinPaths(cwd, selected) - local yes, cancel = messenger:YesNoPrompt(question) + local yes, cancel = messenger:YesNoPrompt("Do you want to delete " .. type .. " '" .. selected .. "'? ") if not cancel and yes then os.execute(command) - RefreshTree() + refreshTree() end + -- Clears messenger: + messenger:Reset() + messenger:Clear() return false end return true @@ -103,7 +106,7 @@ function preQuit(view) return true end -function scandir(directory) +function scanDir(directory) local i, t, proc = 3, {}, nil t[1] = (isWin and driveLetter or "") .. cwd t[2] = ".." From 9312f6f10745b7f5cdbb0824b1bb19805478f832 Mon Sep 17 00:00:00 2001 From: Nicolai Date: Thu, 9 Mar 2017 16:29:38 +0100 Subject: [PATCH 19/89] Add ToggleTree, rename 'filetree' to 'filemanager' --- README.md | 9 +++++---- filetree.lua => filemanager.lua | 27 +++++++++++++++++++++------ repo.json | 8 ++++---- 3 files changed, 30 insertions(+), 14 deletions(-) rename filetree.lua => filemanager.lua (88%) diff --git a/README.md b/README.md index 90115d2..6f7d7b7 100644 --- a/README.md +++ b/README.md @@ -3,19 +3,20 @@ A simple plugin that allows for easy navigation of a file tree. Place this folder in `~/.config/micro/plugins/` and restart micro: -> git clone https://github.com/NicolaiSoeborg/filetree-plugin.git ~/.config/micro/plugins/filetree +> git clone https://github.com/NicolaiSoeborg/filemanager-plugin.git ~/.config/micro/plugins/filemanager Now it will be possible to open a navigation panel by running -the command `tree` (ctrl + e) or creating a keybinding like so: +the command `tree` (Ctrl + E) or creating +a keybinding like so: ``` { - "Ctrl-E": "filetree.OpenTree" + "Alt-E": "filemanager.ToggleTree" } ``` ## Example -![filetree cli](https://i.imgur.com/MBou7Hb.png "Filetree CLI") +![filemanager](https://i.imgur.com/MBou7Hb.png "Filemanager") ## Issues diff --git a/filetree.lua b/filemanager.lua similarity index 88% rename from filetree.lua rename to filemanager.lua index f85ce2e..a4db537 100644 --- a/filetree.lua +++ b/filemanager.lua @@ -1,4 +1,4 @@ -VERSION = "1.3.2" +VERSION = "1.3.3" treeView = nil cwd = DirectoryName(".") @@ -19,6 +19,22 @@ function OpenTree() refreshTree() end +function CloseTree() + if treeView ~= nil then + treeView.Buf.IsModified = false + treeView:Quit(false) + treeView = nil + end +end + +function ToggleTree() + if treeView == nil then + OpenTree() + else + CloseTree() + end +end + function refreshTree() treeView.Buf:remove(treeView.Buf:Start(), treeView.Buf:End()) treeView.Buf:Insert(Loc(0,0), table.concat(scanDir(cwd), "\n ")) @@ -93,9 +109,8 @@ function preDelete(view) -- Clears messenger: messenger:Reset() messenger:Clear() - return false + return false -- don't "allow" delete end - return true end -- don't prompt to save tree view @@ -103,8 +118,8 @@ function preQuit(view) if view == treeView then view.Buf.IsModified = false end - return true end +function preQuitAll(view) treeView.Buf.IsModified = false end function scanDir(directory) local i, t, proc = 3, {}, nil @@ -137,5 +152,5 @@ function isDir(path) return dir end -MakeCommand("tree", "filetree.OpenTree", 0) -AddRuntimeFile("filetree", "syntax", "syntax.micro") +MakeCommand("tree", "filemanager.ToggleTree", 0) +AddRuntimeFile("filemanager", "syntax", "syntax.micro") diff --git a/repo.json b/repo.json index 648ad3f..446535a 100644 --- a/repo.json +++ b/repo.json @@ -1,11 +1,11 @@ [{ - "Name": "filetree", + "Name": "filemanager", "Description": "File manager for Micro", - "Tags": ["filetree", "file", "manager"], + "Tags": ["filetree", "filemanager", "file", "manager"], "Versions": [ { - "Version": "1.3.1", - "Url": "https://github.com/NicolaiSoeborg/filetree-plugin/archive/v1.3.1.zip", + "Version": "1.3.3", + "Url": "https://github.com/NicolaiSoeborg/filemanager-plugin/archive/v1.3.3.zip", "Require": { "micro": ">=1.0.0" } From d408132eeeb3cfbd818342e7bee421df90b28420 Mon Sep 17 00:00:00 2001 From: Nicolai Date: Thu, 9 Mar 2017 17:03:02 +0100 Subject: [PATCH 20/89] Cleanup after name change --- README.md | 3 +-- syntax.micro | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 6f7d7b7..3c66302 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Filetree Plugin +# Filemanager Plugin A simple plugin that allows for easy navigation of a file tree. @@ -10,7 +10,6 @@ the command `tree` (Ctrl + E) or creating a keybinding like so: ``` { - "Alt-E": "filemanager.ToggleTree" } ``` diff --git a/syntax.micro b/syntax.micro index 12c0a0c..b50f1dd 100644 --- a/syntax.micro +++ b/syntax.micro @@ -1,3 +1,3 @@ -syntax "filetree-plugin" "^$" +syntax "filemanager-plugin" "^$" color special "\.\.|/" From 8fdc59f3bbfd51ded38b81e12fd5278d69a61412 Mon Sep 17 00:00:00 2001 From: Nicolai Date: Thu, 9 Mar 2017 17:04:14 +0100 Subject: [PATCH 21/89] Fix example keybinding --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 3c66302..2e363c6 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,7 @@ the command `tree` (Ctrl + E) or creating a keybinding like so: ``` { + "CtrlW": "filemanager.ToggleTree" } ``` From cf762df762b57ab2f49f21c0c2731625e63f3ba0 Mon Sep 17 00:00:00 2001 From: Nicolai Date: Mon, 27 Mar 2017 17:20:29 +0200 Subject: [PATCH 22/89] Fix: new syntax for Micro 1.1.5 (view-refactor branch merged) --- filemanager.lua | 4 ++-- repo.json | 6 +++--- syntax.micro | 3 --- syntax.yaml | 7 +++++++ 4 files changed, 12 insertions(+), 8 deletions(-) delete mode 100644 syntax.micro create mode 100644 syntax.yaml diff --git a/filemanager.lua b/filemanager.lua index a4db537..5393dc2 100644 --- a/filemanager.lua +++ b/filemanager.lua @@ -1,4 +1,4 @@ -VERSION = "1.3.3" +VERSION = "1.3.4" treeView = nil cwd = DirectoryName(".") @@ -153,4 +153,4 @@ function isDir(path) end MakeCommand("tree", "filemanager.ToggleTree", 0) -AddRuntimeFile("filemanager", "syntax", "syntax.micro") +AddRuntimeFile("filemanager", "syntax", "syntax.yaml") diff --git a/repo.json b/repo.json index 446535a..c8086bc 100644 --- a/repo.json +++ b/repo.json @@ -4,10 +4,10 @@ "Tags": ["filetree", "filemanager", "file", "manager"], "Versions": [ { - "Version": "1.3.3", - "Url": "https://github.com/NicolaiSoeborg/filemanager-plugin/archive/v1.3.3.zip", + "Version": "1.3.4", + "Url": "https://github.com/NicolaiSoeborg/filemanager-plugin/archive/v1.3.4.zip", "Require": { - "micro": ">=1.0.0" + "micro": ">=1.1.5" } } ] diff --git a/syntax.micro b/syntax.micro deleted file mode 100644 index b50f1dd..0000000 --- a/syntax.micro +++ /dev/null @@ -1,3 +0,0 @@ -syntax "filemanager-plugin" "^$" - -color special "\.\.|/" diff --git a/syntax.yaml b/syntax.yaml new file mode 100644 index 0000000..61dcd52 --- /dev/null +++ b/syntax.yaml @@ -0,0 +1,7 @@ +filetype: manager + +detect: + filename: "^$" + +rules: + - special: "\\.\\.|/" From 05fb55ea06dc2a5e5699bca18a57c63156fac145 Mon Sep 17 00:00:00 2001 From: tommy Date: Thu, 17 Aug 2017 08:03:21 +0100 Subject: [PATCH 23/89] vscode config files for spelling --- .vscode/cSpell.json | 15 +++++++++++++++ .vscode/settings.json | 22 ++++++++++++++++++++++ 2 files changed, 37 insertions(+) create mode 100644 .vscode/cSpell.json create mode 100644 .vscode/settings.json diff --git a/.vscode/cSpell.json b/.vscode/cSpell.json new file mode 100644 index 0000000..ad5f04d --- /dev/null +++ b/.vscode/cSpell.json @@ -0,0 +1,15 @@ +// cSpell Settings +{ + // Version of the setting file. Always 0.1 + "version": "0.1", + // language - current active spelling language + "language": "en", + // words - list of words to be always considered correct + "words": [], + // flagWords - list of words to be always considered incorrect + // This is useful for offensive words and common spelling errors. + // For example "hte" should be "the" + "flagWords": [ + "hte" + ] +} \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..7cab8cb --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,22 @@ +{ + "cSpell.enabled": true, + "cSpell.enabledLanguageIds": [ + "c", + "cpp", + "csharp", + "go", + "javascript", + "javascriptreact", + "json", + "latex", + "lua", + "markdown", + "php", + "plaintext", + "python", + "text", + "typescript", + "typescriptreact", + "yml" + ] +} \ No newline at end of file From bdd762daf0c542f7343b22c46f5da2b3ff058026 Mon Sep 17 00:00:00 2001 From: tommy Date: Thu, 17 Aug 2017 08:07:22 +0100 Subject: [PATCH 24/89] added debug error messages and comments --- filemanager.lua | 28 +++++++++++++++++++++++----- 1 file changed, 23 insertions(+), 5 deletions(-) diff --git a/filemanager.lua b/filemanager.lua index 5393dc2..af59813 100644 --- a/filemanager.lua +++ b/filemanager.lua @@ -4,7 +4,9 @@ treeView = nil cwd = DirectoryName(".") driveLetter = "C:\\" isWin = (OS == "windows") +debug = false +-- OpenTree setup's the view function OpenTree() CurView():VSplitIndex(NewBuffer("", ""), 0) treeView = CurView() @@ -15,10 +17,19 @@ function OpenTree() SetLocalOption("autosave", "false", treeView) SetLocalOption("statusline", "false", treeView) SetLocalOption("readonly", "true", treeView) + SetLocalOption("kind",2,treeView) tabs[curTab+1]:Resize() + if debug == true then messenger:Error("test") end refreshTree() end +-- mouse callback from micro editor when a left button is clicked on your view +function onMousePress(view, event) + local colunms, rows = event:Position() + if debug == true then messenger:Error("coloumns location rows location ",colunms,rows) end +end + +-- CloseTree will close the tree plugin view and release memory. function CloseTree() if treeView ~= nil then treeView.Buf.IsModified = false @@ -27,6 +38,7 @@ function CloseTree() end end +-- ToggleTree will toggle the view visable and hidden. function ToggleTree() if treeView == nil then OpenTree() @@ -35,6 +47,7 @@ function ToggleTree() end end +-- refreshTree will remove the buffer and load contents from folder function refreshTree() treeView.Buf:remove(treeView.Buf:Start(), treeView.Buf:End()) treeView.Buf:Insert(Loc(0,0), table.concat(scanDir(cwd), "\n ")) @@ -45,16 +58,17 @@ function getSelection() return (treeView.Buf:Line(treeView.Cursor.Loc.Y)):sub(2) end --- When user press enter +-- When user presses enter then if it is a folder clear buffer and reload contents with folder selected. +-- If it is a file then open it in a new vertical view function preInsertNewline(view) if view == treeView then local selected = getSelection() if view.Cursor.Loc.Y == 0 then return false -- topmost line is cwd, so disallowing selecting it - elseif isDir(selected) then + elseif isDir(selected) then -- if directory then reload contents of tree view cwd = JoinPaths(cwd, selected) refreshTree() - else + else -- open file in new vertical view local filename = JoinPaths(cwd, selected) if isWin then filename = driveLetter .. filename end CurView():VSplitIndex(NewBuffer("", filename), 1) @@ -66,14 +80,14 @@ function preInsertNewline(view) return true end --- disallow selecting topmost line in treeview: +-- disallow selecting topmost line in treeView: function preCursorUp(view) if view == treeView then if view.Cursor.Loc.Y == 1 then return false end end end --- don't use build-in view.Cursor:SelectLine() as it will copy to clipboard (in old versions of Micro) +-- don't use built-in view.Cursor:SelectLine() as it will copy to clipboard (in old versions of Micro) function selectLineInTree(v) if v == treeView then local y = v.Cursor.Loc.Y @@ -121,6 +135,7 @@ function preQuit(view) end function preQuitAll(view) treeView.Buf.IsModified = false end +-- scanDir will scan contents of the directory passed. function scanDir(directory) local i, t, proc = 3, {}, nil t[1] = (isWin and driveLetter or "") .. cwd @@ -138,6 +153,8 @@ function scanDir(directory) return t end +-- isDir checks if the path passed is a directory. +-- return true if it is a directory else false if it is not a directory. function isDir(path) local dir, proc = false, nil if isWin then @@ -152,5 +169,6 @@ function isDir(path) return dir end +-- micro editor MakeCommand("tree", "filemanager.ToggleTree", 0) AddRuntimeFile("filemanager", "syntax", "syntax.yaml") From aa0ef2fa0cfc0f2987f7fb3e03b3b427424acbce Mon Sep 17 00:00:00 2001 From: tommy Date: Thu, 17 Aug 2017 08:35:33 +0100 Subject: [PATCH 25/89] check options for errors --- filemanager.lua | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/filemanager.lua b/filemanager.lua index af59813..4db30a5 100644 --- a/filemanager.lua +++ b/filemanager.lua @@ -12,21 +12,23 @@ function OpenTree() treeView = CurView() treeView.Width = 30 treeView.LockWidth = true - SetLocalOption("ruler", "false", treeView) - SetLocalOption("softwrap", "true", treeView) - SetLocalOption("autosave", "false", treeView) - SetLocalOption("statusline", "false", treeView) - SetLocalOption("readonly", "true", treeView) - SetLocalOption("kind",2,treeView) + -- set options for tree view + status = SetLocalOption("ruler", "false", treeView) + if status ~= nil then messenger:Error("ruler -> ",status) end + status = SetLocalOption("softwrap", "true", treeView) + if status ~= nil then messenger:Error("softwrap -> ",status) end + status = SetLocalOption("autosave", "false", treeView) + if status ~= nil then messenger:Error("autosave -> ", status) end + status = SetLocalOption("statusline", "false", treeView) + if status ~= nil then messenger:Error("statusline -> ",status) end tabs[curTab+1]:Resize() - if debug == true then messenger:Error("test") end refreshTree() end -- mouse callback from micro editor when a left button is clicked on your view function onMousePress(view, event) - local colunms, rows = event:Position() - if debug == true then messenger:Error("coloumns location rows location ",colunms,rows) end + local columns, rows = event:Position() + if debug == true then messenger:Error("coloumns location rows location ",columns,rows) end end -- CloseTree will close the tree plugin view and release memory. From da6f5c4b3a442f4dbdc04af6347abc063828e00a Mon Sep 17 00:00:00 2001 From: tommy Date: Thu, 17 Aug 2017 09:43:48 +0100 Subject: [PATCH 26/89] moved function and updated comment --- filemanager.lua | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/filemanager.lua b/filemanager.lua index 4db30a5..8412ffa 100644 --- a/filemanager.lua +++ b/filemanager.lua @@ -6,6 +6,15 @@ driveLetter = "C:\\" isWin = (OS == "windows") debug = false +-- ToggleTree will toggle the tree view visible (create) and hide (delete). +function ToggleTree() + if treeView == nil then + OpenTree() + else + CloseTree() + end +end + -- OpenTree setup's the view function OpenTree() CurView():VSplitIndex(NewBuffer("", ""), 0) @@ -40,14 +49,7 @@ function CloseTree() end end --- ToggleTree will toggle the view visable and hidden. -function ToggleTree() - if treeView == nil then - OpenTree() - else - CloseTree() - end -end + -- refreshTree will remove the buffer and load contents from folder function refreshTree() From 519f69fb74e6b65a61036dd11df3da95a67d3f3c Mon Sep 17 00:00:00 2001 From: tommy Date: Thu, 17 Aug 2017 09:45:35 +0100 Subject: [PATCH 27/89] correct spelling --- .vscode/cSpell.json | 5 ++++- filemanager.lua | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/.vscode/cSpell.json b/.vscode/cSpell.json index ad5f04d..ca5fe56 100644 --- a/.vscode/cSpell.json +++ b/.vscode/cSpell.json @@ -5,7 +5,10 @@ // language - current active spelling language "language": "en", // words - list of words to be always considered correct - "words": [], + "words": [ + "concat", + "popen" + ], // flagWords - list of words to be always considered incorrect // This is useful for offensive words and common spelling errors. // For example "hte" should be "the" diff --git a/filemanager.lua b/filemanager.lua index 8412ffa..9b9204c 100644 --- a/filemanager.lua +++ b/filemanager.lua @@ -37,7 +37,7 @@ end -- mouse callback from micro editor when a left button is clicked on your view function onMousePress(view, event) local columns, rows = event:Position() - if debug == true then messenger:Error("coloumns location rows location ",columns,rows) end + if debug == true then messenger:Error("columns location rows location ",columns,rows) end end -- CloseTree will close the tree plugin view and release memory. From bde6916e5db77ced808bfeab4a41bbcc899f3d78 Mon Sep 17 00:00:00 2001 From: tommy Date: Thu, 17 Aug 2017 09:48:08 +0100 Subject: [PATCH 28/89] moved out options to its own function --- filemanager.lua | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/filemanager.lua b/filemanager.lua index 9b9204c..c3b0889 100644 --- a/filemanager.lua +++ b/filemanager.lua @@ -18,6 +18,12 @@ end -- OpenTree setup's the view function OpenTree() CurView():VSplitIndex(NewBuffer("", ""), 0) + setupOptions() + refreshTree() +end + +-- setupOptions setup tree view options +function setupOptions() treeView = CurView() treeView.Width = 30 treeView.LockWidth = true @@ -31,7 +37,6 @@ function OpenTree() status = SetLocalOption("statusline", "false", treeView) if status ~= nil then messenger:Error("statusline -> ",status) end tabs[curTab+1]:Resize() - refreshTree() end -- mouse callback from micro editor when a left button is clicked on your view From c684046cc7ad952307825890486ff08f5a8a376c Mon Sep 17 00:00:00 2001 From: tommy Date: Thu, 17 Aug 2017 10:07:29 +0100 Subject: [PATCH 29/89] Added path name --- filemanager.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/filemanager.lua b/filemanager.lua index c3b0889..b086d6d 100644 --- a/filemanager.lua +++ b/filemanager.lua @@ -17,7 +17,7 @@ end -- OpenTree setup's the view function OpenTree() - CurView():VSplitIndex(NewBuffer("", ""), 0) + CurView():VSplitIndex(NewBuffer("", "FileManager"), 0) setupOptions() refreshTree() end From 5f28b3642027bae356c3d168996c9d4318648495 Mon Sep 17 00:00:00 2001 From: tommy Date: Thu, 17 Aug 2017 11:49:32 +0100 Subject: [PATCH 30/89] added micro log to each function --- filemanager.lua | 26 ++++++++++++++++++++------ 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/filemanager.lua b/filemanager.lua index b086d6d..6f724c7 100644 --- a/filemanager.lua +++ b/filemanager.lua @@ -4,10 +4,11 @@ treeView = nil cwd = DirectoryName(".") driveLetter = "C:\\" isWin = (OS == "windows") -debug = false +debug = true -- ToggleTree will toggle the tree view visible (create) and hide (delete). function ToggleTree() + if debug == true then messenger:AddLog("<--- ToggleTree() --->") end if treeView == nil then OpenTree() else @@ -17,6 +18,7 @@ end -- OpenTree setup's the view function OpenTree() + if debug == true then messenger:AddLog("<--- OpenTree() --->") end CurView():VSplitIndex(NewBuffer("", "FileManager"), 0) setupOptions() refreshTree() @@ -24,29 +26,32 @@ end -- setupOptions setup tree view options function setupOptions() + if debug == true then messenger:AddLog("<--- setupOptions() --->") end treeView = CurView() treeView.Width = 30 treeView.LockWidth = true -- set options for tree view status = SetLocalOption("ruler", "false", treeView) - if status ~= nil then messenger:Error("ruler -> ",status) end + if status ~= nil then messenger:AddLog("ruler -> ",status) end status = SetLocalOption("softwrap", "true", treeView) - if status ~= nil then messenger:Error("softwrap -> ",status) end + if status ~= nil then messenger:AddLog("softwrap -> ",status) end status = SetLocalOption("autosave", "false", treeView) - if status ~= nil then messenger:Error("autosave -> ", status) end + if status ~= nil then messenger:AddLog("autosave -> ", status) end status = SetLocalOption("statusline", "false", treeView) - if status ~= nil then messenger:Error("statusline -> ",status) end + if status ~= nil then messenger:AddLog("statusline -> ",status) end + messenger:Error("Error -> ",treeView.Type) tabs[curTab+1]:Resize() end -- mouse callback from micro editor when a left button is clicked on your view function onMousePress(view, event) local columns, rows = event:Position() - if debug == true then messenger:Error("columns location rows location ",columns,rows) end + if debug == true then messenger:AddLog("columns location rows location ",columns,rows) end end -- CloseTree will close the tree plugin view and release memory. function CloseTree() + if debug == true then messenger:AddLog("<--- CloseTree() --->") end if treeView ~= nil then treeView.Buf.IsModified = false treeView:Quit(false) @@ -58,18 +63,21 @@ end -- refreshTree will remove the buffer and load contents from folder function refreshTree() + if debug == true then messenger:AddLog("<--- refreshTree() --->") end treeView.Buf:remove(treeView.Buf:Start(), treeView.Buf:End()) treeView.Buf:Insert(Loc(0,0), table.concat(scanDir(cwd), "\n ")) end -- returns currently selected line in treeView function getSelection() + if debug == true then messenger:AddLog("<--- getSelection() --->") end return (treeView.Buf:Line(treeView.Cursor.Loc.Y)):sub(2) end -- When user presses enter then if it is a folder clear buffer and reload contents with folder selected. -- If it is a file then open it in a new vertical view function preInsertNewline(view) + if debug == true then messenger:AddLog("<--- preInsertNewLine(view) %v --->",view) end if view == treeView then local selected = getSelection() if view.Cursor.Loc.Y == 0 then @@ -91,6 +99,7 @@ end -- disallow selecting topmost line in treeView: function preCursorUp(view) + if debug == true then messenger:AddLog("<--- preCursor(view) %v --->",view) end if view == treeView then if view.Cursor.Loc.Y == 1 then return false @@ -98,6 +107,7 @@ end end end -- don't use built-in view.Cursor:SelectLine() as it will copy to clipboard (in old versions of Micro) function selectLineInTree(v) + if debug == true then messenger:AddLog("<--- selectLineInTree(v) %v --->",v) end if v == treeView then local y = v.Cursor.Loc.Y v.Cursor.CurSelection[1] = Loc(0, y) @@ -111,6 +121,7 @@ function onCursorUp(view) selectLineInTree(view) end -- allows for deleting files function preDelete(view) + if debug == true then messenger:AddLog("<--- preDelete(view) %v --->",view) end if view == treeView then local selected = getSelection() if selected == ".." then return false end @@ -138,6 +149,7 @@ end -- don't prompt to save tree view function preQuit(view) + if debug == true then messenger:AddLog("<--- preQuit(view) %v --->",view) end if view == treeView then view.Buf.IsModified = false end @@ -146,6 +158,7 @@ function preQuitAll(view) treeView.Buf.IsModified = false end -- scanDir will scan contents of the directory passed. function scanDir(directory) + if debug == true then messenger:AddLog("<--- scanDir(directory) %v --->",directory) end local i, t, proc = 3, {}, nil t[1] = (isWin and driveLetter or "") .. cwd t[2] = ".." @@ -165,6 +178,7 @@ end -- isDir checks if the path passed is a directory. -- return true if it is a directory else false if it is not a directory. function isDir(path) + if debug == true then messenger:AddLog("<--- isDir(path) %v --->",path) end local dir, proc = false, nil if isWin then proc = io.popen('IF EXIST ' .. driveLetter .. JoinPaths(cwd, path) .. '/* (ECHO d) ELSE (ECHO -)') From 3d7937b15f6e89a16162c8b34be6d30fb23c2c0d Mon Sep 17 00:00:00 2001 From: tommy Date: Thu, 17 Aug 2017 13:30:19 +0100 Subject: [PATCH 31/89] changed AddLog back to Error as it is automatic logged to log. changed v to view changed t to list --- filemanager.lua | 44 ++++++++++++++++++++++++-------------------- 1 file changed, 24 insertions(+), 20 deletions(-) diff --git a/filemanager.lua b/filemanager.lua index 6f724c7..6e39169 100644 --- a/filemanager.lua +++ b/filemanager.lua @@ -32,21 +32,24 @@ function setupOptions() treeView.LockWidth = true -- set options for tree view status = SetLocalOption("ruler", "false", treeView) - if status ~= nil then messenger:AddLog("ruler -> ",status) end + if status ~= nil then messenger:Error("Error setting ruler option -> ",status) end status = SetLocalOption("softwrap", "true", treeView) - if status ~= nil then messenger:AddLog("softwrap -> ",status) end + if status ~= nil then messenger:Error("Error setting softwrap option -> ",status) end status = SetLocalOption("autosave", "false", treeView) - if status ~= nil then messenger:AddLog("autosave -> ", status) end + if status ~= nil then messenger:Error("Error setting autosave option -> ", status) end status = SetLocalOption("statusline", "false", treeView) - if status ~= nil then messenger:AddLog("statusline -> ",status) end - messenger:Error("Error -> ",treeView.Type) + if status ~= nil then messenger:Error("Error setting statusline option -> ",status) end + if debug == true then messenger:Error("INFO: View Type --> id - readonly - scratch --> ",treeView.Type) end + -- TODO: need to set readonly in view type. tabs[curTab+1]:Resize() end -- mouse callback from micro editor when a left button is clicked on your view function onMousePress(view, event) - local columns, rows = event:Position() - if debug == true then messenger:AddLog("columns location rows location ",columns,rows) end + if view == treeView then -- check view is tree as only want inputs from that view. + local columns, rows = event:Position() + if debug == true then messenger:AddLog("Mouse pressed -> columns location rows location -> ",columns,rows) end + end end -- CloseTree will close the tree plugin view and release memory. @@ -106,12 +109,12 @@ function preCursorUp(view) end end end -- don't use built-in view.Cursor:SelectLine() as it will copy to clipboard (in old versions of Micro) -function selectLineInTree(v) - if debug == true then messenger:AddLog("<--- selectLineInTree(v) %v --->",v) end - if v == treeView then - local y = v.Cursor.Loc.Y - v.Cursor.CurSelection[1] = Loc(0, y) - v.Cursor.CurSelection[2] = Loc(v.Width, y) +function selectLineInTree(view) + if debug == true then messenger:AddLog("<--- selectLineInTree(v) %v --->",view) end + if view == treeView then + local y = view.Cursor.Loc.Y + view.Cursor.CurSelection[1] = Loc(0, y) + view.Cursor.CurSelection[2] = Loc(view.Width, y) end end @@ -159,20 +162,21 @@ function preQuitAll(view) treeView.Buf.IsModified = false end -- scanDir will scan contents of the directory passed. function scanDir(directory) if debug == true then messenger:AddLog("<--- scanDir(directory) %v --->",directory) end - local i, t, proc = 3, {}, nil - t[1] = (isWin and driveLetter or "") .. cwd - t[2] = ".." - if isWin then + local i, list, proc = 3, {}, nil + list[1] = (isWin and driveLetter or "") .. cwd -- TODO: get current directory working. + list[2] = ".." -- used for going up a level in directory. + if isWin then -- if windows proc = io.popen('dir /a /b "'..directory..'"') - else + else -- linux or unix system proc = io.popen('ls -Ap "'..directory..'"') end + -- load filenames to a list for filename in proc:lines() do - t[i] = filename + list[i] = filename i = i + 1 end proc:close() - return t + return list end -- isDir checks if the path passed is a directory. From 45b7a01c3e3859d7deaa3a0460d7cc37c27d85ed Mon Sep 17 00:00:00 2001 From: tommy Date: Thu, 17 Aug 2017 13:39:20 +0100 Subject: [PATCH 32/89] made clear messages --- filemanager.lua | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/filemanager.lua b/filemanager.lua index 6e39169..0306622 100644 --- a/filemanager.lua +++ b/filemanager.lua @@ -8,7 +8,7 @@ debug = true -- ToggleTree will toggle the tree view visible (create) and hide (delete). function ToggleTree() - if debug == true then messenger:AddLog("<--- ToggleTree() --->") end + if debug == true then messenger:AddLog("***** ToggleTree() *****") end if treeView == nil then OpenTree() else @@ -18,7 +18,7 @@ end -- OpenTree setup's the view function OpenTree() - if debug == true then messenger:AddLog("<--- OpenTree() --->") end + if debug == true then messenger:AddLog("***** OpenTree() *****") end CurView():VSplitIndex(NewBuffer("", "FileManager"), 0) setupOptions() refreshTree() @@ -26,7 +26,7 @@ end -- setupOptions setup tree view options function setupOptions() - if debug == true then messenger:AddLog("<--- setupOptions() --->") end + if debug == true then messenger:AddLog("***** setupOptions() *****") end treeView = CurView() treeView.Width = 30 treeView.LockWidth = true @@ -48,13 +48,13 @@ end function onMousePress(view, event) if view == treeView then -- check view is tree as only want inputs from that view. local columns, rows = event:Position() - if debug == true then messenger:AddLog("Mouse pressed -> columns location rows location -> ",columns,rows) end + if debug == true then messenger:AddLog("INFO: --> Mouse pressed -> columns location rows location -> ",columns,rows) end end end -- CloseTree will close the tree plugin view and release memory. function CloseTree() - if debug == true then messenger:AddLog("<--- CloseTree() --->") end + if debug == true then messenger:AddLog("***** CloseTree() *****") end if treeView ~= nil then treeView.Buf.IsModified = false treeView:Quit(false) @@ -66,21 +66,21 @@ end -- refreshTree will remove the buffer and load contents from folder function refreshTree() - if debug == true then messenger:AddLog("<--- refreshTree() --->") end + if debug == true then messenger:AddLog("***** refreshTree() *****") end treeView.Buf:remove(treeView.Buf:Start(), treeView.Buf:End()) treeView.Buf:Insert(Loc(0,0), table.concat(scanDir(cwd), "\n ")) end -- returns currently selected line in treeView function getSelection() - if debug == true then messenger:AddLog("<--- getSelection() --->") end + if debug == true then messenger:AddLog("***** getSelection() *****") end return (treeView.Buf:Line(treeView.Cursor.Loc.Y)):sub(2) end -- When user presses enter then if it is a folder clear buffer and reload contents with folder selected. -- If it is a file then open it in a new vertical view function preInsertNewline(view) - if debug == true then messenger:AddLog("<--- preInsertNewLine(view) %v --->",view) end + if debug == true then messenger:AddLog("***** preInsertNewLine(view) ---> ",view) end if view == treeView then local selected = getSelection() if view.Cursor.Loc.Y == 0 then @@ -102,7 +102,7 @@ end -- disallow selecting topmost line in treeView: function preCursorUp(view) - if debug == true then messenger:AddLog("<--- preCursor(view) %v --->",view) end + if debug == true then messenger:AddLog("***** preCursor(view) ---> ",view) end if view == treeView then if view.Cursor.Loc.Y == 1 then return false @@ -110,7 +110,7 @@ end end end -- don't use built-in view.Cursor:SelectLine() as it will copy to clipboard (in old versions of Micro) function selectLineInTree(view) - if debug == true then messenger:AddLog("<--- selectLineInTree(v) %v --->",view) end + if debug == true then messenger:AddLog("***** selectLineInTree(v) ---> ",view) end if view == treeView then local y = view.Cursor.Loc.Y view.Cursor.CurSelection[1] = Loc(0, y) @@ -124,7 +124,7 @@ function onCursorUp(view) selectLineInTree(view) end -- allows for deleting files function preDelete(view) - if debug == true then messenger:AddLog("<--- preDelete(view) %v --->",view) end + if debug == true then messenger:AddLog("***** preDelete(view) ---> ",view) end if view == treeView then local selected = getSelection() if selected == ".." then return false end @@ -152,7 +152,7 @@ end -- don't prompt to save tree view function preQuit(view) - if debug == true then messenger:AddLog("<--- preQuit(view) %v --->",view) end + if debug == true then messenger:AddLog("***** preQuit(view) ---> ",view) end if view == treeView then view.Buf.IsModified = false end @@ -161,7 +161,7 @@ function preQuitAll(view) treeView.Buf.IsModified = false end -- scanDir will scan contents of the directory passed. function scanDir(directory) - if debug == true then messenger:AddLog("<--- scanDir(directory) %v --->",directory) end + if debug == true then messenger:AddLog("***** scanDir(directory) ---> ",directory) end local i, list, proc = 3, {}, nil list[1] = (isWin and driveLetter or "") .. cwd -- TODO: get current directory working. list[2] = ".." -- used for going up a level in directory. @@ -182,7 +182,7 @@ end -- isDir checks if the path passed is a directory. -- return true if it is a directory else false if it is not a directory. function isDir(path) - if debug == true then messenger:AddLog("<--- isDir(path) %v --->",path) end + if debug == true then messenger:AddLog("***** isDir(path) ---> ",path) end local dir, proc = false, nil if isWin then proc = io.popen('IF EXIST ' .. driveLetter .. JoinPaths(cwd, path) .. '/* (ECHO d) ELSE (ECHO -)') From 637d8f6d7c3c35dac8862211f6056e1161eb24f2 Mon Sep 17 00:00:00 2001 From: tommy Date: Thu, 17 Aug 2017 18:14:30 +0100 Subject: [PATCH 33/89] moved functions around to try and group then together. Moved debug messages so they only display when is is a tree view. --- filemanager.lua | 106 ++++++++++++++++++++++++++---------------------- 1 file changed, 58 insertions(+), 48 deletions(-) diff --git a/filemanager.lua b/filemanager.lua index 0306622..5f567d7 100644 --- a/filemanager.lua +++ b/filemanager.lua @@ -39,19 +39,10 @@ function setupOptions() if status ~= nil then messenger:Error("Error setting autosave option -> ", status) end status = SetLocalOption("statusline", "false", treeView) if status ~= nil then messenger:Error("Error setting statusline option -> ",status) end - if debug == true then messenger:Error("INFO: View Type --> id - readonly - scratch --> ",treeView.Type) end -- TODO: need to set readonly in view type. tabs[curTab+1]:Resize() end --- mouse callback from micro editor when a left button is clicked on your view -function onMousePress(view, event) - if view == treeView then -- check view is tree as only want inputs from that view. - local columns, rows = event:Position() - if debug == true then messenger:AddLog("INFO: --> Mouse pressed -> columns location rows location -> ",columns,rows) end - end -end - -- CloseTree will close the tree plugin view and release memory. function CloseTree() if debug == true then messenger:AddLog("***** CloseTree() *****") end @@ -62,56 +53,26 @@ function CloseTree() end end - - -- refreshTree will remove the buffer and load contents from folder function refreshTree() if debug == true then messenger:AddLog("***** refreshTree() *****") end + -- if debug == true then messenger:AddLog("Start -> ",treeView.Buf:Start()," End -> ",treeView.Buf:End()) end treeView.Buf:remove(treeView.Buf:Start(), treeView.Buf:End()) - treeView.Buf:Insert(Loc(0,0), table.concat(scanDir(cwd), "\n ")) + local list = table.concat(scanDir(cwd), "\n ") + if debug == true then messenger:AddLog("dir -> ",list) end + treeView.Buf:Insert(Loc(0,0),list) end -- returns currently selected line in treeView function getSelection() - if debug == true then messenger:AddLog("***** getSelection() *****") end + if debug == true then messenger:AddLog("***** getSelection() ---> ",treeView.Buf:Line(treeView.Cursor.Loc.Y):sub(2)) end return (treeView.Buf:Line(treeView.Cursor.Loc.Y)):sub(2) end --- When user presses enter then if it is a folder clear buffer and reload contents with folder selected. --- If it is a file then open it in a new vertical view -function preInsertNewline(view) - if debug == true then messenger:AddLog("***** preInsertNewLine(view) ---> ",view) end - if view == treeView then - local selected = getSelection() - if view.Cursor.Loc.Y == 0 then - return false -- topmost line is cwd, so disallowing selecting it - elseif isDir(selected) then -- if directory then reload contents of tree view - cwd = JoinPaths(cwd, selected) - refreshTree() - else -- open file in new vertical view - local filename = JoinPaths(cwd, selected) - if isWin then filename = driveLetter .. filename end - CurView():VSplitIndex(NewBuffer("", filename), 1) - CurView():ReOpen() - tabs[curTab+1]:Resize() - end - return false - end - return true -end - --- disallow selecting topmost line in treeView: -function preCursorUp(view) - if debug == true then messenger:AddLog("***** preCursor(view) ---> ",view) end - if view == treeView then - if view.Cursor.Loc.Y == 1 then - return false -end end end - -- don't use built-in view.Cursor:SelectLine() as it will copy to clipboard (in old versions of Micro) function selectLineInTree(view) - if debug == true then messenger:AddLog("***** selectLineInTree(v) ---> ",view) end if view == treeView then + if debug == true then messenger:AddLog("***** selectLineInTree(view) *****") end local y = view.Cursor.Loc.Y view.Cursor.CurSelection[1] = Loc(0, y) view.Cursor.CurSelection[2] = Loc(view.Width, y) @@ -122,10 +83,27 @@ end function onCursorDown(view) selectLineInTree(view) end function onCursorUp(view) selectLineInTree(view) end +-- mouse callback from micro editor when a left button is clicked on your view +function onMousePress(view, event) + if view == treeView then -- check view is tree as only want inputs from that view. + local columns, rows = event:Position() + if debug == true then messenger:AddLog("INFO: --> Mouse pressed -> columns location rows location -> ",columns,rows) end + return false + end +end + +-- disallow selecting topmost line in treeView: +function preCursorUp(view) + if view == treeView then + if debug == true then messenger:AddLog("***** preCursor(view) *****") end + if view.Cursor.Loc.Y == 1 then + return false +end end end + -- allows for deleting files function preDelete(view) - if debug == true then messenger:AddLog("***** preDelete(view) ---> ",view) end if view == treeView then + if debug == true then messenger:AddLog("***** preDelete(view) *****") end local selected = getSelection() if selected == ".." then return false end local type, command @@ -150,10 +128,36 @@ function preDelete(view) end end + +-- When user presses enter then if it is a folder clear buffer and reload contents with folder selected. +-- If it is a file then open it in a new vertical view +function preInsertNewline(view) + if view == treeView then + if debug == true then messenger:AddLog("***** preInsertNewLine(view) *****") end + local selected = getSelection() + if view.Cursor.Loc.Y == 0 then + return false -- topmost line is cwd, so disallowing selecting it + elseif isDir(selected) then -- if directory then reload contents of tree view + if debug == true then messenger:AddLog("current working directory -> ",cwd) end + cwd = JoinPaths(cwd, selected) + if debug == true then messenger:AddLog("current working directory with selected directory -> ",cwd) end + refreshTree() + else -- open file in new vertical view + local filename = JoinPaths(cwd, selected) + if isWin then filename = driveLetter .. filename end + CurView():VSplitIndex(NewBuffer("", filename), 1) + CurView():ReOpen() + tabs[curTab+1]:Resize() + end + return false + end + return true +end + -- don't prompt to save tree view function preQuit(view) - if debug == true then messenger:AddLog("***** preQuit(view) ---> ",view) end if view == treeView then + if debug == true then messenger:AddLog("***** preQuit(view) *****") end view.Buf.IsModified = false end end @@ -193,9 +197,15 @@ function isDir(path) dir = true end proc:close() + if debug == true then messenger:AddLog("is Dir Return = ",dir) end return dir end +function Test() + messenger:Error("Current Directory -->",WorkingDirectory()) +end + -- micro editor MakeCommand("tree", "filemanager.ToggleTree", 0) -AddRuntimeFile("filemanager", "syntax", "syntax.yaml") +MakeCommand("treet","filemanager.Test",0) +AddRuntimeFile("filemanager", "syntax", "syntax.yaml") \ No newline at end of file From 72aa0ce002c0eaf90263449bdbf0ead1ebe0188a Mon Sep 17 00:00:00 2001 From: tommy Date: Thu, 17 Aug 2017 19:16:11 +0100 Subject: [PATCH 34/89] changed version number and needs micro editor 1.3.2 to work. Tested working now. Stopped crashing due to current working directory bug. --- filemanager.lua | 13 ++++--------- repo.json | 4 ++-- 2 files changed, 6 insertions(+), 11 deletions(-) diff --git a/filemanager.lua b/filemanager.lua index 5f567d7..974f9cd 100644 --- a/filemanager.lua +++ b/filemanager.lua @@ -1,7 +1,7 @@ -VERSION = "1.3.4" +VERSION = "1.4.0" treeView = nil -cwd = DirectoryName(".") +cwd = WorkingDirectory() driveLetter = "C:\\" isWin = (OS == "windows") debug = true @@ -167,7 +167,7 @@ function preQuitAll(view) treeView.Buf.IsModified = false end function scanDir(directory) if debug == true then messenger:AddLog("***** scanDir(directory) ---> ",directory) end local i, list, proc = 3, {}, nil - list[1] = (isWin and driveLetter or "") .. cwd -- TODO: get current directory working. + list[1] = (isWin and driveLetter or "") .. cwd -- current directory working. list[2] = ".." -- used for going up a level in directory. if isWin then -- if windows proc = io.popen('dir /a /b "'..directory..'"') @@ -201,11 +201,6 @@ function isDir(path) return dir end -function Test() - messenger:Error("Current Directory -->",WorkingDirectory()) -end - --- micro editor +-- micro editor commands MakeCommand("tree", "filemanager.ToggleTree", 0) -MakeCommand("treet","filemanager.Test",0) AddRuntimeFile("filemanager", "syntax", "syntax.yaml") \ No newline at end of file diff --git a/repo.json b/repo.json index c8086bc..f92b4b5 100644 --- a/repo.json +++ b/repo.json @@ -4,10 +4,10 @@ "Tags": ["filetree", "filemanager", "file", "manager"], "Versions": [ { - "Version": "1.3.4", + "Version": "1.4.0", "Url": "https://github.com/NicolaiSoeborg/filemanager-plugin/archive/v1.3.4.zip", "Require": { - "micro": ">=1.1.5" + "micro": ">=1.3.2" } } ] From 2570f9de580ccec43bc652d3353083c8b16093bf Mon Sep 17 00:00:00 2001 From: tommy Date: Mon, 4 Sep 2017 20:09:57 +0100 Subject: [PATCH 35/89] changed debug mode to false --- filemanager.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/filemanager.lua b/filemanager.lua index 974f9cd..c8c7a8f 100644 --- a/filemanager.lua +++ b/filemanager.lua @@ -4,7 +4,7 @@ treeView = nil cwd = WorkingDirectory() driveLetter = "C:\\" isWin = (OS == "windows") -debug = true +debug = false -- ToggleTree will toggle the tree view visible (create) and hide (delete). function ToggleTree() From 5d0a2844296c18ba2e50c3a71de48233d846f2ae Mon Sep 17 00:00:00 2001 From: tommy Date: Mon, 4 Sep 2017 20:21:11 +0100 Subject: [PATCH 36/89] changed to version 2 as not backwards compatible with micro editor before v1.3.2 --- README.md | 1 + filemanager.lua | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 2e363c6..8ae0b33 100644 --- a/README.md +++ b/README.md @@ -27,3 +27,4 @@ Please use the issue tracker to fill issues or feature requests! * Limited Windows support (also; can only read files from `C:`) +* Version 2 not backwards compatible, only works on Micro Editor version 1.3.2 and above. \ No newline at end of file diff --git a/filemanager.lua b/filemanager.lua index c8c7a8f..b634706 100644 --- a/filemanager.lua +++ b/filemanager.lua @@ -1,4 +1,4 @@ -VERSION = "1.4.0" +VERSION = "2.0.0" treeView = nil cwd = WorkingDirectory() From 4de10554fcf8fc8dfddecc68ba1ed12df762ae47 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicolai=20S=C3=B8borg?= Date: Sat, 30 Sep 2017 17:01:32 +0200 Subject: [PATCH 37/89] Add .gitignore --- .gitignore | 44 +++++++++++++++++++++++++++++++++++++++++++ .vscode/cSpell.json | 18 ------------------ .vscode/settings.json | 22 ---------------------- 3 files changed, 44 insertions(+), 40 deletions(-) create mode 100644 .gitignore delete mode 100644 .vscode/cSpell.json delete mode 100644 .vscode/settings.json diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..16135d0 --- /dev/null +++ b/.gitignore @@ -0,0 +1,44 @@ +# Compiled Lua sources +luac.out + +# luarocks build files +*.src.rock +*.zip +*.tar.gz + +# Object files +*.o +*.os +*.ko +*.obj +*.elf + +# Precompiled Headers +*.gch +*.pch + +# Libraries +*.lib +*.a +*.la +*.lo +*.def +*.exp + +# Shared objects (inc. Windows DLLs) +*.dll +*.so +*.so.* +*.dylib + +# Executables +*.exe +*.out +*.app +*.i*86 +*.x86_64 +*.hex + +# Visual Studio Code +.vscode/ + diff --git a/.vscode/cSpell.json b/.vscode/cSpell.json deleted file mode 100644 index ca5fe56..0000000 --- a/.vscode/cSpell.json +++ /dev/null @@ -1,18 +0,0 @@ -// cSpell Settings -{ - // Version of the setting file. Always 0.1 - "version": "0.1", - // language - current active spelling language - "language": "en", - // words - list of words to be always considered correct - "words": [ - "concat", - "popen" - ], - // flagWords - list of words to be always considered incorrect - // This is useful for offensive words and common spelling errors. - // For example "hte" should be "the" - "flagWords": [ - "hte" - ] -} \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json deleted file mode 100644 index 7cab8cb..0000000 --- a/.vscode/settings.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "cSpell.enabled": true, - "cSpell.enabledLanguageIds": [ - "c", - "cpp", - "csharp", - "go", - "javascript", - "javascriptreact", - "json", - "latex", - "lua", - "markdown", - "php", - "plaintext", - "python", - "text", - "typescript", - "typescriptreact", - "yml" - ] -} \ No newline at end of file From 2f8ab43c702239311710ba7d2279eb9c7950d89f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicolai=20S=C3=B8borg?= Date: Sat, 30 Sep 2017 17:11:22 +0200 Subject: [PATCH 38/89] Release version v2.0.0 --- repo.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/repo.json b/repo.json index f92b4b5..165ad21 100644 --- a/repo.json +++ b/repo.json @@ -4,8 +4,8 @@ "Tags": ["filetree", "filemanager", "file", "manager"], "Versions": [ { - "Version": "1.4.0", - "Url": "https://github.com/NicolaiSoeborg/filemanager-plugin/archive/v1.3.4.zip", + "Version": "2.0.0", + "Url": "https://github.com/NicolaiSoeborg/filemanager-plugin/archive/v2.0.0.zip", "Require": { "micro": ">=1.3.2" } From 580c2c24228e94a55a450b866cd2f12f677160dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicolai=20S=C3=B8borg?= Date: Tue, 24 Oct 2017 23:41:25 +0200 Subject: [PATCH 39/89] Change debugging --- filemanager.lua | 38 +++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/filemanager.lua b/filemanager.lua index b634706..f478577 100644 --- a/filemanager.lua +++ b/filemanager.lua @@ -4,11 +4,15 @@ treeView = nil cwd = WorkingDirectory() driveLetter = "C:\\" isWin = (OS == "windows") -debug = false + +-- Uncomment to enable debugging +function debug(log) + -- messenger:AddLog(log) +end -- ToggleTree will toggle the tree view visible (create) and hide (delete). function ToggleTree() - if debug == true then messenger:AddLog("***** ToggleTree() *****") end + debug("***** ToggleTree() *****") if treeView == nil then OpenTree() else @@ -18,7 +22,7 @@ end -- OpenTree setup's the view function OpenTree() - if debug == true then messenger:AddLog("***** OpenTree() *****") end + debug("***** OpenTree() *****") CurView():VSplitIndex(NewBuffer("", "FileManager"), 0) setupOptions() refreshTree() @@ -26,7 +30,7 @@ end -- setupOptions setup tree view options function setupOptions() - if debug == true then messenger:AddLog("***** setupOptions() *****") end + debug("***** setupOptions() *****") treeView = CurView() treeView.Width = 30 treeView.LockWidth = true @@ -45,7 +49,7 @@ end -- CloseTree will close the tree plugin view and release memory. function CloseTree() - if debug == true then messenger:AddLog("***** CloseTree() *****") end + debug("***** CloseTree() *****") if treeView ~= nil then treeView.Buf.IsModified = false treeView:Quit(false) @@ -55,24 +59,22 @@ end -- refreshTree will remove the buffer and load contents from folder function refreshTree() - if debug == true then messenger:AddLog("***** refreshTree() *****") end - -- if debug == true then messenger:AddLog("Start -> ",treeView.Buf:Start()," End -> ",treeView.Buf:End()) end + debug("***** refreshTree() *****") treeView.Buf:remove(treeView.Buf:Start(), treeView.Buf:End()) local list = table.concat(scanDir(cwd), "\n ") - if debug == true then messenger:AddLog("dir -> ",list) end treeView.Buf:Insert(Loc(0,0),list) end -- returns currently selected line in treeView function getSelection() - if debug == true then messenger:AddLog("***** getSelection() ---> ",treeView.Buf:Line(treeView.Cursor.Loc.Y):sub(2)) end + debug("***** getSelection() ---> ",treeView.Buf:Line(treeView.Cursor.Loc.Y):sub(2)) return (treeView.Buf:Line(treeView.Cursor.Loc.Y)):sub(2) end -- don't use built-in view.Cursor:SelectLine() as it will copy to clipboard (in old versions of Micro) function selectLineInTree(view) if view == treeView then - if debug == true then messenger:AddLog("***** selectLineInTree(view) *****") end + debug("***** selectLineInTree() *****") local y = view.Cursor.Loc.Y view.Cursor.CurSelection[1] = Loc(0, y) view.Cursor.CurSelection[2] = Loc(view.Width, y) @@ -87,7 +89,7 @@ function onCursorUp(view) selectLineInTree(view) end function onMousePress(view, event) if view == treeView then -- check view is tree as only want inputs from that view. local columns, rows = event:Position() - if debug == true then messenger:AddLog("INFO: --> Mouse pressed -> columns location rows location -> ",columns,rows) end + debug("INFO: --> Mouse pressed -> columns location rows location -> ",columns,rows) return false end end @@ -95,7 +97,7 @@ end -- disallow selecting topmost line in treeView: function preCursorUp(view) if view == treeView then - if debug == true then messenger:AddLog("***** preCursor(view) *****") end + debug("***** preCursor(view) *****") if view.Cursor.Loc.Y == 1 then return false end end end @@ -133,14 +135,12 @@ end -- If it is a file then open it in a new vertical view function preInsertNewline(view) if view == treeView then - if debug == true then messenger:AddLog("***** preInsertNewLine(view) *****") end + debug("***** preInsertNewLine() *****") local selected = getSelection() if view.Cursor.Loc.Y == 0 then return false -- topmost line is cwd, so disallowing selecting it elseif isDir(selected) then -- if directory then reload contents of tree view - if debug == true then messenger:AddLog("current working directory -> ",cwd) end cwd = JoinPaths(cwd, selected) - if debug == true then messenger:AddLog("current working directory with selected directory -> ",cwd) end refreshTree() else -- open file in new vertical view local filename = JoinPaths(cwd, selected) @@ -157,7 +157,7 @@ end -- don't prompt to save tree view function preQuit(view) if view == treeView then - if debug == true then messenger:AddLog("***** preQuit(view) *****") end + debug("***** preQuit() *****") view.Buf.IsModified = false end end @@ -165,7 +165,7 @@ function preQuitAll(view) treeView.Buf.IsModified = false end -- scanDir will scan contents of the directory passed. function scanDir(directory) - if debug == true then messenger:AddLog("***** scanDir(directory) ---> ",directory) end + debug("***** scanDir(directory) ---> ",directory) local i, list, proc = 3, {}, nil list[1] = (isWin and driveLetter or "") .. cwd -- current directory working. list[2] = ".." -- used for going up a level in directory. @@ -186,7 +186,7 @@ end -- isDir checks if the path passed is a directory. -- return true if it is a directory else false if it is not a directory. function isDir(path) - if debug == true then messenger:AddLog("***** isDir(path) ---> ",path) end + debug("***** isDir(path) ---> ",path) local dir, proc = false, nil if isWin then proc = io.popen('IF EXIST ' .. driveLetter .. JoinPaths(cwd, path) .. '/* (ECHO d) ELSE (ECHO -)') @@ -197,7 +197,7 @@ function isDir(path) dir = true end proc:close() - if debug == true then messenger:AddLog("is Dir Return = ",dir) end + debug("***** isDir return ",dir) return dir end From a94f36e189c300f74c1f373de9eb8da79a1efcf1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicolai=20S=C3=B8borg?= Date: Tue, 24 Oct 2017 23:49:07 +0200 Subject: [PATCH 40/89] Add mouse support --- filemanager.lua | 21 +++++++++++++++------ repo.json | 4 ++-- 2 files changed, 17 insertions(+), 8 deletions(-) diff --git a/filemanager.lua b/filemanager.lua index f478577..343fd0a 100644 --- a/filemanager.lua +++ b/filemanager.lua @@ -1,4 +1,4 @@ -VERSION = "2.0.0" +VERSION = "2.1.0" treeView = nil cwd = WorkingDirectory() @@ -72,6 +72,7 @@ function getSelection() end -- don't use built-in view.Cursor:SelectLine() as it will copy to clipboard (in old versions of Micro) +-- TODO: We require micro >= 1.3.2, so is this still an issue? function selectLineInTree(view) if view == treeView then debug("***** selectLineInTree() *****") @@ -86,18 +87,26 @@ function onCursorDown(view) selectLineInTree(view) end function onCursorUp(view) selectLineInTree(view) end -- mouse callback from micro editor when a left button is clicked on your view -function onMousePress(view, event) +function preMousePress(view, event) if view == treeView then -- check view is tree as only want inputs from that view. local columns, rows = event:Position() debug("INFO: --> Mouse pressed -> columns location rows location -> ",columns,rows) - return false + return true end end +function onMousePress(view, event) + if view == treeView then + selectLineInTree(view) + preInsertNewline(view) + return false + end +end + -- disallow selecting topmost line in treeView: function preCursorUp(view) if view == treeView then - debug("***** preCursor(view) *****") + debug("***** preCursor() *****") if view.Cursor.Loc.Y == 1 then return false end end end @@ -105,7 +114,7 @@ end end end -- allows for deleting files function preDelete(view) if view == treeView then - if debug == true then messenger:AddLog("***** preDelete(view) *****") end + if debug == true then messenger:AddLog("***** preDelete() *****") end local selected = getSelection() if selected == ".." then return false end local type, command @@ -203,4 +212,4 @@ end -- micro editor commands MakeCommand("tree", "filemanager.ToggleTree", 0) -AddRuntimeFile("filemanager", "syntax", "syntax.yaml") \ No newline at end of file +AddRuntimeFile("filemanager", "syntax", "syntax.yaml") diff --git a/repo.json b/repo.json index 165ad21..14e8eeb 100644 --- a/repo.json +++ b/repo.json @@ -4,8 +4,8 @@ "Tags": ["filetree", "filemanager", "file", "manager"], "Versions": [ { - "Version": "2.0.0", - "Url": "https://github.com/NicolaiSoeborg/filemanager-plugin/archive/v2.0.0.zip", + "Version": "2.1.0", + "Url": "https://github.com/NicolaiSoeborg/filemanager-plugin/archive/v2.1.0.zip", "Require": { "micro": ">=1.3.2" } From 2f43d8c7037968e7e19426e9eaf899c4854b6d80 Mon Sep 17 00:00:00 2001 From: Tommy Date: Thu, 2 Nov 2017 07:46:58 +0000 Subject: [PATCH 41/89] Update filemanager.lua Handle "Quit" properly from NopeDK issue #6 --- filemanager.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/filemanager.lua b/filemanager.lua index 343fd0a..670a31f 100644 --- a/filemanager.lua +++ b/filemanager.lua @@ -168,6 +168,7 @@ function preQuit(view) if view == treeView then debug("***** preQuit() *****") view.Buf.IsModified = false + treeView = nil end end function preQuitAll(view) treeView.Buf.IsModified = false end From d8dccc8dfbe3ef12f63bd4efde556bb13fdc3ec8 Mon Sep 17 00:00:00 2001 From: sum01 Date: Wed, 1 Nov 2017 23:58:09 -0400 Subject: [PATCH 42/89] Add proper way to install plugin in README --- README.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/README.md b/README.md index 8ae0b33..cb77ad2 100644 --- a/README.md +++ b/README.md @@ -2,8 +2,7 @@ A simple plugin that allows for easy navigation of a file tree. -Place this folder in `~/.config/micro/plugins/` and restart micro: -> git clone https://github.com/NicolaiSoeborg/filemanager-plugin.git ~/.config/micro/plugins/filemanager +To install, simply run `plugin install filemanager` and restart Micro. Now it will be possible to open a navigation panel by running the command `tree` (Ctrl + E) or creating From 206e9877de8009c9aa725261a694142f2bb1cf25 Mon Sep 17 00:00:00 2001 From: sum01 Date: Mon, 20 Nov 2017 00:34:07 -0500 Subject: [PATCH 43/89] Use Go's standard libs instead of Lua's Fixes the portability issues with io.popen Fixes Windows support Fixes hard-coded commands run by preDelete() Fixes hard-coded commands run by scanDir() Fixes hard-coded commands run by isDir() --- filemanager.lua | 92 ++++++++++++++++++++++++++++--------------------- 1 file changed, 53 insertions(+), 39 deletions(-) diff --git a/filemanager.lua b/filemanager.lua index 670a31f..a93b42f 100644 --- a/filemanager.lua +++ b/filemanager.lua @@ -2,7 +2,6 @@ VERSION = "2.1.0" treeView = nil cwd = WorkingDirectory() -driveLetter = "C:\\" isWin = (OS == "windows") -- Uncomment to enable debugging @@ -117,20 +116,21 @@ function preDelete(view) if debug == true then messenger:AddLog("***** preDelete() *****") end local selected = getSelection() if selected == ".." then return false end - local type, command + + local type = "file" if isDir(selected) then type = "dir" - command = isWin and "del /S /Q" or "rm -r" - else - type = "file" - command = isWin and "del" or "rm -I" end - command = command .. " " .. (isWin and driveLetter or "") .. JoinPaths(cwd, selected) - local yes, cancel = messenger:YesNoPrompt("Do you want to delete " .. type .. " '" .. selected .. "'? ") + -- Use the full path instead of relative. + selected = JoinPaths(cwd, selected) + + local yes, cancel = messenger:YesNoPrompt("Do you want to delete the " .. type .. " '" .. selected .. "'? ") if not cancel and yes then - os.execute(command) - refreshTree() + -- Use Go's os.Remove to delete the file + local go_os = import("os") + go_os.Remove(selected) + refreshTree() end -- Clears messenger: messenger:Reset() @@ -153,7 +153,6 @@ function preInsertNewline(view) refreshTree() else -- open file in new vertical view local filename = JoinPaths(cwd, selected) - if isWin then filename = driveLetter .. filename end CurView():VSplitIndex(NewBuffer("", filename), 1) CurView():ReOpen() tabs[curTab+1]:Resize() @@ -175,40 +174,55 @@ function preQuitAll(view) treeView.Buf.IsModified = false end -- scanDir will scan contents of the directory passed. function scanDir(directory) - debug("***** scanDir(directory) ---> ",directory) - local i, list, proc = 3, {}, nil - list[1] = (isWin and driveLetter or "") .. cwd -- current directory working. - list[2] = ".." -- used for going up a level in directory. - if isWin then -- if windows - proc = io.popen('dir /a /b "'..directory..'"') - else -- linux or unix system - proc = io.popen('ls -Ap "'..directory..'"') - end - -- load filenames to a list - for filename in proc:lines() do - list[i] = filename - i = i + 1 + messenger:AddLog("***** scanDir(directory) ---> ", directory) + + local list = {[1] = cwd, [2] = ".."} + + local go_ioutil = import("ioutil") + -- Gets a list of all the files in the current dir + local readout = go_ioutil.ReadDir(directory) + + if readout == nil then + messenger:Error("Error reading directory: ", directory) + else + local readout_name = "" + -- Loop through all the files/directories in current dir + for i = 1, #readout do + -- Save the current dir/file name + readout_name = readout[i]:Name() + -- Check if the current file is a dir + if isDir(readout_name) then + -- Add on a slash to signify the listing is a directory + -- Shouldn't cause issues on Windows, as it lets you use either slash type + readout_name = readout_name .. "/" + end + -- Actually add the file/dir to the list to be displayed + list[i + 2] = readout_name end - proc:close() - return list + end + + return list end -- isDir checks if the path passed is a directory. -- return true if it is a directory else false if it is not a directory. function isDir(path) - debug("***** isDir(path) ---> ",path) - local dir, proc = false, nil - if isWin then - proc = io.popen('IF EXIST ' .. driveLetter .. JoinPaths(cwd, path) .. '/* (ECHO d) ELSE (ECHO -)') - else - proc = io.popen('ls -adl "' .. JoinPaths(cwd, path) .. '"') - end - if proc:read(1) == "d" then - dir = true - end - proc:close() - debug("***** isDir return ",dir) - return dir + debug("***** isDir(path) ---> ", path) + + local go_os = import("os") + + local check_path = JoinPaths(cwd, path) + + -- Returns a FileInfo on the current file/path + local file_info = go_os.Stat(check_path) + + if file_info ~= nil then + -- Returns the true/false of if the file is a directory + return file_info:IsDir() + else + messenger:AddLog("isDir() failed, returning nil") + return nil + end end -- micro editor commands From 0049026c4a064b38bfeb2e5db8c3afe44e3671df Mon Sep 17 00:00:00 2001 From: sum01 Date: Sun, 19 Nov 2017 16:40:05 -0500 Subject: [PATCH 44/89] Remove 'limited windows support' from README --- README.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/README.md b/README.md index cb77ad2..b689b25 100644 --- a/README.md +++ b/README.md @@ -24,6 +24,4 @@ Please use the issue tracker to fill issues or feature requests! ### Known Issues -* Limited Windows support (also; can only read files from `C:`) - * Version 2 not backwards compatible, only works on Micro Editor version 1.3.2 and above. \ No newline at end of file From 91ffa47bba398f6d2423ea00128bcec6e96d2c10 Mon Sep 17 00:00:00 2001 From: Tommy Date: Sun, 26 Nov 2017 10:50:07 +0000 Subject: [PATCH 45/89] stop out of bound error for long lists --- filemanager.lua | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/filemanager.lua b/filemanager.lua index a93b42f..e341634 100644 --- a/filemanager.lua +++ b/filemanager.lua @@ -60,8 +60,13 @@ end function refreshTree() debug("***** refreshTree() *****") treeView.Buf:remove(treeView.Buf:Start(), treeView.Buf:End()) + -- get current directory list local list = table.concat(scanDir(cwd), "\n ") treeView.Buf:Insert(Loc(0,0),list) + -- make sure view is at the top for selecting + treeView:CursorStart(false) + treeView:Relocate() + treeView:CursorDown(true) end -- returns currently selected line in treeView From 2797d2846fff48d425d0abac10461b6cf33ad9bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicolai=20S=C3=B8borg?= Date: Sat, 9 Dec 2017 23:57:17 +0100 Subject: [PATCH 46/89] Release version 2.1.1 --- filemanager.lua | 2 +- repo.json | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/filemanager.lua b/filemanager.lua index e341634..e79b510 100644 --- a/filemanager.lua +++ b/filemanager.lua @@ -1,4 +1,4 @@ -VERSION = "2.1.0" +VERSION = "2.1.1" treeView = nil cwd = WorkingDirectory() diff --git a/repo.json b/repo.json index 14e8eeb..c1efb21 100644 --- a/repo.json +++ b/repo.json @@ -4,8 +4,8 @@ "Tags": ["filetree", "filemanager", "file", "manager"], "Versions": [ { - "Version": "2.1.0", - "Url": "https://github.com/NicolaiSoeborg/filemanager-plugin/archive/v2.1.0.zip", + "Version": "2.1.1", + "Url": "https://github.com/NicolaiSoeborg/filemanager-plugin/archive/v2.1.1.zip", "Require": { "micro": ">=1.3.2" } From 9cde2153bb8a997385d33911e2b36b6c209ad3ec Mon Sep 17 00:00:00 2001 From: sum01 Date: Thu, 7 Dec 2017 00:46:01 -0500 Subject: [PATCH 47/89] Big rework, brings tons of new features & fixes Fixes #13, fixes #14, fixes #19 Adds directory minimizing/maximizing, without moving into them, by putting the content below it. Adds file/directory renaming, creation, & deletion. Changes to read-only so you can't mess with the tree. Changes some keybindings to work safely with the tree view. Disable most keybindings that normally cause issues. --- filemanager.lua | 1469 +++++++++++++++++++++++++++++++++++++++-------- repo.json | 39 +- 2 files changed, 1263 insertions(+), 245 deletions(-) diff --git a/filemanager.lua b/filemanager.lua index e79b510..dc6a16b 100644 --- a/filemanager.lua +++ b/filemanager.lua @@ -1,235 +1,1242 @@ VERSION = "2.1.1" -treeView = nil -cwd = WorkingDirectory() -isWin = (OS == "windows") - --- Uncomment to enable debugging -function debug(log) - -- messenger:AddLog(log) -end - --- ToggleTree will toggle the tree view visible (create) and hide (delete). -function ToggleTree() - debug("***** ToggleTree() *****") - if treeView == nil then - OpenTree() - else - CloseTree() - end -end - --- OpenTree setup's the view -function OpenTree() - debug("***** OpenTree() *****") - CurView():VSplitIndex(NewBuffer("", "FileManager"), 0) - setupOptions() - refreshTree() -end - --- setupOptions setup tree view options -function setupOptions() - debug("***** setupOptions() *****") - treeView = CurView() - treeView.Width = 30 - treeView.LockWidth = true - -- set options for tree view - status = SetLocalOption("ruler", "false", treeView) - if status ~= nil then messenger:Error("Error setting ruler option -> ",status) end - status = SetLocalOption("softwrap", "true", treeView) - if status ~= nil then messenger:Error("Error setting softwrap option -> ",status) end - status = SetLocalOption("autosave", "false", treeView) - if status ~= nil then messenger:Error("Error setting autosave option -> ", status) end - status = SetLocalOption("statusline", "false", treeView) - if status ~= nil then messenger:Error("Error setting statusline option -> ",status) end - -- TODO: need to set readonly in view type. - tabs[curTab+1]:Resize() -end - --- CloseTree will close the tree plugin view and release memory. -function CloseTree() - debug("***** CloseTree() *****") - if treeView ~= nil then - treeView.Buf.IsModified = false - treeView:Quit(false) - treeView = nil - end -end - --- refreshTree will remove the buffer and load contents from folder -function refreshTree() - debug("***** refreshTree() *****") - treeView.Buf:remove(treeView.Buf:Start(), treeView.Buf:End()) - -- get current directory list - local list = table.concat(scanDir(cwd), "\n ") - treeView.Buf:Insert(Loc(0,0),list) - -- make sure view is at the top for selecting - treeView:CursorStart(false) - treeView:Relocate() - treeView:CursorDown(true) -end - --- returns currently selected line in treeView -function getSelection() - debug("***** getSelection() ---> ",treeView.Buf:Line(treeView.Cursor.Loc.Y):sub(2)) - return (treeView.Buf:Line(treeView.Cursor.Loc.Y)):sub(2) -end - --- don't use built-in view.Cursor:SelectLine() as it will copy to clipboard (in old versions of Micro) --- TODO: We require micro >= 1.3.2, so is this still an issue? -function selectLineInTree(view) - if view == treeView then - debug("***** selectLineInTree() *****") - local y = view.Cursor.Loc.Y - view.Cursor.CurSelection[1] = Loc(0, y) - view.Cursor.CurSelection[2] = Loc(view.Width, y) - end -end - --- 'beautiful' file selection: -function onCursorDown(view) selectLineInTree(view) end -function onCursorUp(view) selectLineInTree(view) end - --- mouse callback from micro editor when a left button is clicked on your view -function preMousePress(view, event) - if view == treeView then -- check view is tree as only want inputs from that view. - local columns, rows = event:Position() - debug("INFO: --> Mouse pressed -> columns location rows location -> ",columns,rows) - return true - end -end -function onMousePress(view, event) - if view == treeView then - selectLineInTree(view) - preInsertNewline(view) - return false - end -end - - --- disallow selecting topmost line in treeView: -function preCursorUp(view) - if view == treeView then - debug("***** preCursor() *****") - if view.Cursor.Loc.Y == 1 then - return false -end end end - --- allows for deleting files -function preDelete(view) - if view == treeView then - if debug == true then messenger:AddLog("***** preDelete() *****") end - local selected = getSelection() - if selected == ".." then return false end - - local type = "file" - if isDir(selected) then - type = "dir" - end - - -- Use the full path instead of relative. - selected = JoinPaths(cwd, selected) - - local yes, cancel = messenger:YesNoPrompt("Do you want to delete the " .. type .. " '" .. selected .. "'? ") - if not cancel and yes then - -- Use Go's os.Remove to delete the file - local go_os = import("os") - go_os.Remove(selected) - refreshTree() - end - -- Clears messenger: - messenger:Reset() - messenger:Clear() - return false -- don't "allow" delete - end -end - - --- When user presses enter then if it is a folder clear buffer and reload contents with folder selected. --- If it is a file then open it in a new vertical view -function preInsertNewline(view) - if view == treeView then - debug("***** preInsertNewLine() *****") - local selected = getSelection() - if view.Cursor.Loc.Y == 0 then - return false -- topmost line is cwd, so disallowing selecting it - elseif isDir(selected) then -- if directory then reload contents of tree view - cwd = JoinPaths(cwd, selected) - refreshTree() - else -- open file in new vertical view - local filename = JoinPaths(cwd, selected) - CurView():VSplitIndex(NewBuffer("", filename), 1) - CurView():ReOpen() - tabs[curTab+1]:Resize() - end - return false - end - return true -end - --- don't prompt to save tree view +-- Clear out all stuff in Micro's messenger +local function clear_messenger() + messenger:Reset() + messenger:Clear() +end + +-- Holds the CurView() we're manipulating +local tree_view = nil +-- Keeps track of the current working directory +local current_dir = WorkingDirectory() +-- Keep track of current highest visible indent to resize width appropriately +local highest_visible_indent = 0 +-- Holds a table of paths -- objects from new_listobj() calls +local scanlist = {} + +-- Get a new object used when adding to scanlist +local function new_listobj(p, d, o, i) + return { + ["abspath"] = p, + ["dirmsg"] = d, + ["owner"] = o, + ["indent"] = i, + -- Since decreasing/increasing is common, we include these with the object + ["decrease_owner"] = function(self, minus_num) + self.owner = self.owner - minus_num + end, + ["increase_owner"] = function(self, plus_num) + self.owner = self.owner + plus_num + end + } +end + +-- Repeats a string x times, then returns it concatenated into one string +local function repeat_str(str, len) + -- Do NOT try to concat in a loop, it freezes micro... + -- instead, use a temporary table to hold values + local string_table = {} + for i = 1, len do + string_table[i] = str + end + -- Return the single string of repeated characters + return table.concat(string_table) +end + +-- A check for if a path is a dir +local function is_dir(path) + -- Used for checking if dir + local golib_os = import("os") + -- Returns a FileInfo on the current file/path + local file_info, stat_error = golib_os.Stat(path) + -- Wrap in nil check for file/dirs without read permissions + if file_info ~= nil then + -- Returns true/false if it's a dir + return file_info:IsDir() + else + -- Couldn't stat the file/dir, usually because no read permissions + messenger:Error("Error checking if is dir: ", stat_error) + -- Nil since we can't read the path + return nil + end +end + +-- Structures the output of the scanned directory content to be used in the scanlist table +-- This is useful for both initial creation of the tree, and when nesting with uncompress_target() +local function get_scanlist(dir, ownership, indent_n) + local golib_ioutil = import("ioutil") + -- Gets a list of all the files in the current dir + local dir_scan, scan_error = golib_ioutil.ReadDir(dir) + + if dir_scan == nil then + -- tostring because Error doesn't seem to support the comma/interface{} thing + messenger:Error("Error scanning dir: ", scan_error) + return nil + else + local dirmsg, full_path + local results = {} + + -- Loop through all the files/directories in current dir + for i = 1, #dir_scan do + -- Save the current dir/file name with the absolute path + full_path = JoinPaths(dir, dir_scan[i]:Name()) + -- Use "+" for dir's, "" for files + dirmsg = (is_dir(full_path) and "+" or "") + results[i] = new_listobj(full_path, dirmsg, ownership, indent_n) + end + return results + end +end + +-- Returns the basename of a path (aka a name without leading path) +local function get_basename(path) + if path == nil then + messenger:AddLog("Bad path passed to get_basename") + return nil + else + -- Get Go's path lib for a basename callback + local golib_path = import("path") + return golib_path.Base(path) + end +end + +-- A short "get y" for when acting on the scanlist +-- Needed since we don't store the first 3 visible indicies in scanlist +local function get_safe_y(optional_y) + -- Default to 0 so we can check against and see if it's bad + local y = 0 + -- Make the passed y optional + if optional_y == nil then + -- Default to cursor's Y loc if nothing was passed, instead of declaring another y + optional_y = tree_view.Buf.Cursor.Loc.Y + end + -- 0/1/2 would be the top "dir, separator, .." so check if it's past + if optional_y > 2 then + -- -2 to conform to our scanlist, since zero-based Go index & Lua's one-based + y = tree_view.Buf.Cursor.Loc.Y - 2 + end + return y +end + +-- Joins the target dir's leading path to the passed name +local function dirname_and_join(path, join_name) + -- The leading path to the dir we're in + local leading_path = DirectoryName(path) + -- Joins with OS-specific slashes + return JoinPaths(leading_path, join_name) +end + +-- Hightlights the line when you move the cursor up/down +local function select_line(last_y) + -- Make last_y optional + if last_y ~= nil then + -- Don't let them move past ".." by checking the result first + if last_y > 1 then + -- If the last position was valid, move back to it + tree_view.Buf.Cursor.Loc.Y = last_y + end + elseif tree_view.Buf.Cursor.Loc.Y < 2 then + -- Put the cursor on the ".." if it's above it + tree_view.Buf.Cursor.Loc.Y = 2 + end + + -- Puts the cursor back in bounds (if it isn't) for safety + tree_view.Buf.Cursor:Relocate() + + -- Makes sure the cursor is visible (if it isn't) + -- (false) means no callback + tree_view:Center(false) + + -- Highlight the current line where the cursor is + tree_view.Buf.Cursor:SelectLine() +end + +-- Simple true/false if scanlist is currently empty +local function scanlist_is_empty() + if next(scanlist) == nil then + return true + else + return false + end +end + +local function refresh_view() + clear_messenger() + + -- If it's less than 30, just use 30 for width. Don't want it too small + if tree_view.Width < 30 then + tree_view.Width = 30 + end + + -- Delete everything in the view/buffer + tree_view.Buf:remove(tree_view.Buf:Start(), tree_view.Buf:End()) + + -- Insert the top 3 things that are always there + -- Current dir + tree_view.Buf:insert(Loc(0, 0), current_dir .. "\n") + -- An ASCII separator + tree_view.Buf:insert(Loc(0, 1), repeat_str("─", tree_view.Width) .. "\n") + -- The ".." and use a newline if there are things in the current dir + tree_view.Buf:insert(Loc(0, 2), (#scanlist > 0 and "..\n" or "..")) + + -- Holds the current basename of the path (purely for display) + local display_content + + -- NOTE: might want to not do all these concats in the loop, it can get slow + for i = 1, #scanlist do + -- The first 3 indicies are the dir/separator/"..", so skip them + if scanlist[i].dirmsg ~= "" then + -- Add the + or - to the left to signify if it's compressed or not + -- Add a forward slash to the right to signify it's a dir + display_content = scanlist[i].dirmsg .. " " .. get_basename(scanlist[i].abspath) .. "/" + else + -- Use the basename from the full path for display + -- Two spaces to align with any directories, instead of being "off" + display_content = " " .. get_basename(scanlist[i].abspath) + end + + if scanlist[i].owner > 0 then + -- Add a space and repeat it * the indent number + display_content = repeat_str(" ", 2 * scanlist[i].indent) .. display_content + end + + -- Newlines are needed for all inserts except the last + -- If you insert a newline on the last, it leaves a blank spot at the bottom + if i < #scanlist then + display_content = display_content .. "\n" + end + + -- Insert line-by-line to avoid out-of-bounds on big folders + -- +2 so we skip the 0/1/2 positions that hold the top dir/separator/.. + tree_view.Buf:insert(Loc(0, i + 2), display_content) + end + + -- Resizes all views after messing with ours + tabs[curTab + 1]:Resize() +end + +-- Moves the cursor to the ".." in tree_view +local function move_cursor_top() + -- 2 is the position of the ".." + tree_view.Buf.Cursor.Loc.Y = 2 + + -- select the line after moving + select_line() +end + +local function refresh_and_select() + -- Save the cursor position before messing with the view.. + -- because changing contents in the view causes the Y loc to move + local last_y = tree_view.Buf.Cursor.Loc.Y + -- Actually refresh + refresh_view() + -- Moves the cursor back to it's original position + select_line(last_y) +end + +-- Find everything nested under the target, and remove it from the scanlist +local function compress_target(y, delete_y) + -- Can't compress the top stuff, or if there's nothing there, so exit early + if y == 0 or scanlist_is_empty() then + do + return + end + end + -- Check if the target is a dir, since files don't have anything to compress + -- Also make sure it's actually an uncompressed dir by checking the gutter message + if scanlist[y].dirmsg == "-" then + local target_index, delete_index + -- Add the original target y to stuff to delete + local delete_under = {[1] = y} + local new_table = {} + local del_count = 0 + -- Loop through the whole table, looking for nested content, or stuff with ownership == y... + -- and delete matches. y+1 because we want to start under y, without actually touching y itself. + for i = 1, #scanlist do + delete_index = false + -- Don't run on y, since we don't always delete y + if i ~= y then + -- On each loop, check if the ownership matches + for x = 1, #delete_under do + -- Check for something belonging to a thing to delete + if scanlist[i].owner == delete_under[x] then + -- Delete the target if it has an ownership to our delete target + delete_index = true + -- Keep count of total deleted (can't use #delete_under because it's for deleted dir count) + del_count = del_count + 1 + -- Check if an uncompressed dir + if scanlist[i].dirmsg == "-" then + -- Add the index to stuff to delete, since it holds nested content + delete_under[#delete_under + 1] = i + end + -- See if we're on the "deepest" nested content + if scanlist[i].indent == highest_visible_indent and scanlist[i].indent > 0 then + -- Save the lower indent, since we're minimizing/deleting nested dirs + highest_visible_indent = highest_visible_indent - 1 + end + -- Nothing else to do, so break this inner loop + break + end + end + end + if not delete_index then + -- Save the index in our new table + new_table[#new_table + 1] = scanlist[i] + end + end + + scanlist = new_table + + if del_count > 0 then + -- Ownership adjusting since we're deleting an index + for i = y + 1, #scanlist do + -- Don't touch root file/dirs + if scanlist[i].owner > y then + -- Minus ownership, on everything below i, the number deleted + scanlist[i]:decrease_owner(del_count) + end + end + end + + -- If not deleting, then update the gutter message to be + to signify compressed + if not delete_y then + -- Update the dir message + scanlist[y].dirmsg = "+" + end + end + + -- Put outside check above because we call this to delete targets as well + if delete_y then + local second_table = {} + -- Quickly remove y + for i = 1, #scanlist do + if i == y then + -- Reduce everything's ownership by 1 after y + for x = i + 1, #scanlist do + -- Don't touch root file/dirs + if scanlist[x].owner > 0 then + -- Minus 1 since we're just deleting y + scanlist[x]:decrease_owner(1) + end + end + else + -- Put everything but y into the temporary table + second_table[#second_table + 1] = scanlist[i] + end + end + -- Put everything (but y) back into scanlist, with adjusted ownership values + scanlist = second_table + end + + if tree_view.Width > (30 + highest_visible_indent) then + -- Shave off some width + tree_view.Width = 30 + highest_visible_indent + end + + refresh_and_select() +end + +-- Prompts the user for deletion of a file/dir when triggered +-- Not local so Micro can access it +function prompt_delete_at_cursor() + local y = get_safe_y() + -- Don't let them delete the top 3 index dir/separator/.. + if y == 0 or scanlist_is_empty() then + messenger:Error("You can't delete that") + -- Exit early if there's nothing to delete + do + return + end + end + + local yes_del, + no_del = + messenger:YesNoPrompt( + "Do you want to delete the " .. (scanlist[y].dirmsg ~= "" and "dir" or "file") .. ' "' .. scanlist[y].abspath .. '"? ' + ) + + if yes_del and not no_del then + -- Use Go's os.Remove to delete the file + local go_os = import("os") + -- Delete the target (if its a dir then the children too) + local remove_log = go_os.RemoveAll(scanlist[y].abspath) + if remove_log == nil then + messenger:Message("Filemanager deleted: ", scanlist[y].abspath) + -- Remove the target (and all nested) from scanlist[y + 1] + -- true to delete y + compress_target(get_safe_y(), true) + else + messenger:Error("Failed deleting file/dir: ", remove_log) + end + else + messenger:Message("Nothing was deleted") + end +end + +-- Changes the current dir in the top of the tree.. +-- then scans that dir, and prints it to the view +local function update_current_dir(path) + -- Clear the highest since this is a full refresh + highest_visible_indent = 0 + -- Set the width back to 30 + tree_view.Width = 30 + -- Update the current dir to the new path + current_dir = path + + -- Get the current working dir's files into our list of files + -- 0 ownership because this is a scan of the base dir + -- 0 indent because this is the base dir + local scan_results = get_scanlist(path, 0, 0) + -- Safety check with not-nil + if scan_results ~= nil then + -- Put in the new scan stuff + scanlist = scan_results + else + -- If nil, just empty it + scanlist = {} + end + + refresh_view() + -- Since we're going into a new dir, move cursor to the ".." by default + move_cursor_top() +end + +-- (Tries to) go back one "step" from the current directory +local function go_back_dir() + -- Use Micro's dirname to get everything but the current dir's path + local one_back_dir = DirectoryName(current_dir) + -- Try opening, assuming they aren't at "root", by checking if it matches last dir + if one_back_dir ~= current_dir then + -- If DirectoryName returns different, then they can move back.. + -- so we update the current dir and refresh + update_current_dir(one_back_dir) + end +end + +-- Tries to open the current index +-- If it's the top dir indicator, or separator, nothing happens +-- If it's ".." then it tries to go back a dir +-- If it's a dir then it moves into the dir and refreshes +-- If it's actually a file, open it in a new vsplit +-- THIS EXPECTS ZERO-BASED Y +local function try_open_at_y(y) + -- 2 is the zero-based index of ".." + if y == 2 then + go_back_dir() + elseif y > 2 and not scanlist_is_empty() then + -- -2 to conform to our scanlist "missing" first 3 indicies + y = y - 2 + if scanlist[y].dirmsg ~= "" then + -- if passed path is a directory, update the current dir to be one deeper.. + update_current_dir(scanlist[y].abspath) + else + -- If it's a file, then open it + messenger:Message("Filemanager opened ", scanlist[y].abspath) + -- Opens the absolute path in new vertical view + CurView():VSplitIndex(NewBuffer("", scanlist[y].abspath), 1) + -- Refreshes it to be visible + CurView().Buf:ReOpen() + -- Resizes all views after opening a file + tabs[curTab + 1]:Resize() + end + else + messenger:Error("Can't open that") + end +end + +-- Opens the dir's contents nested under itself +local function uncompress_target(y) + -- Exit early if on the top 3 non-list items + if y == 0 or scanlist_is_empty() then + do + return + end + end + -- Only uncompress if it's a dir and it's not already uncompressed + if scanlist[y].dirmsg == "+" then + -- Get a new scanlist with results from the scan in the target dir + local scan_results = get_scanlist(scanlist[y].abspath, y, scanlist[y].indent + 1) + -- Don't run any of this if there's nothing in the dir we scanned, pointless + if scan_results ~= nil then + -- Will hold all the old values + new scan results + local new_table = {} + -- By not inserting in-place, some unexpected results can be avoided + -- Also, table.insert actually moves values up (???) instead of down + for i = 1, #scanlist do + -- Put the current val into our new table + new_table[#new_table + 1] = scanlist[i] + if i == y then + -- Fill in the scan results under y + for x = 1, #scan_results do + new_table[#new_table + 1] = scan_results[x] + end + -- Basically "moving down" everything below y, so ownership needs to increase on everything + for inner_i = y + 1, #scanlist do + -- When root not pushed by inserting, don't change its ownership + -- This also has a dual-purpose to make it not effect root file/dirs + -- since y is always >= 3 + if scanlist[inner_i].owner > y then + -- Increase each indicies ownership by the number of scan results inserted + scanlist[inner_i]:increase_owner(#scan_results) + end + end + end + end + + -- Update our scanlist with the new values + scanlist = new_table + end + + -- Change to minus to signify it's uncompressed + scanlist[y].dirmsg = "-" + + -- Check if we actually need to resize, or if we're nesting at the same indent + -- Also check if there's anything in the dir, as we don't need to expand on an empty dir + if scan_results ~= nil then + if scanlist[y].indent > highest_visible_indent and #scan_results >= 1 then + -- Save the new highest indent + highest_visible_indent = scanlist[y].indent + -- Increase the width to fit the new nested content + tree_view.Width = tree_view.Width + scanlist[y].indent + end + end + + refresh_and_select() + end +end + +-- Stat a path to check if it exists, returning true/false +local function path_exists(path) + local go_os = import("os") + -- Stat the file/dir path we created + -- file_stat should be non-nil, and stat_err should be nil on success + local file_stat, stat_err = go_os.Stat(path) + -- Check if what we tried to create exists + if stat_err ~= nil then + -- true/false if the file/dir exists + return go_os.IsExist(stat_err) + elseif file_stat ~= nil then + -- Assume it exists if no errors + return true + end + return false +end + +-- Prompts for a new name, then renames the file/dir at the cursor's position +-- Not local so Micro can use it +function rename_at_cursor(new_name) + if CurView() ~= tree_view then + messenger:Message("Rename only works with the cursor in the tree!") + do + return + end + end + + -- Safety check they actually passed a name + if new_name == nil then + messenger:Error('When using "rename" you need to input a new name') + do + return + end + end + + -- +1 since Go uses zero-based indices + local y = get_safe_y() + -- Check if they're trying to rename the top stuff + if y == 0 then + -- Error since they tried to rename the top stuff + messenger:Message("You can't rename that!") + do + return + end + end + + -- The old file/dir's path + local old_path = scanlist[y].abspath + -- Join the path into their supplied rename, so that we have an absolute path + local new_path = dirname_and_join(old_path, new_name) + -- Use Go's os package for renaming the file/dir + local golib_os = import("os") + -- Actually rename the file + local log_out = golib_os.Rename(old_path, new_path) + -- Output the log, if any, of the rename + if log_out ~= nil then + messenger:AddLog("Rename log: ", log_out) + end + + -- Check if the rename worked + if not path_exists(new_path) then + messenger:Error("Path doesn't exist after rename!") + do + return + end + end + + -- NOTE: doesn't alphabetically sort after refresh, but it probably should + -- Replace the old path with the new path + scanlist[y].abspath = new_path + -- Refresh the tree with our new name + refresh_and_select() +end + +-- Prompts the user for the file/dir name, then creates the file/dir using Go's os package +local function create_filedir(filedir_name, make_dir) + if CurView() ~= tree_view then + messenger:Message("You can't create a file/dir if your cursor isn't in the tree!") + do + return + end + end + + -- Safety check they passed a name + if filedir_name == nil then + messenger:Error('You need to input a name when using "touch" or "mkdir"!') + do + return + end + end + + -- The target they're trying to create on top of/in/at/whatever + local y = get_safe_y() + -- Holds the path passed to Go for the eventual new file/dir + local filedir_path + -- A true/false if scanlist is empty + local scanlist_empty = scanlist_is_empty() + + -- Check there's actually anything in the list, and that they're not on the ".." + if not scanlist_empty and y ~= 0 then + -- If they're inserting on a folder, don't strip its path + if scanlist[y].dirmsg ~= "" then + -- Join our new file/dir onto the dir + filedir_path = JoinPaths(scanlist[y].abspath, filedir_name) + else + -- The current index is a file, so strip its name and join ours onto it + filedir_path = dirname_and_join(scanlist[y].abspath, filedir_name) + end + else + -- if nothing in the list, or cursor is on top of "..", use the current dir + filedir_path = JoinPaths(current_dir, filedir_name) + end + + -- Check if the name is already taken by a file/dir + if path_exists(filedir_path) then + messenger:Error("You can't create a file/dir with a pre-existing name") + do + return + end + end + + -- Use Go's os package for creating the files + local golib_os = import("os") + -- Create the dir or file + if make_dir then + -- Creates the dir + golib_os.Mkdir(filedir_path, golib_os.ModePerm) + messenger:AddLog("Filemanager created directory: " .. filedir_path) + else + -- Creates the file + golib_os.Create(filedir_path) + messenger:AddLog("Filemanager created file: " .. filedir_path) + end + + -- If the file we tried to make doesn't exist, fail + if not path_exists(filedir_path) then + messenger:Error("The file/dir creation failed") + do + return + end + end + + -- Creates a sort of default object, to be modified below + -- If creating a dir, use a "+" + local new_filedir = new_listobj(filedir_path, (make_dir and "+" or ""), 0, 0) + + -- Refresh with our new value(s) + local last_y + + -- Only insert to scanlist if not created into a compressed dir, since it'd be hidden if it was + -- Wrap the below checks so a y=0 doesn't break something + if not scanlist_empty and y ~= 0 then + -- +1 so it's highlighting the new file/dir + last_y = tree_view.Buf.Cursor.Loc.Y + 1 + + -- Only actually add the object to the list if it's not created on an uncompressed folder + if scanlist[y].dirmsg == "+" then + -- Exit early, since it was created into an uncompressed folder + do + return + end + elseif scanlist[y].dirmsg == "-" then + -- Check if created on top of an uncompressed folder + -- Change ownership to the folder it was created on top of.. + -- otherwise, the ownership would be incorrect + new_filedir.owner = y + -- We insert under the folder, so increment the indent + new_filedir.indent = scanlist[y].indent + 1 + else + -- This triggers if the cursor is on top of a file... + -- so we copy the properties of it + new_filedir.owner = scanlist[y].owner + new_filedir.indent = scanlist[y].indent + end + + -- A temporary table for adding our new object, and manipulation + local new_table = {} + -- Insert the new file/dir, and update ownership of everything below it + for i = 1, #scanlist do + -- Don't use i as index, as it will be off by one on the next pass after below "i == y" + new_table[#new_table + 1] = scanlist[i] + if i == y then + -- Insert our new file/dir (below the last item) + new_table[#new_table + 1] = new_filedir + -- Increase ownership of everything below it, since we're inserting + -- Basically "moving down" everything below y, so ownership needs to increase on everything + for inner_i = y + 1, #scanlist do + -- When root not pushed by inserting, don't change its ownership + -- This also has a dual-purpose to make it not effect root file/dirs + -- since y is always >= 3 + if scanlist[inner_i].owner > y then + -- Increase each indicies ownership by 1 since we're only inserting 1 file/dir + scanlist[inner_i]:increase_owner(1) + end + end + end + end + -- Update the scanlist with the new object & updated ownerships + scanlist = new_table + else + -- The scanlist is empty (or cursor is on ".."), so we add on our new file/dir at the bottom + scanlist[#scanlist + 1] = new_filedir + -- Add current position so it takes into account where we are + last_y = #scanlist + tree_view.Buf.Cursor.Loc.Y + end + + refresh_view() + select_line(last_y) +end + +-- Triggered with "touch filename" +function new_file(input_name) + -- False because not a dir + create_filedir(input_name, false) +end + +-- Triggered with "mkdir dirname" +function new_dir(input_name) + -- True because dir + create_filedir(input_name, true) +end + +-- open_tree setup's the view +local function open_tree() + -- Open a new Vsplit (on the very left) + CurView():VSplitIndex(NewBuffer("", "filemanager"), 0) + -- Save the new view so we can access it later + tree_view = CurView() + + -- Set the width of tree_view to 30% & lock it + tree_view.Width = 30 + tree_view.LockWidth = true + -- Set the type to unsavable (A "vtScratch" ViewType) + tree_view.Type.Kind = 2 + tree_view.Type.Readonly = true + tree_view.Type.Scratch = true + + -- Set the various display settings, but only on our view (by using SetLocalOption instead of SetOption) + -- NOTE: Micro requires the true/false to be a string + -- Softwrap long strings (the file/dir paths) + SetLocalOption("softwrap", "true", tree_view) + -- No line numbering + SetLocalOption("ruler", "false", tree_view) + -- Is this needed with new non-savable settings from being "vtLog"? + SetLocalOption("autosave", "false", tree_view) + -- Don't show the statusline to differentiate the view from normal views + SetLocalOption("statusline", "false", tree_view) + SetLocalOption("scrollbar", "false", tree_view) + + -- Fill the scanlist, and then print its contents to tree_view + update_current_dir(WorkingDirectory()) +end + +-- close_tree will close the tree plugin view and release memory. +local function close_tree() + if tree_view ~= nil then + tree_view:Quit(false) + tree_view = nil + clear_messenger() + end +end + +-- toggle_tree will toggle the tree view visible (create) and hide (delete). +function toggle_tree() + if tree_view == nil then + open_tree() + else + close_tree() + end +end + +-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +-- Functions exposed specifically for the user to bind +-- Some are used in callbacks as well +-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +function uncompress_at_cursor() + if CurView() == tree_view then + uncompress_target(get_safe_y()) + end +end + +function compress_at_cursor() + if CurView() == tree_view then + -- False to not delete y + compress_target(get_safe_y(), false) + end +end + +-- Goes up 1 visible directory (if any) +-- Not local so it can be bound +function goto_prev_dir() + if CurView() ~= tree_view or scanlist_is_empty() then + do + return + end + end + + local cur_y = get_safe_y() + -- If they try to run it on the ".." do nothing + if cur_y ~= 0 then + local move_count = 0 + for i = cur_y - 1, 1, -1 do + move_count = move_count + 1 + -- If a dir, stop counting + if scanlist[i].dirmsg ~= "" then + -- Jump to its parent (the ownership) + tree_view.Buf.Cursor:UpN(move_count) + select_line() + break + end + end + end +end + +-- Goes down 1 visible directory (if any) +-- Not local so it can be bound +function goto_next_dir() + if CurView() ~= tree_view or scanlist_is_empty() then + do + return + end + end + + local cur_y = get_safe_y() + local move_count = 0 + -- If they try to goto_next on "..", pretends the cursor is valid + if cur_y == 0 then + cur_y = 1 + move_count = 1 + end + -- Only do anything if it's even possible for there to be another dir + if cur_y < #scanlist then + for i = cur_y + 1, #scanlist do + move_count = move_count + 1 + -- If a dir, stop counting + if scanlist[i].dirmsg ~= "" then + -- Jump to its parent (the ownership) + tree_view.Buf.Cursor:DownN(move_count) + select_line() + break + end + end + end +end + +-- Goes to the parent directory (if any) +-- Not local so it can be keybound +function goto_parent_dir() + if CurView() ~= tree_view or scanlist_is_empty() then + do + return + end + end + + local cur_y = get_safe_y() + -- Check if the cursor is even in a valid location for jumping to the owner + if cur_y > 1 then + -- Check if the current y is a root file + if scanlist[cur_y].owner > 0 then + -- Jump to its parent (the ownership) + tree_view.Buf.Cursor:UpN(cur_y - scanlist[cur_y].owner) + select_line() + end + end +end + +function try_open_at_cursor() + if CurView() ~= tree_view or scanlist_is_empty() then + do + return + end + end + + try_open_at_y(tree_view.Buf.Cursor.Loc.Y) +end + +-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +-- Shorthand functions for actions to reduce repeat code +-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +-- Used to fail certain actions that we shouldn't allow on the tree_view +local function false_if_tree(view) + if view == tree_view then + return false + end +end + +-- Select the line at the cursor +local function selectline_if_tree(view) + if view == tree_view then + select_line() + end +end + +-- Move the cursor to the top, but don't allow the action +local function aftermove_if_tree(view) + if view == tree_view then + if tree_view.Buf.Cursor.Loc.Y < 2 then + -- If it went past the "..", move back onto it + tree_view.Buf.Cursor:DownN(2 - tree_view.Buf.Cursor.Loc.Y) + end + select_line() + end +end + +local function clearselection_if_tree(view) + if view == tree_view then + -- Clear the selection when doing a find, so it doesn't copy the current line + tree_view.Buf.Cursor:ResetSelection() + end +end + +-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +-- All the events for certain Micro keys go below here +-- Other than things we flat-out fail +-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +-- Close current function preQuit(view) - if view == treeView then - debug("***** preQuit() *****") - view.Buf.IsModified = false - treeView = nil - end -end -function preQuitAll(view) treeView.Buf.IsModified = false end - --- scanDir will scan contents of the directory passed. -function scanDir(directory) - messenger:AddLog("***** scanDir(directory) ---> ", directory) - - local list = {[1] = cwd, [2] = ".."} - - local go_ioutil = import("ioutil") - -- Gets a list of all the files in the current dir - local readout = go_ioutil.ReadDir(directory) - - if readout == nil then - messenger:Error("Error reading directory: ", directory) - else - local readout_name = "" - -- Loop through all the files/directories in current dir - for i = 1, #readout do - -- Save the current dir/file name - readout_name = readout[i]:Name() - -- Check if the current file is a dir - if isDir(readout_name) then - -- Add on a slash to signify the listing is a directory - -- Shouldn't cause issues on Windows, as it lets you use either slash type - readout_name = readout_name .. "/" - end - -- Actually add the file/dir to the list to be displayed - list[i + 2] = readout_name - end - end - - return list -end - --- isDir checks if the path passed is a directory. --- return true if it is a directory else false if it is not a directory. -function isDir(path) - debug("***** isDir(path) ---> ", path) - - local go_os = import("os") - - local check_path = JoinPaths(cwd, path) - - -- Returns a FileInfo on the current file/path - local file_info = go_os.Stat(check_path) - - if file_info ~= nil then - -- Returns the true/false of if the file is a directory - return file_info:IsDir() - else - messenger:AddLog("isDir() failed, returning nil") - return nil - end -end - --- micro editor commands -MakeCommand("tree", "filemanager.ToggleTree", 0) + if view == tree_view then + -- A fake quit function + close_tree() + -- Don't actually "quit", otherwise it closes everything without saving for some reason + return false + end +end + +-- Close all +function preQuitAll(view) + close_tree() +end + +-- FIXME: Workaround for the weird 2-index movement on cursordown +function preCursorDown(view) + if view == tree_view then + tree_view.Buf.Cursor:Down() + select_line() + -- Don't actually go down, as it moves 2 indicies for some reason + return false + end +end + +-- Up +function onCursorUp(view) + selectline_if_tree(view) +end + +-- Alt-Shift-{ +-- Go to target's parent directory (if exists) +function preParagraphPrevious(view) + if view == tree_view then + goto_prev_dir() + -- Don't actually do the action + return false + end +end + +-- Alt-Shift-} +-- Go to next dir (if exists) +function preParagraphNext(view) + if view == tree_view then + goto_next_dir() + -- Don't actually do the action + return false + end +end + +-- PageUp +function onCursorPageUp(view) + aftermove_if_tree(view) +end + +-- Ctrl-Up +function onCursorStart(view) + aftermove_if_tree(view) +end + +-- PageDown +function onCursorPageDown(view) + selectline_if_tree(view) +end + +-- Ctrl-Down +function onCursorEnd(view) + selectline_if_tree(view) +end + +function onNextSplit(view) + selectline_if_tree(view) +end + +function onPreviousSplit(view) + selectline_if_tree(view) +end + +-- On click, open at the click's y +function preMousePress(view, event) + if view == tree_view then + local x, y = event:Position() + -- Fixes the y because softwrap messes with it + local new_x, new_y = tree_view:GetSoftWrapLocation(x, y) + -- Try to open whatever is at the click's y index + -- Will go into/back dirs based on what's clicked, nothing gets expanded + try_open_at_y(new_y) + -- Don't actually allow the mousepress to trigger, so we avoid highlighting stuff + return false + end +end + +-- Up +function preCursorUp(view) + if view == tree_view then + -- Disallow selecting past the ".." in the tree + if tree_view.Buf.Cursor.Loc.Y == 2 then + return false + end + end +end + +-- Left +function preCursorLeft(view) + if view == tree_view then + -- +1 because of Go's zero-based index + -- False to not delete y + compress_target(get_safe_y(), false) + -- Don't actually move the cursor, as it messes with selection + return false + end +end + +-- Right +function preCursorRight(view) + if view == tree_view then + -- +1 because of Go's zero-based index + uncompress_target(get_safe_y()) + -- Don't actually move the cursor, as it messes with selection + return false + end +end + +-- Tab +function preIndentSelection(view) + if view == tree_view then + -- Open the file + -- Using tab instead of enter, since enter won't work with Readonly + try_open_at_y(tree_view.Buf.Cursor.Loc.Y) + -- Don't actually insert a tab + return false + end +end + +-- CtrlL +function onJumpLine(view) + -- Highlight the line after jumping to it + -- Also moves you to index 3 (2 in zero-base) if you went to the first 2 lines + aftermove_if_tree(view) +end + +-- ShiftUp +function preSelectUp(view) + if view == tree_view then + -- Go to the file/dir's parent dir (if any) + goto_parent_dir() + -- Don't actually selectup + return false + end +end + +-- CtrlF +function preFind(view) + -- Since something is always selected, clear before a find + -- Prevents copying the selection into the find input + clearselection_if_tree(view) +end + +-- FIXME: doesn't work for whatever reason +function onFind(view) + -- Select the whole line after a find, instead of just the input txt + selectline_if_tree(view) +end + +-- CtrlN after CtrlF +function onFindNext(view) + selectline_if_tree(view) +end + +-- CtrlP after CtrlF +function onFindPrevious(view) + selectline_if_tree(view) +end + +--[[ Cd doesn't seem to actually have a callback +-- Update the current dir when using "cd" +function onCd(view) + if view == tree_view then + update_current_dir(WorkingDirectory()) + end +end +--]] +------------------------------------------------------------------ +-- Fail a bunch of useless actions +-- Some of these need to be removed (read-only makes them useless) +------------------------------------------------------------------ + +function preStartOfLine(view) + return false_if_tree(view) +end + +function preEndOfLine(view) + return false_if_tree(view) +end + +function preMoveLinesDown(view) + return false_if_tree(view) +end + +function preMoveLinesUp(view) + return false_if_tree(view) +end + +function preWordRight(view) + return false_if_tree(view) +end + +function preWordLeft(view) + return false_if_tree(view) +end + +function preSelectDown(view) + return false_if_tree(view) +end + +function preSelectLeft(view) + return false_if_tree(view) +end + +function preSelectRight(view) + return false_if_tree(view) +end + +function preSelectWordRight(view) + return false_if_tree(view) +end + +function preSelectWordLeft(view) + return false_if_tree(view) +end + +function preSelectToStartOfLine(view) + return false_if_tree(view) +end + +function preSelectToEndOfLine(view) + return false_if_tree(view) +end + +function preSelectToStart(view) + return false_if_tree(view) +end + +function preSelectToEnd(view) + return false_if_tree(view) +end + +function preDeleteWordLeft(view) + return false_if_tree(view) +end + +function preDeleteWordRight(view) + return false_if_tree(view) +end + +function preIndentTab(view) + return false_if_tree(view) +end + +function preOutdentSelection(view) + return false_if_tree(view) +end + +function preOutdentLine(view) + return false_if_tree(view) +end + +function preSave(view) + return false_if_tree(view) +end + +function preCut(view) + return false_if_tree(view) +end + +function preCutLine(view) + return false_if_tree(view) +end + +function preDuplicateLine(view) + return false_if_tree(view) +end + +function prePaste(view) + return false_if_tree(view) +end + +function prePastePrimary(view) + return false_if_tree(view) +end + +function preMouseMultiCursor(view) + return false_if_tree(view) +end + +function preSpawnMultiCursor(view) + return false_if_tree(view) +end + +function preSelectAll(view) + return false_if_tree(view) +end + +-- Open/close the tree view +MakeCommand("tree", "filemanager.toggle_tree", 0) +-- Rename the file/dir under the cursor +MakeCommand("rename", "filemanager.rename_at_cursor") +-- Create a new file +MakeCommand("touch", "filemanager.new_file") +-- Create a new dir +MakeCommand("mkdir", "filemanager.new_dir") +-- Delete a file/dir, and anything contained in it if it's a dir +MakeCommand("rm", "filemanager.prompt_delete_at_cursor") +-- Adds colors to the ".." and any dir's in the tree view via syntax highlighting +-- TODO: Change it to work with git, based on untracked/changed/added/whatever AddRuntimeFile("filemanager", "syntax", "syntax.yaml") diff --git a/repo.json b/repo.json index c1efb21..f19234f 100644 --- a/repo.json +++ b/repo.json @@ -1,14 +1,25 @@ -[{ - "Name": "filemanager", - "Description": "File manager for Micro", - "Tags": ["filetree", "filemanager", "file", "manager"], - "Versions": [ - { - "Version": "2.1.1", - "Url": "https://github.com/NicolaiSoeborg/filemanager-plugin/archive/v2.1.1.zip", - "Require": { - "micro": ">=1.3.2" - } - } - ] -}] +[ + { + "Name": "filemanager", + "Description": "File manager for Micro", + "Tags": ["filetree", "filemanager", "file", "manager"], + "Versions": [ + { + "Version": "2.1.1", + "Url": + "https://github.com/NicolaiSoeborg/filemanager-plugin/archive/v2.1.1.zip", + "Require": { + "micro": ">=1.3.2" + } + }, + { + "Version": "3.0.0", + "Url": + "https://github.com/NicolaiSoeborg/filemanager-plugin/archive/v3.0.0.zip", + "Require": { + "micro": ">=1.3.5" + } + } + ] + } +] From 5f0a66fffee410e4079e5bb961edaa07ceff24c4 Mon Sep 17 00:00:00 2001 From: sum01 Date: Thu, 7 Dec 2017 00:46:19 -0500 Subject: [PATCH 48/89] Remove pointless gitignore --- .gitignore | 44 -------------------------------------------- 1 file changed, 44 deletions(-) delete mode 100644 .gitignore diff --git a/.gitignore b/.gitignore deleted file mode 100644 index 16135d0..0000000 --- a/.gitignore +++ /dev/null @@ -1,44 +0,0 @@ -# Compiled Lua sources -luac.out - -# luarocks build files -*.src.rock -*.zip -*.tar.gz - -# Object files -*.o -*.os -*.ko -*.obj -*.elf - -# Precompiled Headers -*.gch -*.pch - -# Libraries -*.lib -*.a -*.la -*.lo -*.def -*.exp - -# Shared objects (inc. Windows DLLs) -*.dll -*.so -*.so.* -*.dylib - -# Executables -*.exe -*.out -*.app -*.i*86 -*.x86_64 -*.hex - -# Visual Studio Code -.vscode/ - From 216da7e4b7c9b6126092835115de46c2ad9e7b97 Mon Sep 17 00:00:00 2001 From: sum01 Date: Thu, 7 Dec 2017 00:46:41 -0500 Subject: [PATCH 49/89] Add an editorconfig Fixes #15 --- .editorconfig | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 .editorconfig diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..a34d753 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,17 @@ +# EditorConfig is awesome: http://EditorConfig.org + +# top-most EditorConfig file +root = true + +# Unix-style newlines with a newline ending every file +[*] +charset = utf-8 +end_of_line = lf +indent_style = tab +indent_size = 2 +insert_final_newline = true +trim_trailing_whitespace = true + +# Ignore .md files, because 2 spaces at end-of-line has meaning +[*.md] +trim_trailing_whitespace = false From 7bdf85f1b9c4fbabf400e4f7572d2c65237a97b2 Mon Sep 17 00:00:00 2001 From: sum01 Date: Thu, 7 Dec 2017 00:47:02 -0500 Subject: [PATCH 50/89] Syntax highlighting fix --- syntax.yaml | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/syntax.yaml b/syntax.yaml index 61dcd52..da001cf 100644 --- a/syntax.yaml +++ b/syntax.yaml @@ -1,7 +1,9 @@ -filetype: manager +filetype: filemanager detect: - filename: "^$" + filename: "^filemanager$" rules: - - special: "\\.\\.|/" + # The "..", the separator line thing, and directories + # Optionally, add this below to highlight the ascii line: "^─*$" + - special: "^\\.\\.$|\\-\\s.*|\\+\\s.*" From f698e9ecc1ae4d8e01a1da55f1a7beaf706f99f8 Mon Sep 17 00:00:00 2001 From: sum01 Date: Thu, 7 Dec 2017 19:47:08 -0500 Subject: [PATCH 51/89] Update README with new commands Removed the png, will replace it with a gif in the future --- README.md | 80 ++++++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 65 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index b689b25..4574de2 100644 --- a/README.md +++ b/README.md @@ -2,26 +2,76 @@ A simple plugin that allows for easy navigation of a file tree. -To install, simply run `plugin install filemanager` and restart Micro. +Example gif coming soon... -Now it will be possible to open a navigation panel by running -the command `tree` (Ctrl + E) or creating -a keybinding like so: -``` -{ - "CtrlW": "filemanager.ToggleTree" -} -``` +**Installation:** run `plugin install filemanager` and restart Micro. -## Example +# Table Of Content -![filemanager](https://i.imgur.com/MBou7Hb.png "Filemanager") +* [Basics](#basics) +* [Commands and Keybindings](#commands-and-keybindings) +* [Rebinding](#rebinding-things) +* [In-depth](#commands-in-depth) -## Issues +## Basics -Please use the issue tracker to fill issues or feature requests! +The top line always has the current directory's path to show you where you are. +The `..` near the top is used to move back a directory, from your current position. +All directories have a `/` added to the end of it, and are syntax-highlighted as a `special` character. +If it hasn't been uncompressed, there will be a `+` to the left of it. +If it has been uncompressed, there will be a `-` to the left of it. -### Known Issues +**NOTE:** If you change files without using the plugin, it can't know what you did. The only fix is to close and open the tree. -* Version 2 not backwards compatible, only works on Micro Editor version 1.3.2 and above. \ No newline at end of file +### Commands and Keybindings + +The keybindings below are the equivalent to Micro's defaults, and not actually set by the plugin. If you've changed any of those keybindings, then that key is used instead. + +| Command | Keybinding(s) | What it does | +| :------- | :------------------------- | :------------------------------------------------------------------------------------------ | +| `tree` | - | Open or close the tree | +| - | Tab & MouseLeft | Open a file, or go into the directory. Goes back a dir if on `..` | +| - | | Uncompress a directory's content under it | +| - | | Compress any uncompressed content under a directory | +| - | Shift ⬆ | Go to the target's parent directory | +| - | Alt Shift { | Jump to the previous directory in the view | +| - | Alt Shift } | Jump to the next directory in the view | +| `rm` | - | Prompt to delete the target file/directory your cursor is on | +| `rename` | - | Rename the file/directory your cursor is on, using the passed name | +| `touch` | - | Make a new file under/into the file/directory your cursor is on, using the passed name | +| `mkdir` | - | Make a new directory under/into the file/directory your cursor is on, using the passed name | + +**Note:** The Ctrl w keybinding is to switch which buffer your cursor is on. +This isn't specific to the plugin, it's just part of Micro, but many people seem to not know this. + +#### Rebinding Things + +Do you want to keybind any of the operations/commands/whatever? +The table below specifies what you need to add to your [bindings.json in your Micro config](https://github.com/zyedidia/micro/blob/master/runtime/help/keybindings.md#rebinding-keys). + +| Command/Keybinding | Used in `bindings.json` | +| :------------------------- | :------------------------------------ | +| `tree` | `filemanager.toggle_tree` | +| Tab & MouseLeft | `filemanager.try_open_at_cursor` | +| Shift ⬆ | `filemanager.goto_parent_dir` | +| Alt Shift { | `filemanager.goto_next_dir` | +| Alt Shift } | `filemanager.goto_prev_dir` | +| `rm` | `filemanager.prompt_delete_at_cursor` | +| `rename` | `filemanager.rename_at_cursor` | +| `touch` | `filemanager.new_file` | +| `mkdir` | `filemanager.new_dir` | + +#### Commands In-Depth + +Note: While these commands are Unix-like, there shouldn't be any issue on non-Unix systems like Windows, and they should still work the same. + +* `tree` + This just opens/closes the tree. Nothing special. + +* `rm` + It deletes the file/dir after prompting for confirmation. Nothing much to say. + +* `rename`, `touch`, and `mkdir` + These require a name to be passed when calling. ex: `rename newnamehere`, `touch filenamehere`, `mkdir dirnamehere`. + If the passed name already exists in the current dir, it will cancel instead of overwriting (for safety). From db6039d408945c330959195610c09539fc8ccc96 Mon Sep 17 00:00:00 2001 From: sum01 Date: Thu, 28 Dec 2017 11:48:32 -0500 Subject: [PATCH 52/89] Add example picture --- README.md | 2 +- example.jpg | Bin 0 -> 115612 bytes 2 files changed, 1 insertion(+), 1 deletion(-) create mode 100644 example.jpg diff --git a/README.md b/README.md index 4574de2..7d3c152 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ A simple plugin that allows for easy navigation of a file tree. -Example gif coming soon... +![Example picture](./example.jpg?raw=true "Example") **Installation:** run `plugin install filemanager` and restart Micro. diff --git a/example.jpg b/example.jpg new file mode 100644 index 0000000000000000000000000000000000000000..ab5bf1c604cd7c54c24f9c58295b8d4eae5a246a GIT binary patch literal 115612 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

Q*bKntbncAhU zOrM(V&y8@C4?Ns_B`x>zHk|G0w{6gABb&@|&rX<5FYfCN|G|yb!{b>5y+rkf7gibK z`-XRGs6cg77rCoXm%Adb_*wb{4-VTQd`@Uw9G1HV>ZMzS4mu>VGI%Y4rPNR9K2>rS zRT(END|Sk`o<4{!G*cp|ujHpWml}%QGPCu%!$W0)-)TZ}EfyksQ(shd>)h|M^-Djd|F9Kl(YAKG zJy_$BVVc>yqAM+=dYQT8(unB55)}FB)TS4P1ASHsl2|%(+EN{D9gtb(eifMvJw_v^3y&GaDgy(sqW>eP$v)|497 z)9-(k&vlK|X&&Nal^aQ!i%Jf561}MM%lIVNoPxE@B+i$2(YF`>b#ASX-=KN5)Q+&% zyE2?!wWRFF+jy`s#CW1s6nsGHSQxuc zO`Cy_t~2+^6SkJz+5N2pq0_NiRMwjv#;&Qv1NHC!Ro?H) zwqAXKxVANTt-FvUlffm<&BGAan}&G2=)E+h=ToK1fPVEewv&C2YPC!AL|#VdHs>Mp zT%dQV48W2fua5(@$LsGZ0zNqL!+bXBq8QD34Sbp|inT3k)pD^6!qMpZnqlD-+OVJ^ zS6XR|xHPvT7>j1)kCOsJF79m0I$Yh>m}nl$$8qEdyByk}Z{@>uuA7UMFbSJ%lk;HA zt_f7E-3HgYEc4D(7Evc|=j8CPRAJ(Fe6%`Hnd_FVlHkN-i%^PE-d!{PIY2$VHmnU>3rKCa=eGLr^#{3=nV6X8d>~8j0jz>;HD1b& zM3>4I=2=HS(n}>MWi&LKhIz`Bh1IS~_q#;&1c-GHwWUXjZW-a8DZQ?aZ34wL-0ODQ zD>ogESYi-TjX>B0Bd9#S;OtwXjD9dWPCxF`sZfT66=<7l3C#=&xT>c@|Plk;T0<}yXV@nE(>x(W6Zt?7jau0 z;EHgXiFrX+(5{*0g`p-K_&qIM^X3kvKB-uE_M`I}t zTZ6L~U!%=>LGU#sfZ>`vW)O1)eWI+cl3@`d0a>90W*_@JD!25;{91`?vSA8@ndJK# zIKeCp0Bc(s!9nT?-Jm>~bezBETsPhVb0@n%-AWdt#Q2zpn|tvNOPm?_ zcd0k8$xucaLmMlOddc`^%oU8~8XT49BG-vkDYVKfNDog`-9;QN1TQQkC(xZ?e3W0e zi&b$4^HTSdpWiNb0D;=p%}l$+rJfDn7yL32Hz#}l*73n%-IWnv+4|R(p1y7eLI|HS z-!J1ufpm|CCsRgPvdhRLCpV^)LTx?cK`OM2-M-}pg2N;-x8EdK6TIll{0>0T-feqh zgLoL!r&oE%?D7N`R5X1d1b4D|&22_v3w z{)F}~s~%+zwM9APm>mdR^ox4?K_P@&DZNlIaUf*ll5$%}>^_gI^me4oNsmru2Ms)9 z%$Z9SOzM%9NclAnwzNP9u$4C0YlqeBaF)N)odVubkBZh071R~Jz*&Ww%>#M&H!BX) zSXhtt3wrb_gHxxm9n+kkJ=#NrBu3mtwXrK1_o4hfz-RMm?4?eLxJl!rAIr8icjc)n#Fz`PKS%nqyI$PYT|CP^0%naeZN(#uM}tft+~+i~fW%vUJf^ zJ=`@cM49@wKAY@wA~cpOQC6;Qn%oJ1Xxf~8#Y;>6Thvu6cg9lD`1LSnbr6oeAR zNylozofPL*><2jW4p%s0^cfRdQdj)f9xp6FSzcGEH+jvp{X*(;g!K>_(Gz(%16E7n z29=0}%MRqFY4VAAP8FHcS8C43wh=IxA<9Bh_Ar_hCn9}TDySl!BAXFH%BMRLdatWk zoFjJy0c@~JZX7veDxTbUn6BVwk2fe2>?k=g3c@73HFG)386qdcCv=^IOufzBaKZKN zxjh(vu$)8gu>PkoOYabe+dKiDAJ=w%2(JdLbEL6%suB$9RwnJa2p3!Jrh% z<+jVqtaVNZl++;1o3oa7l3kEl`q%ee)d<(x?e8p8S~NuyOD7tr+Yv+)soPGC2=PAB zB>IPrYudda>1vCaEwvIR%h$)W_JsMbAUC_YbocmRunN71!b` zez+Z3$r^LzeF3;d^{b$XtBKhU_eJc@wYZr%w03;Gum*qc*y*(_4^nz(B!JNx*AW6S2I$Rbyt{ zr!gieZXEUq0E$*rcf8k2T2N8rA=1NK-`P>DI)`L)5{1|v_|@<&D4Pq zXgijp=Y9uCXP6Uuk1Vv|T2P{DHyf(!=Ses|+(nunjg#jA??cuo4;dQiqI!e`W+D;*N0 zDV;MCaa$T0gpxyGs{3a$myKjkbL4Lsv`$y0RW=!U`h@XbX4f8{sV!8e`2xMf{82dC zBJhHtq)3fL^J9E;ZD*9@4VyX%8l3USQEc%~F7Z#P#3zdR%RLcqbe=_*r7)BYQqLSw z_e#oYe{o7CXjr(_nTl|nZwOJ41Mr3-KD$K=9+wQwkj-02`2v3MAhU84A3Mj%O%p@b ziDsMa2Y(r9D*hAPzpQ$+Ji8++Y|7`x?^)`f+_pP%J6R@=KSJn`_IjZF&xDlR!wt=n zQUJ$Zsa(31h=|B#&;sq>R^ATN4-bT%)_HX7R|l>f2#ul*4}{c;W5s7w?6&n8p0D*o zKbMpwzBrM8* zMcfSOoO&~^#e3U_qV+XP0+%hEYAptntCE#WGxHC6F-d5R5^$V;ln0=Uo85JUb5VV?1trZ}nR( zv>Y7kV}{eM0xVA}{)mcqOg1ty8J{M%h4}?0c>&_e+gQ`6_cR4=ZKV1Kj22^`)rHnyh zxU6S=0nkW&>8oN~)o$LLmukjr9;fte??dwN00o4#N#yLvBCMhm7m)(1_MHX1g<3@0 z0%cddEA}DWE%KSNV6?WeEqOJLO~!6DqjlT*BW@O4dFPk9+wLeU`{Mm>Cf&|O zTu|a@A0AroNtG&W<5K@8mh+jHJ?dSy7=J9 z;j#h2qtw0M+=xUoC-hGA>)CR3z!ibK4a+LLAex=?SOYm z3WV9axrldt(@KRN)5g=g8cPGo`RyR^4h(uo3G+l_+6N((=0@aB*;$OY%e_SNhMi{1 zG_=J&l)h(S)CDWR7))+&m9OEbQ2GGyZ8CGSB+?fEc{Z3vlkjHwXvt#STp&7&45k1$ zX#lpg1eab>IrB2pbq(*OGuV)|(qv#q(&!qLB|(kKtheG8D?3eF@HpU)D=pu$M=B{q z-`u+O`1QlZwONLx;l5nnfzXOss#aTXW}@NkUgcc=lIZME9>%+cOZKXuq{@#*G&we1 zT_@$GPmmO}MXao>9>4wm_doge{~D3$)3+2(r3g-SOK=jL-j!|m*`7o=grE1m(iYo5Wt{XuPFek8V6rhmWrv68!r5EMOI~&hUz2)GQO5%Bo zhkt_mmsKy54up*3cPbBe$9ZnN{&$*hU2OB+k$0^0KqMxz#rG;D+W&E7DFM&4p0H|1 zqBX9TaJ10p-L3@e$Y#K1T_I$n#j5OP@e#aUiVysc+6XLkvj>M?t zyb&AF(NFDKu38I!!7g?9h&*fRUo8SqZNlQz@E?=gQ4Ki8N0fs8A?G#VgaIFiluUJ$ zcUi(LG}7s-!JeUHT`_YrCvS|Iz>~!(%@VvPHWBlbH4&Tu><1!>L2o^D>eR1F$#oKHV+Kl zAQnH;w}lj+%_?e1I@hbKANtf&2lX5l+2VM8d&?=(y<;7>jv>G3*OZJWGP8h>U@Ta4 zHE-P@EFn3y-li5ma`DHC{l->*L-H!qzpjvQm^8)#!+N@3RK(bYBwrGTU#@b z_qp!!0>M9+J}gt(RqY9XUVPv5?We)X+tX-7|)O148N)pF~@|L zn)o*R0Qp{=iT@d~sn^o{z)XcQX&0s!fHhR8HhHQOoKfzpU>PEdX&B&SZ34X3)iNEu z6#VTf+JY*blmN>ikkN%Qe;4k@z-JLT#*ZeLGTkh+^b1FQRO;nOHZS*f&Yr@Xe_b{ywUo+-a^%8`$;ay7{l zDWaI5q5%ffOAs8eGc)hQlKITiS3v|^i3(H*MF)v<8urgkAZR0#$eid^rtCkuqko8e zRMza@Ka70j_#6nu#(W%kTR9C~e3gBu^7v$4$LDr6XiQdqA$$N4dr|dZuE|?hjrU{ zx=ygD)N%7|&F*t2Ot_Wn-a#v!v(*N5$$vc97QXMycef1)*CfL>`_>S4v&$LHI|(SA zcbfNT0=>j(&rqQ1dT<%V!9?_QH`HC(USDv_%wSf%33q^-D`o0K+NSY0!Ex07S44xl%fJIq88v5mkV@gWVzJU?RRuCmZrz!EvRKIIt1Ja z91}AWw#!J*q~;RJORDtC8`_qt*}v@coUfW;{lr$sYFKm>VeXe<&Lz-WOC#x_N+$NU zqe*}Ka?RNl?E7L;ibU5AVSbmJw4SaGixb8nLo!hDbp_0f<~&=k?!1}-d&{cwVrqvf z*H5X6-h(`Ynw~pc8`Wo^I*cMukv!HDoOF8q*RD3^&E_w*C{|j{Ohk<`g;_C_2VmL3 z9wJyK(*cDrk$taTndC57E3PW9#U|?bg1W36r51wO!$?J(V~CMO9u*xK5;4=$VR`K} z(rLps=?pgUp|hz}7TE&KmU>v!6E)w3raRVBR@Y#vfdzW{_GiL+W<}E+^V2t#wp+bIgO*2%!K`XK{HLf51(g>*?0DKh zzH4Dh?)+=~G;Fp2q0_#`1{Q((NRN5fOiO98l*^UVT;dZ~}nqK=+*6eE9O$s+f46Pn!v_m|jww1=~+3S)>vh44g&wI}> ziu>p>H}RHvR!u#hCKFJu#iu&UyCTF$eJ-eny}EP^p8gqbXj{Y~RR@lacFk7|AvYj| zHCh$sW-PRDhF!RG$#ZZ%)5f&lBXA80K}1*($nzTd`Q;H7@9n1xM|@9}Io6MVb%LCv zI(?#O4Z~=ziO>e_t0{0BraI$evgDo-SibvXJtv~TI!L+Fr(gg-UfI0q;MClZ|0734 zL4Sy1LmT?i;;AA&UlWMFURBnOJo9k&JjS%S9{+iUt90oSkqN(pUU zH8&oi-IRNM6ob+0J{UWmI$2vU^a-|1{fBhe{Kf0wmi`WdQF;KU*bLb7T=s@;liB*n zeETclcLfkYShMP~l{KX9`i68PWFtFuOvbTcP&^NQ+t+H8ZVfIyTuaVB`-_|F zv`YN)YjJ3q^C}pgQUt#^umlFMdHI=Hn@(P-8v_4e_AdlcZ@pXHL&D9J#tL4Y(v34S zUXOqJ2jBdsZADZu-(HpOx_Uvt9<=ps4y|`@Wnbi?KOA#^!*>&>731}f68=Ht|4c&x zbXyHR$k>hl&mEKmvsZsRN1og0YId@35%kHEblL_YiN7_Cawq!jvQg?-X((%^N7{xJ zHV^XU!j3;}dg5W76Q+gHt~ZaGue7pIG*UqEfTr8`kWox2_43uEBC*J}gzjKH8PM{z z)P`M5>YfLuqDhk1+vr)Yc-{l007%{|Dykv*<>v4#*iQT^v|pY0)1Ho`Z&sWfMWBP> zkZPZb(RY;21;42Ut3c?e&Ch=rPpy>eNO*6~u9g32Pbf96(lCdZUZolZSlJB@WHn9C ze4k}TdT!Gya6`_|Q97K|E>_4_bs-c6hmk5&CG~D{^(wqbU#qC4KUGuQBC(us`-Hw#q_ATji4~OHttMTYE>Md)JtuvA6Expo%MO$d7Zd+ z;@r)S(yu=_+IZLxMMn^J_AD_FQ#@uH4g9xU-N9Y1=c4Q#Y#iB|d6FTGeqYpzZ11Q2*} z@Ts*T=%1MEw;$I7x4&+?Z1SV`s=hnl_OoVYC6wGB`HM5lcx>@%iN#D;S)yr!hO@IUM{JzZt-`v2Oi12Y2H)?VUVoR!+?6}>>s7NoML66Bv?8$!u z`ajp=okm_*t#n55H6=t}?_J>LChUwsGmFJ!1rbQ+idi3!E-!ee~TqAB}UuSCIL-C%hA2xMMX(q1IZC~xM8>S)8}#9)EdcUdM9&$F47#+Jr6tPSnCsQ%aBOM z`C>G0(cM~1-m9Fm@JRl;bUEEpH{NO@#Xa43x5(!O3j8rg3wQsN-) zK{e-Ep-Eu-wX9yQn*V*7(vTNLQ+e}~Oh^8BKFAZCKW(C5F~JBPP^GZzWb%AO%bwPB z&nT2`D>4>f+k;DXVU-I=WPltqD2f>7lNZF$HgR6^?~lwof9HZ+N>;H~ztdfeE;y!S z?2k0QzF3aj@J#09Ez=QzwL^1l(Ejx*RNc0dpth2}Dt(fp@G8u5vGen;Z1*^0yQG4h z$V+FF#3QTAp6V-q7+JpVJkR_a&0J3O`FiLj!ylX>j5KxOfpTLNC9ow-n=f)c!II}9 z3(@aAjp(^eTaVh7!drb%uw-TDt)@B{I;cJ#sC$IY>q5#|^=)TdNlxp`V_7)Y#WTFJ zHoa{Fr`3E&O%hEuOxtmcm53O}B>bbWh;`Mc`h}DAr2yGAM=`>}hxVO$>x4{svuAK@ zlR;PtI7d=-IJt7_PIno(fQFQ@B_5^P;oPQv9`FQI7{2i3TW1m zrt-~-nW~ra?CYPHkN-(m`>zWFI!lFns6F#tSg#=`yyeu6e$=qxh?ShkMulPU;q*m1 z=0*NLk@G*^d?A!4Di|B~qR3DDU);TCSku|sH|p#eN5wXXsPvH*=>teFZj~ZI!UR$v zKv1d>iu4k|Jt`=DC;_F8w2*`(QUU}BjPzna5<)LhLJ0(~pz67z(D~;U2K_N@BrSkk5LwlGg z*V*T!>+S1|U^w#2G20J8KYfSDH;6b72ET)$v;lz{w;sFDHUf{*hQS*@7|;chq`S=BIjRrL)$$<4C&`bFjt5U5V-_<7W}1|I^8r(X=OQ)6Y#jf zvp68rGrn!^rvB3npk`uHIEPuqe5*39H$B;_$948#VN*y0q=ii(;Pn1{iw$rc9n&i_ zC~SL3|HTOKd_`CPjp!$waPiahbZfe{RX+JoGTLIYGW<>Tfsv(_^>rQpSD}$tx|~jC zY}-@3wIU8^56go`%;pGv=U=to3xRG3^HFd9;n)y=>B2_Pl#%H(KCJVxrgk;{ zYUwZ|*|bxk<$OtZhp(kKH8$G~SHZ@%f9SQ0(YMjBi9#Qhm~gF6AiGP9dI3Ak^Zal{ z@)JFC&8_5(gKLQ&*XmSvB+RUqP^HAu#leJUS1h$B`*(-QViK}dga{DYZbVHSYZ%Zg zeqzMvd-sb_r`v#>`;FhqjGeMWPqkP|$_Aq7ai084nK4wzFHow?rFmC6RRarQByztuq4g2oYX3-$SidHQI zXDAj&QXROMn=wiT>8DxB6!dQD^}AU?**92`1dO1Opd-Gy0FNDKyu>k+008;)#?Z#i zPz%)O^o4}Fuy+~EFT$b8St|0|i*l?>ThI@i?NJFN}n(>Wk5 zcLm$Q!12Cp?3R3P3_GEFT>?rx#^XGNe;-z=%81%_KAot3poMZda3Z0hva@2GJcBib zZ70D`yf6EgUKRd24-Sw%840`hTyi_$x=PHNTKQZHT0U448DxJ^G7LUSw?$M-De1W>Yag#W0(b$OZ2WKnZz2gdp{FPQi3R-}&AnG$Vb zflA8kD@Ly38>_i1wRweGv9I|EC&TE0htKwm3qI_458oyuWAbxQCsuvOrMO*!-k0Ys z6{PH{k0;4~(QkkLyU6@sdu{#=D*u1J=ed{BS~YYV(n!|$(;MYmzgT(1Dh2f|61&9m zI)cQ+{k#Kr^>EXRTjfzp-%f#<1;*hQj07+mS|a_5deY+{I#qrx$6wj$^(%kmb;`c8 z<&k5X6|v_M!2#vitgu!c2(&QG0{f{WhQeMi*Y6}p%t{Qjd^>fYfn})UQVPB&YaZHO6|moAT)|bA6LWfwuTui?>bNaT zxXG;U?b5-0?;H3xqvl-2TR4Nd5V0})Mp@D>aIaC&-qtd)ahsCpV`o8W`f84>^rPS^ zYu#|NEXO62y~r{uyR4g(9vZMD=b^;#_&EIqdbG5baf5M&sMGP zi-%zTdK3Kz3*>q*E|OOFsbfy1t>$h*dy7iQIyA1sQ>9S36A)RXmoV1Yd_RWROggB3 z=Lsy1ORVtP?~|1{uBs>B-kDph%_ze*oBN+#jEq=poL~BLR`9 z&4>}ljEnZQ$I7(uTzA7Yf#E;9;DMDrZO{n#R&rQTP;b=b_Ikfu9Mev37H`{)HLSzx zWUsNKxDX309tLI+_oqqzxqS5~DNc)~-BgHc6`#qdAieO`MBZoX~P)R6GxpYUk9j-1sFYiS?yPeU_KltAfwa&w7j5jDp+kwpY^}rEf?cjV zJzI-H*(mLUJjWt_Ax>G%VAip5;LfZ$a6&Hf)&iG*%8GbU?=H~T0U#ADG@|E;y`7joiNV7uu5GCc9 zUzV6D7QX6(og0zr4r0dEhEbV2Mwee58k`?Ak{fK1JAK;uG61YnGKZtc*ZSA+W^6wN zfXT3&2^DTD2kcz@q&>vCJeS>E_#{)sPb41Y{$5D`*t(wQUTQfknkXH#RgV%aXwkG? zlV}l$x-$rgw3R{>hzdYf5f@F7N`x0RPBknVwfe#xm*)Y4i|IR6u&ruE*WAVsoj?3q z!3AGqrW@azN<;0l;LLXmlf!r|_yRw1#$t9^0ceX~E?Yh1FTJ|TenbaElEiEpRqW9%aX@QfUo zU)&ly%&qLW;214DLR4$&Zq&#+&%3|y{8FTF5M>F?xAH)9j!;0tesBGhSB*1C`v|74&ofjenaG z|KE6*o%$iyVWoDy)>ZD~Y`}Y!vmRTk9xM(|CYVN>7L(8%O$`%B9G`}>jC*ExQrn}& z21ACg3+W7NMOD6BWY9=kX7vK$~mKCQ&g9luS!`X0VM&EvF(R)let1~!Dwc_cp{pXx+#;p4pTS|u+g4-V7d3$12 z3yuh1PG`0!*ZF<49S$51GYCPg&(B==i5Mo0}}b*cqtBL+nK3VIt)oz*!)x}%gdEX7jci5iAq{ppnf#h6UFB# zVgomK;nUF_02|00r=-+9IG+(^6AIt1YLO<5e4_sOS<}D_S$oF0bKu!c-`-KB)@dY< zk957W@S5=Ll&s$7{;`hfEIYB&>Rw)$zk3@`LE)BCciv8-bKJuMmiJfcH3t>atdC=9 zLY+YO$e3GYCp*gdc;F%l%}QLRHRD*Am?472Apx1{rJym0lZibeGL+xfGiI zkuqYAQ#wrFqtVVWAdlaU z>8Ge{rMiB#zwn$+V@8Z<(dMn%R_&;oze1~Oo~F&`bs8;?m>q}?1%AmpLEy1187oTn zB+d`&R`{FFc_Q&;3)Rq&5DSWIu2-aNL1%cpJ_MU>-9P>HYuq!x&T6&Njcc{W1unb@ zN>V;zG{S2>JvVRZ+1v;-ZQpOz$-KZ#mZ{ig<7ayNnM_9Itllx{B6bj z+o{WsL)S|zww&WSNd}VZ;m$^3Z*quVYdhxoiFMC?);_=Hs(1ax=|U~exa!&GQZLk! zVdCWLP(oLxKVYTg*(7sEt7q5kUw?CQocdoZ)L_WSd9qTg%SJmWTcO5B6lF37!=0-+ zuUifU-X~#0DOMAI{=Wpf+csX6IY@F(I z@^nZ&GylTFtDpC5EU=3ONpYDq9?n){-l#^QpabBTgTIT>$Cx{coNM-q{3=&WU!9Y< zvBVt7=wB&F#xd;fWWKv)YTyLQ!ClKfbLJMwF}PA#sC@lkHQN8g=wS;uGA)w>a=5bZ zJr(-~zXD1G(BxMA1G|?93;EqC>E=dXcT7x7yrltWq}rS>*e>g9-MYub*dy&9y%QTV zHTwl_VK0ZODBNQ70E!yJHfI<;H>L)3hTi%b*mJHqyGQ3a=99V>eeB?@jMTsm z#jUQVEsw9i`1EJ6wb>&%#Ww<1SEx@WWo}Flc+6pVOYM22^C}hjg~xY%6P0&+>OG_h z=AHu{YNp`Vl*cXJ_6|bALMfdzI7L`@J~j7+Bf#{LzHw02m6r;=PHUA(4l&xmuO+7L z$;EXG;9R%S3VUj3=QBB#q$CBz0+}LH=poCU(-SB3O>Bv5!7~q3j?N+_Bwk zx2NVZp+&r5#c?AfA$Re*$M9^?7Z`6zOQsbG2`VtSrvQxzK z#w@c8{n!+xH=}c)=^?I}mQ)vhiwxZ{Y%mhcD8=s(Efxb`J{Cr~TkwC|W)J2VqQ~3h z+QW?`;u;y-=s%H`Ji;TU`u3W?WIYv8?l*ITrW6=gWC0=p_%Gdqj!n;OBo|y2oJuP% zfs;vlYI2`+>O)0%3N;lw5xs*AUYjYMAG79?D>j0rF8iYqNe4iDo^<3<($L(jV*IXa z^5kx%vtkOY=~iq%%cP4VW?gzU_$JjY>fe_lb>3(F1L7gzoJ32eWgMK^pJEhxKg`WM1s{`%76F6Sy zXqwb~$6{pRBS})}GO4a?bMCjKbhD^hGgdB*!!mz5EofU{2?AzavCDN@dbh-~zC z%g@U86*>zf$?KvgEPA$Uq^B^uz6WE@_soa1gSTx{OY^Np^c|9LmpWckV%7x%-(H8> z&rxq*K+A2Pdz=t2Cbgm%RdOU5`OD2tuVAi}V`SlUB7YlIM2HBKV$;qcrjccGBHbaMd5Qo=a>c9CwUkrSn!HzK1 zLPhJ$qbF=od?^2ek_jI2#pK}HD4z>aBl2qfTtVKg zE--9rG1_Bg#jkbGL$&xFuuR&if+FtQrkJd~Tiy6^e@ms|On!Utk^O4)x!@*0m*MLI zyZUQ;YVfRfsdLa+6csff<>TF=mK92a4>}UrWdd~{gC8nsP3J3-A~L5=RIq0{j8tX} zb^3N{$^lbQo5+}}Ba3zH*3LU)8D8ycw22#8t6;`9-S76_R zJ<1f(yQB|LBn|k%MaW4a!GePu!t4z9X;}F%y!0AK1>alZ`Ytp1=EIsfxL9zuW|4DY|n|-RuAYN zn%*TJzb)?*`Kuh|-!KbJUM-F-Hs4NNrws6%HCkf^oc9LyAr>9c+{;rfa}VZj{@2iy z{`wU2433`nUTJ3D4`Bpubb3^#264uHe9~OuT8NGGogl}A5yhwFI1W-XQ?+Q%>JH!} z@@l^BkcEX0lLuPP49+O;!G-+T&(5C=H8iZ9t;(2co9E1Ag?((IWx%U;?<-rgr|5y3 z1Cj}_myhoyzLSt~OQ*0fHDPRDDUH#1Ye6V1tBM}F8#V&<(}@`|a~PMY82w6G;B`M8 zL^{S)ChTh~!Er3CQC)*)@4+=1{OJnE>`hGRw#2ghJlcP_@u z#hInk$J|dIBAu4dhhuf_rBf92TiYW=V3GlT*pY~q>rtX%)z;>yu6$ zIzt~?#n9{P+-S;(<@L9hT0$Ju@MXDgM_1hf=w+FrNA)={RgDI~IWga!E2Ai|k>C}R zS)-re&guZg+}d2U6VZ>`LOM28dk_3&%t@}nVyk&N_vsylkRw7pSMN3uojEusX0^Wy z!2IUZ0p{Qj*J>&@#{{;TI>!W?6tUt8S}iq7kP+1W1ZunP{EXYJlNWSrD+lDo^%404 zg0Z~(D@a|FYlu9!f8VPZ5k2{j2s?Ps$AetWd``zEe{L6cB@BQS4_HQM1`tL_yN-2V zIdrUAhf3JUhpWN!ih(5~9@d)MUP?!%=!mS~IyzZOvP;;I95+>an)t^0A6nb$72JFW z&P>`)_vpI)cyk^a^K0+bd4cm6CFcUX!aKAi!UlDg+NVRXHjV8)yVIza&9IsY=f2;( zDtr#OnvdR5ybts$$9x>Ql{Q0sc&RixJJ!_nNFag#;!98X3Z$29NLoc|dC*{S!U~-g zfpo8Lr$ot~xXPV^I(7z*#QAZx-u>yYQ)+tp_%LM+)i}CgEdaFqJe&oPZlsDA5L(G{ zUS?O{*(hxyR)cq!DCw|Tx{!T;#XG>h(?+!eCucs4xM%f_D>&@)3?3M{p!Q5!ZQmdrZm}6iAcQcPdeGf4q|#HW>`f$-W}81wAyD`UP78qgRz=bY7bPhI#1mH zW1Hsu{;B(~j-3BUwu;>=`CQ+;=S~wZAyAH1t7bYZW8Rf7Q_-h$s_v?=>_)u6%uYiEB~rSdbi>>`s;`dKRnX?#V~1anZtv1-6ke zzPx$IcGU}-92r13KUTtGS|h#2$nqJEK#Hda`1a4)JFDP!pX$Xx1Bt$nFNeEY2L|z0 zqP6lSXc;#!fr^3-q_rr3!9tkX=&t_b`;5_o6v!|8w>pN&hH3ODf;m9J0q#~1PiVeH z_6|zc-E7xl(? z9NEW~J~fhJ2(>E)665lI&awG5Vn|{<(Es7LQ}BPd@qr#G>k)T!`8}QbPO^24$I<DzsDl@NU zBD@hmRV#w-mn_^D1%%&K#UY2b?FQdxb00k{v7_GM659$Ax%@$JJ~6R;#3-MfV4fe_T^^22^M*M1H`@#>XQm3FGKTpi`MRS6{S?(6r5hW~YJwItd6{t(xTG^WJ zT6vqB`$MDredm~vVMY3d;8n$;kn&l(DX~I8HwxTuUdNh_Uj_l@NZG-q47+fq-IHNR zrd!F9+_0jbeP*UBaoir62tz&(k+U>ri2^5uvK$Up(1QI5rjj~(nVi#QTBw!!o~pDx zfJ#WmoFehtslsF#iEpPC6xNR20SRq`%i;d2W6)nXc-M} zR8A?D(ZsIp*3h$32KYQmn$fvyX7H9X$=|aOxVEw0pbfSz1OhQNoPM+15Uu&V;KvH~ z*?}ZUdlmoUk!+O&kbw6<+g6yYk=A(gg^4PW^VB?f`7#^IYrl@PF1kzX>xDqQN5Q{J z(}-+AutO^ak*6qmr#=7LrmjHniVe;tn0@{FxlS!<21+AyJTY_-i=JBUgJxpE5QG7( z-pSz5r~+6ABD-uFm=&h2@S;rk?J(tFR@$Rd7lA95nrwJU0H5nAMiEn^t(z-Rk-CPe zvHQSgz;}tojIEr5gakzImGVr8Zw@%cvQ7d5O&`?gQN{m)#(5mijeONV7!zG@-ps`j zKMi|_|J0V;*P?}N6Wx%g74II~SgNBK%Hsk4hTE&#QqH!2e4$ig0x+smK<~VaT1Q;Z z7H!%yI`lIPY?Cyrf<+xVJv-7gs^KN7P-d&_B0{3WM5%-{=brI*$R~?+hS8HoCk$%* zstgO9>>Y@Yh!**hd?~S=+1^P*>p-<}_7u5_Qgf`)#UD zrmNlVJ``1*u*nA*k=CQqTNdz}H%|hlb-ZnVy=s`V1nELg(EfFR7uHpeG!mAq?42G%(O}n6BAFKzyZPIzjz%i`R{n1xB9Pv zK@0f;iqV}jI5%y_(7OC|fnb+P(?{)eO$}B1F`?r4HXdy3S{7??iV5n253hy0Jp07uLf@^{(oQnR)ldC}N{? zZsTqm4S{(bcw^tUv(iFSa8esa6$@4c(@&Z}hqHy)Ij-F!-y)E6d3p zZ!FCf6ysX`?$EYW-=dQwa~20qZ}2M$`a(Y~#)o|+kB-RxG3otegw&KYv#omMWL(!c zuja`|8w)M~Qg4ziY)UE#Z$?V4HJNL}wUCLe6k2xzZr!l_*zFzT%W}geb|T~XQ=$m{ zcCP&j8>CR$4~x{Oi5a(t?fT~oIy;*l$H6+Hy8g*=SAP8T({yoFwP;_6`tR_3-h`E7 zG9PJ?l7^;19r5^Pe97mPfpeQsM?*z=DFF@+O`J#9V5fa(`gE$g7hh9Ajt?Zl~J21>=W{XuWJRil9*8`}pP8 z(GVCUEy9pq-6Q)q*Y>IZ;H|4=RsRofT_vfM8nE-;?i>LUwI2M7g$!vw#Z?%MSPIpo zg7uKY+v|MZx_7L{9u&dntpog(YPs~f_B?A3;?;Vl?|#!3e7pA5e-Uq8gy*qsif_zSn#k z<#yW4*Cfq6K0tp-kqXB^ouRf&3pqD8kK4!nGR|X97B$Bw^$vyI;8rO)?(tbd6tJM< zYyLpA=qP)Nlkh5^dBf29!6R&3D?HfS(yJslr76E|BG@J%5SCawL*Bk+LvOrZb+>Yk zW9r0G)8>Zj!8Npe^0}T$obo*B=!}P;)2_eRet>6q2L$*hHdIHE+w)9hUMJ00VNO)7 zR6-mC9m>^tXW)$H%Q+D$78zKS^y;sZmbVr^w$CQnjS|s=LM%Q?EBbQYtJeLTsVj#{ zQt<=X@)l+Smo8lk@TbVh=UA;33=h??6(|PdUf5^Cvxf(nTM{s`S(c12lkOhUYnN9Y zRG#7FP;C`b9xs;1a}}yg1_{2N$&Y|8Tde7X4RS>kWFxK|%L2HWWUN_e0j2goSL zdQOK$2t@%0kCf|1lpWz0=jIRZz|6FWd*@Rbq~!@?oV-is$V4VVR^&8J_SzM>WN6Qqpc=w6SrZx`-QUPyNa+H?Vv|E?aD-?4?bo;g@e;!~mKPnQHWi6B7j4>@ zYqdExI53Af#(wF;twjl0()hNv34CV+v?==c?^iAUE~x)As{b!lqYzO+BU*C)WC!xx zfBXHj*L}Y}x1h8ES9Z!(Gim7}a&WRU)Y`=PKwNh{z4QhDkGC>pmCA`BddjH=t36Mf z%P3QuS6)dncb^@pV*`FamGNX;g3F0|84`4($x*kLPI2isvk(zNtk?_LuJ}Z5k<;SW z=SMIQL)E+$j?rmRzf!%S5HX9p@;iwc(Ku6k+3CH71faL-Kt@a=%(Lo5U~&`vqnabx zQz*SUv)VhyxHLN)x`H}&vDY6L1o{iC2o&;SXX$0iSSlt~5+n3;@ zClV4d+iO`Vhqn`Tp3H;=6Fb-G=o#z&PJqV><-{*pdm*dysX1N*p}T#U=L*4`>Q$;dgy#KAjjeYrrjC zFFCN0g%-r^3jS1fSuo|@rrg8RKKF4JjMXav&wD4ws{)c7EWE~jx7}>Qf)a zkmOE8h-ECOjiq6i3mUENQRx-9tJ+G;>k zs6!oU^QK-0m9&-VMh&%rJsXLS@8>pLMU+YwdM6FET$Xp&I54Q2jm>f_AGlcAAtbr@ zbXaKw^Q<=zJ5(eYcc!SpejdNmtjB~lG?N5hWIP|yVgs)7FtsW10dDr4nwbU+3se<_ zyF_4(2}e?DcJ}p`rDpuCC$P!J|u;S1Ju zr^Td$(bbw8q-r?c>2SBoB~Sae(h+1blpSx@Ym3wVP}Tb@iQ z6+d}Py+zR-TNReishTDKb}iF;$-$l+TDYZ2GvatqaHuhrP;nBSOgFaezyGEU_S`a5 zNyfkd=@y(uKoml^khs3=OlMQW!q>*4ojW9r0(E97La_i2swgQIL9RcoaSz=cRv1&d zEp8Bqf-rzb1ZQ4DOWK@Hi@+A$2a{%@22Gxn>4cVF zv6l>r@t^h2fA^(nAsXJ$(qOlU9nl!vs(+OYYh+M4G(LK$c~so6}`V zF`Ok@?~)#z#cy@SyuDFgD*Qe(FE?YVz~bk>JVgH2_BsEC@sj?;NqD+vy zAH`nHdT#Q0#jhi&DF;%@8*V<{VE{#vb%YODrtmcCuu&~^4N6xq6L&A#_203|K4?w} zQF5BtM9AC1-cl=;UVL%G{FtKb8Z*{5`AdBure`*KgNWM={Xa zbr#!|)w|Z7MsRHY%}(yo3*tgG)yM5XG60=*MZaeM?dHACKS}B6CW2spu)ym)LJW>H zn;2~c!i$wkARGD|?7h`2ER2;Eu2DY`zWe9$6}z`%KaeYhQw%V43~^Q$BpAJB2|+Q7>N~Aw^4zlBiJ}&S zRW$9k^s8|uPUl1Cw3THruQ4FBijoF*9y|=I+)?tKLJg0u`s%-YvyfxH@F>QZfvDR5 z`taO89(3@@0|!pMhlhZ7j*$FYjC`X~!<28QKIf5Y=cOc!I?fc7tXRJk$27^E9s72Q z(B`676#Syx2}tx_fA>5gGL!8lq ze7Y|TMHoLKxodW5Chx*y&gY`lZ1f5~c<_51Q(=!g2lzscWzlG;r>D>F-K<%9V04CP zhzv~eX_y%ks~FfPN9Os*oc=BFUw;{X|K6D#{yw75olh59zNR$fcjvo=_GzNhjfJ;c z2nPjeBWN$nJNyE7wj9?-h3?GjUnZ8?_=gV#Ank9ep?ePPa{37Qy5x>CfW?w|uG|0E zA^-I=|9@Zo4oKd4($eUEaIG*R*Twh{>CaQdsLST-cdY2;4~QgjvhSns^y_zFsR0>A zzqjs&uZsJ6_P4|J`e$1_@v$bG8DzO9JW$T!G$NnlQ z7{!{K8{dRCZ0Se3j5bh8O{cefoju1JT3*b5_ED+!T0y>JPQQB;JY8%>o#v>T zHwwpGB>%9H@BEl4EwiRb6={#=nrtBX2&nrR_{?8+e#tQ=+2>`A-}@D)6dmL=_0-bV zo&~Lh29Q>7yJdz?+a%@hBlLVItj4jQNI35JZdd6}n^0_bL}KK6o$4JqssE8oARcKH z>ji-&wq#wlSP5B406mnNL&UPG!n&HG!eG zMk!8J(DzmPI<<{b(qUSCt;Kv30pxl3bK*UL-KXWHekj>F?p!s1#D2uK{%Hw?J!jrS zy=QR{ir~rTt8h{h@0V)s;CE*Uk5%VF4fGK9R*9A#esN|^m0eQB5=bvpcY%POunZ4X zA6WFxi#`%7KpVTKsuD14Pb#50mDPZH$`A_Rd;}V&TQfYB$;ye6ST7)Y6LDpc*jl%} zm_IyfeVRWw7P1|F;ABzsO6)hHxUPwm$LI6g%xPn%bP5djc$d z_|G3vHhxPe`D6Knx40^iH&SNl?63q=oc`gzU%0?1GRjVYlINE?muWWjk#pIOPud~7fvea$6{2ru<<9AyZ zi&^DKfm`ui7oFdiYIZU(J1OK3Dh^RKBR1|#YcQLTsAPN5ga!YX)56V#N+XL( zuf1P-sdXsyssvb=Yn>otzYHFvR9QCO@UVUnF)!xaGx>7nQ(Ydc5$@l!5-}dGgpY?S z80$abfC%e+amZqc0Vxff=8@hlYqNc$-uqmkOf5_F(_j91#Zb!XFur=0=o2{{Tgk4x zjiFb^q$ozL{z)_mO50M$4}VEWXd)Ng!tH_wjIQex{d8pE7DrB>EX6+&o8G(4+lf{( zs$EYNmnNB8(E!Q`YMHRvKek^8JVfte_i{%*jq2iNK`yczzWBkYItZbQVBf-b$iEQwYzYwU|=GK$c#q2d|VaNk|=yMN__n8!$e2lg@F{^ zM^XMM04-T`!HVP1tS)lqt}So~XUo+yvk9Hj?NLH|_t{!}A|N7f^_~li$jlw-R}j4T zqLc<2!ZpfJg86tFsLu+NxuAYcsWU?{FpW^q4&qru{9B$2*11{hC4YB#ukMn;8+-l7L**fvQ|>E^AL$Wx>GDpa zx#ukGnwP>)^7GWj7AV$LUJ%Cd&Www~32_UB;*utg0%U#`;o9Dv-s|xs7q8fJ6A(o6 z{*N$ilwUky9WHAX=B*~hw_J7HWxf^3@~IoD$%c>pk*Huez7Ep;l3sfucgn`ZSHzKx zve(B)EA%c>%Ts2dVB5u8*y}gwMzU*#KXTEkemyO6f&D5LABZ-z`U%r#N;NdNCX;7W zW=J#?&l!eNm0t!}fpT%(8KWt%;-aX4&BYu=@ z%mhjr$(|opeWh|x1NWBPKk3j^c|OSbQqT8rKb;0jRDrg4)yKTeJ`owPW8PsTps56r zcUZrw>KHO%eT4z~WmT?dsm-c4_YO$^Rmm_REa=m&@Cg0)*w)b z36Hk`Ia0VaG}s(@HoC}9bmI4SOw|(i5Ne&WvvsY*8v!nw-8<4gAg4P%K!86SRuUBJ z!s8F4=CEV3oF|1wIzJtmXNk6a?;4CAOMJrddza3+1S}gf|IWq^#pG|Y$~V<%7|u@M z2O$YDC-E4ubo0G0EEraR@FDVd4zAN?8q^amhi)Wxcc(2r&pa)M4CFbsaoXG z>Q$^B=dsl0OQ}I(mP-ETE&(69iRf)I;BHKvk+wj!Kn>)yc;%i_Muw>#hN#?8@#A%dbB%k{t4qX?2+Rbw*(Qn zv@C8kq0&|({n0K~4VZX@{`d8{!qym(K{ zAH2LsVB~@3DIDszQ@=%~xf-RtIGs(#pl*eSh^Fh^4C3qY6_j^9C3MxVmLk4^>qqF%su{e0s1OJZi_0e&fgSDv zY2eX0no-V22h|+&f2P|1%&sy^NJT-fGZ*!qjy)axlR*?~2ocLc5ZtOqJj0$Da6*Q1 z$hOyEiG8A(T(RYiVbfCob+W1Nh23rHqqi8&0C=4j^nD&PEIQONhDI9cl^s6zGE`F# zT8@S~D~%E+cPT>E3yOOpFs#ju$TyLxS{4u+8w63&BdFzk>t^)`1!XS+&h$jYA}nQZ zp0_Z%!>!kKNM7;`2`NKO?k_KE8dG;4Wt5H6B@2Bss~U2*J}pTZ4`_dAxFe`7E#E)n zif3)ky2myAunvXfQX^a_g1X4O!B~?PZoL6*BI9EoVz>|GMcfF=0*5HjjXZNrtEw!p zF<~vjD3!BIK#jE?$OO+Iv?dYZVeT2SG(w}AlHGnzqtV^*RaCA|(wAx6_#$wc?|q<# z-+c5g_jFvGTzx%pBvPduht!{OZ#~os3Hr2hu9TshQy(34XRAlSqH`dYJm66ibvw+a z997@^hmS7T+%5M}8@#)}SI>gxR(C}Y?H2eYzqiG=HE3g2Pa!UoZJ|fFRlU1HelVT>il-<*jz~vo?-yL zfX$&eLKOP!2Vc4Z#_TccWIuI#wK>v${<&6+z!jWWIV>;}M(kSSRY$FJI|nSQn6`a3 zgD%Vc=fgRCGYncH$10CP(9(`PIO`3L3Q4Q#({u|oVWDM_l0@f7JU~7r0AuG4>w87M z{#8N29X?i91Z$D?y!{nX<#&J|M0F^S<=4%T1AoZ(d=a=J$tEoNcYz#@-`&3qG|;=9 zHl|nHFPC~vTTc)@akU>byr-y@(6qVyc7%cuz{9fb%|QzHUVfg*@Y3n&0nFpQ3siq~ znt+FW-tO11{Hiy;2=I(cIaymN8hzKWTG8!w=Ry~z%S~ew-n@|0CKvlC&Rk~6TnlP^ zPfZ{p@q;6x=XJ_W?^2K;6IOyXn0m^vm_a$GrCeDTzD1J^lZ(y?{#+9i;LSHUBjeGcYzIBB) zX2r4bRFz&MGyoB5_WyAA-cf0->%M>XUL`AinZ#&p*@-QJB~h_!_DZ5gjTj4{ptxd< zAoh-Bua#(GUlug6E-L~8v0z76G-?D(EP#q77VKc`HRiI&D3 za}36u6W(_|&-*^#=kq07fOy#PbddNCn4@mTGb$5fh4{xlvV}NhI##kbS`kyiL5xu= zF(bWJ1O$5mQ2b7Xf+4e>vMA+YpTiB9RqPbq%DCQ^9Cec&flI_S=4N`M$0ENh*gTh) zw(t!c+?D4iSL>#S-fD!W2&*u>A7_(1U%eZ`$ltc>Wph%@+L!O?)Jsk(c6B>W+;)`& zZ{V~g*S)mCP>kOiL$_`)P$5H*6Q@WzkWv8`f2ta@a8l`B8Kn!8#y*Q1S?=a*X$B*P zJrcUIS!&mbbo%jDH^A4a1<|#mr-zf3kM4h_OtI#y)JrO&{eSfD=>B#ESYhJDb*#=H zWx=CV&6fL*e>iO^O24t1Ldsq7eG+_(i@CT8oZo2t5a>UPProkvEA#ktTYa@>HQ0zW zPio+OD-#ASGUE{d7!!DDEV@K9VR=hl>`PwIHz^+8(*n3?cVTf&iv57 zYdB=;${1?|lJseg98!r(@xA&r|Hbyq5(!PulIQd+;(?IMrUem_=S9*%4s9EqaZ5Nd}zhZ3dH_7f#d6LTf@C2rU5KHFlQX+&*U4U61W3{EwAyIB2kuB66t$3{_4*E7(5 z5!4HIn4IiLv%Wf&UliZ&LLDn#C~Y7y@-pg0$IzDfjjx@!B4%WdBG10=O3N^#p@?;Y z!t=>9;P21~-hm{E)HmTNW-ldCKD9ZPl3 zPUsh0>5T1nX&z>YMCpPpiwyRlsB!dsLEeFL{s)2-Wsm;#)Z*qHg}9&piS~rloTfMv zViLy}52(mzbwjQf_2rx@aBUcxgH@M62!ZX zEp%a13hRWnrwBk^<$)9$6uw+*;~ZZfAxN0%`g>uU)ke4{y%8rGYB2;ul?mzt@QeL0 zft&N(Et+iC{9g*X(K#);k>8K2W$AU!idW=?PJXRuiG_meMg3`@x@4v}NvK1z4ScD?lr8m(kC+^BFKqc>F=?rBa; zs2U~B9MkMzcXjvTO1eS?k3#pFQT6-geUJvr`CBe;uh|0)To`=B3O66DBDe$?DDxi=P?7#Unk zrLnUfudFG;2sP!bEaWU7Xr|)LFW;*Pr#w9@tHs`5Zyh)^>i*k!T}yvuAByEv_bkB1 z?9cWo+sAdYp;53GZ#Sk2$o#nsQmNfFB}Va@xn|Bh&n>?tkQ_E`Tli;woZWKh93Y+> zED4OsR^fL9MeactEx4stHVbVv_k3Y$&m#;fFLZLLH7Z|EeHk(mjStZIdg=_4{m#I5#+}@ewlcM!(b%%5)~Nf| zrU3Y-xWFs~X>t$YpS;DNqz$)bM`*N_-aM~uHY*BxqrgTgDVdK&A_pwJX~r)fY#vc! z`MQluuM6dzsFAPLz~L^8%D= z$=I%~pmnzZ*6ciGw%Vo3clg^_m1M}p6He2b1GDk*15b?8ndou%3qKr%w3RoSpjM8t0aoqs*`&Arr*yA$QP zxRnd%^?97nSwG{WS1cstG%N>ROtC^Ew#Hj465^K`_}hS}%%{3btEkd4 z@%5{v$5%qECjR(Wf7!ns`zMQ!8vCZE}3@vAWFq0fen}ET4XM%E8V#H$u3T=P(>NEnA-yXc)QWZIeXj57eSW z-qH)+S~aucR`jpsn<^&OT*$#*4Zr)aeVJdyPH1lH~~i7(kzPUBH5JU>=p3;09F6c0f~)A!8k z$cy!N;gxV!U6lF_3CSl_$;NiB2%G{VGs_COKkEUqdIkAU6QHR+=%*H_miYXgl* z!17AQ>`II)0iKeeq8ME1)3_Rx?wc}RYWO>T*-0Aa&T=ku>h`v8sl5Ai`n1*Wf2co) z?`gre#@q=`7)mOOk|w&cAluvkG1#}u4;v(jk)o&_L9^Q*YAwyDl$`YQYw!}H0GoP` z@o1nv$wG?{3fk8%Prs-4OH!vGU%3~)oJFF3QbOD3Ks9s>D7n&)JLS;D$y*(`wZO^} zUML_nEfq?)7O!{-)vnQYKAFM-pUvg0+|3N*dI%<2lPQjW4tViw-5|Mc$ELbDJyMgU zBKGarQKjo$Y_}N^6=lE*KGy2Ag457Su&2YWIo{PKJ@V;%gXY(J?|Jbe_T6ERQ8jUm z6L{sAkl>PuUlYB&ZjTvje_5r6((^Ne#J}a~3`O(@cp1(LCQE!fd<5*K=4bKS8}1WP zxlI|*Ev?Rl2XH@6a5vt81&#uZ|p}9fqJR@$&(hWs^ zTuQoWA@-`HZnHhWdX|qfQ#hWaGT&$fWL=AO%g8LDepkx-e%rAG>mNxCXuH3yisK_c zj?!%mXkRkmpOwd`i~e3v^sY_98wcN8T{V=;{Zm} z9+WUNX!kJaQk>-FIy3tdotO)uXqFVI{q`U4V9V$A;O%e=AB7?R5}M?9;=c-L~bMwUrG%?h`)kblZnU%8|M2VLL?uLsE0>(jb#*hEcpHjrm#c35J5>!vN#`c zR~NUsX)VT?LWn9?cMO{|?0AZOG_Cq+S98s|+Z+Oxmv?#7KO-XIR{dW$$CF;4pHwbr zOe(2zuO5uN@s_9APX?=e?!HEp7 zp&u+XM~H*Tq!BW#*}Z-y6w}hAkTV!i7e6ub#fqbmlWMJHh;}EXkTbj8@NpCNY!L4H zEyM6f@R5h!F{nJnGwptPe*RBAB{DOT574bC{p%^Dz#5S=wgfMPT|urxDQA9AT<`pU z4%Pn@o&O&<;L2`roI)cQu)ftsY(q=3X(*@ByyH`WqJOtCR#b{L1JN};=&?cVVwacp zkfr7T1Xb8dLwf63QU4c)L-P(dJO1Yna!};^MhUu1Wp?bLD>jGYC$P;l-Yaq>JRlzb zy%@o@x+s;{Xf{ykOQW;|Pdm#PFq^OxUdm%z|05q1@n>hb*QIgCm7AB+!qYzbt(O6_ zS&){1)=h?`g7f^kIqzh4$y;Gll^vm>6E)2j68O*u zsxVnleHUx5#0i*JQ8*HN)%Cik1=1tMvXZ<6>)!!Za7|?DgqpY`1%#+2d}YMf-Ls$t zFfv2@FvUYdL?BmQY~LT^4^(DVFLqB<8ujbmES|3ecpTzK_}OPUL73uK2$f<%CmVopDLuc)Vmt);>nvTDn1RWM0=bQ+si#kW9o7j%e(P#Eu zx-&kX4F9pd#y_FxDB!t>O7_GK*Y~N_TI;%%iuUxxR>k}wj_@upTW>g1f z7TySyz80hZopPMk`N{odUS9V+u$nof@8gQ=b$}VdnCA~F$;?d{RrHZFD%5=GQ*y=VEUFO}2da3* z#uwl6i!G=Q#MX!G*Y|C?tOHWNtB)fuuW+|8SvzZ1Heq~7!6T2@5Bu}U49;C)LSKw0 z0@V5+yUlJHkKz1npxEpD+=|KjlhBfSm35Z^)q91v8x2Z#TiOf)XZBN&0|D5Ey_+Iy zcB`(XjT?It`7^8&8Arn;6POCFf4*`d@Qr@BECA?mvJjattAHd=qdCt|O9{a94 z(1a>gFrw{}aq%wvM&sjtS6zdUnsCIMGey4w2781zv+zLDI;|^#2Qi^lB%`A~yn0pe zpYFyDIcWW<)*^k{3fh|~xx0gK$S@~2c@}fZ16m(HP}k7Pm>@BTf26EI7CEeMPuPqI zF8cH4k4IV)n!Z^7t-IA8fmd9f{$YV@ICl4%_K*PQGrzSpl;C_AJ>aABl2E`jT{~2u z;3w5#Fnr7*3^>JXKRkG`SuV^&_i41_hU>`)f|JQ0X_QbZ9OiA`)rVSL0|E_A%v=1K zL)svzmWYUSZf1aCqu)(qCE?_7i|)%6Q?~Ho#VZzgR;Dv$S;8M99QJoXJ&c0`idV>s zljPtw0ihYqO6fnPadzm580LdtSbn-v{osLs4vQO*UBwI{hL`n>t{$?o9h>jWA^Q8kIt%R z4Pn%-;l_7IUg5RnZ?sd6MTgp&A9eltKc3hBxB}J({f+IfDbySDA-g$Q)z=-N3becj zBCR0tPY9qCb!J5{775$k?7|OCwl{N&TKHp0wUO#$ zXkJo`(d%p}i;AswMW{H)FvWWu3l&zarP#W+<{J4KSs}6qGhqQ1sh@>aiR(?j2T`BP1UOY7R04UP{F(7p|T?zzuATO z{!GmyTLaf3K=ve z1xC1d_2YgB_SFc%P{8nt&n-4+AyjSIjk>Fv`n0a~_wBvY&so9B7pZlNlCKh}LIcKE6vg4- z5@ThdQIcHs;eptp5YKgo9V^dBWMKi7G*3$k8HoMR`Mo+t<^(mF_*O1aYON`o)O|%B z<#)1#OXf*UP5XC|%qzVhFK44jp>~c4OJ^EAtG+3;x{3ubPd3l=+=nLb)hOU{oN_^- z<$Wq}rS(J4$D7g`HMvWaT#Cn%tN+O22VSdGOYZj|;gZ066J8>h9P8@fSh*gjSS6e? z^R^{o5y6gGnOoy3sGLwnS(II2aZ|PKXRnd+5<`@GOU5vyG7nRduoJ>c(t0y&m|IGV z$+gsW9It!rm!<6K`oU8T(r;V)9$T72woBJz|0P8eJt&b*x0arW|D$X73clxm5~GF6j2y5-KNhEZkUAkZTQGVYRZ!yi3D^p3sX`*^!mJ_NUw9?;JYp zqZo}AOWD&Z`1+ru0nHhJzgAmVhxZ2H1{sbHULAx9?f ze9W6av>2Mon%5h^s^GPKNu@n691WEV?)Sx<)n7LbR0wvE4J;KpDC`x7f?%o0i$=7e z5>cSJQxHL`p#qRYk(fuqRKz%}7bRg`b`XmI%<$kwb^q5>vW`2Uc!_8=Vpx(^{cj}F z&mrEbYPwgdYx)et-`87b=xKlGGJ;i6ASg9-emmaakWl;cTs$={vsGZJuuoD_X;}4v z%_B?OY6dAKm8@|`nedwC->hV zQ7<2MoBjFm6jon~a3!x$37%pH_Oosxs4Y1PZ41-0!Xy3Rj8A z0W|9m70EMC>MrM!Q;WTKX2%OxxFfbahN*R^l~5}*DXan z`4Uq(D9Li>P@hKW{=|cwV-le=cOQDZyWobZps%I7zvM*#cDwTYm;9{>%J(5!Y`ChD zmN1tIvcpSUOJr{(Hh^-%ILKHd23}jVYuRBn8tdf>IAeOrr1l8xkJ;B}0N6aYucvH$ z|9_%ce{B%Fuky#^nFNO~U=f?}MkS0SoRd;nb~;Nuf~M~jXjb;l+~@4n-N~tyJ?KL* z=b~XJwW6?;-`AY(bPKL~_cMQcPn>2TsaJo5{o2eO9k1?Y;ygkDJm-K2vG=so_w!)U zmop-L%4b`&RD|dvv}Nmji73;)r~W{uNj70=H|9cKs{SiBMxfz#YhPBh#=~dMKedz` zioI3!&Di$INf$m9=)hfNQ00gLL0(jt<323}DzAulZXlM6-&4_?~ya<+T0rmb7M6GsxV|qN)bv6P8kV;SU#aiy4KN zRIAg|!yB6s7(oNP|2TQIo0MNlu3tnuY|vSa6WKxfmaLl8iMr%r5hItB3~JAteHncr zpSWyeHum56#P9zTpV)Ho_F%h(h}+aN;{bmBc*s=Gv)blo{XgOyA3sBmo3%#0s&|i8 zV>1rL@*UVTJ$S({EGMzks+7J~$=<4$?qi-QwJgO)S@v=`sPox}0TG7%c7cxhjDn@u zr8%7Bq)t0Jr*}RlWFwjL{FXZ8?Ph-EoZ^ks&$`U0GOWUL#jb2iJ?fJ$wCNHzpVl(( z6Hk%*Ufgc%b(IJ{#MdZ)LJfcK-nT*Sw@1hhR_-><^#(Nrpqdz3O+#YG-Re6N|7l`b z>v%h0{hd#5++fzA57sc)LZmc^VQ8XkG8s!y6ylHJG*DfAKP}~rLO_Wdm=ict0w=B$ zz!xKM@~_%|UTy2t)mOnSPn)($qV^qklm_j+P1G(mC+j;2_|S8Zy5@ZhVe8CC|Nls4 z{*l@kJZE{xJIvmvQ(}k*Syy%=zMg80X)gu?*Pli6zn;2mDsth`e|+=*^y`0RSbIgk zWL*+wxtk+N$;gh@03~4yp~J3ji4u)OPGojT&OEfyHrCk6W+SgCSggtG;NI%T(&esV za|@3`VyN+B*c0vZ@-2b~Vs9d^?V1k{_l!O&395OZkv2q=A;V0k2pzZt7L-VX(I8^t z!%4RzYhkW6`2Apu{0uX$#}Iujxy;6vx}_eP&uvSz)MgygLmic!AuH^!r!0eCn&ze$ z50_*~0R~K3m~bU2N+gP!9BF?L*x;7m=PT6a^DG0ktyxivqGGeSA*;B4&J-I~ZQ>vX zV?|4iwe^*gvIvHnt<80g7(=HO=O=FOZ5ay^goL*>t^gN*TGhsbrz=x#DH3U2)1r{d z11DjXcExQ`%AnQ5rY2#sbp^g0b$NDofeAJn!s&8>EIuWRQ;gWx0%|$0~YzH}pjiy<6SqJkoj?#rrAF&M|}^gI0KCFnbb#2`jA~sSXgvIuL6t zjBXX)-xh59l!HMvp5M8>u{V=Ke_s|`0!I7awTAcLf4yb_3`fIW(ybvPu=E|6*m236m@+vB`MWEI#&o`=I*>ae?F{ zmiH*K8%2YVwg>DWR{o&YNsz54`ZK)_%q3qtDNfxDDYG)kMJMO7SB~^sznA=JC1qn+ zQoC|F{MCwr&9~$b$*ZIZs*7fTZpFU$-Hqm#w*Y|pOy__i?{!)HyV@_1HI4#h$B{Ab zzybqS2L&yKvhw&HCQ(o7jUwtuE_jX!w_ua{Qb-<6W5y7(H|Gaib>_jamsR8d#bUrs zd!(?3GI2;h5RNcyqT|+f>Pq_nIC&_78EuuEGe%m=`S_kWLSE>U&b{I5Qstr273RGt z{j@U6qPp|ehwdiRTXazwo^v{3z^J9D5b_=PYWiB|0`g+N_HM>>{+{Zhl3j2vACx_` z%$))<*zjxmM$=V3P{QPxx=es~v{9XEbBluM7_`Mp>h9;RVl}kL6&#Pb4uNP!5i^KK z!@`PFPjtMu@%Rg`&k5C^EgPfB)p7KksS9z5c&4iEop?6v4}zox8Zs`3=aUTmQr)mVcjZ$w07I&01+N;W*)LE#(P9Ds89Cseb5G z&t^#5g@)=lN#d6WoWoc7_MQA|u3VdudZVjpl{u!Cw{&F|L40U)W?)9FF<5NECUrMV z$7WEI(|Xe4OW&b0IVVHwFsC*kV$i{hRy$hHZc1%NNjf&SHovv91wHj}SPM~qm@mKB z>0Lz^O}{%q4U}S`j+czDR@kn1QToUjRu&Tpg>JwUYQhroF{b16^_n&irhaB#n^G)r zGG{!N^6H%lI1;%YTF%}4)Npxz z!cIq32HU3HDd;WWunWorth*pBr}(=37eU}MuT1m6ay z#U@NBXdh-xdXc<@SZppr;3;mRX#}54Cb#A+Q!gNM#d7hrr=bzC)V@kgv$EK2c$H|g znG7I5oNOflP&ZphP+)4LNv*?#TKV$87ap_YV|D3+3anf+ulip~6#@SKiIZ~sbs3gG zdgvogY{K9EJp}$^3>ErF{f{Ic_pa(e16|(P_qsYIoA2*87WVRhu#6X;0lTKk$=EMK zH7P)iif{Y4rT3uxZNquHJG2^)+@X<0c~V_Jot=-LcxGkRgO?B9)kCqN^OXe1+nr2O(Oy=3fZtoRd8s7FyU7nEnj!ifl z5bIz>35cC=($|`0@k2|RuFe$%w*4>}zL4-mQf@dX70In5zKM9NeIdS)@Kfz%5xn<_ zT!7)p*^cvd$h7wyvyL>RkcT&MORZS#<)uXbe6hcUMWm7|tF9DXZ(zWz9cs)q*0z5_tln zM64CXoB<*eSZJJNH*lg*-pcm74Q+GI(we*b%T>T&orNs{47!4-uya_K_Q?pXOSSP- zAb2LQcejUW1Bt^?2Ih6O6Fx;FbOq%QYL&|y*SiiqM&@6zZxJq5H_)qn-+H@dOb?u0 zkBE2^!&(ih|LYtNP6woDE|%WZF;|nL;IA`mtZ+kfMOh%V%nCMf04XV>w^8a^OMb4R z<%6j4zP?tLmu&yZ&OCToFI)#khqr2`9G$l_@$bMnF%fO=oIuXyZ);B=#!r{;-@!~9 zlowOogVTXWjLk{8%IRQOK|sYWhC8@2t){Zy?7b4KM888V_6l&_Nm>*0`Ba`>*>(oT zH-LgRd8JSlk(PQS8e9@Eryc+&^9CI-7I9wcKIum*0+v-1RI=-2M?x*nbE0#r; z+nZM)Kayh-4sEUiw%;oaAD9PANg;izDhA}r^Eb)C)=Z+)JfjPm1h5;&$orgO0;2U+ zF`BaoG~IByhL*3z=6JfM$~JOoVBppA=ggs?z&eG-4Lwj$JaZ5t!bggPX)RxRrH2yl z4fvxgqb$}sBPp}SQpYnS9ap?o+u|H=W*1asQ@Q&b-NjI98+pG32i{saTt!G!o5TB< zS^?{*fKIAKqtJwGY5fxI-tnV|8$WI{3p>7^(s(`;_Nq(sV1eXDr^Y;ZY^lK8+=yDt zI(oJjov`-O`(QN}+?_F|esQ7SW}=RFf;VIct{i3*Tq;{y6E#Y1w|O@x7*0so6e^89 z@LKE%g9f#St`|G*{Oh#*A0JWw6Vm+44)~q3{Uz~?Ple=}Ks)tKSWcL_fA2!wBmHA- z?e;9;PS=8dCek|k-PRYi&&PU*D;C#CBV{%@>EMe%Gx+jCQY()3<_sKcCa6576t1KN zUV_!_?=tnhnfE&cw)8QF15{Fifdk?pd<$4|xEfb@<{)7rxy|TKU^MKlX{9 z@k|;iw8xVId)LGDo2Qni*l$q>0n6EFzp0bG(AGU=RRK=L`)bYGXE}X3gnF9)$Di~@ z3a1$#QgH*2180rllq)GZddoo|^Dx~R_XLunVkEy?kJ~hI)n<4((nqewRCRpn>nYAR z@-ZY`#JxZLl*GAXkJ39aoq>-$>(u`B$e;?csa#E8Wtfc2NX2(Ir-pZ!&OA1deHFZF zN7DPe!qe4Ru&yyvewzfT(VmPDK>dWsR5dT(0gpCdHAiD)?d07n=2H#OGdMZD$1LmR zdso)z>SaSGwJhX`J!h*UM=&p(0|M|I{>+ewO13sf{E=Ng}P zM-9%tO=VdB-oOa&0q6#Z5=juDgV6NYQa2FJbD@ibnJN}_!ant0fN$m!tZgz6du(i+ z5c%sNYQ8e0z3w<7KLx#HnqjdQVo@B$kgs;{&1a%^c?Njk?;3Sqf2 zn5RUtw6f!2{m{y=l^odym8Ljt^9jLtPb;S6S-Qjz3Eg{Tutzm;oO8D?Gw_zD)Yi&6 z_!3%j5K?6b{59#Rx0bXyg5+8E$b{48p$guu8=n|6x1p>IWYi~SrH*Uw{l$~@=J40fpLJXwF7Xw}kJ zbt6hm`w(aT`ptP6dcoLwxte496vGj!9FNQo?NdZyIkXB=dsIfM^pH3>sf;s+>dI3? z_f(KJo2voyC1^S`GS28kVSB%~Olbu{y47iLrK>VsvG_ANP#Ct1+H*frEkw;h6=vp_ zVV3$rCNrqScpM$D=tOu$&&69AM)&Ede@M=WV5Nsk6l$M`Rf*wdj0if$i<}g(?9x>i zzkNNN-)}ZKFmoFQeZ;+*g9#Z91XTMyPuFU5@?HuGAxWr;F|13H7-Ym57=(I$=Vt0W zppNmlnk#HLdsHc~v$8vA75kLq1cd!bCR1oR0Dql zuCEPc@4@K%_s;o$W^1!6ks8-?I1BC7zY6EsVEsAb>~Sgyw@;GPX*|``}XlPoCK_NkM2ie`dJob*U7TVlk-C zIN(;JSb;$cObDpgF)&i_vwmK7X5N?lt5E3?kSyupZss^!6U^tQAz5{@m-kdv|E8TLTCR`#hy*?6e2X6}>U1-8cofMfA%9g&RY_7n5ST z8hW*~%Ec}{x3$juJ(rNRnGAv~r;WLDVIh_LMsQU{BdE@Icm3-rDoeZ%TWXd*zIB*l zBf$24qBLd9x33PlixWAGDcB=7m#+8x#R5@SVQA6xkn^?3^3#`6izLEP-5?^=u7IG4 zo0C_hYPMJcT8ZJIb92J|wCY-ENkVFm4=YrqYLwCR_%o-*&>(^vw5m*!6isrFoiSvz zP;V!A+Rh%*mVb61Y>2M5uV7f)6c~$&mkUNbDRqNaH0L0;7GVq9YbQ~qt^v~OiDGRd z!}GmPZ5B77k|ToI4c{8WgsuDO?-$qtbTdv=1TnePa!9;6_FxtkzZ4#n>D6^5(LWPq z_aw>vx_f%CGAjeFkM0psd}dNS?zlZWyx6Isk-ek}O_}@~2k5SlQWu>$_Wc-RuCxfG z^K^ZK9U*|}61(0n-nvB%0Qj}y^Iz<*#73u3lX04h=BVpleRfGaOovr<$F-t_&&iU9u*QJ!a zgA|)y3bNj()X9`9WLwOz#O~)1HwP+PkiLQrBy-qVT`JS7zvNg&O@E5LtFF)aYXCf& zoHj<9=mqzJUj5$k=0bF2kgu8aXgW0A*jYON_||4p=tX1x3>f)*en5?g|*x z9N%vGVtxH3nCtzs-1zG1b%#4|iKTC@*`}fIT3eHPEAj~|6+$d{M8QOpYspNa(+$99 zNWsV68cZ~Qy{_5&Aw1VVg&fl<+*CB9G!8k10x7EwkJ%>;iAR`3QGeWIV$esqH1jlk zvihS$3Eh5QqKQCv$H=wRNV)6j-7unkq@sh>`Qk;@DkZpe-`MlyGWU0V&c77GORn1$ zq$Sow$JhIw%g)y=Q_+cdUc44|UUSIhj)TS7gN?Tq9`p~`>r7=J)T9I=_QKTv@9X#O z*|WqlpOj?-d51D6S!WE4y8I%utWpg(Z3%Dt1}0{4Mchc$Y;)*x4Ds=c8O^V!;xzdg zn0?cCCsukn;W4M1yEoa7>M()I9ASZ%8Ju_aWZD zY8m!BS&i-o9-KUUlP~qKUkgVs)*EdapLEKW{VmEELjG3hw0@zg1m_kLS?Ll{-v}#A zfBEw3DfUHsV<_%4poXk>C(dWj~3GHC(WHte3!p z`tC)i-m~$&`)M17Z{9E>aSuN`Ocb;{nm_3Cx6c_JcvbQ9v%U)J=LDO+(uwA)v;3xY z^MQ1aAR0l*FQGn`C+y0&7xdTE%72H=|5K9k^Nv9)e4tkGiC>+17O#8g-OmbS>#fF* z@8nXNXLXYxe02V3KkuQ?s&=69{98IvG6&`Gt-$LQmYfWGFH_WNEnX{~)-Rg1f zAD7Yv{pG(s9r-U#_%A_B=D*ojL9MRUZazHG5xC+=2Tw|u+lfrgoQ)9~w{$b)vOBim z@2J;o9OMX4Rw62z1^6ICvgEB9yVf_iW1YJV0^7IVv`gYNzH z6w`?bsyn!$_?bTD6CNY>{&VvLi96XI>Oau-;_IojcOU6O%BShliu8?KZJ2o1E^UpB zb>cGChHUzLzs;_;IJlmi8`;i>Q)I9I#**_0y>MU3QTNg2*)*eC-@u#{>ex=CE@E=J zd*I!|w)0Oe5kP4;S^0ht0Bq~$$1;b~@wsRcSLjtJ&+3q6p?%LapOBAYr~R^P5qJZS5xWr|PUAXuL~`Sn2XcI8ErVxU*t@gI(!isU?B9SQ6qL?fW#>CBY!t z-K+20p@2lm`OgJo_VL|PRia`zuNTE;4cZ$ zzSsV=9Yvhx$?EK34~lVnYvY_*hqhW<$D7$-Pf5pc53)7&hH7`k12)m9LlWs73Gu7G zAi?3L+hZ5z)jrR=hLBfnHuDc5#=NVG;YLDtrxMHW3sgGaA_XiY|%7X5rk|` zV!Qt)SQ*y7eqnI5XAafaUpZs5O%UpL-L3ucIX+N-OVTRgaz?C9g`n(>;Z8VoqL$OX zU>i9eOuynda(7(Pe@A^Z@A!a6g=zKn`cb`AlAR5_y-^f<&b)Sk#59BlmU+HZ=?R{# zVnrY{36L@g7*5f+^(f>;UMV@ZWe*FyHDe>0RXKP;_yHaK5G6rxkln(T9{tv0L8fS2=u&gj?eSz&gu;MJ@Nwmtd4 zkp6=SX@u~Z>x1?fm>s?=(+%f4mIdXbo1hLm5mif^5MAl^;$30{HkGjKENr>>=E0d4 z1eV_A7ldXHvv&>(6biY76`l+6DT6?s4Y$P+NLcl{wvh{=r&T-SLvlg+&Mpb!ye^70 z!bLQGX@81qB!LgmZ!U11v1a6n&mDeqJ=xy-u|Gg3N0)3w*+@^X&4|O~(8&5E9|*}3 zY>=}B=I}Zd;*pmsKJs(>YM&$Dv)Y|FxBeLI;??vL#{hqf4(2&x?b*F4$tlLr4Tf5A z!S~uT%R#2b!*nl33(%aHucsQ4Igv*dy2-sGXzsL*$6F}c6_q5Q^M`vUkEOq?x$4bi zu$(TIRH7)PnNR)!#x8n|S3ek^ueMJIzprM~s)@2`5O{tCWyY{O=ADjXWOU&5RQjr2 zQ@~5;_>&~xw7R*v_~tPuftU2{+lE_aM?8z|(VEc+LhJxW9K+M>0eTYPWu7-R0#Hjn zDj`)Z>hh_}hEoy%YT%b>*T~cDqm^Q&u%+yHo1)wOcN|o1d$w-&acSE_iUt)69g)p3 zF9Ffl>X&^G;%nJ9Mn8558iaVBmV3N7qW+XfY5sHQp$ApKyr6z&tYLG6(4o! zWm6LPk_g+Gx6H^VPxbyzZO!VbYhEv>+YlmZ2s^--Cba|mrj;xU>F+Msnzp+EC-77k z`=iTjTy>}X5pd4lE)ZfpN$*Xa{(6e>X<^^_0z9xYr~q(LgK9lyrpeL?`Abd&sg!r( z3V>TX&QBCl;_uxqTNyiLRQucij+7T(t}XAp+wR_Ao;wQqi}dr6jq9kXkNH3K*>`di zZ-jpLw>Vn;9}67@X-(^|*oL;WeqXG9npmd5*JxY4efJsEDilKFv20+QO{n8x$20GD zh2WJ~t-|L6ioW_Nzxn?c=m4iqadlEXCYfr9c%YUcvJR`56ykZK(w;6GP!Etp%Obnv z(3gQFD0*dNGB&rp1eUpQ_Y!Ypg0{k1^lc}H#>feVnk|AGN_P9Du1*$DC4ditwzA!| z-KKiev4&WqW{>fvm>OSQzLn(j(ZJujtwdhe12G<}-TDsd53c$*>O-u1jnf88@)BMV zg9l$cg~_qF&v9AHI!s`sHVi(+*wrPO+mtCicv#>#fLF!C#4$OQYPmQNldJg znQ?i3a~~P9Mw@=X?z}IujLN)R>uW7vjZp%5P>ujf$ z;}gC`&WaxmyRxPUE3YTAE`el(h>B0@`>Q@t=Mt79k@@?Sf`6=h7hVJx$Ip{64GcN?NBjta^xEZ~}Kms@%H*tU3bu98QU;L`0rc1*%boJs#*% zhFh1_UpEQ(ye*T;N1PgZ{<2hRpnj{3BJ~Q?zbqWNVZ#_7GvBRm^H^CqbR=J0b?~0A zk=w?=_ndirhcPZf?ELOi?g9tpzUm|KVhnM(w^>}*#H}d@I|kexQcgw}J#w6Pl@Ywp zZjPh=P|Arib%=^+_Nbp}N^Is@m?1f|55j_q*N2kwkP+X`@;P_3V0MTefA685(-28o zeqhH6+vn9N3E8~5fvY-bZcJcRD7Wf0daL|kVESRVJyGJWz(p~DZXJeNePx~uz@Qct zsFcBk5TxE}FdEJYRWjK@jevJw*w*DtmW9qVLD@$n)Xz=WA}>V2H$=(v!Js#5b9cpr zdX6*MJwLfhN~tAJ`P}$2xmMlJ2?1r8bJU2!4!=BF>GIDI%Rmo&XxUI8W0swWAlrt( zNo&4R)m=omd+pHF5bDCD;WlG32R8a5Xsb-Opoqm>knNlC6E`?_E5!6}&f9(BUzZ)p z3HQpXHjPY)`f*h$ND0zXe9z$R3R{T6T5k152t7L;7I}Gc&83uQL;(mm(Ptc-+O*PV zxgYCRd@VP>D33XiJ=DW`6U$3rvdAedSPwE{SJvzn_L|AvDRO|?*%4ciA{(5ED?5#& zN-7@rxu3I*eJCiWv`{!JZ>}gm`A z8oKJmk@)?*+9zY=@n+QO3IcgpnxAB9*~A?}4Q>xBjU*u4Bd0CAw|?4(?^OOT?%p%1 z$!%@_b+2MC8x>HROR5lx2%&>}seph4DM?5IVM!06NDC0!UMe760s(13X@P_gDFH)W zg7hYYBodmG&^v^#p6qvw_l&jo+5d6Ao&BCSUo!K2$b6D9=RKb}@9X|uA8=*qxu}%h zRI|%1JE0R3yL($q3((_nmL+xj@w(P6VC%#8#f0TbCX+zg8*XdiJ9bvB&G}ix^?7!c z>yU|ojW2`D4&RcNby?#nqqn&Be;=!WJ<=ySjf$=^lZN&*j8xaq5_5D4mo%48G$0C| z6-fGVc1hps0#wWN4l-GZzJzfC=1a z<%wRUPCLUSqs8J_wU!$O4;=Ler4u&=J12OCQ&;NeHJ)U zzr0u^lIZC2vZQ)xTs^%Gj;$@;%J#u==78?h^Z?Yl{JhupN@4oZt@XC1lT>zXy-1$T zEuP#3SY=OqUzIBI`d5Lat_Guu<52oc)R%z*=MBr^A__h)j+UE;Us(wV$WAgSg55XN z;B3^_?aGMX@7>Xq^Pb}}7Hi`|mNRfisv$aoH%!U7a-p2`OwCv+OR!}_3)2B)`el2^ zvo!G1onmJ8AV=4|oEktSAyQ+rI(Vt&W%Hq<;GkxJQdd(Cn}8n(3RyC!cs?>Sp%W8V zk=O{D|9u0g&8iO@d)Lh_s|v!@SCl*HhJFQ)-OM#rPGOKQ5!Uq?d=1$u<{a zJKysTlxI^Xi_0igA>u%}9^+6nRa7YWThk&_=Xv3(pOZhp8nJl9GxPLR4Nk79_WUp@ z5}c!Gzg-amHh!9Pptlk4u^9-_ncSOk-^a*zIM_B)i(ap%Njz*-cnc$|_CIu;4Lu8{v0X^&_Mh7p?*O_$glI3g}gL~4R^brKh3>q4lxrfVF^i?tww5|r-269M9ByPO z{>QQtXVO&)zCP%kavJ#b2hhVDY4)_R7Tv1;$taGB(?agxjPik#@)VmL>$%Mv!T}n> zS2qFynBg@00!=}={P?W+XIP3@s$}-EbHN1!hBpiC*g4d$gJn!Awz--teJa~iMKQIb=VYqilB)K7oTnR$a;A50wyZKy{q3n>kb2^ezn1`;Oys&e1 zBE-bt?6+Fz4m;)w+X8Eeo7_NL^e8$6GA>xkM5WFu{88C6{c(c5UkP+_^NeefBzlmf zLb$>5j7dSu1w~iUGgg0q|Iks#pZCRy4b;eOY%n zpPw0t%Ttht&*@Gsc4Jk}5{R<8r_P59-zzM-y5C~;qMCcFny+F7`nfQhiq#@IThc1_ zOG?`yal!AVmLcydJHRQOOq_-kOC76ABdrZT@HzeRW$bp|nPZK^Qx}=AzCQZG63DVD z8&%Tvo`|+D!mMM~x?)^&9QP`l^M0o$m!FLIru#Ss)HYjjBp5GxeplAz7)=5=Q7%VL zHC7(wlpme>e&TS3-PJm+V-zk0PxMgG3ND!?IFDto%ZW3a$s6~kH(&iIZ;!Y>7^Sg#(~|*>;VV;E0PF55pj!#0@Jsxl4vn$*nalu0^*FbnZ~)oN`RA z%&h^z>qhxGz&;qLUw9zb^GFGXoH?gb-O5PMbf(RtG$RkVZlxrw*J~!#*P7(MtrjVz$YwjEBG^Jrk;~mif;(D!yb( zn-UVipjqyj{rGdqAN7Rpo-x*27*KzkB|p+)iP9MM&Us&*cjxKT}bQYlz*MGKb%5W*0-GwPS0ARBwxb?!T1K6?h^w7&XFeI02x2K{& z{(b^zy^69oUIkTz;HQ-gf zI1U{S&As80wb}oX+vl;BfG=sv@r!REVM%5(@HE0kTK@4g2eVQ4>80LIDGLEoFzedh z)DSlp4eSjt^6K|-7XtvXmt5p;Yutf>!!YUE(sB9s@aY%#G~@umk&aluIf57H#lo0` zq}KkEMP5}S@tUz3CO*TXYupuD&yMPBh4ZOVyKv@##t_!Qh_N~Ma<^bmti6ZS%HLRD zfIe;+uaeOVq(ra0JHDwN799JdWSlX*Y|TS^iB%yOyf&YvZ_+{T&R#XKSJr5xb^(g< zoW|T2GPykpLpqutG%562FVECZJW>Fty@xVabM)1S!CNu7q9h??7NT9QpRV|@OJd+$ z41l!F&deRjM6NK(b_TM#)<-P8kL!Dq?@aEw8KP5UIh?*~%2M;aO9OxkZUob9<9^ zrNS{pW#hiQy^=T&YKDOS)tbuum8eKLbRP>lpyXEjn0;#7`O&X?-q-fwR-lt za|MGeyJR}v%@swQQN#r`P3Q>i=m33mhjUIn;@R(655PaqCMk@F2q{vu2wXo*cjezk zY)ePGiP7Yi{KGAIL#Drlp`g6RZnfj=DtZGs%gk~AV7`pOr|)^mG{>tVnqpouzC_za z+5q+QVKM<$zebyVlNOU>-tA>ZHTW-XfuVV?`#`JG)gF6Ot!%{Bm*WdX=IsZ$S(sW0 zgDgazprfQ+@4uORo_F0W$}bh)O|o?ueS2bs^`q;@iKX315}Qc^cRE(sW^4GZK=#=M zxt@i-ccZ219XIkud&cq}1)ZKm`oGy8DwW9xi^1o%?!g-Prh>O9!Sa;Pxmt}rC)v+> znGNOC<%$-md;g~2|H!4E6`BY0zpXaU!o%mhULGxmG!K((KK|oY{sMR!es1+j;ay*j z+mZymZXw85iCuB-b6@NAIp!y)-;!*HDhFl&gPZimmlIr@DFGuTo^9EK2@=?qNvlg6 zCuA@X2OiMVRa3Wjc72Ig*Nd_c9*;jPm8w)G`AgJCX9yXsb(#GZBd*LyPMRV83Svkf z-Y+(}44SXFEaMYO2Z?rP!Nda z(*r+}UD9p}_i5RC)tjp`OqMufSyy`Q*@h_DaAl?o3p$(`iL@yv?ZIr8uf5<{pm?M{ zpT?nQPLlzEaa~mn>%Ci`lD9Ddccig$f!E>aW1XJ6)FZgAc2%j2 z8~X`kKGj%poGzWq_Y%m*%Na$<6K}bdUMGffpLXxD9a@z!?hM6o)2evO3`h>V1_!<= zQ!9w3b?I)ZHJbGdf~wFe3#Y7s*kSjD>ZM-|oaC%@D%nAi;pYr=%Fcf45}?K*ugp^Q zRDPB>DnmOKSe61>RMLh~j3k$E=1e%y97O`ioq62aE#EfbS2=MJtYA9EOskxX3(Guu zmQi$=Ty1PzwZRSOHL|R#X&>K^u*7{??8osbfpX9~SwO)ml$LDEX$rmwHME8~LQqx?cGuoJ>|EU`{ZKqKXuwA3K|bW((tf$;oRNv{I8_vNAV;OX9R-)t->-)5 zf8!Tw!m|>_=KsxX`e$5)&SvpfzprM;NcLT3SkIXw{m99?Q z&7*ixc9mRZYGxSD0FB}czUGfR2(e`XR(Ke=fiCu%l2dVg6Zh}^D~A&o*s58>N_lcR z7FYCy0LXP5n=UT7+aZO&mSlq#<34!6H?dK$57`QRLSDPM$M^RJ~cgTQk;C_fBj}wd4BeN`n*+RSt5MJOE9K=Gfxqxp&@C!g%_K(;Tb-#u>n5y06Xp# zuIgig%-Ap`D*aJcdm4)wn9TQ}{C*;%Sw-sSI(Jc_=dLvK*!>_AuXyRmlk@V<*)1yP z#+GoM2@Xqs`K7olzo*m9x%;cFhjr>tJru%jY}H=ipDIF}pH@8ni{g+@xDV#zD&1q` z8mY>vr5>mz(dO6xk#H+iw1Xa!DU3fzF?6VGty_1wW&G89GQ~r68La?_%l>}ikzKQ{ zZS1o*|2q8tC5f5!hy73yf33VP{Id4q;7P#cV;Y+d5f#j-39Gu6yYzAn;JS8j;jreg zey%?%S0PD6xq>*O_$p|td%A(ah|l7HvIqy_4N{ql^I7Z3c|ZP7x5at+kN-n(AreIS zx_!6rV#BFbo94C~%zR}okIG&A+ArW)KQ9kG_i zR|MlmU=EGaFu^iEy6|L2O5_SAd7L+(36#;E4LY>)<8CjBqG}p20 zZT|R{#Ofcym`fY)DM@*8>NFX`XhPri+Biq^@8i+ewtT|R=#4kyhN)`5f!%KHm|R;Y z@wi&|>kI3zky10BxD>AUDLigE431ox`9pnU(0*;){BHjNx@0l1JSU(Qz6cTy<7{hX zXC7(no+lX82&BJhG~Ek4JwtGVB|2LCpbgea0$mOaI=2SQ6-@XTmLUFZfIOQg0W z`$(|JG-^0O-a0F)z={&mW^e|SYZ`>S&_?Ut{4|%S5C_U3?xrma#it)O^)ingb9|QU zmNWIr_n6~8UKLbXf2rz#NHx@c&3=wOz9+oYUQfm|NB4yzMnN}8%LaL7N&K`^DMGg^ zCd~Ut3tj82Y2MsxROn%LN^4h=#56l9)uAYb$fj(|!2-S(*mcjW%wxsxKANgO+ks5Y z440~&)8%$1)hi~0LA#Rqx-@-#0WcV4K?5_a>M;y+6=D@?4K4H8swSt4YNqp~DuBqT zC1N8Mf0k~bhuJe-LRCi+oigFOVZ|h(o=W}q6Am^u;H6fZ9&3SXd!;RvTOMhIyo}UM zV_8}4jUCcAkxY>-e=`SHi#xe)*=6C9qrRes$Hj(O?X?LRvPY{<>5sD9L^=?$og^-n znVAA$$0%q<89p$C8(jb<~#iWp2@f>orT+npU~k*TF%R>2>+W;> zc2piy6Er@RKH}x2qK0s1wB6tj}ur|YitlxF2Sv~?k%iDLV(|ICVU5b=Ty5?m6$ z+(JFe$0@%(voM#F5oZC2I6NI*=yq#WD0RcjUd8E`{?zq5JBJY^AMW4Kpo0f1dpEd? zUHXAI!$g9NYj;)0*p3Zofsw}B!=`{q7ZoC7M|@fRkh+l(O)9`!{%g4lpC7i53ZRxB z(rWMg@+Wr2fhaA-fGKrxFFsk(cqWU z1}PcDhwM3Khk4@aF>ubr5BhZ}g|(?BA+^rbnzENF#J5tv{dr{k>t61Mbi6RM=4ij6 zkzNzKX`Aj+s!w{wE@M`432iG6p!)WjK|MjknuE;-0g9zIkC}mq&1amS)uw}m=O!Ai zvRDCQd!4H@Vgn9(Y&*U8Ig;wX{JFRPWc%OhZ2$I;JCfWeX?9vk6~>S0TNE7fhL6W5 z(gLckE1$A-4xyOBB&#w7{!CMMsygnFwezO)_0vRy{`m|_*vf-X(*%46rsG#MAb#F?!3e-|R zXxuPV$X>c2NPgvrh1KHtBPzb1XdT+WZA6W9w8H}Gt=TWQ+78RtI_Zz!3~?%t7r_-* zMQrJE?P>hd>$JSZQq6^QT0T1WQeFuxOlP#O*)2ox70zBlJo6Si>Dss$h^qfG+u@^s z;i1n(zgecqjIVYx`YsAi{@s)t4GCt!H+bFCzjBiVSnZ zAHBr}oB9$~rxo|S+EoLrF|5AA^@5Ui6Z0azJ2ID`dpnUk~LsN5y_z>{>+PBir%@&LODdB?s*33;PpLzQz zHwPaQ_`V{rybtU}!`&6@=(=jQQ{%^?qU$Zk2V(P#cDutxXtZX4)PTQ?f zlQfht3f+#Y3bo4$^tT+O2IRS$sXC4m7?>PaWxGW2%NI};cU%bCuL)P!ea5{HX9E(P z7`1uF?dtZ5;72kxoG5sbQmuDSl9yAZ$7s2ins<4C)XO3zm$M-__-@*axJ-RrM(X%= zrES}e$4J`F*W4RyUo0z?QsWgQJN6UO&sZj_ENN&~ut~9;f4UgS+$HG_3jDRZdLDX4 zhbIIhRV1dT!#KXR;+N}#d?I8v(xyaqN`IYCC^a>2^gb+OZ&cTqcELySYg2W?jB#t* z*7-nPy52>f$IoxNVzF0+J?*N1E&=J1y>+85QG<_2xrvF_J|@EF>hXT7Ls;;~PeldM zua5PuBxJMiq%tRsvP}SD`#0(f;PAO{YFh*YB*Xxizz_(IdHv_%eZ{W^IY&CbVU7(l zN}Q{%CsO6^_4UsEP;UMWDZ+CQ^ZYYF*g zA-H|b%keT@tg`$J5AQBzAB420g|iU(>sy+wZBCO-;}sFYp8hb=h-T1DB1xXz^#9;k zAc$GC`F`TV>uyuQ5>rqYxajy{UgPPZCc)-m#f$znX0Qi?e(4o~%9iW7itQEj%6lEP zbZMg+LqnhLDe;be!|7@LIZM$!SnMISR5 zi@LkI`X|`$UMFZsslINVRdLArCSO_8!fr$@E&p|Q59V-InN`tAnl-8&E9NC$s1@f} zzeQ+l)=Z?#r1H(%D_<-Q4{+FB6KTFE#L7 z?ybi4#Ljs?5_yTdRA^jrv0TSE)PF$fQ z*|mTXhujRwek(seAE@)O)yQ4tpRu|%^9jv~T{9XhOtbgyVKo{bg}F&^%OLFuF=cd$ zlaC5^mu9K$pD}N>^`N80$oSPvnJ&1O*E#HjBEZ5AUTlb(1 z0cjz@CfFiZdayuXziWo@#S8wzP*G%0`F)GtyMH9LX0hrZ9RnWva8^G33f?H+e<3;} zq`*LRWQ%p^$9A~WAQr6a(9WhEaMmV#GPC(Q@Zr^ZL7{V=OB#dO?f2%IbYNm_@wZjs zIY29I35wx^JuLRG!)`!rGHreB5#oj2&3xD1zR*WTjVfIM9aev=UR~lY-cQP#qO^xm z;IvFBX@3Q8>Zx^+Sz?&p-* zfSpnY;Z5<|F`W44BWv&P8azSC0PC0r^4oUt9@o$9lixXS2DuUa$`Z5A3C;%iJ=mox z`P(rKJ@OCpiKXuSk3;fTy1Nz7iul2-`eEdoS&Vy-T{Q0<=QA2psEw*jZCVL0DVZ2y zjxV+-Y_=TwL9&*z19p5uQ4u1CLV28`>r3hEy|01SoALvHbm?V&-ql}z(#WO8+-L-Bo6;nq37dEzvdWnz00P3MHc47$&F=w@7Ft91Wlusojh@6({I$5UCqB$;G32`t31lA z8~b8qO^+oTI}>f$pK|^mWKPH7%SjT6sNJ0D(T$)qFr~ZeTX9=PA+le@^~xy<@5j4k z!?aB1GIxbL-<-LRJO?2M4=Q0d*nx8Q*xCuJp*i!C+TcD19DeaqB|$Rrf{W>r23s9x zK??<#tfJvcx;4-0&z~GMnc4iF-1(V^q+uVn z1(eXn*os5@v798ZqZByQ&i@HnVdq^2oE42>ErZ1>rXrN8_u?wX;m@P&?k7|eZ?(y_ zFM+l;OyJ#|N&C~M5Lh?Fy=7T{Vpgh(0~HyZYb31pu`(e`=+S3Fq6J>+GKe{)4<#2M0SY-!00!4_n14jCRUf{9eg2ouIT{vr_$jFH z^)kaYS{@89$?c~~`|+hje1|1{OKq<9{roFt_6Nbi`Kj@vKFdzwfnGYksmn1S|8XN% z(h?q&-vy_e76&Njuv=|`1+A@Y3Uc5SC!V-gV2+u zIkh;7Pu;`8UOxnW>%OkY4zi;Lh}=O=oSTxTPh;!$>@xr;Qk)mpW3L_-gu&De=R6v| zJw(3O-v_8)m>5YXM9Ayj=|M5E(D!_I`;zN@cZt@E8Q36P(NBKLR&CT5l)0omeG?4Sb9(ieewGna!rbg8PJCd3$;dPK{XUs%T7@v!r` zZlrvyq{xQ5Gt0Yttn1tcS(8f{7QFHtwNojJYt7c$#=w*C*`YC{hjhi~IMj_B7q?AI zDrj+W!+UWw(E@7$@jH;E)ejwIX+nqNF#UtfF9obX^?54HOn<*rA>1{6XaHQX^|s>A z;_m+nN+(-)0}j6&I_-0#zSjNqVgst#uGxxT=13dhIoxT97;xAd*taoh3tzo7-aP$q z`ey0>9m@X0jsKI(iGN!^s#dQa2`xr6S-5tF^Z?yi9ryTk$|<{S>J`VR9MzI|PCMV% zog2&rLx;_3tpXjeK5Cmj$=^@>R6Kms+)L2qOZnu|h+GkyP&zbxJzusB$r2G6vZe~I zfOT=K-xo?2rU>r2ib^~OBmI(`!>52q_oKc5(<5D_R5a8_LPWrLMSO9Qa;(xnU>>}` z^oVq1;U$t`q8@&VcwK%|TRd{g*jdRefvB+eIj>2MNib@%%}Ibt(g`A4r7hHk1S1co zPecdw+Ex(A8&2_Lm_=Ly1+;#!?q$695`dDf^G- zH+8)}KO5KC%MXm+9@V+iD#7Z`pQiNx8Q080)B5uF^^iuQ~*RR|P^c}#rM znM7i@n=g0#g(?31gze-AQ&EHelvA#IXKdff#&fFp;=6SF_ldXB;eA zyiO3P2NaW#wJQpiCD@W%r^>26mh6W_`ZHl$Suaa+s0}Yo!^1tcDg(t=>&z6QXz)eB za81dl#9_u8s=MzfR2y{Y<+{WE{l@5F*Ra==s%QUvev+bMe^v5x(PTsoCe z^jGpU(z_ZtcIB{Ho#x?3BYN8UT4T;`<55He;vl{W5crLDe-_rVO~U5-T;Pl%Bkbki(A{%`6?;9vIe z#xh)-z+QtxQEb^^7Xx>E+c&}+gk4DKEPl5>^+_L~>H`|NB9r~i$fdd=x+Al4B!6=m z@umym+TgQs1dmty&8Q%YyPDfXYAO)?nm6Zp^eW-)49g|Kdfp?+gu^jhrE5jj+Ccij zVlwUB`o+fv-9yl(CK2Iqy#=DN8hqf9)99MF>O8g}VofKvERa-cAuf$UAmvQXO+mYv z{+#k@Bdq_2!qPf?6C^2hX?ShdYo1{a^$!nH1*gdm2v{Oj2?(W?f|KLCL;6$s0OJb` zMejV8spu}{b&vCH+a&1<fMr`_IJn06b(HQ-Ry)LS)#{yyX= zEr0kKuY-6kB4zb)z#2ev)$>|hN^zTdgGMa@8?+yoRfcZRQ0?^z#fOgfWVZ&---0B1 zqz2T-jyPagxQKJWmDF1#X|9OPHx;J4Bk|_@bq4w z_}S}hPPcVJhB5_?LF|oLZ8T@HWkBz(98u1P&r}LgtR0OMuO9n-Q53{@wToD4DoFe6Gy3tj`47i8j1U(C1i^cSFd#7bAU{-Ra#6Oiet1O1E69=V)Z;{q zx=q7ZG`0DNU{nXfD(^ecr3{l_YOe(a70yJA551u!YE1Z)MqDduO!Xs_!f4aClS`KY z`eH*SepsS1)Hfuckr4_*UXNWyGNaurq{zu_+B&ERyCk`fafX|-xE@ahi;uBm6T6af zZjPG+RD+@^&GW{+<-3NL&MAIBk>~Xd?%!@xprH0Ku zu1os97jXjuOS}i6c&(s|rrp{z zFq364QLb$zC#(=>HO);!UZ(Wlg<9rNDfMq||kG6POffko&XtWVWdYR)szT99ejNbA>W$x3QCqD(HaA|SsjLASRobhquMe#h%eloU#Ba$6_L0{P8pBgmm-nD(Z>?}S z;%EJ+OpXWy_)DIexE%9QQ5*XD^zOr0$r>$5>N$H!DZCUPxg9JSD2EVLbSc|0>qE=? zR^`yJjdvbYc+I+cH8j9~f{|_1miXDRz5rF>TGNa6g;AcJ zO@~Fy-dY$V&2Aq56qT5p^th_3_pw$y}?S1jS zobaV{9ObV16jmVBBbmfE6O_FxxY2Y{FAV&~ws32}G;rpb^6q|aH>oqLe5$LPwAq4{ z9V+2k(Tp3BmEXi2arlNRUF0#JeEHL&bx{ie5s9CJ^2Q1nP310!AwrQ7<34|R=ZJC7 zkC%z>gQlAfm01O+=I{!X=C$xf`(8LKms7#mR?UOqpTRN`S`@& zAg2g~Hfm1=51g;713D>jExSSj17;$FX#*gLY_oTfys`TS;(TuNV2;xNoIJBL@2jrY zz*JGsZ?uGRBmC(>FBlnQ;`qBjZqLMAenGGgmD>B%3F}m_q4j#I0z7p#HT-qAiMIU+ zOf#rF%NJNaTygo?#E~zv{#t88=jX<|Z%bL9G#XnShskd9ZDZ|B%7u8$FHWL&lc!Z~ zH^z~7YBmRM<^8rlkxLyC#5$cDe6M%E;Cf48w&ja#-!AWC7b0qc6(N03*&o+5*j(;T zopZS}F4cnk{G|D1H($^QV@-41(ub5X-J$#^pL;JSn3)rroo>Thad9L9>FJb#(#N3> zJn$m#eG%N|aBfaFI*Sz$kiwxKsQ^8YCF!qlV!ySW9DZjSgdFBz4 zvzjVR5iLb>irhjeCp`xnfFYHmiQ|-owcO+0fI9EQ$lqF)IfSqN01ay?!81kKXK4jd z61y~MH#Kc%5Ol1hZRa3~#t@f}{@|$Ec=JUf5OfixK+mr99(b3A8FzgE$+I>EOO)$r zuk#82_mG)o!C%X_TEHkL%1o} zC)<;xqRg4pIP|J1ra3V|g%K|%bF&T5Zl>@ zgEE=YhK|~69)1ZyVu!h3I5%PX+#he@I29R-4?5g3=D5c~Vi__RlrL@MO$l~R^-xG%F+~Owg zWX?_3D8_FF>-5zm$6T6mR#HW>w#ALimO12}jE_?kP(;MViL8BG^S!}V(vB00<*hT^ z15yGkTec<*uGsTkgqEd1dSD&{jhZjdSsA(9KeL%uW$m<~eGVql=MTHeU~COFcE@(> zD9`h=*=aNM@QzPkHU&vfz@BzjWzf|{nJV&dO$gf=4vDMh$=E;XnC7iUCuJlSLF9qG zE?Dg|Zxc(Fh4D=?G5Vc3bohag`aI#X`u&#udreX|p5>W9Q^Ui!TE&yD!qu1UuEbd} z;yr)W8hn<%>KZx6R5A2Hyi!~Gr||jTq4Zyl{UwMQJ+R#uOBaROt;XIT+A99UBpC$h zT$4^=hZ(EJHB$WRuaEK1Hqaq^H`9e__o_lk6cuGsYAt>gGdDU7lBF+13H!5P) zhiY>!HdE$-_8E|>%z`XJ5q|4Hx_E!m;o~aLws6gG6Ph+9%$Nupc(fy-BA)bi=$hEQ zA|2nH!03SCt;x-nJ{Lx$q>y`8mp-$ZPn=9ib%T|eRgN~Nut?^=(__4wR;2w6G>nto zJUYbusQy{I^u$PP{?;1UI_k@G zjqQ+Dd0z0cWg0cJOlOtP$lI9Q2SimmCvshcsN|radMRaFj)>j}b(-R1=uDX}jp_EP z3lrWv$Q3rN{k_w&UVJ$0Ah2Fo5jjUIvwckg%^LFjGhWQ(9z9UH+{3lc z1-4lgG*+27X>*4UZi$k`RZ) zP^{1$9_DzI&|1}F)Veq~`g+5+yn&mPz5a1wVwdW3wj^!>EHbh7(Qt;5)V7g(YbQeV z)O8shP~tou=@D-(N8x+qP?aWTYjfQ|a6zpwGPn^<5XO1O;9&JpXpFY&RBLADOg1U^R<{4`%RM&*5TA!%wdV>pk z0h0C9RK=D;=j_qG27Ib*;~9VXjE%hXM}*v#u$0obFt&t$Q>*OCVRF&t$ml@#-gZYX zI&R-#E^_HE+9x|MIf(AA#5DuTGnzvtx6BT%0wdlsTIP0jL%jT_^pJx{ zk6M(*^am;1gKRZ0uE4%EYgdyAFKKEb6n_PL67(Iu-E#p!CdYVKrY5-r3Jc|UmuLBG z%F=TMNjmuP;DynQBwQnVo^E`u3Z+%{wYQ-9xDM=B@p*sAjn$ZCO5klC3rjI0kI#Iy z6YdCbGnw9a^`_#ledS@p{zgIH<^s`?vHO`I?W`KsXaxNG3A-*=(ZH7VT-VYjuiMOa zr+2(j?8CA#<1Td9hhEPx>Xcg+v?38alM%!h4t{aM3Sarue4#1k54ue*%(*Tx4GhC% z#nrvIDYQz#rx$|bDAyi`>KZog)O$6X6)hw>WOUp0yOhsW-YKi0e#Z5#JMgKMfShK$ z%~6@M1mWU~+dKGzpK!=CFj1R4n5MGJTl(MrJI?+2{hy1iKkHivq*GSU42v-n?B7@H zJ+(cJMuc7TRO=Eg-%oU6>e|iiRnY}PKy%yc_Z3xKqn!5w5lDNmOeCO~+6Vk2$HIdm z^xhh^ueXKSt|4AD|C848&%OSC-@dLC3UAK(gX_n0`Tf-w)P3(BXaAG|CvMVJBT318 zYCxg`*y4f1ftc1BuIPE&V5_+vvq(l1-c>@^UG93?R7@M+yc{FJ*o{0=CHki54+0Yv z=5j06baO)J)2;BgkXx$h`M>ugBvh3Bxf-+2|4mAtx)t8J4 zvrDg=(bKzJ(!9b(muDZ?5anfBG{2t+fNm>q<%-di2T$rkd+sX>t%>Nzi4jb!>H&}a zuyq|iLFm`tY)dBA>^nTL`PZH_-6o3tNKGtex(^4~YhiLo%75jfNLOW7UFttY0kv8l zvw=)HFyL6dDrgo4Us!AVIlZz5dI6iWA0Ltxa<;0GSAHKxs=5P&Na2ijNdO5JNddbN z7Z)(JUw_ItZi$$ouvNLW2%+_@@ostAZyoL93!4CdeCGlO1m-~4I3+C;2`tdq=(Znp8Y#M&O zaU3_7%TdEWY`H%$^(L27vy7hMNkpUO3KP0DI(w}RW->*`~G-J@w3bl`zQI+tx( zQCDB>1wjAd!Mu%8Gf6Q^0arQNPQ2@7sakiWED_?GxCi}_F}_;shv~Y1i7#Iwfe6rB z9}I@o3}zh(O+U-)@%59#ly9G#=eh&1^3@LQ;3^60)4fH9=TeE!ag5ZPjwB!wr<$8f zDD$9Gpask;X{p5k8@P+i48lT4SV0SbnMgm2J?VveNO_91JU14oC-b&y_6_d9ozFZ zQOaqI6x%`~Nm-|Hty3@J_X*KwiQS)y_*>ocyTz$J!ue@AMS`xoCN(Q7QFfowzOX7L zTO;>F{iN%g%|ug|(s{-|8c6;4j8#S^R(P$ucvR3=okU_qC3s-JjhBfI%U`y1M5lwI z7Yve1;}Vwn{@^ZGa|(wVa%X1*T^czX>SXu+t21QZ`0pn?y}wS*p&!jDC~8=FdWE%} zCrhrb)*223-0>TBz){imi~NdYYv$FXM-uYW3q}o#-Pw`RnBIn!*~aWoF~x-$B<$La z$K8%R4?!WtsPpD-uFfzqE^ddiiJTAwi+&!te&fOep5yj%*YvRppGA@0YSY2OPwC*6 zm^hmHR)IUTPSR>xkY} zuu8zBiAIEQz6!K3{>PN!{&aMI*>#d4&wUe$PT2*SNwr&FnL# z1uK0tL&{v_gUbEg7 zubN%WggiSJi?g7BJgKn;dOw&4=D)0$O09_Za!^(#M)K0>T}n8GQSX&(_j-d23-b~6 z)o#0C0avfbwDbGj6`(mvq;NW2I^95f(>e+iJ~6{R^eNB*QB9`m%nHC7i|vK*oFa*D z2w?i1#>!PG1xwPCe4d`04(xKTe{f_n9FcgOg%Y9G&pJp8KD z>?UGj?hqJ?Z#a}BID9{0;&kf?id6tn5k5OqK5Bl?3g?>^P;c42LZHBO1q6p@m%g94 zh@MPB&uuXmn^=GuovOkwRoEoC8}YrcVH)Q^WS7$5!Wu6}C z@bz@l)kQtb6=5Xhp|LRBhAT#|yiY=XurC{M9qq2A#l6=u4D_L8nlu9dp(>Dl;p=OS zgClQQip~`plFdn#m3Sp8x3+7+^Ee7?;?KyFRbSj-OCSH zvRqmuM)Vfed&9ltWt5b8SpT@{;SzAcg)5Nc7Z9%-WiR^?CANtDq_>ZwDZ{Cr{p_wG zsA_ISn^=uRsa*F?&=t)WK7LpgYJFblg-=>pT209xO)YCA(n@=v7*27zJ%1Gf$x zKMgK4;XVY6hk4&1<3$eCT>Y$*9&%TTgo*;a#yp>xXt`RA@T-StfQW~DxKeEar0YrD zb06Vs`{tixlf{3wJyv->pMFc|v|Cu*x(&0a(`1oG8Nk{}ZqrDGMKu%oA7eV~IE z8zXpE$Xn>W!B;j0SoP(HvpN%eu349kKQ~9jS@mW_*E!g035TmLLuyN&067|dq6U{e zWq;jMF^;1Al;WCISuj{5w&>)i0MxsCyg1sDFPSrW`B*q{F$|2c-+R})@BU_f@BIfyTBnZcRPEZeYwypu zJPRXe&@F0_1>dPZU$=rulaSB}46ElWusMk?g6HiQdOiuI-BF;_Sz=9`LxxC2fd&SzxV?zqCi!zRqgVy}JORJ4V?Z!r|VS(fZwAXOY# ztg~&Edjv=<{v2+W5d3&Bcq*-;-2!4_bl2i}7GlZm$O1Z6ZP1~ArPdD(aq1+-rCKhU z+sk?=@A=;(yH5IY~|q2Y4gG-Y43s&l~V<^=rXz;j?w1Z*zt7R;MTJ0Z<*v@m3@ZyMOQ~- zPn2N!i*@eLkMru+^4CjJ6AoD-w3w!pCSA+ly2t;HYo`YUPh;9$6x~X|8!#8yR+s5R z*2FmtPzog+tRL2auzJppirD48I5}b6i!Y1smJ(%=B?J)E%-r3*z~5eAaGGB(yGXf&0&!}0mg!Zs(D4)!xI_7x(&CFbRyz8mVEZ}Q#f z*zom{b3X}0W@c~ZZXN-7I=4AtLIpzf^Bd8;*julfZ5*B8s2r!i1pVNBpppabEKZVJaz(^8k^pI}u z6b0#g#3~qkk6|meAP;Ra-VPRlwv*ra`79(o5tq#m}c?d#Q4eG;3>5Jp2t@=K4^f_B z2UJDR**M&E)irhbxZQZ2>D{HTo9D6TuiZ2~V z4V_TuxZBgJwR@>Ey}=F%lYvNbnKtMy`|+4_6YLih{p~%{s&|?F_ACXu2G<_NwQWWz ze-`18FmVhkM%<$3)iuAqT%epCPHLDwsLcJ<@L8*mj%B?Soam_kNDv5BZj~ zrLmUY&4A9z;j1rxBZh2d{9F-BEtBt4G#I!f;)0R6Iaw#7e1Bj=DJ(`uP+*)71O$n^ zC<(5+L3})YWjd}h(4cfuD}E`Paz?Hd=8=X)EV+3)s`}*4>cjZb}1X{>B zg_U}pRf1=&cw8?k0Ba3(_~};-+L7y+ghA0`NEqXz)6<6FWJ4EjxVv8jY>72az^D0o z4P(>yx1@?gNB!K5mGFyfYz2JWlsvepc<{#a?!@N4PND0;&%*YZ(j$+Cv|k^}xyYm- zy&KwWn_t+qM`p-zb5Gd3&qQJ$^^Tp1EBwq{4|}20ZGXK{4iZ(&FQGQUhgoW>z45q3 z#irb(QQ!CW%6@u;$jOy?l!j2g=;{hO+Blkj@KgV+w1w5Omgjx0=+(`O>-EQdQ{F?! z$rn%G*0);rW^X4v<+xcKg+#FA`Y1|_X1KtYLzO@O!*%WN_c8xM_>oKDEdG`032&mz zY;4Yj^v;x)IrXcul#)5T)J6TEtZ~SkwRsK}zKsK0&xvCt`>njxx$umZ4o6LIUuS5q zgC94H{W0h=_-*jXz;tq_``1%xnvsADf#l52Sn>~-9OLN_Id+1sW{vq@M*bV)Df4Xs-iy)c?KH5;5#Sz;365FfWyY;8q9o>VzykA#29K09Sm0zWOogO5~G9yC6 z#y5FYYW=g+X&-}8-l8DHth~90DtDdwU=8 zz{-KlCQ?q`7Xa0%WstJaoIRl=E+B7XwdtT-Y)OAJ+TkUxLvV9sag~=XHLN=vckj#M z-Q0WNM6hr{KcaK#xLwHLrnEp#4e3jo~FAS(*bxxAqBV!nOtqHI5|KbwYnY+#u>#q9H4AT7VNt0Ud$ z;3n0iyKV@iGv9$L5Z{!t%fYx}b;)95qs(&w-5o&|7Ik@b#U66lC)Wg03KPiR89Op% z3>5Yy`=Xy0N4VSPzZN$4Vqw#TU7_u>;y?5BmcIbXYpFR0R$ohOi0O2#f&8Jz%S71e zO6th%Lcx7U!mF9oiqUsv#%vR`E76*p({%4$+ny>xY(4J9-n zk~My*0dJ50@!APlG8uYv zL)l|aBOK@f#kl%{jU2B>mt(?&z;+NZ4JEbS6z1c^dl-R8T|i9=E_DUpfP=*=jSCYU z#_ySEV1yuOOle3|j`U$WGl z#Z-YEStPl#T5T-kztj{kF5ZHaV98mg)d-m$lxi^IrooV6ZkU zQrrCO=hu134dX#L0o0~fyHysP>PDInM}J9hdB~2%$ikkPFvbq6+H$>~Xc<<16S`d0 z{fwH><`$@qA8#E&En3@I>OGCP)(mzj+f+Wr=eDs3t#7u5UQX2nO`4+{KjQ>mApz26 zir;%HDn3cXkb|>odd3I)7n+O#k_6~RXSM^>RGoP2>~fwCJG?4fTK{_d-SFCt27-to zNO(bL6}aIrQ0>qa-I2ndue%866?xX;lrDH)OWk;XLCbqI)l16%+t2<0ZcuJ_{$36B z0^+oT(@NsVt%3Q-vk3$V@pi+x>n^ez&0>yrkf!Y*J_Id%dN8$l&p7ZyNG_lACCz@Y zZ0+z3!|P_2iC~2c>wygVcA2q1;5*$UEAwOXiD1)EMAmV(qu%0a(|YG@k;EUl_+^4xv> z%y^cJI_|&o+Aq;O7+Z05SQET_M|WXWgD=-m>#b;?!XO!ji8XZ0wGf9vW~yz8PW(&@ zt681KUurPBVq(CU>=ULs#i*Dnm}+jU1LPL0OONai4b>=4pichm#Talb zehaZnZ(PyM=^k!wusB-cHO>odHN|FHKc?6?6+ZKqJgtC0i>gGk?X;LoBNMrV+1_2R7}bjg zm7!}tS;F2~x%F0Q*yht3cS93TPzp<(h65zNSZ~=4Qo650mXzLI!$90-X zQzcDf%2Olv#%f|TQrphQ&-iw_jt~Z>CM?^uCk_cE_u00J9$~g^j-PeTn`L49(mbKL zz@HidpCxUM8EfylOYFb!(l_;4+gHz@O}yPgl@#HP>$szoP&L}AVm2f@Y$S#56=(Jr8p*$vppexUN1f(tEg2yVNM z=vea0V0lWkSxcKXrp%yUKPZo*2R5LTkidG*dd|9xh3K@x&qKiJva|17pE9dhM&c}H z6`IMHRzA{oA;n=mBx^&BJmF)nZt|Hz{!A$tKf0Zza&0V8HLlfN*>)uF zqEDwhIRYoh*W1!s#HDDO%@zki2`pUq*>358DXlvf6UipLP>o#4QO9v4K?62SQ>U1X zerMdGQ-7bC9SZwHm~jSSBM4dII_5wt#vgRulcy9B|oYBr-=@^ge`M3MVObCH)1Aq9pzb^2dmSG{GLbP z-zr6ZyY2tktLnwZn^9_nzFbnBKYwmW+bw3ce?!M$e3ez#2cpk~kT5x+8~_9pBRvS0 zx?TL?$Ru{5CKvE5PXK#6A*cf$6cOD8>0;_-!Ljbb&;oAW@TO!?zl_niV{}pSR%71@#AEr(>HS*|}xL6gzggU4@i9e&2=ko~B?gp~)4#D>sAg zwW~5WYq9et03kD1n{l|*b4G85a2gKTR)i_lD8DzpPn6?fI0xc^_?LAj5B5*enjh7e z6u|v?V62ydm64?xBe3FqmdV8nhavTtg;!m8n(v8^GI4RLJv**3P!UA6z(u^D zIK$q1nJ*)@fk5y~9IsC3F4XZkihMm~UwR@!ZQ}$m(vO|(Ge?xp$kUhW`1^aW*YzHc zDa^6xwFmwyKq54 z$VsDM$y9cgZs&%0PXovW!cZ;2+SXSfXSBbbB60z@hv6Pe{UnsPpzCb?JMBMJ+g9+4 zXL>|gZ`jWFLS#2zmQGXi%jN^OuhghcPP`1D3#2{fkW!1Ul;c975tcnWWCT3n`;A`%+hSFx5pYzp8A5mA%8GjV*|U@YVw8r-ye_dPpDF{M87}r7oR=K?hVuVrESy0 z*I;RAP&Ve!w966#i0*)uJ9q0YM*sNr)T*Px(|OF8=|KLT*Dmi?30GA>MvX(t|C~d2 z_PCSnfAsdfPUowCGy{LN+4$Fq+W#h_xbuq|z17F9*PE5+slsaS&m20qJai8;x{g}* z-q~u{=pM3Sv~G7BY+EG-?7g%M9JxI$xbNXT;{lkT1wesqeQP!!@C2*=VHnIDX-%l5 z3u}NKhxHg1yUTW@k&`3?+Me+|D>|53Ewnr`J>EHoW>(>owLG^@Vh`hfx49S zVOaGo_ji1kw!HiJ*O-*K&fvQIQ*ZokxI)hzuWVl`e^&XRcV^!g8Uv^JaA@NxGp%99gG-ba_(SD$Q{ml0p~> zBlOSVfQA=s)+&F=Pa{hl*!!N>c{7D)1I7y&uX{E$C77xfHXla|6WmW{lHctO&}ae! zjO`-B0=!%*H80$X_KUJ=!}Nhpo*&E$t;1@&MG@Gq{Gl)r*gDqF|8+pv!5LHhG2=^5Hae@ zL8k`$WHXO*D`#DGZTn4@xCMT3N4&?EOd1-WH!mWB!)a(wN=JluPa{EZu4V8i`5sVU z6eC64yG+Lzz%bLkvVS9UA;Y*7jxc^;mq@MqjRcoW=mw|JAiMsYf4phWXaxW`IFUtSbjT& zKNc^#j&KC_yf&`qd&G_>Xv*~BD9~2}ig_uOm`Y~JFo@;rkYM5HZgMQMcFf)n_id_M z7*K*1_GQ!RAXnQ|dgo?}1FWp$6x##DeG#skYw7BLP7cc7OyP?*gdxH??7e5asHWh4 zxXKF?OQdxB9hQWCTUB3rc!OVrB~-)LudgnJXw>f?;;T+s3xfsqp{rQw*BtUqVg839 zIiWbeDwU>y3VxTJ)70sv`y}tSsXj!`_@L~)mQ7NS6^h4tx%E+vH(CG78~Y!ymKsZ9 z=SbOjba_AtIt;2&WSzIdYq=WWpN{h)W_X2%JF)h8h~0)mr5rO7PE9izloO>S{T?50 zLzsRy5N_@RDqXKO6s^V&;FWaZC_RqAXV}k|7p?(kii)Gl?TW)Sg4q!sOglA}H!GoU zaY2?~%^XA#5o5L$OE&`3{bN2EzKqc6YhZ6C$pa{wz0oum49`j=%hA5a>m6ZvgQuRnUC&x zk`L#OWW}fIcH&kixEh^5^#A3zZt}n3+8<`q`V39WTIE>7E-tyDZfT^^LUxrm}k+eA+lT{T0^Ru5d`MrKlt3r z6IYm6G(Y$>?Z!e;-i&u2lm( zkYRI}fzdYy1K-hkd(xz3ZJ}4D%@&Oelr3s1EREzGANNhbu6i{W?M0jK^TWqhQXz4C zlzjyH%R`3lou{dUA1l16utPA&ihk`xn+ACfKgQ^07ron4#DpAxjAdzD83ZSb=d0vK z1LjIdXcE4631|I0xTYdHjJoAH%js9hj_w$#&+MF;D^_SDS{Hh6r;EKEzCkiDKyB;k zIe~aU<1EHZ3uf>CXf&H${fSdnz#UaavRz*5o9Ij<&Qu*A*mR-GkwRN<4JpYh9 zw227_1KTj9{qD7I$rhiV5Kw~Ac|Z$W!Wg*HH_kjl?$BEp+ZCxRGqr!Mr<0~Bsf<<^ z)~#vQn!LNl=+?HnHd{@?erxdfi**=Nv}I5%+zFCzZgp<#bWeDaA!DG?3gdXg^Sf#< zsuy9Q(Sk2!7L`u}FEm@4&c?VH_b+ zK?}ygY%_uJg2bdq1XM|-+`MGx=OO9pMCX7Vfqk8w}*?o_i<@`gtP{@dOVh zY_nNykODD&x?OAIm&O((f&|@fNzR2BoxfJi`K|ie=*$bA%ifK3}OZ{KE}o0aMG^!cSJV zOBtQ|)Tk(p=)vhdIK#?uSFrRN$)%W#j}M4?;|c9#+2T5rFy?Pg(_hVXk9PuHlF5*& zV|-no<$$hSLeoISOBaonLZS2Su9zc`_32ZWpP*>BwWA<_`_A-GE9yzWJt2Wd08H3VxrL1Q3ZvXm3n zRx#lTFZbsYAIK%Qtt}jjIgXKqO;VW&qw1L#P{@kLvB~zAwPGWq$T082zBK*GD2q$w zSPh#TPwDqu?EuQ)vkLDeWVVw8w4&^t+Ue-lKH{Jr%u(WH!86xins!SuIi;CAI_-_B z+>rQe_p60bx?rEJA_i_?SHjpc^>`LDi>&Xoym+^wt~b%CY(=Tf2#!9C$h!7@`J#aJ z>k=jXQb;Roeoe!LMi>o7Mr9U8UN)FPc%<7D%iAdOu_F=;;)se8W&9)$W)d$_XnV0a&mHG3RgQ^ zsl5F1h7f+AUgJa7+5t6Q#utPrIk=kmm&T>f-u>mYrfmrn9ak!bm@H+cT=fKwjgj=G z^MK~rd6#lSHuAp@&Np(|;L8&YUjK{^OmeNn+X4QBA-4>cCFVooN~dd{981+_2q$?y zpwuM~Hn__?T>Dbusk~e8G|5_FrZ}rJTD07TW%8h0osMg)PNo12rTpJ-Yn?BO77fn| z33!6jy8J9K2>D$}`(QGJ*<~33Cm64JMs+Yk6azB2AmfESFMNG!RVlq|K1wIrHFTj% z!zK^8qQU2~Z8zwM*&InP2RKjbvDdTOv@@t>L~C{)X=d(2@PtWA>^In@sVkESIW7y* z&C)XC!{Rj4hOtscG3n*Y;O;~i8ayZ@Jh%x_zWCrO`#qf@+WQL4z zClQy zzMjekDJAB2o1Fj5r6<>00Has){i1i0Vmd|BmhLLKvtrmI<8~ILzaBq9FjmV|@?)Hb z|7g|!*NwUlm68l%Rp8dEMi;K1+i3JwC!bXnn57G>mA{;&#LT{Qf7#GOh623xfj?3C z+ba90g^vg7Fl6NVMSKqJ}EpxOjrtZ+oqb2WIbtN z)xULMRq!qs@FM-Z@P%zQm*8Ac!&Dm}Jd@Kj@a|QRYM=3S7iV+_LY&hZSW&3e8x?Xi z%u#Xo*kW+&c>}epeEK1w9e!6SM7HmF>qFSwz#&?w)q1yfHWbwzqlxTrbt>m-Xp{o5 zMtm0F)!_U7h>T>2HpV$;W4-=Du+62jR(~+VESMW!H@33*lJ2D~@lj6VL%hE6bqV~* z`?U{8!|*q>mCshhtPZ`h2k(|bMl6x=$8BS+D#tgI*h{R5v|4fjZ_mVqZD)PsqIdmc zvuW;GR=HE;$2)8V09@hhxZ)-2-HnNuP<2%CDlPX)>vFZ$?lrVJ#AH3{@m$J^zFu&u ziW*vf60(?6Sg0c~-xZ4+`0~rt3d}P*!Tf1b(X@!(bA)V1N2ZkwGw+SZlkW!t?S)bw zL|S~54d+q!nodTwn5aV0?E~vMfK_WQQuyY5sRsI`pPP?X&dHdn50nQarmrgdwkT`_ z7^RrX8h_s0bjXnSlFZ7rn~;8#{WBIraH{aGNp(vW1Xo#}0YJ;J&y!mwEx zNv8y2{62TTlkPJRR5Hy~5HOU_sH2ZH1DKW5A1B)O0&g>_TpupHh!VSgI(Ceb5p6D~ zI@IsV`qZn{Q)balGT6P(X7e%^T6H%24`eHA0;6OcOB1qQsF6d$vV0fZMl6gO^Dh7x z27N4}U~tQSQ}5ovAOC#nkG0!=%%^mM+FXL5FJv%fgJ934s^d$++kv75#WGvJz;Bad z_JcfshVaer4T4uEM0;-Hcq(f zH9o3Hw?V=|*T$r}oee8E3i1DL+Wsoim-XTCun&am7MwIEpjziPxU$>@uA>zy#sqga zNC8Cv=`5UE3j~LUN$2Iv{S;!2|7lGYWzr!owiaG*!fVG#Etc4OICV6dLofYjatCC< zCrJKXUv83X-5`E0U`%2t`QG-J!Acs#@nII9_8vw<^#782X7R0IaRkY02MR4#yf^z4!f0$dX z5{azur%)&tQy{F28>o%BFFHaRE~HF9bO7KXi*6!GZC$x#keWrDco3zWV#|2}rITTO zqeO?t2H|l~B*>G0IMiFkNCrT{FT5XLkx}p$CPIs(@8(8~{j{5ovS79><)4MNy{CDJ z@zw+zrx!CvFX;yiF%lmQ%N6~w{KOI=wn4nJkerYa86%MtZ*kj8JBw|2=eeiQyRBHs z(osOMAh9+hwZ+Hvh9D^ z_X;%o`D|iE z;p?d>bYlG;Ii#cM{rJHjdW|!Y)5RJ_Joib z>G&EwC#L>%nw0=0x(f@BVuc6^AVx|TE2K{3)QJ`ADvcDK0zL%uVNuqwhIuKa+75tN zeH5xdeaxaKD_bnnzSWf;HQPy(mY{u)k+bmtXp1ISN1O1@dc$w}_``T(OAfmLq`f+( zLJJQYC7QKL!gs{deUCfWOhK8IkyXjK<-;Anex*^gT|r)5cWjG(JbpO$ya)PxulT@; z)eW|f<4dF&Py4Ec7OdBLQI@~59Ot;0imnBxS5ZhuKB;B7z;^2~n4^pux~H~^M(g8_ zJVlo2HN}i=ZT+F?5$_pAwuaEonOpA{ZG}BmjWO9w`*9TQn^F~Ll`d%iy-C;H&6KG2 z*eh=rCuLl&2(!Z^S_Je7#IaRQk9^IlFIYAl_qns2v~-Zm8sD)o0{J5rD%7f4jW76p zM7IvK&OFwIj+PlhL|>-=0&^%y%8sSn)|-xtOylTp->r0;Gc~GQYxbRYDh)JkVMMIn zKhWq~@ej&&?^>m9f;nS%E*y2;z3rmP7&gDE=^86_FQG-Q2vbXE1Oj*;!Kw* zpTNZ%?I59k>p-dR!ve1qdbuoEl&oAIU+u2&NGE#+uT~9@U1$1f!ZAOR zrI$%Gh`D=>7dC3p3s$X5Iu`A#2{NG2we3gdS{X4PiYsk9Ej~?dzA8{^k7qv8OE7#g zDDH>pf<@??Js_S--A@R}3Dh7#>XtkUyaK%JUG`lTj4dLnn2*U*d4z`94?CY0Vnu95 z)W0wCOP#@1S1CNEB)n#O-<|lj^kNnw2#E3ySVaDufK~!^*=V4ab!Lm^6%TPX=IBJi z9gfkS*A0y*^m}tkLofa?rPxeI(a|9ay)ob%XV%02sp1WX{yMnxt+^pfyxtXnf&J`JNF_?E@qMLQBntJFRdMSiK%M6Is3 zI5U~8Dw}Qii0LkE@I$M&@XZBzP18T#DDqH$JA3eYPFqk?qyy_azpaIK27Kl5b%Ex~^ew#Ug+wX5(nWnwoxGBO9(m;>is0m z1+frVv`B2lV7ht-FSC}L6NJ`CV~0Cg^;_o5U{g=TdxW9bEX?1o%D)0j{;R`dO>07!Npof}_p)1$-c>K(h)ChOEexArj=YuxIKk}N z&b`($ht&xfcpDGJT7)q|}<(Tw?H^^MK9{ar%yFL%$LYhNW+&<#Gcz#u z<&mjTcxdUw!kg-v;}s?IN376Aka7d;2iGdukbr?Ib@}xMu3Q2+__S+k>=H+5%Xfk0_^~7IasIw zpt1+#-xgwLYGV8?2dZvM-klD-|4bF?bv3ss-eqGl*s`T$we?BK>MZ%}rYS+OF6&uO zE1-yKM?^e1l3A${MK9uO2==U{XYC`9P1?w_G6X54Pwr)ZiI?`(_~O-TquT6fxyeN< zBSnxzSa4FpJ<0g?<`~6gcy%qFd;=r^Z%IG`j8CCYuh#d^1IayY7o+mO{&uK7BN&?r9k>I~5 zA#63h^l{}=!YirCw3rCk7gdr$3CbSJ6C@^wd_^59EckSr~blh$91K zwXRt2OQW_YEi5ef^B#0x|WYyx+jGQ9CLPAd$f}@u3o*f z-97z;s(hI9M4{I0RVT%p6)S1T*WNa@+pG=k(xzdAP=P?93B4j5?x&B(fmxAOX*oq1 z&*)iUy`z@yUQvn9k|&?WJukdgR1xGoXbSCyN%kO_a7N0YnwM4tx|ida{X>TPr@s3P zyL^fBDlJ1ZX#Pg|80!Jm^m=+Nh5woH*Hf&U*XTl439#U7ngf;T72GV<&uH^5>hoG2 z_4n-d^xaW`LMm;Bv0uurSq}glMzi{RhX%{9Cn1U{v5}}X@Xw4CqP3CEYb2%|K;r1# zWlG7^F0dj3XYJVo9lfhvgGTc^G6Dfc4bYFY_jGcBe;!VbFg#*y#L=OB0o27gGQLFF5k-q^Z1HQNBTlaYYQk&wFP7aYgP1Oo`BkA;~Woii$?G!HcG%t z=i+1XhjNAuNtEbu7y;e&v!&)5|8;2xmPQ zG>;4E5wqa!;o&*gixp7r5aFL13G>~n8u8$!XKJ(B#L@QR7_^g-Z9~?T-&yBcm!NNB zLY`#G_5qe)cGoOwW^$2#$QdZlaD6p>920jy_mkOvN+F7qW+bi%%e#2;(oL#K(h5p zQ1*D&j6PdCyPJOD9X*RLb&Tm75n_!wB4yR94gN6zgs+57nihZAY8&ZdP8- zKl6=$tk?JZ?f-o!R{Q^08pb~srvLr^`4xwgEZ{$=)L-Ac5_Amde3xG_c)WGOcR9cr zp}Xt9ht_=Q{pa7m-T&ODSjd5z^BDCg(Y%&XCK9Ejaey&?{g?l^{ePG1Tgxt|JB@6g z=Udbe`Af5lzYQiS@czDnLR!iVJlmUc``ggdx%SflNJ1-LeeWDgS56zxwa#osenM1NK429-S0}-%sb=;RasoVj2p}JZ@4_ezJ2k8 zCiE|%eER#n^!JwZU#K4UziDmtc*h@%gVU*>o2L{SD}PF8s#geE1;;jfD&)(zNWlw$ z3B$BH-DqMRX}{u)r*UCyVyV|&hq&-j`Fpb3_S9@u>f7QvL;vq);Ssa6>A4S+Ja2x# z!~f?#{T<1h-+zd|PiP%PYAj_eI#2CXpAcqk&4-sx_P?Gw7q3aCzd58H842nAALmXy zO6n=oBu)eC2g`hiFC1%G`!wxR9=V0sfRVN@`vZZIVY`8B{mEKU_*O1}dG~?6v0Z+i zZ7ZwCiOMHpCv9|cjqPMwjTp-pDgHdSV-%{s<4$*G@rxu@E<%WJKPIhv55ZDUv5%@m2%YF_ zoJ-p;L?*v`FTxjBF*lj~;OnV-7AHSATuNvh-db-hi7Ar)dg?pHGtG~d_xD`;@AO`Y zdCvdM)VrO?BjrVrg>!cr&j0+kH#+@cuN0Jj=K?(L;BNBz(=U-WH@fpJgWtTZndzy z7r3%`0s{qsc&x*r#ZyRHF4~^@^;8QPZAw3Ha6L6O)B@tWjOH?4iQ7<&>eo}c#lxx= zO&@bcYs#pww9?Q*$xct@%8}6-_BDcnM}hVMojuzx#jGM9jAW>!t=R~>n2p;z=Zi@p z53WTsBC>vP4-o$IIN7qYDg8X4yiy{;MyZ;C@WuoAmkUxQq;HqoxEWYv)jED|i@I>f zqZDvd2wK_5dA!a)11dhgZl z)A%He?VFd5vSzcGI@0LBeKB_xFKC~{iIn@;!tAf)Il1=A zn`6WvxBYVoB_(V6oNb`7 zR|;8uJ*ATByhl6W1u#oA_O|Sgs=3-5Xev(ZZ^zaPzd!%&zyGD7_Fov?&;C#m_5Fm~ zvG&cU%|5wCi>Kx60oyGuh4vxNvpxiHn}D2`tVR!kTlzmchxy5rLjivmmX$`F6m~mS z;wmV`c$zXZLe$99Qf~D7Ur+rHOr!t8Z~O0>x&MEY|1X)uf3}YOR+WES9fa9Rb%Q-T zD11Njw#keV$YaHDEsiou0or0=viP#27DixqDUKrYlw@SbXg^qA&wBdg z@#8`%Qya$t7RN}PUPNziW8;{hl^6q74FY5}l9C@c{}`cc+XWr?Wl@qNX@dzT4!s@4 z@}*0=LDV&>>#TZ&%IG}N(M*}^o~EtOD%L~kbc&>7dg|EQ!2~nLVaC@}|4mEZ$K;{9 z*suyeB6()Vhcwsk(c9FYwSqx$q=2IF3A<17teD~62iFIP0zZUElmiNl!HWiI8mZ+R zfwj+!9X}S}$fF~16rMb@**{5lZlddx*4Zwg+TyfrC!w4-%$J3?0bUk)8@_kcmV+z^ z0^8G;f`Fi#f4ll#@O%9GOYB-AVK^d;n>@+X(7{Hky-tp0)&*v~VAuMe$y<}*IDS2) zF{EIsmBJ+q{oiJ|+@oAtW3S?W8DS8YJfmANtXr(9H?(V3#Q8=L74ll9Xq9TCxA7LP zqQ2g4<_rPy=4qP4@lrV3TF#@ppoQ8php}vGIU+@o0c1uE$x+)BC1ZZahxqsx^0pS} zwSqsSChY25Kl#PR-Y1p5g3+QBY(x=S{mGil;_7^Udlc%l#z0FD>a({JqqJjF)YmHQ z$6a))lZDrvqgQ=8asK6T56X5nYr-tyKc^53j+5Nu1^r5+(jSKTl&v4!BQzW>%-3y$ z@49UDzHlGQV(>G($@`$T43DA(oo(T)ndRJi)jNONz5h?Vq^HP6KA^MZ7yuE}S$65Z%dWfLK>C)zMKeg^HYP^YT1I9jG4+e198FZB0P)SCC`Yo3djQe@eMtxG+r7e15Zd{Gtv@e7 zNy+KZ))w}vw`@UyY{1u3YHAj#ZwZP^zVM=Kr(P$v+h+e_-cEg?uOlcAxAKYJb$ltt zmFTPh`9noW#iJ&7r2w{<)39>LTs~$}TP(^gsM^_F&+=82&%t|`A{TIQ|D+2zS1|YC z)zN?6qx|nOpUGYmx`g4Fi2J=?s5)5SEg<5WNwh*ccRe3}xU!)gvukd1R5TeK*uI)3 z_aw^~ZftSv(w5a`X`Q!0`Fe`MC$X<~FZyp>-<$TY^wj+y=DV5R@oMs-@juYQT1n() z3H0-SFp@J>BL&%sWO{%e!kD&e=(&OdDcKxZ88v^WLhSdOGwlhK^>Nb^6r%Twa zs;@N^O|N2NaY}#d4L$$01OL4Z^M7S_i5cx?R*9`)XSw>pIII6e)%mYN9}8E`=uoAV z9>c%gDC;+x`M7pSvcHsutgWV$Kk*nnsM6#XS)=vZZU!Ew-*qksn?F)LGE161k+6_y zJ^E~Hl5P~GH#Gqhd}-gn`@FtU_PpHJQ}S%Ce<$@bsXSMvkw$$VYr%jlvYj*=P84{6a$YM&tg(9L z{mpW`rxqr?y^I=^)EQ%KW{?v$x~hH9YxF?(r}o*P8bw<65H8|ANte%(xp<79I2YD5 zcyFA4tPx|C{FfOA*^)5sLa_MY$Xa+eMM}SW|OT6O*Vc`P4_BTX$ zavb_IMxJ+cqtZj3R)}2IvK1O!M}eJyU)YUSU>Labu(Co)r<|uAh4%Ed3ki1@r-X>o zRjfmI6K?WpVbIOND)+zbMh~g(Uk~eG87MA=o^c_Xd>O<17?Sf$GuUXdnYF;f*R^fN z=Wv&iSo|Dq0=nlB^TJ6M66(_DgCS}HF9MWUjfdWIT%8ze7^xxmoAiSv+mU9X4W1@Om>Q za4TIgJE7KHE5&$?!guYxVwm6J8U=TAC-NCSql)h7?XhGcMq(>lcD!~Q`zc@Na5YF^ zzVs(Mj*G9My1Ah`z(CB2xWfK{gEhM%1x?FvS8QLi!zl!<_96G*E;Ex=01{5QTGSN{ z@?=d#ZQycNC@i7Ev|>>#X;CMtYc)6G8B2`+`J1AL6Z)(jvwM$efn~yo2UTnvY_&id zrZlr;M$hxocFMw(l!)xDJ6>A!&@9V(A!>+!25g+QizpjEFq5k|7v;>5% zPwUJ7Y}9=Nw`luf%B!oSN-j5g{zOBmvCP+DY;if}L~t{}2KnRM?5dA>Id}F!WsBeE zh^zpx&6W{So~O0pb`k0Zl3!2B{fR~@4T_Mq=33D}C){-s#aAm2XLZT*X0hvzZ=smS z{m_c!JZa3PEdrPlX~z@!9keCPM5=S(Fo#ZbfMSeuP31h%aC#b;#BOOD(I@yy5CAqv z4NL5%K9L&8)K|QoR~5-O5_-RSJ~q)Ao2&e$)HJ=&i<9rY(uNl`w3I2%+-m=1-~H(Y zDh1l_90bkD9>|etP&Q>hxAs?HB~*DF`Nmu%zd-`DC{h-r{u#D=x&PCn+VQIXlzQM> z9~OT0dge!Wz+vgW`q*t7>CW6S+~&RwNPEvbroM07)8=vlg{UAKUjZj;y_-KKwvGG+4$FWj0ijYn7JFvR45?{2ZX{6|7V7xmQ$pWc+iCU3bg+;s> z4RzOvzCQm~duJZhRJQ&5{@w1@c5Y-+0l8O1gfO&>0)ju4fPjDuVF)BBqa+LxWJ(C! zRsm&*GRqXMK!7A97(xOe2{g(egn$8(kT8kNv&=L7@_V=LtM{sY^{QUIe}1p(oj=bx zb?Tgb_FjAKwb%N5lS)#V@8IT@y@uRZzo$a{N12bFQ>#*636`U0AI2^% z9Xo*RNU*!Zw;CD-91b)dau%ob*|7>~&mGeAL`4tcIZAeX<;?0~PJMUru?d0eZnAr! z;hPlwH`u9&cUuf8X})|(cqm4oB$H=ydl4Ut@))iYm%W-h(F@Ei+sOH;^q?>bYI2cW zN$$@SHLg#)XgW>l6eAD0NT=AT$Uv#1#bf=$jnLj1_0NIiJF>W|A z&ufBA)R?cU-^I}@c(_aaxyOFh#aB0YBVDCzL8CvkU5_}_eweags1qOn3?}BEEpU!c z09ZD9(30P)F3&Bsc(~2g1e&iv2S5q%eUuv+r|}H_?fd4{32T>%q8dVNb!p9x3q9fh z;4Rykgm;$udy6o|i7??<^*w~3wM%Qn()q9tl6Sgw8mO%ao$DqMrYCZaqY z#hq4XEUiyxdSWax9YD)dkdNzIX2)ITeHI8rK%!}Ro%Lq+#6lU8IfjOoe+g{t{#-dIfn?#Y${;(j=2TT^7Z({fw99JXn zM(7u^YvO;Vdn0@1Vh-1Cxj9lr>9;EtlBl7#>O|IB_Bj=qIxnwt{N)>4!)b?ILJ9Tk zkPI6CB|R>C)(1u;*H#;@SP@~`Lxu#K&ed>-$cP{wi@+nx!b9=<2-TAm6)Rh0MMNOgZHeah)SdA&)hry z3a=jZ4c{`92-F}{cpuL_bGyRUIT(QRR7$E;dTiX~c(iGH$MUE)v;Jl#CPTZWY6T6b z9$A5P_s<{8f~)2di@F`&`i9<9vR2Rs=oL{kcGj-Y2g5w$ zbXj5z)6O&g`r2bx=a1u*b8KV(xPoU>T6A8AsN?}QRvedAG;i4ATnV{bzO zcm30)$;pta>z!G6(J5&r`$MBVLkhnJA|t{(R6pbr_6)_NAr5VLzMXAxE=2iMjLh7r zK8%SNoS7Y`V|j@TAB&@G!{%i(ZCWMp#3*3R+s&%PO-62eBmDLy3iBe*WxoL)P-~bHpVGpx1-hpzXkU^xYuX1YCI1 zOjh%bz8&%g__KKKmcKVnGk#;)#oG`;v?t*K^%o&#Au=_^i0~*HQ`kxN58Y`mn(Z8j zvL+fd!9`YWwl2c6+v8V{dUkI{pjAHw2;rTJMu-zyX~os4qghtyUhf-!X`S-c&afW2 zbQhauc}2~9bGg;BNYVGsC>E6KIh|gY73WnShb;heFxdn+r~1zy z@Jw`K3M%P2#Get1I~ zDXL;TzSL$LT>s5Lidx?&N4Rlb$6YjBm2N*Zqm5yy3pm7dck$lnhJ?ns0vUmf0rNsm zuO2dZ+|p+=QFXL(Iy}T6ir%<_XVK&8ou15u=NKsIoXKr{KzfkK$?q5_s0HjGI!?M} zsE#+`IijvTB&xHLWb1H$ePp$%ASV3qlzmt36S&qh(NoD&NE`j_)8+F}U{ol>@0lI7 zYM12jWAcDc_XK=G58(sJpk3|P4f4Jf3LhOTR16v~V>-7^Iik;6+uNQ?C6|O2XS@AW zq)7ElpAre?{OOCL5tmPWw$xj~sBcA1={rw`>-b}tw++&U=-xgDm!pyMqn^&0Tg|SX zOuuMZ`d1{o`N66i-)m|-rHaiNnPuT;H`07BeG%%6u=SbYsc`>4X5c@#UIGY6cZl2S)@u>c`Uw<3CB#!?`{ zqzll+!Wf`#4YZn1m(+7d##FX+Le#!RJs$QooF-Qvgp^V1W#x&U`ZRq*c~kqdMVrz0 zIy-0r(W?06cBrsxsc#XabUJ6zAPe+io8gX$fDCsS8X*lN=mbEYZaVZ0Q3C}ORaEx} z>DQ3>biQbGONW<->x+qTiAGTEg8MSy{8tZ8xQcjU)a- zOg9}2|Ih)eGfD{{hui3np9bDCGb$*;GmJwwD)a3RbWAO~I5ap!NfxKht8pcC-n8f0 zI_#&Ml$(fwjEu|`Y^jJ6o}8_Cv}yOE;;ra&k65U8DRt|(s95;5N7U^5bw^|ref_n^ zE7m^GFHy>Q81c1CGx#Z`kRDoLG^2s2AddBDF;_XJ!*gx+H^H>|q5Csc!k9xgs=S$aSc z8FdRJnkx1X-=1&lHEm|HI!yi=g}V%oC(_*Nv+W%pwiiB>XlfU5O7)^pIP>QZaNFgYaq`QAU~WjE8>K>l7|SL4RZ@KZni5khqe~B8sy|+W(Wl*d z$WF^^I%epQ%ePoTR4NwNXeq|a;VjAuT=d$uopZhBdx6yj2MvnfpW|(%?-q8K5+~!J zpj+exx7u|YH-AaGC{O+OFRS97@%Z8gN2{r=P}++wb5?K7mHV}pSsy1OV#kTIRZSBo z^^x5nCK2oc!Y-t}NW7JKkgAb#(}boH(3jXSWN=??*Jv4D{Nm}z9;$2;<=nm!D^i~y zvQ$>~G|s47$Xku!bXP4nX|5q=cI*m3@UdZ%1IRlw(C&b%spGXMQCyuA22$l?7Fy^7 z3ad?1ep8mT_zL5qCxnbtiD>%VTw7FxXNYDX@^QsN9s3{K>Cxwyiyn(nRu*mRZ>f}C z#L#(LLtY#JeWV#*qRu7*Va(jsS?KeKPv0J^!a+%5^9kf?9BsTzmoN~16P6Lp(ug5% znuXAtE}c&ZD59$OkLmC4gBG^F57EInHxUevP%jMC$;fha4m;;H%6Gd$sA0P;zV5A_ z-81kV?KB-Kz&pZ2)%~HP$2R|p_tm)fVqV;we$yz-D50B=zW#Y>uv^WtF;C4g>LBNE zEU-S2+us>bS#KGmI7DdkJ8$f9>K$!j$(`41qI0Xz@p&TB9X(-kabz<#=eqse3NzZ% zBfF!ki+z7~+*5;|3Lx^uL&1kU|JtSewA|*BdEgOg@mbVCwA0`IX!)I3l1l|J97Ge$J(xq429fcvhqR zq{R|ksVm}v*?nGN4ph$-g^QFaojB6+w5H=ec7UFke0Uk8#6p$WrExQ~=PJ)$0BeGHS zy%9AR5iFh3dN1@;-+^&B)zi$ig0f}z34Q_lQqq!gLlT)Cssy;I;Xb%Hl0+Qr&CUR2 zx>L&yXSn*2D z>zpkJ|JvtgGmi5WVNt{D7rUn~nHpWp7?kW9%&A(q0bq=UQxe-u!?*BWy?na%>>qXE zm_4==iXDAWr*7{c72ErAx{l{z<7mS{-!`*h8+A@R*|*xnBn~ISwn9vyaldi^?z@gb zjH+rN6f&QJEiq2IIQ>Thi4QB;aY`r+bOlhM+1Q@!gx79IZVd7!d(3)n6emy`disYC zl}Xoo+OSAEwPwkQaP!pk{|Uw>BktymNSBQh#;n2@x^h~e?q0Gt!_=>Ll4Vsv?RGou zbGxbmZ%M~*K~<_ncULFp+A}kuB$jYz-2zkO2SHHc5Q<0TZxPlifxlEg_?}5BE7CB`%?nR| zfawx&b9L(#c2ZtHbZ_Y6-y_mCIr)7(**(m$+74YR8f#&5@)}-D}v734# zKD~ER_cAQ*S+tx!TY+qx31A&#GFwr8&w|vnt)jj^&KH*5tLE9%{bOVx?59!zy>BfP z8#aRv0y+|nY+dnzoIrrhx~Dg^%b9};3dt`Nl+Fu?)tmArOO4DAXZp3gaMDH`1%pPB zoaMG|w=);2)}}Dj+d1Vq-0E72eDC$FSo4hEa(MvJm3+Ayh_6C_6%_izOR9&*G&IOYrZkXRmXPl0_8vLcY2hmEL z_?#jj7&yOixHr)1oUR$AcS3fsBI}L87DldV$slB>jFaf$a4Mmuz!(rXeifHY2Pv-j5vcPajn2)tWXPvHhq(VGK_Sgev zzGv$6PHESQGrqbXZUz4cx7_1qXq!|=`h`rJxS7efA*o4GQT2QdCvidTj?keVYYXkO z(ek%91>4flBO9=GT9FD^#*caS#9Ucc$TZAz)R2~&B8lw|vY%11an;WdTN9yKI&P!C zo$VS-Ja7$OP)yI@B1)gOWuRqaw%?Ckbb@Hf+uk?uu-sp;iGl{2-5fwGxS6uDgXqFU zaCAcVs?ZA;JmcKxNbDR?Buje@Di?TWJ*KWr#Bs)}J1kOf>A-5v&Ga+*vG)oR4NNv& z#-eilXRs1Xp(w218JWpRbV~X22gZf^@!YcdBz$~Q+8eQzSk0Z10OnDsE8rtF)hLY7 zBPvAT&;H5P&lj|=m()eF+yeB^GmW7jkVD-Ls`ROuI1hrgP=eVzpF9tRl)-3*b06OY+mve1ONxHOUlN+!?wFAhnHmPOOwA(wl zo~>cQSOq~|glUG9k{I8Gyq^AT2$Sa;uQ4~&HD?-OolNN3Vjo7jty5t~I{ChVv-v@8 zzX=V7;=3+buXeYe?b)|YrosNt1tvI_{b>Fq==^71`ahv7V5n_>>- zt2cCVL$~8LY@x`jtW02q$}DDM$h%!z{>Mc$$v1of2H-eVxUzoReK(B3gtmXNl$!g>UtY?^97k^2Hb6HS475I%|{)Bsb7XQt!tD`7OR9m+8@Q*#p zaMWjh@mn@GV#4}X9^!^qJAo#j=vsR483Y6jrPoZr#Rp5#p>A;;v*405EFCth+x*n* z+l?8wZqXpE#7`+qDyIPxk~)C+;zrRmjzySTI!cI87N9BgWJ~1J`SH@JPlFCis#s)G z7{dBrzuV`J@@8NPv)WL+f6J0eNmcNNyf@_$st5z#YP}LHXUV|t0O+vgly^FD{|CvY z`ky~g<$k({AG1i7A6Pp3tmM7UhC3Pket2t`ASEq?nr!4Ws8KbEy_bF^Hh2DDbHY=$3^#kq?x>M zYl-*O`kCB8xMH>xoDI4_Ub08UY^x1w-TVkrx=&0{?b^?-m^le6!l7%v2^B(mhs(!G z9m(U*hB1W_+G{|ZHXgj;L`!?0{X1g?RIXLA9!wA ztCn6%2m*sjl+I7dqJXiGk>#O~KK6@Tj17i#!lfe55Hc^ zm6s}T*;k#rxD7#>Ecn%tbE3=kuV0Hy>POg;T0?j!QY%jWN|%$9vx7@@i2`hCDr$^4 z4N9IZTj1Afa)etx9YCZ>5XQeESTmSgl{)-Zr7utYl1@#a8Efx5*sj+wFiy;ABhK$G zZ%^-Lr@5$HX!Ef0=Y%kV>5~k(hLuu(+~fXB?@IhntLpq77xp5qDfGfYUizOuq<-Dv zIh;)vt|qD)*r9H`%xO> zV=0{+t<*Y5(QCLk*DCt+uJ4*zO+@ip5g7>b25nv+zj#+)+-ItN3R6TIc-Pr(AI@Ws z$>V4n*0BJY1@!r)OS!d&+9kE2C(j@)iOR22^-cvD6IcYz6IK*QH{r;N&b8@plgktE z7>^Op94s*lAoD)15xTwFNNq0Q?a1q)WwZiXMW+nS@+g^{T!Pr+gmOuGPd%h+GFX?; zH+`RXdBc*KZNH5~;;nw^(mwNKL_EEiGiKzMvwIb+`!w5MK2S+MslGqAajx|+dA1ZD zx#&w0Zh5!2nO|lSd~pFS3f}g|RUCPD@wV>=#Y=)3g5hi;W&+>d*49}DJj1=u&A?%bwftmheKju|DhB`|uP=7KrLJwNZZ z^dpXD{_M#2mmxlsi?{Wg3I=?Lj>=t63D|Fn(#fv-_3Q?3yO3UB&GShQ8ylOT`JAkY z5D1Grv>DeM8&;Z33j7czh-l&rc4IEzACg|(T3_XQbW6w~?#0&Nsv@-$9hJY>0fn{( zc94~5GV9>LrXTVE(a!@5z(E|&AMexmu8E^A4$h1pW`7@mB-f}uKnQw(=lh=nfpHHd zedr9vl?Eb>%fFl%_UN*=51H??TetPw#kD*ImEvKWG%Sw0w(gZdn$gi0Ty3r{Wf zT3xPRY+7mtP;_-}JU)o#sD)fzK{eQWu?XLb@Gd0TpKC==!D9}tV}+XgP7x?3e6pII zoBBu7zgqXkBlG`02TBI3`?>$g_Wn2lJNn~xTf$=7-~a!u|F5@zoX4BevgE<={>SS_ zT$eQ$&8Jx)28I5_Z``qSxTDXt=H#m;{EEd1u#N4re&`uk!K#DHj&xL(SHD9i(O^>W zra)fe9Vw{$q_grQ-ciKFwRL@5iLc)qq7IDO`Wj*l!bl4LhDYU-HBi~AL4lqk;wp6{ z*8EmiT@k{pE^0iozW%J`ZIz7&`e{v{96~rKwMWY{H$K01Q1dl7ox5;FgK#y%B!!J7 zG)*}mUuhHUAM)5s9Z5|E(zokU(l%{>pYh#O1iuvQ^k)_(*nP2@Ee?JBk!`bkP{ z*A>c332jqS=$o5&e&+X*k}{|3x6dzqBIZ&go=EROi1*;}9pd4;4+d^v^H=79L}`~4 z?euwrS=Yqw`_#yBuH9)eXd1R=d+NOW)^}_*0`ZC!*RK7%FgLo)wclE`y-%2+bq3@` zu>l04BOP*tNvuf5bwT{A(f8yHp?u$KcEQ5b=0SkDB0|`?m@(-8Zo*ZE$(Kr3bTucbS_k>ULaoe^gl|;Re99jo79Uk==5SjVc&T3ZJ(FmX!@>l>1&dZf~ofwB`C=3AeZr& zoWh?%&^Hy+t$-(S+P*$z7g7Y@Q_{mKOXU#Rl^W?=IOey_rW#D+LWdJU$}#PHp9!wT z)YBep$SO{?QMRB0+k#!`2i~>NW7w#94UaCj?oUb#9{Ok1{NQjtVK(w-*xTjMCUwR2_Y_a^U^sP-vOUCW}ebV{Wz zOmXra)Ym7jpb+elCHBa+kAr!zc-s253`t^1{r$hD^|td5|DGUOV~MgJS#Bki-s>+G zkHjU-EWRMkEIHeY*Qa#9fHP%guovd86KKYp8@r#=TvV|p=5f{`UB;J(8!i&Pf!+!z zCGE>AR5V=XS|YsD6_O)KG}L%n9U+$oqdE5{+TWB;A;#^EVaxAqx_08|l;rlq^Y++r zO7{1L1V6hd%x$G`UHy4iPK%7el3HWTp!mAreo@Ui3oGP4XS#TxhG3`j({h1VmR&`| z$ok)as#kL}X$K(zH9g#GtUQ$~y`>p7ZQ3?Z4&SXVX6aWk)aoDmn9P@**Z0WlMC&D8 zF`9~4bCezqkoZTHBh0ArXlA`pN4}r9#GNSytk$w~4z!o1yQ7F3)<|@zd3S+Ttv0*H zeCD$MU^PotF4SyfK~`=%ylelc6V+*@x-s|eYHnJ6kFxz z2h50vIH4Y9lwb0BC#C3$n&N7UL?PAx!YroGT2+65 z#$>KWbECOqp6kL0r_jq{r^p|xq(YcsRxK?)7|Pk4fio5Kj5aJy zd~2sCL{YbWi@gDPsr8>eLpp_6j%j@5YwtT=MRB7|&n12a7W<~)i^GyiCX)Q1Ei!dB z`CDTziQgY}Il1xl6=>4P-s++wdn;d>S*YNLR&|d>sOi9>qa=c=5nUE4c~TC)Zhl@@ zJzPH536GR;Jj&<+c>8SqZc@TolMrRzzt=kq$0I{MpXB>^0U8}Sd>)m z_1r!3^xCo%uDaD$;p4fAtY2+IWMjQ$7fVys01In5Xd6F>^ZOm;vx*WFfk(YYF?=Or zV%eYK4Rn@$-Y)p`=&%sxTjiYow;I=q&x4N>UoXQii0=zZ(J$>!b#{fWMQfCn)vj(L zo1^jRpa0_xbw2bDAx1j~O&Q&YE6$yR+6d_AHrq>^H;f2r6K z!1zr>9}u6}YZV}iKowPIX4Yr#-;#Ycajg(s+U1nqhkCn=+fRU_Em~mW`lH?p!|~GK z9%qiW-uSC2V!$s_v$Qy!>jDFe)oSm_*Bt}+^2k<8Hk6;8Q=<4K+@WuKW1Jxsd%Y*! zOwXVHv~J>dk6^R;cb$)i(p(46j|takWBzIOI=@zJ9VpwH%E&fE^VMEB=5{B?ScLjHo`qmxu*0)vu{k{@&Mexc!-o6Z zu&L;*Wm##WQ{D}VV;T@11GKE~=bd8MIOkaIJB_R&$J{y~krQ~}FIDX)f|Q)T7LzKq z6rlF)4*&8%y9Oyq2|UC8a$J~#T5ZKezRt*loW|uI3(6q<4g8=reeISxbnC{*0*3*2 z!#e(jh4B0yoxmg_5&Zf}xiKm2QHlMR1Z)J<>1tBoC%aTg*=)wfdNc((I=7fio|&mA za?4ZCecnI5IWwu54f0&r`X$Xp?Vkp}VG@P->PJlgDnmr!=NI;ssb@_Asl}w=y3obb zSSC+aR*Kh@NpqBy#yC$)Kd)LbgC4P36)H$)+(WYO&( Date: Thu, 28 Dec 2017 12:55:09 -0500 Subject: [PATCH 53/89] Increment version in lua file --- filemanager.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/filemanager.lua b/filemanager.lua index dc6a16b..e0e1a96 100644 --- a/filemanager.lua +++ b/filemanager.lua @@ -1,4 +1,4 @@ -VERSION = "2.1.1" +VERSION = "3.0.0" -- Clear out all stuff in Micro's messenger local function clear_messenger() From c107d8a0e7ff59bd0ce503a52eb857ee276b9a34 Mon Sep 17 00:00:00 2001 From: sum01 Date: Fri, 29 Dec 2017 13:34:33 -0500 Subject: [PATCH 54/89] Follow the user when cd is run Fixes #20 --- filemanager.lua | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/filemanager.lua b/filemanager.lua index e0e1a96..1de1680 100644 --- a/filemanager.lua +++ b/filemanager.lua @@ -1098,14 +1098,15 @@ function onFindPrevious(view) selectline_if_tree(view) end ---[[ Cd doesn't seem to actually have a callback -- Update the current dir when using "cd" -function onCd(view) - if view == tree_view then - update_current_dir(WorkingDirectory()) +function onCommandMode(view) + local new_dir = WorkingDirectory() + -- Only do anything if the tree is open, and they didn't cd to nothing + if tree_view ~= nil and new_dir ~= current_dir then + update_current_dir(new_dir) end end ---]] + ------------------------------------------------------------------ -- Fail a bunch of useless actions -- Some of these need to be removed (read-only makes them useless) From b35b015a02e0d43cd8ded5c3832a06f2480b338b Mon Sep 17 00:00:00 2001 From: sum01 Date: Thu, 4 Jan 2018 18:09:38 -0500 Subject: [PATCH 55/89] Add missing zero's for command completion I think it works without them, but it's better to be verbose. --- filemanager.lua | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/filemanager.lua b/filemanager.lua index 1de1680..a42a763 100644 --- a/filemanager.lua +++ b/filemanager.lua @@ -1109,7 +1109,7 @@ end ------------------------------------------------------------------ -- Fail a bunch of useless actions --- Some of these need to be removed (read-only makes them useless) +-- Some of these need to be removed (read-only makes some useless) ------------------------------------------------------------------ function preStartOfLine(view) @@ -1231,13 +1231,13 @@ end -- Open/close the tree view MakeCommand("tree", "filemanager.toggle_tree", 0) -- Rename the file/dir under the cursor -MakeCommand("rename", "filemanager.rename_at_cursor") +MakeCommand("rename", "filemanager.rename_at_cursor", 0) -- Create a new file -MakeCommand("touch", "filemanager.new_file") +MakeCommand("touch", "filemanager.new_file", 0) -- Create a new dir -MakeCommand("mkdir", "filemanager.new_dir") +MakeCommand("mkdir", "filemanager.new_dir", 0) -- Delete a file/dir, and anything contained in it if it's a dir -MakeCommand("rm", "filemanager.prompt_delete_at_cursor") +MakeCommand("rm", "filemanager.prompt_delete_at_cursor", 0) -- Adds colors to the ".." and any dir's in the tree view via syntax highlighting -- TODO: Change it to work with git, based on untracked/changed/added/whatever AddRuntimeFile("filemanager", "syntax", "syntax.yaml") From 6d3035bce3de9287d9ce01aa15bdc3047d22cd17 Mon Sep 17 00:00:00 2001 From: sum01 Date: Mon, 8 Jan 2018 23:36:02 -0500 Subject: [PATCH 56/89] Fix incorrect mouseclick position Uses a new function in micro-1.3.5-dev.67 --- filemanager.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/filemanager.lua b/filemanager.lua index a42a763..955b878 100644 --- a/filemanager.lua +++ b/filemanager.lua @@ -1007,7 +1007,7 @@ function preMousePress(view, event) if view == tree_view then local x, y = event:Position() -- Fixes the y because softwrap messes with it - local new_x, new_y = tree_view:GetSoftWrapLocation(x, y) + local new_x, new_y = tree_view:GetMouseClickLocation(x, y) -- Try to open whatever is at the click's y index -- Will go into/back dirs based on what's clicked, nothing gets expanded try_open_at_y(new_y) From 418b88e4901eeb8a0ff6812cf409ced4002d2495 Mon Sep 17 00:00:00 2001 From: sum01 Date: Sun, 14 Jan 2018 01:56:22 -0500 Subject: [PATCH 57/89] Clean up the README It seemed pointless to have two similar tables, so I moved them together. Also added the missing documentation about the API for compress/uncompress keybinding. --- README.md | 83 +++++++++++++++++-------------------------------------- 1 file changed, 26 insertions(+), 57 deletions(-) diff --git a/README.md b/README.md index 7d3c152..2442c25 100644 --- a/README.md +++ b/README.md @@ -6,21 +6,13 @@ A simple plugin that allows for easy navigation of a file tree. **Installation:** run `plugin install filemanager` and restart Micro. -# Table Of Content - -* [Basics](#basics) -* [Commands and Keybindings](#commands-and-keybindings) -* [Rebinding](#rebinding-things) -* [In-depth](#commands-in-depth) - ## Basics -The top line always has the current directory's path to show you where you are. +The top line always has the current directory's path to show you where you are.\ The `..` near the top is used to move back a directory, from your current position. -All directories have a `/` added to the end of it, and are syntax-highlighted as a `special` character. -If it hasn't been uncompressed, there will be a `+` to the left of it. -If it has been uncompressed, there will be a `-` to the left of it. +All directories have a `/` added to the end of it, and are syntax-highlighted as a `special` character.\ +If it hasn't been uncompressed, there will be a `+` to the left of it. If it has been uncompressed, it will be a `-` instead. **NOTE:** If you change files without using the plugin, it can't know what you did. The only fix is to close and open the tree. @@ -28,50 +20,27 @@ If it has been uncompressed, there will be a `-` to the left of it. The keybindings below are the equivalent to Micro's defaults, and not actually set by the plugin. If you've changed any of those keybindings, then that key is used instead. -| Command | Keybinding(s) | What it does | -| :------- | :------------------------- | :------------------------------------------------------------------------------------------ | -| `tree` | - | Open or close the tree | -| - | Tab & MouseLeft | Open a file, or go into the directory. Goes back a dir if on `..` | -| - | | Uncompress a directory's content under it | -| - | | Compress any uncompressed content under a directory | -| - | Shift ⬆ | Go to the target's parent directory | -| - | Alt Shift { | Jump to the previous directory in the view | -| - | Alt Shift } | Jump to the next directory in the view | -| `rm` | - | Prompt to delete the target file/directory your cursor is on | -| `rename` | - | Rename the file/directory your cursor is on, using the passed name | -| `touch` | - | Make a new file under/into the file/directory your cursor is on, using the passed name | -| `mkdir` | - | Make a new directory under/into the file/directory your cursor is on, using the passed name | - -**Note:** The Ctrl w keybinding is to switch which buffer your cursor is on. -This isn't specific to the plugin, it's just part of Micro, but many people seem to not know this. - -#### Rebinding Things - -Do you want to keybind any of the operations/commands/whatever? -The table below specifies what you need to add to your [bindings.json in your Micro config](https://github.com/zyedidia/micro/blob/master/runtime/help/keybindings.md#rebinding-keys). - -| Command/Keybinding | Used in `bindings.json` | -| :------------------------- | :------------------------------------ | -| `tree` | `filemanager.toggle_tree` | -| Tab & MouseLeft | `filemanager.try_open_at_cursor` | -| Shift ⬆ | `filemanager.goto_parent_dir` | -| Alt Shift { | `filemanager.goto_next_dir` | -| Alt Shift } | `filemanager.goto_prev_dir` | -| `rm` | `filemanager.prompt_delete_at_cursor` | -| `rename` | `filemanager.rename_at_cursor` | -| `touch` | `filemanager.new_file` | -| `mkdir` | `filemanager.new_dir` | - -#### Commands In-Depth - -Note: While these commands are Unix-like, there shouldn't be any issue on non-Unix systems like Windows, and they should still work the same. - -* `tree` - This just opens/closes the tree. Nothing special. - -* `rm` - It deletes the file/dir after prompting for confirmation. Nothing much to say. - -* `rename`, `touch`, and `mkdir` - These require a name to be passed when calling. ex: `rename newnamehere`, `touch filenamehere`, `mkdir dirnamehere`. +If you want to [keybind](https://github.com/zyedidia/micro/blob/master/runtime/help/keybindings.md#rebinding-keys) any of the operations/commands, bind to the labeled API in the table below. + +| Command | Keybinding(s) | What it does | API for `bindings.json` | +| :------- | :------------------------- | :------------------------------------------------------------------------------------------ | :------------------------------------ | +| `tree` | - | Open/close the tree | `filemanager.toggle_tree` | +| - | Tab & MouseLeft | Open a file, or go into the directory. Goes back a dir if on `..` | `filemanager.try_open_at_cursor` | +| - | | Uncompress a directory's content under it | `filemanager.uncompress_at_cursor` | +| - | | Compress any uncompressed content under a directory | `filemanager.compress_at_cursor` | +| - | Shift ⬆ | Go to the target's parent directory | `filemanager.goto_parent_dir` | +| - | Alt Shift { | Jump to the previous directory in the view | `filemanager.goto_next_dir` | +| - | Alt Shift } | Jump to the next directory in the view | `filemanager.goto_prev_dir` | +| `rm` | - | Prompt to delete the target file/directory your cursor is on | `filemanager.prompt_delete_at_cursor` | +| `rename` | - | Rename the file/directory your cursor is on, using the passed name | `filemanager.rename_at_cursor` | +| `touch` | - | Make a new file under/into the file/directory your cursor is on, using the passed name | `filemanager.new_file` | +| `mkdir` | - | Make a new directory under/into the file/directory your cursor is on, using the passed name | `filemanager.new_dir` | + +#### Notes + +* `rename`, `touch`, and `mkdir` require a name to be passed when calling.\ + Example: `rename newnamehere`, `touch filenamehere`, `mkdir dirnamehere`.\ If the passed name already exists in the current dir, it will cancel instead of overwriting (for safety). + +* The Ctrl w keybinding is to switch which buffer your cursor is on.\ + This isn't specific to the plugin, it's just part of Micro, but many people seem to not know this. From 708f20cf37d9776f5fa57a40ae1580e96c019054 Mon Sep 17 00:00:00 2001 From: sum01 Date: Sun, 14 Jan 2018 18:14:59 -0500 Subject: [PATCH 58/89] Add the option to hide vcs-ignored files and/or dotfiles Options (both true by default): 'filemanager-showdotfiles' and 'filemanager-showignored' Fixes #22 Note: vcs-ignored detection currently only works with Git. TODO: Supporting Mercurial and whatever else. --- README.md | 3 + filemanager.lua | 152 +++++++++++++++++++++++++++++++++++++++++------- 2 files changed, 134 insertions(+), 21 deletions(-) diff --git a/README.md b/README.md index 2442c25..2d61917 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,9 @@ The `..` near the top is used to move back a directory, from your current positi All directories have a `/` added to the end of it, and are syntax-highlighted as a `special` character.\ If it hasn't been uncompressed, there will be a `+` to the left of it. If it has been uncompressed, it will be a `-` instead. +If you want to disable dotfiles from being shown, run `set filemanager-showdotfiles false` in Micro, then close & open the tree.\ +If you want to disable VCS-ignored (aka `.gitignore`) files from being shown, run `set filemanager-showignored false` in Micro, then close & open the tree. + **NOTE:** If you change files without using the plugin, it can't know what you did. The only fix is to close and open the tree. ### Commands and Keybindings diff --git a/filemanager.lua b/filemanager.lua index 955b878..a63105a 100644 --- a/filemanager.lua +++ b/filemanager.lua @@ -1,5 +1,15 @@ VERSION = "3.0.0" +-- Let the user disable showing of dotfiles like ".editorconfig" or ".DS_STORE" +if GetOption("filemanager-showdotfiles") == nil then + AddOption("filemanager-showdotfiles", true) +end + +-- Let the user disable showing files ignored by the VCS (i.e. gitignored) +if GetOption("filemanager-showignored") == nil then + AddOption("filemanager-showignored", true) +end + -- Clear out all stuff in Micro's messenger local function clear_messenger() messenger:Reset() @@ -62,6 +72,66 @@ local function is_dir(path) end end +-- Runs the command and returns the readout/printout +local function get_popen_readout(cmd) + local process = io.popen(cmd) + local readout = process:read("*a") + process:close() + return readout +end + +-- Returns a list of files (in the target dir) that are ignored by the VCS system (if exists) +-- aka this returns a list of gitignored files (but for whatever VCS is found) +local function get_ignored_files(tar_dir) + -- True/false if the target dir returns a non-fatal error when checked with 'git status' + local function has_git() + -- io.popen readout returns an empty string if it fails + if get_popen_readout('git -C "' .. tar_dir .. '" status') == "" then + return false + else + return true + end + end + local readout_results = {} + -- TODO: Support more than just Git, such as Mercurial or SVN + if has_git() then + -- If the dir is a git dir, get all ignored in the dir + local git_ls_results = + get_popen_readout('git -C "' .. tar_dir .. '" ls-files . --ignored --exclude-standard --others --directory') + -- Cut off the newline that is at the end of each result + for split_results in string.gmatch(git_ls_results, "([^\r\n]+)") do + -- git ls-files adds a trailing slash if it's a dir, so we remove it (if it is one) + readout_results[#readout_results + 1] = + (string.sub(split_results, -1) == "/" and string.sub(split_results, 1, -2) or split_results) + end + end + + -- Make sure we return a table + return readout_results +end + +-- Returns the basename of a path (aka a name without leading path) +local function get_basename(path) + if path == nil then + messenger:AddLog("Bad path passed to get_basename") + return nil + else + -- Get Go's path lib for a basename callback + local golib_path = import("path") + return golib_path.Base(path) + end +end + +-- Returns true/false if the file is a dotfile +local function is_dotfile(file_name) + -- Check if the filename starts with a dot + if string.sub(file_name, 1, 1) == "." then + return true + else + return false + end +end + -- Structures the output of the scanned directory content to be used in the scanlist table -- This is useful for both initial creation of the tree, and when nesting with uncompress_target() local function get_scanlist(dir, ownership, indent_n) @@ -69,36 +139,76 @@ local function get_scanlist(dir, ownership, indent_n) -- Gets a list of all the files in the current dir local dir_scan, scan_error = golib_ioutil.ReadDir(dir) + -- dir_scan will be nil if the directory is read-protected (no permissions) if dir_scan == nil then - -- tostring because Error doesn't seem to support the comma/interface{} thing messenger:Error("Error scanning dir: ", scan_error) return nil - else - local dirmsg, full_path - local results = {} + end - -- Loop through all the files/directories in current dir - for i = 1, #dir_scan do - -- Save the current dir/file name with the absolute path - full_path = JoinPaths(dir, dir_scan[i]:Name()) - -- Use "+" for dir's, "" for files - dirmsg = (is_dir(full_path) and "+" or "") - results[i] = new_listobj(full_path, dirmsg, ownership, indent_n) + -- The list of VCS-ignored files (if any) + local ignored_files = get_ignored_files(dir) + -- True/false if the file is an ignored file + local function is_ignored_file(filename) + for i = 1, #ignored_files do + if ignored_files[i] == filename then + return true + end end - return results + return false end -end --- Returns the basename of a path (aka a name without leading path) -local function get_basename(path) - if path == nil then - messenger:AddLog("Bad path passed to get_basename") - return nil + -- The list of files to be returned (and eventually put in the view) + local results = {} + + local function get_results_object(file_name) + local abs_path = JoinPaths(dir, file_name) + -- Use "+" for dir's, "" for files + local dirmsg = (is_dir(abs_path) and "+" or "") + return new_listobj(abs_path, dirmsg, ownership, indent_n) + end + + -- Save so we don't have to rerun GetOption a bunch + local show_dotfiles = GetOption("filemanager-showdotfiles") + local show_ignored = GetOption("filemanager-showignored") + + -- Hold the current scan's filename in most of the loops below + local filename + + -- Splitting the loops for speed, so we don't run an unnecessary if every pass + if not show_dotfiles and not show_ignored then + -- Don't show dotfiles or ignored + for i = 1, #dir_scan do + filename = dir_scan[i]:Name() + -- Check if it's a hidden file + if not is_dotfile(filename) and not is_ignored_file(filename) then + -- Since we skip indicies of dotfiles, don't use i here or we add nil values + results[#results + 1] = get_results_object(filename) + end + end + elseif show_dotfiles and not show_ignored then + -- Show dotfiles but not ignored + for i = 1, #dir_scan do + filename = dir_scan[i]:Name() + if not is_ignored_file(filename) then + results[#results + 1] = get_results_object(filename) + end + end + elseif not show_dotfiles and show_ignored then + -- Show ignored but not dotfiles + for i = 1, #dir_scan do + filename = dir_scan[i]:Name() + if not is_dotfile(filename) then + results[#results + 1] = get_results_object(filename) + end + end else - -- Get Go's path lib for a basename callback - local golib_path = import("path") - return golib_path.Base(path) + -- Show dotfiles and ignored (aka everything) + for i = 1, #dir_scan do + results[i] = get_results_object(dir_scan[i]:Name()) + end end + -- Return the list of scanned files + return results end -- A short "get y" for when acting on the scanlist From bbbf0fbd9e12efb587222a4d6b2bee3208868bd4 Mon Sep 17 00:00:00 2001 From: sum01 Date: Sat, 20 Jan 2018 11:49:08 -0500 Subject: [PATCH 59/89] Fix weird behaviour with 'rm' command Things with the same ownership were getting inappropriately changed during a delete, causing undefined behaviour. --- filemanager.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/filemanager.lua b/filemanager.lua index a63105a..98cf7a4 100644 --- a/filemanager.lua +++ b/filemanager.lua @@ -425,7 +425,7 @@ local function compress_target(y, delete_y) -- Reduce everything's ownership by 1 after y for x = i + 1, #scanlist do -- Don't touch root file/dirs - if scanlist[x].owner > 0 then + if scanlist[x].owner > y then -- Minus 1 since we're just deleting y scanlist[x]:decrease_owner(1) end From 37792e1644cd57a75b2a73df03b5c3b3018e1fca Mon Sep 17 00:00:00 2001 From: sum01 Date: Sat, 20 Jan 2018 12:06:39 -0500 Subject: [PATCH 60/89] Add a CHANGELOG, adhearing to http://keepachangelog.com/en/1.0.0/ I didn't bother writing pre-v3 stuff, but anyone is welcome to do so. --- CHANGELOG.md | 47 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 CHANGELOG.md diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..02151d4 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,47 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). + +## [Unreleased] + +### Added + +* The ability to hide dotfiles using the `filemanager-showdotfiles` option. +* The ability to hide files ignored in your VCS (aka `.gitignore`'d) using the `filemanager-showignored` option. Only works with Git at the moment. +* This `CHANGELOG.md` + +### Fixed + +* A bug with the `rm` command that caused weird, undefined behaviour to contents within the same dir as the file/dir deleted. + +## [3.0.0] - 2017-01-10 + +### Fixed + +* Issues [#13](https://github.com/NicolaiSoeborg/filemanager-plugin/issues/13), [#14](https://github.com/NicolaiSoeborg/filemanager-plugin/issues/14), [#15](https://github.com/NicolaiSoeborg/filemanager-plugin/issues/15), [#19](https://github.com/NicolaiSoeborg/filemanager-plugin/issues/19), [#20](https://github.com/NicolaiSoeborg/filemanager-plugin/issues/20) +* The broken syntax highlighting + +### Added + +* Directory expansion/compression below itself for viewing more akin to a file tree. +* The `rm` command, which deletes the file/directory under the cursor. +* The `touch` command, which creates a file with the passed filename. +* The `mkdir` command, which creates a directory with the passed filename. +* An API, of sorts, for the user to rebind their keys to if they dislike the defaults. +* An [editorconfig](http://editorconfig.org/) file. + +### Changed + +* The view that it spawns in to read-only, which requires Micro version >= 1.3.5 +* The functionality of some keybindings (when in the view) so they work safetly, or at all, with the plugin. +* From the `enter` key to `tab` for opening/going into files/dirs (a side-effect of using the read-only setting) + +### Removed + +* The ability to use a lot of keybindings that would otherwise mess with the view, and have no benifit. +* The pointless `.gitignore` file. + +[unreleased]: https://github.com/NicolaiSoeborg/filemanager-plugin/compare/v3.0.0...HEAD +[3.0.0]: https://github.com/NicolaiSoeborg/filemanager-plugin/compare/v2.1.1...v3.0.0 From f52e005669f901411ba4098d5b05429a80136df4 Mon Sep 17 00:00:00 2001 From: sum01 Date: Sun, 21 Jan 2018 12:01:00 -0500 Subject: [PATCH 61/89] Workaround to fix #24 --- CHANGELOG.md | 1 + filemanager.lua | 9 ++++++++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 02151d4..d53d404 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) a ### Fixed * A bug with the `rm` command that caused weird, undefined behaviour to contents within the same dir as the file/dir deleted. +* Issue [#24](https://github.com/NicolaiSoeborg/filemanager-plugin/issues/24) ## [3.0.0] - 2017-01-10 diff --git a/filemanager.lua b/filemanager.lua index 98cf7a4..8b545bf 100644 --- a/filemanager.lua +++ b/filemanager.lua @@ -1208,11 +1208,18 @@ function onFindPrevious(view) selectline_if_tree(view) end +-- NOTE: This is a workaround for "cd" not having its own callback +local precmd_dir + +function preCommandMode(view) + precmd_dir = WorkingDirectory() +end + -- Update the current dir when using "cd" function onCommandMode(view) local new_dir = WorkingDirectory() -- Only do anything if the tree is open, and they didn't cd to nothing - if tree_view ~= nil and new_dir ~= current_dir then + if tree_view ~= nil and new_dir ~= precmd_dir and new_dir ~= current_dir then update_current_dir(new_dir) end end From 9ba6f0dbaa2910f345efa4b5c9801f6f1af8f60b Mon Sep 17 00:00:00 2001 From: sum01 Date: Fri, 26 Jan 2018 14:42:31 -0500 Subject: [PATCH 62/89] Don't bother getting ignored if we don't need it Just a minor optimization. --- filemanager.lua | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/filemanager.lua b/filemanager.lua index 8b545bf..16caf5a 100644 --- a/filemanager.lua +++ b/filemanager.lua @@ -145,18 +145,6 @@ local function get_scanlist(dir, ownership, indent_n) return nil end - -- The list of VCS-ignored files (if any) - local ignored_files = get_ignored_files(dir) - -- True/false if the file is an ignored file - local function is_ignored_file(filename) - for i = 1, #ignored_files do - if ignored_files[i] == filename then - return true - end - end - return false - end - -- The list of files to be returned (and eventually put in the view) local results = {} @@ -171,6 +159,19 @@ local function get_scanlist(dir, ownership, indent_n) local show_dotfiles = GetOption("filemanager-showdotfiles") local show_ignored = GetOption("filemanager-showignored") + -- The list of VCS-ignored files (if any) + -- Only bother gettig ignored files if we're not showing ignored + local ignored_files = (not show_ignored and get_ignored_files(dir) or {}) + -- True/false if the file is an ignored file + local function is_ignored_file(filename) + for i = 1, #ignored_files do + if ignored_files[i] == filename then + return true + end + end + return false + end + -- Hold the current scan's filename in most of the loops below local filename From 63ac6517e449f60aa873346b0cf6fe2c5a1b0151 Mon Sep 17 00:00:00 2001 From: sum01 Date: Tue, 30 Jan 2018 12:53:40 -0500 Subject: [PATCH 63/89] Increment to v3.1.0 Also fixed the wrong year on the v3.0.0 changelog --- CHANGELOG.md | 7 +++++-- filemanager.lua | 2 +- repo.json | 4 ++-- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d53d404..1536560 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,8 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) a ## [Unreleased] +## [3.1.0] - 2018-01-30 + ### Added * The ability to hide dotfiles using the `filemanager-showdotfiles` option. @@ -17,7 +19,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) a * A bug with the `rm` command that caused weird, undefined behaviour to contents within the same dir as the file/dir deleted. * Issue [#24](https://github.com/NicolaiSoeborg/filemanager-plugin/issues/24) -## [3.0.0] - 2017-01-10 +## [3.0.0] - 2018-01-10 ### Fixed @@ -44,5 +46,6 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) a * The ability to use a lot of keybindings that would otherwise mess with the view, and have no benifit. * The pointless `.gitignore` file. -[unreleased]: https://github.com/NicolaiSoeborg/filemanager-plugin/compare/v3.0.0...HEAD +[unreleased]: https://github.com/NicolaiSoeborg/filemanager-plugin/compare/v3.1.0...HEAD +[3.1.0]: https://github.com/NicolaiSoeborg/filemanager-plugin/compare/v3.0.0...v3.1.0 [3.0.0]: https://github.com/NicolaiSoeborg/filemanager-plugin/compare/v2.1.1...v3.0.0 diff --git a/filemanager.lua b/filemanager.lua index 16caf5a..44f91d1 100644 --- a/filemanager.lua +++ b/filemanager.lua @@ -1,4 +1,4 @@ -VERSION = "3.0.0" +VERSION = "3.1.0" -- Let the user disable showing of dotfiles like ".editorconfig" or ".DS_STORE" if GetOption("filemanager-showdotfiles") == nil then diff --git a/repo.json b/repo.json index f19234f..9faf6d9 100644 --- a/repo.json +++ b/repo.json @@ -13,9 +13,9 @@ } }, { - "Version": "3.0.0", + "Version": "3.1.0", "Url": - "https://github.com/NicolaiSoeborg/filemanager-plugin/archive/v3.0.0.zip", + "https://github.com/NicolaiSoeborg/filemanager-plugin/archive/v3.1.0.zip", "Require": { "micro": ">=1.3.5" } From a2d0408552c9579356b8e8406181b2b4f1a325d6 Mon Sep 17 00:00:00 2001 From: sum01 Date: Sun, 4 Feb 2018 00:15:36 -0500 Subject: [PATCH 64/89] Fix some issues caused by how files are opened Fixes block comments not being parsed till you move your cursor into them. Fixes an errant tab being inserted in the newly opened file. Requires Micro >= v1.4.0 since it makes use of NewBufferFromFile to fix the block comment issue. Ref https://github.com/zyedidia/micro/issues/992 for all discussion on this. --- CHANGELOG.md | 7 +++++++ filemanager.lua | 16 +++++++++++++--- 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1536560..4bf6d93 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,13 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) a ## [Unreleased] +### Fixed + +Ref https://github.com/zyedidia/micro/issues/992 for both of these fixes. + +* The syntax parser not loading correctly (mostly block comments) on opened files. **Requires Micro >= v1.4.0** +* An errant tab being inserted into the newly opened file. + ## [3.1.0] - 2018-01-30 ### Added diff --git a/filemanager.lua b/filemanager.lua index 44f91d1..6d432e2 100644 --- a/filemanager.lua +++ b/filemanager.lua @@ -545,7 +545,7 @@ local function try_open_at_y(y) -- If it's a file, then open it messenger:Message("Filemanager opened ", scanlist[y].abspath) -- Opens the absolute path in new vertical view - CurView():VSplitIndex(NewBuffer("", scanlist[y].abspath), 1) + CurView():VSplitIndex(NewBufferFromFile(scanlist[y].abspath), 1) -- Refreshes it to be visible CurView().Buf:ReOpen() -- Resizes all views after opening a file @@ -1158,9 +1158,14 @@ function preCursorRight(view) end end +-- Workaround for tab getting inserted into opened files +-- Ref https://github.com/zyedidia/micro/issues/992 +local tab_pressed = false + -- Tab function preIndentSelection(view) if view == tree_view then + tab_pressed = true -- Open the file -- Using tab instead of enter, since enter won't work with Readonly try_open_at_y(tree_view.Buf.Cursor.Loc.Y) @@ -1298,8 +1303,13 @@ function preDeleteWordRight(view) return false_if_tree(view) end -function preIndentTab(view) - return false_if_tree(view) +-- Workaround for tab getting inserted into opened files +-- Ref https://github.com/zyedidia/micro/issues/992 +function preInsertTab(view) + if tab_pressed then + tab_pressed = false + return false + end end function preOutdentSelection(view) From 1ec95b681ec231d281afe85aec02b77a6dc9b6ce Mon Sep 17 00:00:00 2001 From: sum01 Date: Sun, 4 Feb 2018 00:19:48 -0500 Subject: [PATCH 65/89] Increment to v3.1.1 Read the CHANGELOG.md for full info. This version is just bugfixes, but requires Micro >= v1.4.0 --- CHANGELOG.md | 5 ++++- filemanager.lua | 2 +- repo.json | 8 ++++++++ 3 files changed, 13 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4bf6d93..788ce6d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,8 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) a ## [Unreleased] +## [3.1.1] - 2018-02-04 + ### Fixed Ref https://github.com/zyedidia/micro/issues/992 for both of these fixes. @@ -53,6 +55,7 @@ Ref https://github.com/zyedidia/micro/issues/992 for both of these fixes. * The ability to use a lot of keybindings that would otherwise mess with the view, and have no benifit. * The pointless `.gitignore` file. -[unreleased]: https://github.com/NicolaiSoeborg/filemanager-plugin/compare/v3.1.0...HEAD +[unreleased]: https://github.com/NicolaiSoeborg/filemanager-plugin/compare/v3.1.1...HEAD +[3.1.1]: https://github.com/NicolaiSoeborg/filemanager-plugin/compare/v3.1.0...v3.1.1 [3.1.0]: https://github.com/NicolaiSoeborg/filemanager-plugin/compare/v3.0.0...v3.1.0 [3.0.0]: https://github.com/NicolaiSoeborg/filemanager-plugin/compare/v2.1.1...v3.0.0 diff --git a/filemanager.lua b/filemanager.lua index 6d432e2..30c62dc 100644 --- a/filemanager.lua +++ b/filemanager.lua @@ -1,4 +1,4 @@ -VERSION = "3.1.0" +VERSION = "3.1.1" -- Let the user disable showing of dotfiles like ".editorconfig" or ".DS_STORE" if GetOption("filemanager-showdotfiles") == nil then diff --git a/repo.json b/repo.json index 9faf6d9..a8f968f 100644 --- a/repo.json +++ b/repo.json @@ -19,6 +19,14 @@ "Require": { "micro": ">=1.3.5" } + }, + { + "Version": "3.1.1", + "Url": + "https://github.com/NicolaiSoeborg/filemanager-plugin/archive/v3.1.1.zip", + "Require": { + "micro": ">=1.4.0" + } } ] } From 17adec47cb5480a402797d990cb6d4372d1d51ef Mon Sep 17 00:00:00 2001 From: sum01 Date: Wed, 7 Feb 2018 19:05:58 -0500 Subject: [PATCH 66/89] Bump to 3.1.2 to fix #28 The Micro function call 'NewBufferFromFile' was used in an assumption it was compatible with v1.4.0, but it's only in nightly. --- CHANGELOG.md | 9 ++++++++- filemanager.lua | 2 +- repo.json | 6 +++--- 3 files changed, 12 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 788ce6d..75e10c0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,12 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) a ## [Unreleased] +## [3.1.2] - 2018-02-07 + +### Fixed + +* The minimum Micro version, which was incorrectly set to v1.4.0. Ref [issue #28](https://github.com/NicolaiSoeborg/filemanager-plugin/issues/28) + ## [3.1.1] - 2018-02-04 ### Fixed @@ -55,7 +61,8 @@ Ref https://github.com/zyedidia/micro/issues/992 for both of these fixes. * The ability to use a lot of keybindings that would otherwise mess with the view, and have no benifit. * The pointless `.gitignore` file. -[unreleased]: https://github.com/NicolaiSoeborg/filemanager-plugin/compare/v3.1.1...HEAD +[unreleased]: https://github.com/NicolaiSoeborg/filemanager-plugin/compare/v3.1.2...HEAD +[3.1.2]: https://github.com/NicolaiSoeborg/filemanager-plugin/compare/v3.1.1...v3.1.2 [3.1.1]: https://github.com/NicolaiSoeborg/filemanager-plugin/compare/v3.1.0...v3.1.1 [3.1.0]: https://github.com/NicolaiSoeborg/filemanager-plugin/compare/v3.0.0...v3.1.0 [3.0.0]: https://github.com/NicolaiSoeborg/filemanager-plugin/compare/v2.1.1...v3.0.0 diff --git a/filemanager.lua b/filemanager.lua index 30c62dc..07cb418 100644 --- a/filemanager.lua +++ b/filemanager.lua @@ -1,4 +1,4 @@ -VERSION = "3.1.1" +VERSION = "3.1.2" -- Let the user disable showing of dotfiles like ".editorconfig" or ".DS_STORE" if GetOption("filemanager-showdotfiles") == nil then diff --git a/repo.json b/repo.json index a8f968f..945a934 100644 --- a/repo.json +++ b/repo.json @@ -21,11 +21,11 @@ } }, { - "Version": "3.1.1", + "Version": "3.1.2", "Url": - "https://github.com/NicolaiSoeborg/filemanager-plugin/archive/v3.1.1.zip", + "https://github.com/NicolaiSoeborg/filemanager-plugin/archive/v3.1.2.zip", "Require": { - "micro": ">=1.4.0" + "micro": ">=1.4.1" } } ] From 59d8c307300f92c8d865dcb4e0ab184a8d37e393 Mon Sep 17 00:00:00 2001 From: avently <7953703+avently@users.noreply.github.com> Date: Tue, 13 Feb 2018 06:07:47 +0300 Subject: [PATCH 67/89] Compress a directory even if you are on a file now --- filemanager.lua | 2 ++ 1 file changed, 2 insertions(+) diff --git a/filemanager.lua b/filemanager.lua index 07cb418..71c1975 100644 --- a/filemanager.lua +++ b/filemanager.lua @@ -415,6 +415,8 @@ local function compress_target(y, delete_y) -- Update the dir message scanlist[y].dirmsg = "+" end + else + goto_parent_dir() end -- Put outside check above because we call this to delete targets as well From 4fb7c5aad68f7404fed92e32b75861f15f7cc24d Mon Sep 17 00:00:00 2001 From: avently <7953703+avently@users.noreply.github.com> Date: Tue, 13 Feb 2018 06:20:05 +0300 Subject: [PATCH 68/89] Jump to top from any file/folder via left arrow --- filemanager.lua | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/filemanager.lua b/filemanager.lua index 71c1975..f0398aa 100644 --- a/filemanager.lua +++ b/filemanager.lua @@ -976,13 +976,10 @@ function goto_parent_dir() local cur_y = get_safe_y() -- Check if the cursor is even in a valid location for jumping to the owner - if cur_y > 1 then - -- Check if the current y is a root file - if scanlist[cur_y].owner > 0 then - -- Jump to its parent (the ownership) - tree_view.Buf.Cursor:UpN(cur_y - scanlist[cur_y].owner) - select_line() - end + if cur_y > 0 then + -- Jump to its parent (the ownership) + tree_view.Buf.Cursor:UpN(cur_y - scanlist[cur_y].owner) + select_line() end end From 32dfb88a78579f85527f9179e9fa2ce40aa871aa Mon Sep 17 00:00:00 2001 From: avently <7953703+avently@users.noreply.github.com> Date: Wed, 14 Feb 2018 02:20:06 +0300 Subject: [PATCH 69/89] Added an option for compressing parent directory --- README.md | 1 + filemanager.lua | 10 ++++++++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 2d61917..6e41663 100644 --- a/README.md +++ b/README.md @@ -16,6 +16,7 @@ If it hasn't been uncompressed, there will be a `+` to the left of it. If it has If you want to disable dotfiles from being shown, run `set filemanager-showdotfiles false` in Micro, then close & open the tree.\ If you want to disable VCS-ignored (aka `.gitignore`) files from being shown, run `set filemanager-showignored false` in Micro, then close & open the tree. +If you don't want to go to a parent directory from any selected file via left arrow key (filemanager.compress_at_cursor keybinding), run `set filemanager-compressparent false` **NOTE:** If you change files without using the plugin, it can't know what you did. The only fix is to close and open the tree. diff --git a/filemanager.lua b/filemanager.lua index f0398aa..b8c6ae9 100644 --- a/filemanager.lua +++ b/filemanager.lua @@ -10,6 +10,11 @@ if GetOption("filemanager-showignored") == nil then AddOption("filemanager-showignored", true) end +-- Let the user disable going to parent directory via left arrow key when file selected (not directory) +if GetOption("filemanager-compressparent") == nil then + AddOption("filemanager-compressparent", true) +end + -- Clear out all stuff in Micro's messenger local function clear_messenger() messenger:Reset() @@ -354,6 +359,7 @@ local function compress_target(y, delete_y) return end end + local compress_parent = GetOption("filemanager-compressparent") -- Check if the target is a dir, since files don't have anything to compress -- Also make sure it's actually an uncompressed dir by checking the gutter message if scanlist[y].dirmsg == "-" then @@ -415,8 +421,8 @@ local function compress_target(y, delete_y) -- Update the dir message scanlist[y].dirmsg = "+" end - else - goto_parent_dir() + elseif compress_parent then + goto_parent_dir() end -- Put outside check above because we call this to delete targets as well From ffcf64e3a7ef42e86c0b3434275ed475fa1eaa63 Mon Sep 17 00:00:00 2001 From: sum01 Date: Wed, 14 Feb 2018 18:06:36 -0500 Subject: [PATCH 70/89] Move the preInsertTab callback next to other tab callback Doesn't change anything functionally. --- filemanager.lua | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/filemanager.lua b/filemanager.lua index b8c6ae9..4138e46 100644 --- a/filemanager.lua +++ b/filemanager.lua @@ -1179,6 +1179,14 @@ function preIndentSelection(view) end end +-- Workaround for tab getting inserted into opened files +-- Ref https://github.com/zyedidia/micro/issues/992 +function preInsertTab(view) + if tab_pressed then + tab_pressed = false + return false + end +end -- CtrlL function onJumpLine(view) -- Highlight the line after jumping to it @@ -1308,14 +1316,6 @@ function preDeleteWordRight(view) return false_if_tree(view) end --- Workaround for tab getting inserted into opened files --- Ref https://github.com/zyedidia/micro/issues/992 -function preInsertTab(view) - if tab_pressed then - tab_pressed = false - return false - end -end function preOutdentSelection(view) return false_if_tree(view) From eb1d6c475bfe558772edfa796f36413328c93ac6 Mon Sep 17 00:00:00 2001 From: sum01 Date: Wed, 14 Feb 2018 22:28:19 -0500 Subject: [PATCH 71/89] Add CHANGELOG entries for #30 Also some minor optimizations, and a line-break on the README. We don't need to save the GetOption call. The compress_target function can also be canceled early to prevent an unnecessary refresh. --- CHANGELOG.md | 5 +++++ README.md | 2 +- filemanager.lua | 7 ++++--- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 75e10c0..7c88857 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,11 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) a ## [Unreleased] +### Added + +* The ability to go to parent directory with left arrow (when not minimizing). Thanks @avently +* The ability to jump to the `..` as a "parent directory". Thanks @avently + ## [3.1.2] - 2018-02-07 ### Fixed diff --git a/README.md b/README.md index 6e41663..c893006 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ All directories have a `/` added to the end of it, and are syntax-highlighted as If it hasn't been uncompressed, there will be a `+` to the left of it. If it has been uncompressed, it will be a `-` instead. If you want to disable dotfiles from being shown, run `set filemanager-showdotfiles false` in Micro, then close & open the tree.\ -If you want to disable VCS-ignored (aka `.gitignore`) files from being shown, run `set filemanager-showignored false` in Micro, then close & open the tree. +If you want to disable VCS-ignored (aka `.gitignore`) files from being shown, run `set filemanager-showignored false` in Micro, then close & open the tree.\ If you don't want to go to a parent directory from any selected file via left arrow key (filemanager.compress_at_cursor keybinding), run `set filemanager-compressparent false` **NOTE:** If you change files without using the plugin, it can't know what you did. The only fix is to close and open the tree. diff --git a/filemanager.lua b/filemanager.lua index 4138e46..bdd8644 100644 --- a/filemanager.lua +++ b/filemanager.lua @@ -359,7 +359,6 @@ local function compress_target(y, delete_y) return end end - local compress_parent = GetOption("filemanager-compressparent") -- Check if the target is a dir, since files don't have anything to compress -- Also make sure it's actually an uncompressed dir by checking the gutter message if scanlist[y].dirmsg == "-" then @@ -421,8 +420,10 @@ local function compress_target(y, delete_y) -- Update the dir message scanlist[y].dirmsg = "+" end - elseif compress_parent then - goto_parent_dir() + elseif GetOption("filemanager-compressparent") then + goto_parent_dir() + -- Prevent a pointless refresh of the view + return end -- Put outside check above because we call this to delete targets as well From f1b4c3213419d2edd73d380c735754794df756df Mon Sep 17 00:00:00 2001 From: sum01 Date: Wed, 14 Feb 2018 22:29:33 -0500 Subject: [PATCH 72/89] Use return without the do/end wrapper No idea why I was doing it, but I remember reading to do it in a random forum... --- filemanager.lua | 67 +++++++++++++------------------------------------ 1 file changed, 18 insertions(+), 49 deletions(-) diff --git a/filemanager.lua b/filemanager.lua index bdd8644..6104945 100644 --- a/filemanager.lua +++ b/filemanager.lua @@ -355,9 +355,7 @@ end local function compress_target(y, delete_y) -- Can't compress the top stuff, or if there's nothing there, so exit early if y == 0 or scanlist_is_empty() then - do - return - end + return end -- Check if the target is a dir, since files don't have anything to compress -- Also make sure it's actually an uncompressed dir by checking the gutter message @@ -465,9 +463,7 @@ function prompt_delete_at_cursor() if y == 0 or scanlist_is_empty() then messenger:Error("You can't delete that") -- Exit early if there's nothing to delete - do - return - end + return end local yes_del, @@ -569,9 +565,7 @@ end local function uncompress_target(y) -- Exit early if on the top 3 non-list items if y == 0 or scanlist_is_empty() then - do - return - end + return end -- Only uncompress if it's a dir and it's not already uncompressed if scanlist[y].dirmsg == "+" then @@ -648,17 +642,13 @@ end function rename_at_cursor(new_name) if CurView() ~= tree_view then messenger:Message("Rename only works with the cursor in the tree!") - do - return - end + return end -- Safety check they actually passed a name if new_name == nil then messenger:Error('When using "rename" you need to input a new name') - do - return - end + return end -- +1 since Go uses zero-based indices @@ -667,9 +657,7 @@ function rename_at_cursor(new_name) if y == 0 then -- Error since they tried to rename the top stuff messenger:Message("You can't rename that!") - do - return - end + return end -- The old file/dir's path @@ -688,9 +676,7 @@ function rename_at_cursor(new_name) -- Check if the rename worked if not path_exists(new_path) then messenger:Error("Path doesn't exist after rename!") - do - return - end + return end -- NOTE: doesn't alphabetically sort after refresh, but it probably should @@ -704,17 +690,13 @@ end local function create_filedir(filedir_name, make_dir) if CurView() ~= tree_view then messenger:Message("You can't create a file/dir if your cursor isn't in the tree!") - do - return - end + return end -- Safety check they passed a name if filedir_name == nil then messenger:Error('You need to input a name when using "touch" or "mkdir"!') - do - return - end + return end -- The target they're trying to create on top of/in/at/whatever @@ -742,9 +724,7 @@ local function create_filedir(filedir_name, make_dir) -- Check if the name is already taken by a file/dir if path_exists(filedir_path) then messenger:Error("You can't create a file/dir with a pre-existing name") - do - return - end + return end -- Use Go's os package for creating the files @@ -763,9 +743,8 @@ local function create_filedir(filedir_name, make_dir) -- If the file we tried to make doesn't exist, fail if not path_exists(filedir_path) then messenger:Error("The file/dir creation failed") - do - return - end + + return end -- Creates a sort of default object, to be modified below @@ -784,9 +763,8 @@ local function create_filedir(filedir_name, make_dir) -- Only actually add the object to the list if it's not created on an uncompressed folder if scanlist[y].dirmsg == "+" then -- Exit early, since it was created into an uncompressed folder - do - return - end + + return elseif scanlist[y].dirmsg == "-" then -- Check if created on top of an uncompressed folder -- Change ownership to the folder it was created on top of.. @@ -919,9 +897,7 @@ end -- Not local so it can be bound function goto_prev_dir() if CurView() ~= tree_view or scanlist_is_empty() then - do - return - end + return end local cur_y = get_safe_y() @@ -945,9 +921,7 @@ end -- Not local so it can be bound function goto_next_dir() if CurView() ~= tree_view or scanlist_is_empty() then - do - return - end + return end local cur_y = get_safe_y() @@ -976,9 +950,7 @@ end -- Not local so it can be keybound function goto_parent_dir() if CurView() ~= tree_view or scanlist_is_empty() then - do - return - end + return end local cur_y = get_safe_y() @@ -992,9 +964,7 @@ end function try_open_at_cursor() if CurView() ~= tree_view or scanlist_is_empty() then - do - return - end + return end try_open_at_y(tree_view.Buf.Cursor.Loc.Y) @@ -1317,7 +1287,6 @@ function preDeleteWordRight(view) return false_if_tree(view) end - function preOutdentSelection(view) return false_if_tree(view) end From 279e712a767bf2fd96c840eeda4a2b5dfc3422f3 Mon Sep 17 00:00:00 2001 From: sum01 Date: Wed, 14 Feb 2018 23:39:06 -0500 Subject: [PATCH 73/89] Add a check to prevent removal failing The return would exit early on a 'delete_y' call, causing the item not to be removed from the list. --- filemanager.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/filemanager.lua b/filemanager.lua index 6104945..7f91c48 100644 --- a/filemanager.lua +++ b/filemanager.lua @@ -418,7 +418,7 @@ local function compress_target(y, delete_y) -- Update the dir message scanlist[y].dirmsg = "+" end - elseif GetOption("filemanager-compressparent") then + elseif GetOption("filemanager-compressparent") and not delete_y then goto_parent_dir() -- Prevent a pointless refresh of the view return From 57df3ff47841916587986beaecace2295970a0f0 Mon Sep 17 00:00:00 2001 From: sum01 Date: Thu, 15 Feb 2018 12:24:40 -0500 Subject: [PATCH 74/89] Increment to v3.2.0 Thanks to @avently, the left arrow will now jump to the parent if not minimizing, and the '..' can be jumped to as a parent of root files/dirs. --- CHANGELOG.md | 5 ++++- filemanager.lua | 2 +- repo.json | 4 ++-- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7c88857..8c375ab 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,8 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) a ## [Unreleased] +## [3.2.0] - 2018-02-15 + ### Added * The ability to go to parent directory with left arrow (when not minimizing). Thanks @avently @@ -66,7 +68,8 @@ Ref https://github.com/zyedidia/micro/issues/992 for both of these fixes. * The ability to use a lot of keybindings that would otherwise mess with the view, and have no benifit. * The pointless `.gitignore` file. -[unreleased]: https://github.com/NicolaiSoeborg/filemanager-plugin/compare/v3.1.2...HEAD +[unreleased]: https://github.com/NicolaiSoeborg/filemanager-plugin/compare/v3.2.0...HEAD +[3.2.0]: https://github.com/NicolaiSoeborg/filemanager-plugin/compare/v3.1.2...v3.2.0 [3.1.2]: https://github.com/NicolaiSoeborg/filemanager-plugin/compare/v3.1.1...v3.1.2 [3.1.1]: https://github.com/NicolaiSoeborg/filemanager-plugin/compare/v3.1.0...v3.1.1 [3.1.0]: https://github.com/NicolaiSoeborg/filemanager-plugin/compare/v3.0.0...v3.1.0 diff --git a/filemanager.lua b/filemanager.lua index 7f91c48..a787308 100644 --- a/filemanager.lua +++ b/filemanager.lua @@ -1,4 +1,4 @@ -VERSION = "3.1.2" +VERSION = "3.2.0" -- Let the user disable showing of dotfiles like ".editorconfig" or ".DS_STORE" if GetOption("filemanager-showdotfiles") == nil then diff --git a/repo.json b/repo.json index 945a934..9d45ada 100644 --- a/repo.json +++ b/repo.json @@ -21,9 +21,9 @@ } }, { - "Version": "3.1.2", + "Version": "3.2.0", "Url": - "https://github.com/NicolaiSoeborg/filemanager-plugin/archive/v3.1.2.zip", + "https://github.com/NicolaiSoeborg/filemanager-plugin/archive/v3.2.0.zip", "Require": { "micro": ">=1.4.1" } From 1cec71132e83af3a362ac50440b2fce33b380866 Mon Sep 17 00:00:00 2001 From: cbrown1 Date: Fri, 10 Aug 2018 21:34:55 -0400 Subject: [PATCH 75/89] Added support for listing child folders before files. --- README.md | 3 +- filemanager.lua | 109 +++++++++++++++++++++++++++++++++++++++++------- 2 files changed, 95 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index c893006..b7da5e3 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,8 @@ If it hasn't been uncompressed, there will be a `+` to the left of it. If it has If you want to disable dotfiles from being shown, run `set filemanager-showdotfiles false` in Micro, then close & open the tree.\ If you want to disable VCS-ignored (aka `.gitignore`) files from being shown, run `set filemanager-showignored false` in Micro, then close & open the tree.\ -If you don't want to go to a parent directory from any selected file via left arrow key (filemanager.compress_at_cursor keybinding), run `set filemanager-compressparent false` +If you don't want to go to a parent directory from any selected file via left arrow key (filemanager.compress_at_cursor keybinding), run `set filemanager-compressparent false`.\ +If you don't want to list child folders before child files in a folder, run `set filemanager-foldersfirst false`. **NOTE:** If you change files without using the plugin, it can't know what you did. The only fix is to close and open the tree. diff --git a/filemanager.lua b/filemanager.lua index a787308..070751e 100644 --- a/filemanager.lua +++ b/filemanager.lua @@ -15,6 +15,11 @@ if GetOption("filemanager-compressparent") == nil then AddOption("filemanager-compressparent", true) end +-- Let the user choose to list sub-folders first when listing the contents of a folder +if GetOption("filemanager-foldersfirst") == nil then + AddOption("filemanager-foldersfirst", true) +end + -- Clear out all stuff in Micro's messenger local function clear_messenger() messenger:Reset() @@ -152,6 +157,7 @@ local function get_scanlist(dir, ownership, indent_n) -- The list of files to be returned (and eventually put in the view) local results = {} + local files = {} local function get_results_object(file_name) local abs_path = JoinPaths(dir, file_name) @@ -163,6 +169,7 @@ local function get_scanlist(dir, ownership, indent_n) -- Save so we don't have to rerun GetOption a bunch local show_dotfiles = GetOption("filemanager-showdotfiles") local show_ignored = GetOption("filemanager-showignored") + local folders_first = GetOption("filemanager-foldersfirst") -- The list of VCS-ignored files (if any) -- Only bother gettig ignored files if we're not showing ignored @@ -183,34 +190,104 @@ local function get_scanlist(dir, ownership, indent_n) -- Splitting the loops for speed, so we don't run an unnecessary if every pass if not show_dotfiles and not show_ignored then -- Don't show dotfiles or ignored - for i = 1, #dir_scan do - filename = dir_scan[i]:Name() - -- Check if it's a hidden file - if not is_dotfile(filename) and not is_ignored_file(filename) then - -- Since we skip indicies of dotfiles, don't use i here or we add nil values - results[#results + 1] = get_results_object(filename) + if folders_first then + for i = 1, #dir_scan do + filename = dir_scan[i]:Name() + if not is_dotfile(filename) and not is_ignored_file(filename) then + if is_dir(dir .. "/" .. filename) then + results[#results + 1] = get_results_object(filename) + else + files[#files + 1] = get_results_object(filename) + end + end + end + if #files > 0 then + for i = 0, #files do + results[#results + 1] = files[i] + end + end + else + for i = 1, #dir_scan do + filename = dir_scan[i]:Name() + -- Check if it's a hidden file + if not is_dotfile(filename) and not is_ignored_file(filename) then + -- Since we skip indicies of dotfiles, don't use i here or we add nil values + results[#results + 1] = get_results_object(filename) + end end end elseif show_dotfiles and not show_ignored then -- Show dotfiles but not ignored - for i = 1, #dir_scan do - filename = dir_scan[i]:Name() - if not is_ignored_file(filename) then - results[#results + 1] = get_results_object(filename) + if folders_first then + for i = 1, #dir_scan do + filename = dir_scan[i]:Name() + if not is_ignored_file(filename) then + if is_dir(dir .. "/" .. filename) then + results[#results + 1] = get_results_object(filename) + else + files[#files + 1] = get_results_object(filename) + end + end + end + if #files > 0 then + for i = 0, #files do + results[#results + 1] = files[i] + end + end + else + for i = 1, #dir_scan do + filename = dir_scan[i]:Name() + if not is_ignored_file(filename) then + results[#results + 1] = get_results_object(filename) + end end end elseif not show_dotfiles and show_ignored then -- Show ignored but not dotfiles - for i = 1, #dir_scan do - filename = dir_scan[i]:Name() - if not is_dotfile(filename) then - results[#results + 1] = get_results_object(filename) + if folders_first then + for i = 1, #dir_scan do + filename = dir_scan[i]:Name() + if not is_dotfile(filename) then + if is_dir(dir .. "/" .. filename) then + results[#results + 1] = get_results_object(filename) + else + files[#files + 1] = get_results_object(filename) + end + end + end + if #files > 0 then + for i = 0, #files do + results[#results + 1] = files[i] + end + end + else + for i = 1, #dir_scan do + filename = dir_scan[i]:Name() + if not is_dotfile(filename) then + results[#results + 1] = get_results_object(filename) + end end end else -- Show dotfiles and ignored (aka everything) - for i = 1, #dir_scan do - results[i] = get_results_object(dir_scan[i]:Name()) + if folders_first then + for i = 1, #dir_scan do + filename = dir_scan[i]:Name() + if is_dir(dir .. "/" .. filename) then + results[#results + 1] = get_results_object(filename) + else + files[#files + 1] = get_results_object(filename) + end + end + if #files > 0 then + for i = 0, #files do + results[#results + 1] = files[i] + end + end + else + for i = 1, #dir_scan do + results[i] = get_results_object(dir_scan[i]:Name()) + end end end -- Return the list of scanned files From 2ac3e9dbdc823a0ac6be0aad9abcbb138b7f62a0 Mon Sep 17 00:00:00 2001 From: Christopher Brown Date: Sat, 11 Aug 2018 19:14:54 -0400 Subject: [PATCH 76/89] refactored get_scanlist to be more readable. There is a problem opening files, however: in function try_open_at_y. --- filemanager.lua | 123 ++++++++++-------------------------------------- 1 file changed, 25 insertions(+), 98 deletions(-) diff --git a/filemanager.lua b/filemanager.lua index 070751e..6aba8b0 100644 --- a/filemanager.lua +++ b/filemanager.lua @@ -172,7 +172,7 @@ local function get_scanlist(dir, ownership, indent_n) local folders_first = GetOption("filemanager-foldersfirst") -- The list of VCS-ignored files (if any) - -- Only bother gettig ignored files if we're not showing ignored + -- Only bother getting ignored files if we're not showing ignored local ignored_files = (not show_ignored and get_ignored_files(dir) or {}) -- True/false if the file is an ignored file local function is_ignored_file(filename) @@ -187,109 +187,36 @@ local function get_scanlist(dir, ownership, indent_n) -- Hold the current scan's filename in most of the loops below local filename - -- Splitting the loops for speed, so we don't run an unnecessary if every pass - if not show_dotfiles and not show_ignored then - -- Don't show dotfiles or ignored - if folders_first then - for i = 1, #dir_scan do - filename = dir_scan[i]:Name() - if not is_dotfile(filename) and not is_ignored_file(filename) then - if is_dir(dir .. "/" .. filename) then - results[#results + 1] = get_results_object(filename) - else - files[#files + 1] = get_results_object(filename) - end - end - end - if #files > 0 then - for i = 0, #files do - results[#results + 1] = files[i] - end - end - else - for i = 1, #dir_scan do - filename = dir_scan[i]:Name() - -- Check if it's a hidden file - if not is_dotfile(filename) and not is_ignored_file(filename) then - -- Since we skip indicies of dotfiles, don't use i here or we add nil values - results[#results + 1] = get_results_object(filename) - end - end + for i = 1, #dir_scan do + local showfile = true + filename = dir_scan[i]:Name() + -- If we should not show dotfiles, and this is a dotfile, don't show + if not show_dotfiles and is_dotfile(filename) then + showfile = false end - elseif show_dotfiles and not show_ignored then - -- Show dotfiles but not ignored - if folders_first then - for i = 1, #dir_scan do - filename = dir_scan[i]:Name() - if not is_ignored_file(filename) then - if is_dir(dir .. "/" .. filename) then - results[#results + 1] = get_results_object(filename) - else - files[#files + 1] = get_results_object(filename) - end - end - end - if #files > 0 then - for i = 0, #files do - results[#results + 1] = files[i] - end - end - else - for i = 1, #dir_scan do - filename = dir_scan[i]:Name() - if not is_ignored_file(filename) then - results[#results + 1] = get_results_object(filename) - end - end + -- If we should not show ignored files, and this is an ignored file, don't show + if not show_ignored and is_ignored_file(filename) then + showfile = false end - elseif not show_dotfiles and show_ignored then - -- Show ignored but not dotfiles - if folders_first then - for i = 1, #dir_scan do - filename = dir_scan[i]:Name() - if not is_dotfile(filename) then - if is_dir(dir .. "/" .. filename) then - results[#results + 1] = get_results_object(filename) - else - files[#files + 1] = get_results_object(filename) - end - end - end - if #files > 0 then - for i = 0, #files do - results[#results + 1] = files[i] - end - end - else - for i = 1, #dir_scan do - filename = dir_scan[i]:Name() - if not is_dotfile(filename) then - results[#results + 1] = get_results_object(filename) - end + if showfile then + -- This file is good to show, proceed + if folders_first and not is_dir(JoinPaths(dir,filename)) then + -- If folders_first and this is a file, add it to (temporary) files + files[#files + 1] = get_results_object(filename) + else + -- Otherwise, add to results + results[#results + 1] = get_results_object(filename) end end - else - -- Show dotfiles and ignored (aka everything) - if folders_first then - for i = 1, #dir_scan do - filename = dir_scan[i]:Name() - if is_dir(dir .. "/" .. filename) then - results[#results + 1] = get_results_object(filename) - else - files[#files + 1] = get_results_object(filename) - end - end - if #files > 0 then - for i = 0, #files do - results[#results + 1] = files[i] - end - end - else - for i = 1, #dir_scan do - results[i] = get_results_object(dir_scan[i]:Name()) - end + end + if #files > 0 then + -- Append any files to results, now that all folders have been added + -- files will be > 0 only if folders_first and there are files + for i = 0, #files do + results[#results + 1] = files[i] end end + -- Return the list of scanned files return results end From 5a5db5319528c1e2d40ab24cdc146297f6ab4fdc Mon Sep 17 00:00:00 2001 From: sum01 Date: Thu, 13 Sep 2018 21:22:23 -0400 Subject: [PATCH 77/89] Fix up PR for sorting folders above filenames Very minor change to not start the loops index at 0, since Lua uses 1-based index. The rest is formatting changes from luafmt. Resolves #33 --- CHANGELOG.md | 4 ++++ filemanager.lua | 19 +++++++++---------- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8c375ab..728958c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,10 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) a ## [Unreleased] +### Added + +- The ability to sort folders above files, thanks to @cbrown1 + ## [3.2.0] - 2018-02-15 ### Added diff --git a/filemanager.lua b/filemanager.lua index 6aba8b0..088d25a 100644 --- a/filemanager.lua +++ b/filemanager.lua @@ -188,7 +188,7 @@ local function get_scanlist(dir, ownership, indent_n) local filename for i = 1, #dir_scan do - local showfile = true + local showfile = true filename = dir_scan[i]:Name() -- If we should not show dotfiles, and this is a dotfile, don't show if not show_dotfiles and is_dotfile(filename) then @@ -199,20 +199,20 @@ local function get_scanlist(dir, ownership, indent_n) showfile = false end if showfile then - -- This file is good to show, proceed - if folders_first and not is_dir(JoinPaths(dir,filename)) then - -- If folders_first and this is a file, add it to (temporary) files + -- This file is good to show, proceed + if folders_first and not is_dir(JoinPaths(dir, filename)) then + -- If folders_first and this is a file, add it to (temporary) files files[#files + 1] = get_results_object(filename) else - -- Otherwise, add to results + -- Otherwise, add to results results[#results + 1] = get_results_object(filename) end end end if #files > 0 then - -- Append any files to results, now that all folders have been added - -- files will be > 0 only if folders_first and there are files - for i = 0, #files do + -- Append any files to results, now that all folders have been added + -- files will be > 0 only if folders_first and there are files + for i = 1, #files do results[#results + 1] = files[i] end end @@ -470,8 +470,7 @@ function prompt_delete_at_cursor() return end - local yes_del, - no_del = + local yes_del, no_del = messenger:YesNoPrompt( "Do you want to delete the " .. (scanlist[y].dirmsg ~= "" and "dir" or "file") .. ' "' .. scanlist[y].abspath .. '"? ' ) From 65a602f9947fda2adbbb5b540b915400d385e04f Mon Sep 17 00:00:00 2001 From: sum01 Date: Thu, 13 Sep 2018 21:26:14 -0400 Subject: [PATCH 78/89] Change to correct Golang lib for basename Fixes #36 The Golang path.Base() doesn't work on Windows, while its filepath.Base() does. --- CHANGELOG.md | 4 ++++ filemanager.lua | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 728958c..f393e89 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,10 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) a - The ability to sort folders above files, thanks to @cbrown1 +### Fixed + +- The displayed filenames are now correctly only showing their "basename" on Windows + ## [3.2.0] - 2018-02-15 ### Added diff --git a/filemanager.lua b/filemanager.lua index 088d25a..427aac5 100644 --- a/filemanager.lua +++ b/filemanager.lua @@ -127,7 +127,7 @@ local function get_basename(path) return nil else -- Get Go's path lib for a basename callback - local golib_path = import("path") + local golib_path = import("filepath") return golib_path.Base(path) end end From 59b5ef058f30b9579439bf377cfb14720c060087 Mon Sep 17 00:00:00 2001 From: sum01 Date: Thu, 13 Sep 2018 21:40:59 -0400 Subject: [PATCH 79/89] Increment to v3.3.0 --- CHANGELOG.md | 51 ++++++++++++++++++++++++++----------------------- filemanager.lua | 2 +- repo.json | 11 ++++------- 3 files changed, 32 insertions(+), 32 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f393e89..047e702 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,8 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) a ## [Unreleased] +## [3.3.0] - 2018-09-13 + ### Added - The ability to sort folders above files, thanks to @cbrown1 @@ -18,14 +20,14 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) a ### Added -* The ability to go to parent directory with left arrow (when not minimizing). Thanks @avently -* The ability to jump to the `..` as a "parent directory". Thanks @avently +- The ability to go to parent directory with left arrow (when not minimizing). Thanks @avently +- The ability to jump to the `..` as a "parent directory". Thanks @avently ## [3.1.2] - 2018-02-07 ### Fixed -* The minimum Micro version, which was incorrectly set to v1.4.0. Ref [issue #28](https://github.com/NicolaiSoeborg/filemanager-plugin/issues/28) +- The minimum Micro version, which was incorrectly set to v1.4.0. Ref [issue #28](https://github.com/NicolaiSoeborg/filemanager-plugin/issues/28) ## [3.1.1] - 2018-02-04 @@ -33,50 +35,51 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) a Ref https://github.com/zyedidia/micro/issues/992 for both of these fixes. -* The syntax parser not loading correctly (mostly block comments) on opened files. **Requires Micro >= v1.4.0** -* An errant tab being inserted into the newly opened file. +- The syntax parser not loading correctly (mostly block comments) on opened files. **Requires Micro >= v1.4.0** +- An errant tab being inserted into the newly opened file. ## [3.1.0] - 2018-01-30 ### Added -* The ability to hide dotfiles using the `filemanager-showdotfiles` option. -* The ability to hide files ignored in your VCS (aka `.gitignore`'d) using the `filemanager-showignored` option. Only works with Git at the moment. -* This `CHANGELOG.md` +- The ability to hide dotfiles using the `filemanager-showdotfiles` option. +- The ability to hide files ignored in your VCS (aka `.gitignore`'d) using the `filemanager-showignored` option. Only works with Git at the moment. +- This `CHANGELOG.md` ### Fixed -* A bug with the `rm` command that caused weird, undefined behaviour to contents within the same dir as the file/dir deleted. -* Issue [#24](https://github.com/NicolaiSoeborg/filemanager-plugin/issues/24) +- A bug with the `rm` command that caused weird, undefined behaviour to contents within the same dir as the file/dir deleted. +- Issue [#24](https://github.com/NicolaiSoeborg/filemanager-plugin/issues/24) ## [3.0.0] - 2018-01-10 ### Fixed -* Issues [#13](https://github.com/NicolaiSoeborg/filemanager-plugin/issues/13), [#14](https://github.com/NicolaiSoeborg/filemanager-plugin/issues/14), [#15](https://github.com/NicolaiSoeborg/filemanager-plugin/issues/15), [#19](https://github.com/NicolaiSoeborg/filemanager-plugin/issues/19), [#20](https://github.com/NicolaiSoeborg/filemanager-plugin/issues/20) -* The broken syntax highlighting +- Issues [#13](https://github.com/NicolaiSoeborg/filemanager-plugin/issues/13), [#14](https://github.com/NicolaiSoeborg/filemanager-plugin/issues/14), [#15](https://github.com/NicolaiSoeborg/filemanager-plugin/issues/15), [#19](https://github.com/NicolaiSoeborg/filemanager-plugin/issues/19), [#20](https://github.com/NicolaiSoeborg/filemanager-plugin/issues/20) +- The broken syntax highlighting ### Added -* Directory expansion/compression below itself for viewing more akin to a file tree. -* The `rm` command, which deletes the file/directory under the cursor. -* The `touch` command, which creates a file with the passed filename. -* The `mkdir` command, which creates a directory with the passed filename. -* An API, of sorts, for the user to rebind their keys to if they dislike the defaults. -* An [editorconfig](http://editorconfig.org/) file. +- Directory expansion/compression below itself for viewing more akin to a file tree. +- The `rm` command, which deletes the file/directory under the cursor. +- The `touch` command, which creates a file with the passed filename. +- The `mkdir` command, which creates a directory with the passed filename. +- An API, of sorts, for the user to rebind their keys to if they dislike the defaults. +- An [editorconfig](http://editorconfig.org/) file. ### Changed -* The view that it spawns in to read-only, which requires Micro version >= 1.3.5 -* The functionality of some keybindings (when in the view) so they work safetly, or at all, with the plugin. -* From the `enter` key to `tab` for opening/going into files/dirs (a side-effect of using the read-only setting) +- The view that it spawns in to read-only, which requires Micro version >= 1.3.5 +- The functionality of some keybindings (when in the view) so they work safetly, or at all, with the plugin. +- From the `enter` key to `tab` for opening/going into files/dirs (a side-effect of using the read-only setting) ### Removed -* The ability to use a lot of keybindings that would otherwise mess with the view, and have no benifit. -* The pointless `.gitignore` file. +- The ability to use a lot of keybindings that would otherwise mess with the view, and have no benifit. +- The pointless `.gitignore` file. -[unreleased]: https://github.com/NicolaiSoeborg/filemanager-plugin/compare/v3.2.0...HEAD +[unreleased]: https://github.com/NicolaiSoeborg/filemanager-plugin/compare/v3.3.0...HEAD +[3.3.0]: https://github.com/NicolaiSoeborg/filemanager-plugin/compare/v3.2.0...v3.3.0 [3.2.0]: https://github.com/NicolaiSoeborg/filemanager-plugin/compare/v3.1.2...v3.2.0 [3.1.2]: https://github.com/NicolaiSoeborg/filemanager-plugin/compare/v3.1.1...v3.1.2 [3.1.1]: https://github.com/NicolaiSoeborg/filemanager-plugin/compare/v3.1.0...v3.1.1 diff --git a/filemanager.lua b/filemanager.lua index 427aac5..ac517f5 100644 --- a/filemanager.lua +++ b/filemanager.lua @@ -1,4 +1,4 @@ -VERSION = "3.2.0" +VERSION = "3.3.0" -- Let the user disable showing of dotfiles like ".editorconfig" or ".DS_STORE" if GetOption("filemanager-showdotfiles") == nil then diff --git a/repo.json b/repo.json index 9d45ada..30d0380 100644 --- a/repo.json +++ b/repo.json @@ -6,24 +6,21 @@ "Versions": [ { "Version": "2.1.1", - "Url": - "https://github.com/NicolaiSoeborg/filemanager-plugin/archive/v2.1.1.zip", + "Url": "https://github.com/NicolaiSoeborg/filemanager-plugin/archive/v2.1.1.zip", "Require": { "micro": ">=1.3.2" } }, { "Version": "3.1.0", - "Url": - "https://github.com/NicolaiSoeborg/filemanager-plugin/archive/v3.1.0.zip", + "Url": "https://github.com/NicolaiSoeborg/filemanager-plugin/archive/v3.1.0.zip", "Require": { "micro": ">=1.3.5" } }, { - "Version": "3.2.0", - "Url": - "https://github.com/NicolaiSoeborg/filemanager-plugin/archive/v3.2.0.zip", + "Version": "3.3.0", + "Url": "https://github.com/NicolaiSoeborg/filemanager-plugin/archive/v3.3.0.zip", "Require": { "micro": ">=1.4.1" } From 24c404c4853bf3a2ebf85f32a12d4144af9edd4d Mon Sep 17 00:00:00 2001 From: Jack Wilsdon Date: Tue, 2 Oct 2018 23:51:07 +0100 Subject: [PATCH 80/89] Don't call ReOpen() after opening a file --- filemanager.lua | 2 -- 1 file changed, 2 deletions(-) diff --git a/filemanager.lua b/filemanager.lua index ac517f5..c728dcf 100644 --- a/filemanager.lua +++ b/filemanager.lua @@ -554,8 +554,6 @@ local function try_open_at_y(y) messenger:Message("Filemanager opened ", scanlist[y].abspath) -- Opens the absolute path in new vertical view CurView():VSplitIndex(NewBufferFromFile(scanlist[y].abspath), 1) - -- Refreshes it to be visible - CurView().Buf:ReOpen() -- Resizes all views after opening a file tabs[curTab + 1]:Resize() end From 712d9026f3df6a8bc7b0c4cd6c84aaa4aea9003f Mon Sep 17 00:00:00 2001 From: sum01 Date: Wed, 3 Oct 2018 17:15:31 -0400 Subject: [PATCH 81/89] Increment to v3.3.1 Performance improvement Thanks to @jackwilsdon, ref PR #37 --- CHANGELOG.md | 11 +++++++++-- filemanager.lua | 2 +- repo.json | 4 ++-- 3 files changed, 12 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 047e702..1334553 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,11 +6,17 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) a ## [Unreleased] +## [3.3.1] - 2018-10-03 + +### Changed + +- Performance improvement by removing unnecessary refresh of the opened file, [thanks to @jackwilsdon](https://github.com/NicolaiSoeborg/filemanager-plugin/pull/37) + ## [3.3.0] - 2018-09-13 ### Added -- The ability to sort folders above files, thanks to @cbrown1 +- The ability to sort folders above files, [thanks to @cbrown1](https://github.com/NicolaiSoeborg/filemanager-plugin/pull/33) ### Fixed @@ -78,7 +84,8 @@ Ref https://github.com/zyedidia/micro/issues/992 for both of these fixes. - The ability to use a lot of keybindings that would otherwise mess with the view, and have no benifit. - The pointless `.gitignore` file. -[unreleased]: https://github.com/NicolaiSoeborg/filemanager-plugin/compare/v3.3.0...HEAD +[unreleased]: https://github.com/NicolaiSoeborg/filemanager-plugin/compare/v3.3.1...HEAD +[3.3.1]: https://github.com/NicolaiSoeborg/filemanager-plugin/compare/v3.3.0...v3.3.1 [3.3.0]: https://github.com/NicolaiSoeborg/filemanager-plugin/compare/v3.2.0...v3.3.0 [3.2.0]: https://github.com/NicolaiSoeborg/filemanager-plugin/compare/v3.1.2...v3.2.0 [3.1.2]: https://github.com/NicolaiSoeborg/filemanager-plugin/compare/v3.1.1...v3.1.2 diff --git a/filemanager.lua b/filemanager.lua index c728dcf..facbbd6 100644 --- a/filemanager.lua +++ b/filemanager.lua @@ -1,4 +1,4 @@ -VERSION = "3.3.0" +VERSION = "3.3.1" -- Let the user disable showing of dotfiles like ".editorconfig" or ".DS_STORE" if GetOption("filemanager-showdotfiles") == nil then diff --git a/repo.json b/repo.json index 30d0380..7c270a0 100644 --- a/repo.json +++ b/repo.json @@ -19,8 +19,8 @@ } }, { - "Version": "3.3.0", - "Url": "https://github.com/NicolaiSoeborg/filemanager-plugin/archive/v3.3.0.zip", + "Version": "3.3.1", + "Url": "https://github.com/NicolaiSoeborg/filemanager-plugin/archive/v3.3.1.zip", "Require": { "micro": ">=1.4.1" } From 01ec427349d3e091fafae2efa563f49c1e03143a Mon Sep 17 00:00:00 2001 From: sum01 Date: Wed, 3 Oct 2018 17:39:56 -0400 Subject: [PATCH 82/89] Add an open-on-start option (default false) Closes #32 --- filemanager.lua | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/filemanager.lua b/filemanager.lua index facbbd6..caee5f0 100644 --- a/filemanager.lua +++ b/filemanager.lua @@ -20,6 +20,12 @@ if GetOption("filemanager-foldersfirst") == nil then AddOption("filemanager-foldersfirst", true) end +-- Lets the user have the filetree auto-open any time Micro is opened +-- false by default, as it's a rather noticable user-facing change +if GetOption("filemanager-openonstart") == nil then + AddOption("filemanager-openonstart", false) +end + -- Clear out all stuff in Micro's messenger local function clear_messenger() messenger:Reset() @@ -1345,3 +1351,18 @@ MakeCommand("rm", "filemanager.prompt_delete_at_cursor", 0) -- Adds colors to the ".." and any dir's in the tree view via syntax highlighting -- TODO: Change it to work with git, based on untracked/changed/added/whatever AddRuntimeFile("filemanager", "syntax", "syntax.yaml") + +-- NOTE: This must be below the syntax load command or coloring won't work +-- Just auto-open if the option is enabled +-- This will run when the plugin first loads +if GetOption("filemanager-openonstart") == true then + -- Check for safety on the off-chance someone's init.lua breaks this + if tree_view == nil then + open_tree() + else + -- Log error so they can fix it + messenger.AddLog( + "Warning: filemanager-openonstart was enabled, but somehow the tree was already open so the option was ignored." + ) + end +end From d3dbabef3fe370fe9ad4edc1a1517c129ee28d7f Mon Sep 17 00:00:00 2001 From: sum01 Date: Wed, 3 Oct 2018 18:11:13 -0400 Subject: [PATCH 83/89] Update options in README Also updated CHANGELOG for new option. --- CHANGELOG.md | 8 ++++++++ README.md | 19 ++++++++++++------- 2 files changed, 20 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1334553..109c81a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,14 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) a ## [Unreleased] +### Added + +- Add the option to auto-open the file tree when Micro is started + +### Changed + +- Update README's option's documentation + ## [3.3.1] - 2018-10-03 ### Changed diff --git a/README.md b/README.md index b7da5e3..028d10d 100644 --- a/README.md +++ b/README.md @@ -14,13 +14,18 @@ The `..` near the top is used to move back a directory, from your current positi All directories have a `/` added to the end of it, and are syntax-highlighted as a `special` character.\ If it hasn't been uncompressed, there will be a `+` to the left of it. If it has been uncompressed, it will be a `-` instead. -If you want to disable dotfiles from being shown, run `set filemanager-showdotfiles false` in Micro, then close & open the tree.\ -If you want to disable VCS-ignored (aka `.gitignore`) files from being shown, run `set filemanager-showignored false` in Micro, then close & open the tree.\ -If you don't want to go to a parent directory from any selected file via left arrow key (filemanager.compress_at_cursor keybinding), run `set filemanager-compressparent false`.\ -If you don't want to list child folders before child files in a folder, run `set filemanager-foldersfirst false`. - **NOTE:** If you change files without using the plugin, it can't know what you did. The only fix is to close and open the tree. +### Options + +| Option | Purpose | Default | +| :--------------------------- | :----------------------------------------------------------- | :------ | +| `filemanager-showdotfiles` | Show dotfiles (hidden if false) | `true` | +| `filemanager-showignored` | Show gitignore'd files (hidden if false) | `true` | +| `filemanager-compressparent` | Compress the parent dir when left is pressed on a child file | `true` | +| `filemanager-foldersfirst` | Sorts folders above any files | `true` | +| `filemanager-openonstart` | Automatically open the file tree when starting Micro | `false` | + ### Commands and Keybindings The keybindings below are the equivalent to Micro's defaults, and not actually set by the plugin. If you've changed any of those keybindings, then that key is used instead. @@ -43,9 +48,9 @@ If you want to [keybind](https://github.com/zyedidia/micro/blob/master/runtime/h #### Notes -* `rename`, `touch`, and `mkdir` require a name to be passed when calling.\ +- `rename`, `touch`, and `mkdir` require a name to be passed when calling.\ Example: `rename newnamehere`, `touch filenamehere`, `mkdir dirnamehere`.\ If the passed name already exists in the current dir, it will cancel instead of overwriting (for safety). -* The Ctrl w keybinding is to switch which buffer your cursor is on.\ +- The Ctrl w keybinding is to switch which buffer your cursor is on.\ This isn't specific to the plugin, it's just part of Micro, but many people seem to not know this. From 1c23bd9cf7cc11112fc0938765202e357a07d12b Mon Sep 17 00:00:00 2001 From: Scott Bilas Date: Mon, 8 Oct 2018 13:51:40 -0700 Subject: [PATCH 84/89] Fixed broken filemanager-showignored * io.popen isn't working for me - either it's an issue specific to the latest Micro, or possibly something with my Termux environment. The result was git query failures with filemanager-showignored=false and stack dumps from deref'ing nil. The latest Micro has new functions for running shell commands, which we'd want to use anyway, and also resolves the issue, so I switched to those. * Along the way also running a simpler git query that avoids a full status query just to ask if cwd is a git workdir or not. --- filemanager.lua | 18 +++--------------- 1 file changed, 3 insertions(+), 15 deletions(-) diff --git a/filemanager.lua b/filemanager.lua index facbbd6..92a70bc 100644 --- a/filemanager.lua +++ b/filemanager.lua @@ -82,32 +82,20 @@ local function is_dir(path) end end --- Runs the command and returns the readout/printout -local function get_popen_readout(cmd) - local process = io.popen(cmd) - local readout = process:read("*a") - process:close() - return readout -end - -- Returns a list of files (in the target dir) that are ignored by the VCS system (if exists) -- aka this returns a list of gitignored files (but for whatever VCS is found) local function get_ignored_files(tar_dir) -- True/false if the target dir returns a non-fatal error when checked with 'git status' local function has_git() - -- io.popen readout returns an empty string if it fails - if get_popen_readout('git -C "' .. tar_dir .. '" status') == "" then - return false - else - return true - end + local git_rp_results = RunShellCommand('git -C "' .. tar_dir .. '" rev-parse --is-inside-work-tree') + return git_rp_results:match("^true%s*$") end local readout_results = {} -- TODO: Support more than just Git, such as Mercurial or SVN if has_git() then -- If the dir is a git dir, get all ignored in the dir local git_ls_results = - get_popen_readout('git -C "' .. tar_dir .. '" ls-files . --ignored --exclude-standard --others --directory') + RunShellCommand('git -C "' .. tar_dir .. '" ls-files . --ignored --exclude-standard --others --directory') -- Cut off the newline that is at the end of each result for split_results in string.gmatch(git_ls_results, "([^\r\n]+)") do -- git ls-files adds a trailing slash if it's a dir, so we remove it (if it is one) From 19fc8aa681e4eb6f9bcd65776c54a5d1a08fc5cf Mon Sep 17 00:00:00 2001 From: sum01 Date: Thu, 11 Oct 2018 11:49:23 -0400 Subject: [PATCH 85/89] Put cursor on empty view when auto-starting --- filemanager.lua | 3 +++ 1 file changed, 3 insertions(+) diff --git a/filemanager.lua b/filemanager.lua index caee5f0..b30191e 100644 --- a/filemanager.lua +++ b/filemanager.lua @@ -1359,6 +1359,9 @@ if GetOption("filemanager-openonstart") == true then -- Check for safety on the off-chance someone's init.lua breaks this if tree_view == nil then open_tree() + -- Puts the cursor back in the empty view that initially spawns + -- This is so the cursor isn't sitting in the tree view at startup + CurView():NextSplit(false) else -- Log error so they can fix it messenger.AddLog( From 156be164dcc7af3ca041c9ed0473285458f78ee0 Mon Sep 17 00:00:00 2001 From: sum01 Date: Mon, 22 Oct 2018 20:53:54 -0400 Subject: [PATCH 86/89] Minor change to changelog wording Hopefully more concise/understandable. --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 109c81a..405c1b5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,7 +8,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) a ### Added -- Add the option to auto-open the file tree when Micro is started +- Adds the option `filemanager-openonstart` to allow auto-opening the file tree when Micro is started (default OFF) ### Changed From 5bddddc4a5802dfd22c2f3c6f8a28bd32bef2e60 Mon Sep 17 00:00:00 2001 From: sum01 Date: Mon, 22 Oct 2018 21:09:36 -0400 Subject: [PATCH 87/89] Add changelog for PR #38 --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 405c1b5..9bf9bec 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,10 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) a ## [Unreleased] +### Fixed + +- Issues with Lua's `io.popen` on some systems by using Micro's built-in `RunShellCommand` instead, thanks to @scottbilas + ### Added - Adds the option `filemanager-openonstart` to allow auto-opening the file tree when Micro is started (default OFF) From 9f425078cdc789dff895e0a32d6a0d62a1e0bb77 Mon Sep 17 00:00:00 2001 From: sum01 Date: Mon, 22 Oct 2018 21:20:12 -0400 Subject: [PATCH 88/89] Increment to v3.4.0 --- CHANGELOG.md | 7 +++++-- filemanager.lua | 2 +- repo.json | 4 ++-- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9bf9bec..46d51c4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,9 +6,11 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) a ## [Unreleased] +## [3.4.0] - 2018-10-22 + ### Fixed -- Issues with Lua's `io.popen` on some systems by using Micro's built-in `RunShellCommand` instead, thanks to @scottbilas +- Issues with Lua's `io.popen` on some systems by using Micro's built-in `RunShellCommand` instead, [thanks to @scottbilas](https://github.com/NicolaiSoeborg/filemanager-plugin/pull/38) ### Added @@ -96,7 +98,8 @@ Ref https://github.com/zyedidia/micro/issues/992 for both of these fixes. - The ability to use a lot of keybindings that would otherwise mess with the view, and have no benifit. - The pointless `.gitignore` file. -[unreleased]: https://github.com/NicolaiSoeborg/filemanager-plugin/compare/v3.3.1...HEAD +[unreleased]: https://github.com/NicolaiSoeborg/filemanager-plugin/compare/v3.4.0...HEAD +[3.4.0]: https://github.com/NicolaiSoeborg/filemanager-plugin/compare/v3.3.1...v3.4.0 [3.3.1]: https://github.com/NicolaiSoeborg/filemanager-plugin/compare/v3.3.0...v3.3.1 [3.3.0]: https://github.com/NicolaiSoeborg/filemanager-plugin/compare/v3.2.0...v3.3.0 [3.2.0]: https://github.com/NicolaiSoeborg/filemanager-plugin/compare/v3.1.2...v3.2.0 diff --git a/filemanager.lua b/filemanager.lua index fb47b9e..2ada8e0 100644 --- a/filemanager.lua +++ b/filemanager.lua @@ -1,4 +1,4 @@ -VERSION = "3.3.1" +VERSION = "3.4.0" -- Let the user disable showing of dotfiles like ".editorconfig" or ".DS_STORE" if GetOption("filemanager-showdotfiles") == nil then diff --git a/repo.json b/repo.json index 7c270a0..6d142f1 100644 --- a/repo.json +++ b/repo.json @@ -19,8 +19,8 @@ } }, { - "Version": "3.3.1", - "Url": "https://github.com/NicolaiSoeborg/filemanager-plugin/archive/v3.3.1.zip", + "Version": "3.4.0", + "Url": "https://github.com/NicolaiSoeborg/filemanager-plugin/archive/v3.4.0.zip", "Require": { "micro": ">=1.4.1" } From 76145693baeb4c06cb7728fcf8931ae2980f30ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicolai=20S=C3=B8borg?= Date: Mon, 16 Dec 2019 23:03:44 +0000 Subject: [PATCH 89/89] Clarify README Closes #46 --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 028d10d..79d983d 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ The top line always has the current directory's path to show you where you are.\ The `..` near the top is used to move back a directory, from your current position. All directories have a `/` added to the end of it, and are syntax-highlighted as a `special` character.\ -If it hasn't been uncompressed, there will be a `+` to the left of it. If it has been uncompressed, it will be a `-` instead. +If the directory is expanded, there will be a `+` to the left of it. If it is collapsed there will be a `-` instead. **NOTE:** If you change files without using the plugin, it can't know what you did. The only fix is to close and open the tree. @@ -22,7 +22,7 @@ If it hasn't been uncompressed, there will be a `+` to the left of it. If it has | :--------------------------- | :----------------------------------------------------------- | :------ | | `filemanager-showdotfiles` | Show dotfiles (hidden if false) | `true` | | `filemanager-showignored` | Show gitignore'd files (hidden if false) | `true` | -| `filemanager-compressparent` | Compress the parent dir when left is pressed on a child file | `true` | +| `filemanager-compressparent` | Collapse the parent dir when left is pressed on a child file | `true` | | `filemanager-foldersfirst` | Sorts folders above any files | `true` | | `filemanager-openonstart` | Automatically open the file tree when starting Micro | `false` | @@ -36,8 +36,8 @@ If you want to [keybind](https://github.com/zyedidia/micro/blob/master/runtime/h | :------- | :------------------------- | :------------------------------------------------------------------------------------------ | :------------------------------------ | | `tree` | - | Open/close the tree | `filemanager.toggle_tree` | | - | Tab & MouseLeft | Open a file, or go into the directory. Goes back a dir if on `..` | `filemanager.try_open_at_cursor` | -| - | | Uncompress a directory's content under it | `filemanager.uncompress_at_cursor` | -| - | | Compress any uncompressed content under a directory | `filemanager.compress_at_cursor` | +| - | | Expand directory in tree listing | `filemanager.uncompress_at_cursor` | +| - | | Collapse directory listing | `filemanager.compress_at_cursor` | | - | Shift ⬆ | Go to the target's parent directory | `filemanager.goto_parent_dir` | | - | Alt Shift { | Jump to the previous directory in the view | `filemanager.goto_next_dir` | | - | Alt Shift } | Jump to the next directory in the view | `filemanager.goto_prev_dir` |