From a68cc783c9fdb20782bb02eb59c39996cd14d71e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Gamb=C3=B6ck?= Date: Sun, 2 Jul 2017 15:55:19 +0200 Subject: [PATCH 01/14] Add function to check file executable bit This is just a plain copy of the file_readable function, with "read" replaced with "execute". --- lib/gears/filesystem.lua | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/lib/gears/filesystem.lua b/lib/gears/filesystem.lua index d5296caa..55d068eb 100644 --- a/lib/gears/filesystem.lua +++ b/lib/gears/filesystem.lua @@ -53,6 +53,17 @@ function filesystem.file_readable(filename) gfileinfo:get_attribute_boolean("access::can-read") end +--- Check if a file exists, is executable and not a directory. +-- @tparam string filename The file path. +-- @treturn boolean True if file exists and is executable. +function filesystem.file_executable(filename) + local gfile = Gio.File.new_for_path(filename) + local gfileinfo = gfile:query_info("standard::type,access::can-execute", + Gio.FileQueryInfoFlags.NONE) + return gfileinfo and gfileinfo:get_file_type() ~= "DIRECTORY" and + gfileinfo:get_attribute_boolean("access::can-execute") +end + --- Check if a path exists, is readable and a directory. -- @tparam string path The directory path. -- @treturn boolean True if path exists and is readable. From 1802a440183f636ab9f045a6bf7ef6bd625e8a45 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Gamb=C3=B6ck?= Date: Sun, 2 Jul 2017 16:14:53 +0200 Subject: [PATCH 02/14] Import gears.filesystem This is needed for some future filesystem checks. --- lib/awful/completion.lua | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/awful/completion.lua b/lib/awful/completion.lua index ef819cb8..3b63e8d9 100644 --- a/lib/awful/completion.lua +++ b/lib/awful/completion.lua @@ -9,6 +9,8 @@ -- @module awful.completion --------------------------------------------------------------------------- +local gfs = require("gears.filesystem") + -- Grab environment we need local io = io local os = os From e8ad3a32f58a1562c429bbd04b57e8dda8d86dfa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Gamb=C3=B6ck?= Date: Sun, 2 Jul 2017 16:15:48 +0200 Subject: [PATCH 03/14] Add string helper function This function returns true if a string starts with a given prefix. --- lib/awful/completion.lua | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/awful/completion.lua b/lib/awful/completion.lua index 3b63e8d9..a496a78b 100644 --- a/lib/awful/completion.lua +++ b/lib/awful/completion.lua @@ -79,6 +79,10 @@ function completion.shell(command, cur_pos, ncomp, shell) local i = 1 local comptype = "file" + local function str_starts(str, start) + return string.sub(str, 1, string.len(start)) == start + end + -- do nothing if we are on a letter, i.e. not at len + 1 or on a space if cur_pos ~= #command + 1 and command:sub(cur_pos, cur_pos) ~= " " then return command, cur_pos From 7c677e01cce7a293ac44a60c569dcbc210a121a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Gamb=C3=B6ck?= Date: Sun, 2 Jul 2017 16:18:55 +0200 Subject: [PATCH 04/14] Replace os.execute with gfs function --- lib/awful/completion.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/awful/completion.lua b/lib/awful/completion.lua index a496a78b..db4f7a95 100644 --- a/lib/awful/completion.lua +++ b/lib/awful/completion.lua @@ -160,7 +160,7 @@ function completion.shell(command, cur_pos, ncomp, shell) while true do local line = c:read("*line") if not line then break end - if os.execute("test -d " .. string.format('%q', line)) == 0 then + if gfs.is_dir(line) then line = line .. "/" end table.insert(output, bash_escape(line)) From 0bd74b4f722c0d94d96b374cf2c3466b2b3c570a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Gamb=C3=B6ck?= Date: Mon, 10 Jul 2017 20:50:04 +0200 Subject: [PATCH 05/14] Append slash on true local directories A slash must only be added if the current completion item starts with `./` and is actually a directory. --- lib/awful/completion.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/awful/completion.lua b/lib/awful/completion.lua index db4f7a95..c62f22c0 100644 --- a/lib/awful/completion.lua +++ b/lib/awful/completion.lua @@ -160,7 +160,7 @@ function completion.shell(command, cur_pos, ncomp, shell) while true do local line = c:read("*line") if not line then break end - if gfs.is_dir(line) then + if str_starts(line, "./") and gfs.is_dir(line) then line = line .. "/" end table.insert(output, bash_escape(line)) From 13b3a18a75ba680ca6cc30ac8e0669b1bd114495 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Gamb=C3=B6ck?= Date: Sun, 2 Jul 2017 16:06:54 +0200 Subject: [PATCH 06/14] Extend prefix for local file Since we want some folders in the testing area, we need to be a bit more explicit about the file completion. --- spec/awful/completion_spec.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/awful/completion_spec.lua b/spec/awful/completion_spec.lua index 3fdc8471..1b963d39 100644 --- a/spec/awful/completion_spec.lua +++ b/spec/awful/completion_spec.lua @@ -137,12 +137,12 @@ describe("awful.completion.shell", function() if has_bash then it("completes local file (bash)", function() - assert.same(shell('ls ', 4, 1, 'bash'), {'ls localcommand', 16, {'localcommand'}}) + assert.same(shell('ls l', 5, 1, 'bash'), {'ls localcommand', 16, {'localcommand'}}) end) end if has_zsh then it("completes local file (zsh)", function() - assert.same(shell('ls ', 4, 1, 'zsh'), {'ls localcommand', 16, {'localcommand'}}) + assert.same(shell('ls l', 5, 1, 'zsh'), {'ls localcommand', 16, {'localcommand'}}) end) end end) From c279dd32ce6063407d654c59ad809146d5158775 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Gamb=C3=B6ck?= Date: Sun, 2 Jul 2017 15:59:20 +0200 Subject: [PATCH 07/14] Make some directories in testing area --- spec/awful/completion_spec.lua | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/spec/awful/completion_spec.lua b/spec/awful/completion_spec.lua index 1b963d39..6bc274eb 100644 --- a/spec/awful/completion_spec.lua +++ b/spec/awful/completion_spec.lua @@ -95,6 +95,8 @@ describe("awful.completion.shell", function() setup(function() test_dir = get_test_dir() + gfs.make_directories(test_dir .. '/true') + gfs.make_directories(test_dir .. '/just_a_directory') os.execute(string.format( 'cd %s && touch localcommand && chmod +x localcommand', test_dir)) io.popen = function(...) --luacheck: ignore @@ -104,7 +106,9 @@ describe("awful.completion.shell", function() end) teardown(function() + assert.True(os.remove(test_dir .. '/just_a_directory')) assert.True(os.remove(test_dir .. '/localcommand')) + assert.True(os.remove(test_dir .. '/true')) assert.True(os.remove(test_dir)) io.popen = orig_popen --luacheck: ignore remove_test_path_dir(test_path) From 8e2d844f6051c01c37a8a2e1f83c053e1ebc0197 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Gamb=C3=B6ck?= Date: Sun, 2 Jul 2017 16:02:36 +0200 Subject: [PATCH 08/14] Use luafilesystem to change directory In some circumstances, if not every syscall was somehow redefined, the directory was not changed correctly. With luafilesystem.chdir it is assured that we are in the wanted directory afterwards. --- spec/awful/completion_spec.lua | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/spec/awful/completion_spec.lua b/spec/awful/completion_spec.lua index 6bc274eb..05deab19 100644 --- a/spec/awful/completion_spec.lua +++ b/spec/awful/completion_spec.lua @@ -6,6 +6,7 @@ end local gfs = require("gears.filesystem") local Gio = require("lgi").Gio local GLib = require("lgi").GLib +local lfs = require("lfs") local has_bash = GLib.find_program_in_path("bash") local has_zsh = GLib.find_program_in_path("zsh") @@ -60,12 +61,14 @@ end describe("awful.completion.shell in empty directory", function() local orig_popen = io.popen + local orig_dir = lfs.currentdir() setup(function() test_dir = get_test_dir() io.popen = function(...) --luacheck: ignore return orig_popen(string.format('cd %s && ', test_dir) .. ...) end + lfs.chdir(test_dir) test_path = get_test_path_dir() end) @@ -73,6 +76,7 @@ describe("awful.completion.shell in empty directory", function() assert.True(os.remove(test_dir)) io.popen = orig_popen --luacheck: ignore remove_test_path_dir(test_path) + lfs.chdir(orig_dir) end) if has_bash then @@ -92,6 +96,7 @@ end) describe("awful.completion.shell", function() local orig_popen = io.popen + local orig_dir = lfs.currentdir() setup(function() test_dir = get_test_dir() @@ -102,6 +107,7 @@ describe("awful.completion.shell", function() io.popen = function(...) --luacheck: ignore return orig_popen(string.format('cd %s && ', test_dir) .. ...) end + lfs.chdir(test_dir) test_path = get_test_path_dir() end) @@ -112,6 +118,7 @@ describe("awful.completion.shell", function() assert.True(os.remove(test_dir)) io.popen = orig_popen --luacheck: ignore remove_test_path_dir(test_path) + lfs.chdir(orig_dir) end) if has_bash then From c296a0b91d3a8b4eac2d3324a9b3c8e0a618817a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Gamb=C3=B6ck?= Date: Sun, 2 Jul 2017 16:04:54 +0200 Subject: [PATCH 09/14] Remove obsolete redefinition of popen With the directory being changed via luafilesystem, there is no need to redefine internal functions. --- spec/awful/completion_spec.lua | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/spec/awful/completion_spec.lua b/spec/awful/completion_spec.lua index 05deab19..79816223 100644 --- a/spec/awful/completion_spec.lua +++ b/spec/awful/completion_spec.lua @@ -60,21 +60,16 @@ local function get_test_dir() end describe("awful.completion.shell in empty directory", function() - local orig_popen = io.popen local orig_dir = lfs.currentdir() setup(function() test_dir = get_test_dir() - io.popen = function(...) --luacheck: ignore - return orig_popen(string.format('cd %s && ', test_dir) .. ...) - end lfs.chdir(test_dir) test_path = get_test_path_dir() end) teardown(function() assert.True(os.remove(test_dir)) - io.popen = orig_popen --luacheck: ignore remove_test_path_dir(test_path) lfs.chdir(orig_dir) end) @@ -95,7 +90,6 @@ describe("awful.completion.shell in empty directory", function() end) describe("awful.completion.shell", function() - local orig_popen = io.popen local orig_dir = lfs.currentdir() setup(function() @@ -104,9 +98,6 @@ describe("awful.completion.shell", function() gfs.make_directories(test_dir .. '/just_a_directory') os.execute(string.format( 'cd %s && touch localcommand && chmod +x localcommand', test_dir)) - io.popen = function(...) --luacheck: ignore - return orig_popen(string.format('cd %s && ', test_dir) .. ...) - end lfs.chdir(test_dir) test_path = get_test_path_dir() end) @@ -116,7 +107,6 @@ describe("awful.completion.shell", function() assert.True(os.remove(test_dir .. '/localcommand')) assert.True(os.remove(test_dir .. '/true')) assert.True(os.remove(test_dir)) - io.popen = orig_popen --luacheck: ignore remove_test_path_dir(test_path) lfs.chdir(orig_dir) end) From 8a13f1cb75259d4f54e395c39471e84eb185b1a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Gamb=C3=B6ck?= Date: Sun, 2 Jul 2017 16:08:28 +0200 Subject: [PATCH 10/14] Check for command with directory in PWD Even if there is a directory in the current working directory with the same name as a command, the directory must not be completed. So, if we want to complete "true" and there is a directory "true" in the current working directory, the completion list has to be just {"true"}, not {"true", "true/"}, or anything else! --- spec/awful/completion_spec.lua | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/spec/awful/completion_spec.lua b/spec/awful/completion_spec.lua index 79816223..5d06cc31 100644 --- a/spec/awful/completion_spec.lua +++ b/spec/awful/completion_spec.lua @@ -146,6 +146,20 @@ describe("awful.completion.shell", function() assert.same(shell('ls l', 5, 1, 'zsh'), {'ls localcommand', 16, {'localcommand'}}) end) end + + if has_bash then + it("completes command regardless of local directory (bash)", function() + assert.same(shell('true', 5, 1, 'bash'), {'true', 5, {'true'}}) + end) + end + if has_zsh then + it("completes command regardless of local directory (zsh)", function() + assert.same(shell('true', 5, 1, 'zsh'), {'true', 5, {'true'}}) + end) + end + it("completes command regardless of local directory (nil)", function() + assert.same(shell('true', 5, 1, nil), {'true', 5, {'true'}}) + end) end) describe("awful.completion.shell handles $SHELL", function() From 79bdcc9ee1b7b704b3ed35ed36c76dd1abf1759f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Gamb=C3=B6ck?= Date: Sat, 15 Jul 2017 17:36:17 +0200 Subject: [PATCH 11/14] Create some directories with ambiguous prefix The chaotic order is intended. Some completion algorithms tend to sort their results by modification time or something similar. The "huge" number of directories is also intended. With more items the chance is higher to hit a bug if no sorting took place. Since Zsh just completes non-empty directories, some files have to be created, too. --- spec/awful/completion_spec.lua | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/spec/awful/completion_spec.lua b/spec/awful/completion_spec.lua index 5d06cc31..d1159a18 100644 --- a/spec/awful/completion_spec.lua +++ b/spec/awful/completion_spec.lua @@ -95,7 +95,18 @@ describe("awful.completion.shell", function() setup(function() test_dir = get_test_dir() gfs.make_directories(test_dir .. '/true') + Gio.File.new_for_path(test_dir .. '/true/with_file'):create(Gio.FileCreateFlags.NONE); gfs.make_directories(test_dir .. '/just_a_directory') + Gio.File.new_for_path(test_dir .. '/just_a_directory/with_file'):create(Gio.FileCreateFlags.NONE); + -- Chaotic order is intended! + gfs.make_directories(test_dir .. '/ambiguous_dir_a') + gfs.make_directories(test_dir .. '/ambiguous_dir_e') + Gio.File.new_for_path(test_dir .. '/ambiguous_dir_e/with_file'):create(Gio.FileCreateFlags.NONE); + gfs.make_directories(test_dir .. '/ambiguous_dir_c') + Gio.File.new_for_path(test_dir .. '/ambiguous_dir_c/with_file'):create(Gio.FileCreateFlags.NONE); + gfs.make_directories(test_dir .. '/ambiguous_dir_d') + gfs.make_directories(test_dir .. '/ambiguous_dir_b') + gfs.make_directories(test_dir .. '/ambiguous_dir_f') os.execute(string.format( 'cd %s && touch localcommand && chmod +x localcommand', test_dir)) lfs.chdir(test_dir) @@ -103,8 +114,18 @@ describe("awful.completion.shell", function() end) teardown(function() + assert.True(os.remove(test_dir .. '/ambiguous_dir_a')) + assert.True(os.remove(test_dir .. '/ambiguous_dir_b')) + assert.True(os.remove(test_dir .. '/ambiguous_dir_c/with_file')) + assert.True(os.remove(test_dir .. '/ambiguous_dir_c')) + assert.True(os.remove(test_dir .. '/ambiguous_dir_d')) + assert.True(os.remove(test_dir .. '/ambiguous_dir_e/with_file')) + assert.True(os.remove(test_dir .. '/ambiguous_dir_e')) + assert.True(os.remove(test_dir .. '/ambiguous_dir_f')) + assert.True(os.remove(test_dir .. '/just_a_directory/with_file')) assert.True(os.remove(test_dir .. '/just_a_directory')) assert.True(os.remove(test_dir .. '/localcommand')) + assert.True(os.remove(test_dir .. '/true/with_file')) assert.True(os.remove(test_dir .. '/true')) assert.True(os.remove(test_dir)) remove_test_path_dir(test_path) From 51e5381b67f467a30b95709bc07a74374a1cfe17 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Gamb=C3=B6ck?= Date: Sun, 2 Jul 2017 16:11:31 +0200 Subject: [PATCH 12/14] Check for local directory completion A directory in the current working directory (starting with `./`) should be completed with a slash appended. --- spec/awful/completion_spec.lua | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/spec/awful/completion_spec.lua b/spec/awful/completion_spec.lua index d1159a18..434582e6 100644 --- a/spec/awful/completion_spec.lua +++ b/spec/awful/completion_spec.lua @@ -181,6 +181,20 @@ describe("awful.completion.shell", function() it("completes command regardless of local directory (nil)", function() assert.same(shell('true', 5, 1, nil), {'true', 5, {'true'}}) end) + + if has_bash then + it("completes local directories starting with ./ (bash)", function() + assert.same(shell('./just', 7, 1, 'bash'), {'./just_a_directory/', 20, {'./just_a_directory/'}}) + assert.same(shell('./t', 4, 1, 'bash'), {'./true/', 8, {'./true/'}}) + end) + end + if has_zsh then + it("completes local directories starting with ./ (zsh, non-empty)", function() + assert.same(shell('./just', 7, 1, 'zsh'), {'./just_a_directory/', 20, {'./just_a_directory/'}}) + assert.same(shell('./t', 4, 1, 'zsh'), {'./true/', 8, {'./true/'}}) + end) + end + --]] end) describe("awful.completion.shell handles $SHELL", function() From 7ab57e1953f4fcf7d0ec03639f1b34c9dcb6f8b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Gamb=C3=B6ck?= Date: Mon, 10 Jul 2017 20:48:17 +0200 Subject: [PATCH 13/14] Check for not completing lone directories Even if there is a sole directory with the name of the current completion item, if it does not start with `./` then do not complete it! --- spec/awful/completion_spec.lua | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/spec/awful/completion_spec.lua b/spec/awful/completion_spec.lua index 434582e6..4c4b246e 100644 --- a/spec/awful/completion_spec.lua +++ b/spec/awful/completion_spec.lua @@ -182,6 +182,17 @@ describe("awful.completion.shell", function() assert.same(shell('true', 5, 1, nil), {'true', 5, {'true'}}) end) + if has_bash then + it("does not complete local directory not starting with ./ (bash)", function() + assert.same(shell('just_a', 7, 1, 'bash'), {'just_a', 7}) + end) + end + if has_zsh then + it("does not complete local directory not starting with ./ (zsh)", function() + assert.same(shell('just_a', 7, 1, 'zsh'), {'just_a', 7}) + end) + end + if has_bash then it("completes local directories starting with ./ (bash)", function() assert.same(shell('./just', 7, 1, 'bash'), {'./just_a_directory/', 20, {'./just_a_directory/'}}) From da987582a63ab450fad4ded7813648e2eaba3496 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Gamb=C3=B6ck?= Date: Sat, 15 Jul 2017 17:39:30 +0200 Subject: [PATCH 14/14] Check for correct ordering of completed items The correct and consistent ordering is lexicographic. Not mtime, not ctime, but pure lexicographic ordering. --- spec/awful/completion_spec.lua | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/spec/awful/completion_spec.lua b/spec/awful/completion_spec.lua index 4c4b246e..448f402d 100644 --- a/spec/awful/completion_spec.lua +++ b/spec/awful/completion_spec.lua @@ -206,6 +206,20 @@ describe("awful.completion.shell", function() end) end --]] + + if has_bash then + it("correctly sorts completed items (bash)", function() + assert.same(shell('./ambi', 7, 1, 'bash'), {'./ambiguous_dir_a/', 19, + {'./ambiguous_dir_a/', './ambiguous_dir_b/', './ambiguous_dir_c/', + './ambiguous_dir_d/', './ambiguous_dir_e/', './ambiguous_dir_f/'}}) + end) + end + if has_zsh then + it("correctly sorts completed items (zsh)", function() + assert.same(shell('./ambi', 7, 1, 'zsh'), {'./ambiguous_dir_c/', 19, + {'./ambiguous_dir_c/', './ambiguous_dir_e/'}}) + end) + end end) describe("awful.completion.shell handles $SHELL", function()