|
197 | 197 | Format.friendly_time = function(timestamp) |
198 | 198 | local now = os.time() |
199 | 199 | local diff = now - timestamp |
200 | | - if diff < 60 then |
| 200 | + if diff < 0 then |
| 201 | + return os.date('%Y %b %d %H:%M', timestamp) |
| 202 | + elseif diff < 60 then |
201 | 203 | return string.format('%d secs ago', diff) |
202 | 204 | elseif diff < 3600 then |
203 | 205 | return string.format('%d mins ago', math.floor(diff / 60)) |
@@ -726,105 +728,6 @@ local function create_shortcut_manager() |
726 | 728 | } |
727 | 729 | end |
728 | 730 |
|
729 | | -local function create_debounced_search() |
730 | | - local timer = nil |
731 | | - local last_search = '' |
732 | | - local last_results = {} |
733 | | - local is_searching = false |
734 | | - local pending_search = nil |
735 | | - |
736 | | - local function cleanup() |
737 | | - if timer then |
738 | | - if timer:is_active() then |
739 | | - timer:stop() |
740 | | - end |
741 | | - timer:close() |
742 | | - timer = nil |
743 | | - end |
744 | | - end |
745 | | - |
746 | | - local function execute_search(state, search_text, callback) |
747 | | - if search_text == last_search and #last_results > 0 then |
748 | | - return vim.schedule(function() |
749 | | - callback(last_results) |
750 | | - end) |
751 | | - end |
752 | | - |
753 | | - is_searching = true |
754 | | - last_search = search_text |
755 | | - |
756 | | - vim.schedule(function() |
757 | | - local filtered_entries = {} |
758 | | - |
759 | | - for _, entry in ipairs(state.entries) do |
760 | | - local match = vim.fn.matchfuzzypos({ entry.name }, search_text) |
761 | | - if #match[3] > 0 and match[3][1] > 0 then |
762 | | - entry.match_pos = match[2][1] |
763 | | - entry.score = match[3][1] |
764 | | - table.insert(filtered_entries, entry) |
765 | | - end |
766 | | - end |
767 | | - |
768 | | - table.sort(filtered_entries, function(a, b) |
769 | | - return a.score > b.score |
770 | | - end) |
771 | | - last_results = filtered_entries |
772 | | - is_searching = false |
773 | | - |
774 | | - if pending_search then |
775 | | - local pending = pending_search |
776 | | - pending_search = nil |
777 | | - execute_search(state, pending.text, pending.callback) |
778 | | - return |
779 | | - end |
780 | | - |
781 | | - vim.schedule(function() |
782 | | - callback(filtered_entries) |
783 | | - end) |
784 | | - end) |
785 | | - end |
786 | | - |
787 | | - return { |
788 | | - search = function(state, search_text, callback, delay) |
789 | | - delay = delay or 100 |
790 | | - if is_searching then |
791 | | - pending_search = { text = search_text, callback = callback } |
792 | | - return |
793 | | - end |
794 | | - if not timer then |
795 | | - timer = assert(vim.uv.new_timer()) |
796 | | - end |
797 | | - |
798 | | - if timer:is_active() then |
799 | | - timer:stop() |
800 | | - end |
801 | | - |
802 | | - if #search_text == 0 then |
803 | | - last_search = '' |
804 | | - last_results = {} |
805 | | - return vim.schedule(function() |
806 | | - callback(state.entries) |
807 | | - end) |
808 | | - end |
809 | | - |
810 | | - timer:start( |
811 | | - delay, |
812 | | - 0, |
813 | | - vim.schedule_wrap(function() |
814 | | - execute_search(state, search_text, callback) |
815 | | - end) |
816 | | - ) |
817 | | - end, |
818 | | - destroy = cleanup, |
819 | | - reset = function() |
820 | | - last_search = '' |
821 | | - last_results = {} |
822 | | - is_searching = false |
823 | | - pending_search = nil |
824 | | - end, |
825 | | - } |
826 | | -end |
827 | | - |
828 | 731 | local function parse_fd_output(line, current_path) |
829 | 732 | local perms, links, user, group, size_str, month, day, time, path |
830 | 733 | perms, links, user, group, size_str, month, day, time, path = |
@@ -915,6 +818,167 @@ local function parse_fd_output(line, current_path) |
915 | 818 | return entry |
916 | 819 | end |
917 | 820 |
|
| 821 | +local function create_debounced_search() |
| 822 | + local timer = nil |
| 823 | + local last_search = '' |
| 824 | + local is_searching = false |
| 825 | + local current_job = nil |
| 826 | + local pending_search = nil |
| 827 | + |
| 828 | + local function cleanup() |
| 829 | + if timer then |
| 830 | + if timer:is_active() then |
| 831 | + timer:stop() |
| 832 | + end |
| 833 | + timer:close() |
| 834 | + timer = nil |
| 835 | + end |
| 836 | + |
| 837 | + if current_job and current_job.kill then |
| 838 | + current_job.kill(9) |
| 839 | + current_job = nil |
| 840 | + end |
| 841 | + end |
| 842 | + |
| 843 | + local function process_and_display_results(results, search_text, callback) |
| 844 | + local filters = {} |
| 845 | + |
| 846 | + if #results > 0 then |
| 847 | + local names = Iter(results):map(function(entry) |
| 848 | + return entry.name |
| 849 | + end):totable() |
| 850 | + |
| 851 | + local res = vim.fn.matchfuzzypos(names, search_text) |
| 852 | + for _, entry in ipairs(results) do |
| 853 | + for k, v in ipairs(res[1]) do |
| 854 | + if v == entry.name then |
| 855 | + entry.match_pos = res[2][k] |
| 856 | + entry.score = res[3][k] or 0 |
| 857 | + table.insert(filters, entry) |
| 858 | + end |
| 859 | + end |
| 860 | + end |
| 861 | + |
| 862 | + table.sort(filters, function(a, b) |
| 863 | + return a.score > b.score |
| 864 | + end) |
| 865 | + end |
| 866 | + |
| 867 | + callback(filters) |
| 868 | + end |
| 869 | + |
| 870 | + local function execute_search(state, search_text, callback) |
| 871 | + if search_text == last_search and not pending_search then |
| 872 | + return |
| 873 | + end |
| 874 | + |
| 875 | + if current_job and current_job.kill then |
| 876 | + current_job.kill(9) |
| 877 | + current_job = nil |
| 878 | + end |
| 879 | + |
| 880 | + is_searching = true |
| 881 | + last_search = search_text |
| 882 | + |
| 883 | + local results = {} |
| 884 | + current_job = vim.system({ |
| 885 | + 'fd', |
| 886 | + '-l', |
| 887 | + '-i', |
| 888 | + '-H', |
| 889 | + '--color', |
| 890 | + 'never', |
| 891 | + search_text, |
| 892 | + state.current_path, |
| 893 | + }, { |
| 894 | + text = true, |
| 895 | + stdout = function(_, data) |
| 896 | + if data then |
| 897 | + for _, line in ipairs(vim.split(data, '\n')) do |
| 898 | + if #line > 0 then |
| 899 | + local entry = parse_fd_output(line, state.current_path) |
| 900 | + if entry then |
| 901 | + local len = #entry.name + (entry.stat.type == 'directory' and 1 or 0) |
| 902 | + state.maxwidth = math.max(state.maxwidth or 0, len) |
| 903 | + table.insert(results, entry) |
| 904 | + end |
| 905 | + end |
| 906 | + end |
| 907 | + |
| 908 | + if #results >= 50 then |
| 909 | + local current_results = vim.deepcopy(results) |
| 910 | + vim.schedule(function() |
| 911 | + process_and_display_results(current_results, search_text, callback) |
| 912 | + end) |
| 913 | + end |
| 914 | + end |
| 915 | + end, |
| 916 | + }, function(obj) |
| 917 | + if obj.code ~= 0 and obj.code ~= 1 then |
| 918 | + Notify.err('Search error: ' .. (obj.stderr or 'Unknown error')) |
| 919 | + end |
| 920 | + |
| 921 | + vim.schedule(function() |
| 922 | + process_and_display_results(results, search_text, callback) |
| 923 | + |
| 924 | + is_searching = false |
| 925 | + current_job = nil |
| 926 | + |
| 927 | + if pending_search then |
| 928 | + local pending = pending_search |
| 929 | + pending_search = nil |
| 930 | + execute_search(state, pending.text, pending.callback) |
| 931 | + end |
| 932 | + end) |
| 933 | + end) |
| 934 | + end |
| 935 | + |
| 936 | + return { |
| 937 | + search = function(state, search_text, callback, delay) |
| 938 | + delay = delay or 100 |
| 939 | + |
| 940 | + if is_searching then |
| 941 | + pending_search = { text = search_text, callback = callback } |
| 942 | + return |
| 943 | + end |
| 944 | + |
| 945 | + if not timer then |
| 946 | + timer = assert(vim.uv.new_timer()) |
| 947 | + end |
| 948 | + |
| 949 | + if timer:is_active() then |
| 950 | + timer:stop() |
| 951 | + end |
| 952 | + |
| 953 | + if #search_text == 0 then |
| 954 | + last_search = '' |
| 955 | + return vim.schedule(function() |
| 956 | + callback(state.entries) |
| 957 | + end) |
| 958 | + end |
| 959 | + |
| 960 | + timer:start( |
| 961 | + delay, |
| 962 | + 0, |
| 963 | + vim.schedule_wrap(function() |
| 964 | + execute_search(state, search_text, callback) |
| 965 | + end) |
| 966 | + ) |
| 967 | + end, |
| 968 | + destroy = cleanup, |
| 969 | + reset = function() |
| 970 | + last_search = '' |
| 971 | + is_searching = false |
| 972 | + pending_search = nil |
| 973 | + |
| 974 | + if current_job and current_job.kill then |
| 975 | + current_job.kill(9) -- SIGKILL |
| 976 | + current_job = nil |
| 977 | + end |
| 978 | + end, |
| 979 | + } |
| 980 | +end |
| 981 | + |
918 | 982 | Browser.State = { |
919 | 983 | create = function(path) |
920 | 984 | local width = math.floor(vim.o.columns * 0.8) |
@@ -993,73 +1057,17 @@ Browser.State = { |
993 | 1057 | if not s.initialized then |
994 | 1058 | api.nvim_buf_attach(state.search_buf, false, { |
995 | 1059 | on_lines = function() |
996 | | - -- Get search text without prompt path |
997 | | - local query = |
998 | | - api.nvim_get_current_line():gsub(state.abbr_path or state.current_path, '') |
| 1060 | + local line = api.nvim_buf_get_text(state.search_buf, 0, 0, 0, -1, {})[1] |
| 1061 | + local query = line:gsub(state.abbr_path or state.current_path, '') |
999 | 1062 |
|
1000 | 1063 | -- Empty query or directory navigation |
1001 | 1064 | if query == '' or query:match(SEPARATOR .. '$') then |
1002 | 1065 | update_display(state, state.entries) |
1003 | 1066 | return |
1004 | 1067 | end |
1005 | | - |
1006 | | - if vim.fn.executable('rg') == 1 then |
1007 | | - vim.system({ |
1008 | | - 'fd', |
1009 | | - '-l', |
1010 | | - '-i', |
1011 | | - '-H', |
1012 | | - '--color', |
1013 | | - 'never', |
1014 | | - query, |
1015 | | - state.current_path, |
1016 | | - }, { |
1017 | | - text = true, |
1018 | | - }, function(obj) |
1019 | | - local results = {} |
1020 | | - if obj.code ~= 0 and obj.code ~= 1 then |
1021 | | - Notify.err(vim.inspect(obj)) |
1022 | | - return |
1023 | | - end |
1024 | | - if obj.stdout and #obj.stdout > 0 then |
1025 | | - for _, line in ipairs(vim.split(obj.stdout, '\n')) do |
1026 | | - if #line > 0 then |
1027 | | - local entry = parse_fd_output(line, state.current_path) |
1028 | | - if entry then |
1029 | | - local len = #entry.name |
1030 | | - + (entry.stat.type == 'directory' and 1 or 0) |
1031 | | - state.maxwidth = math.max(state.maxwidth, len) |
1032 | | - table.insert(results, entry) |
1033 | | - end |
1034 | | - end |
1035 | | - end |
1036 | | - end |
1037 | | - |
1038 | | - vim.schedule(function() |
1039 | | - local filters = {} |
1040 | | - if #results > 0 then |
1041 | | - local names = Iter(results):map(function(entry) |
1042 | | - return entry.name |
1043 | | - end):totable() |
1044 | | - local res = vim.fn.matchfuzzypos(names, query) |
1045 | | - for _, entry in ipairs(results) do |
1046 | | - for k, v in ipairs(res[1]) do |
1047 | | - if v == entry.name then |
1048 | | - entry.match_pos = res[2][k] |
1049 | | - entry.score = res[3][k] or 0 |
1050 | | - table.insert(filters, entry) |
1051 | | - end |
1052 | | - end |
1053 | | - end |
1054 | | - table.sort(filters, function(a, b) |
1055 | | - return a.score > b.score |
1056 | | - end) |
1057 | | - state.search_results = results |
1058 | | - end |
1059 | | - update_display(state, filters, false) |
1060 | | - end) |
1061 | | - end) |
1062 | | - end |
| 1068 | + state.search_engine.search(state, query, function(entries) |
| 1069 | + update_display(state, entries) |
| 1070 | + end, 80) |
1063 | 1071 | end, |
1064 | 1072 | on_detach = function() |
1065 | 1073 | if state.search_engine then |
|
0 commit comments