na 1.2.84 → 1.2.86
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
 - data/.cursor/commands/priority35m36m335m32m.md +0 -0
 - data/.rubocop_todo.yml +30 -35
 - data/CHANGELOG.md +18 -0
 - data/Gemfile +2 -0
 - data/Gemfile.lock +9 -1
 - data/README.md +2 -2
 - data/bin/commands/update.rb +120 -37
 - data/lib/na/action.rb +74 -14
 - data/lib/na/array.rb +9 -0
 - data/lib/na/benchmark.rb +8 -0
 - data/lib/na/colors.rb +11 -4
 - data/lib/na/editor.rb +89 -1
 - data/lib/na/hash.rb +9 -0
 - data/lib/na/help_monkey_patch.rb +10 -1
 - data/lib/na/next_action.rb +93 -19
 - data/lib/na/project.rb +8 -0
 - data/lib/na/string.rb +51 -12
 - data/lib/na/theme.rb +58 -41
 - data/lib/na/todo.rb +8 -0
 - data/lib/na/version.rb +5 -1
 - data/lib/na.rb +11 -1
 - data/src/_README.md +1 -1
 - metadata +2 -1
 
    
        checksums.yaml
    CHANGED
    
    | 
         @@ -1,7 +1,7 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            ---
         
     | 
| 
       2 
2 
     | 
    
         
             
            SHA256:
         
     | 
| 
       3 
     | 
    
         
            -
              metadata.gz:  
     | 
| 
       4 
     | 
    
         
            -
              data.tar.gz:  
     | 
| 
      
 3 
     | 
    
         
            +
              metadata.gz: 05b84c85234711e95bc6fae91b279577d0908ba7bc0b1fb254729d65c2823f99
         
     | 
| 
      
 4 
     | 
    
         
            +
              data.tar.gz: acf70b87b92d32a81ae7802397e3f72fd72b28410c50556c680ad0f05c6ea21f
         
     | 
| 
       5 
5 
     | 
    
         
             
            SHA512:
         
     | 
| 
       6 
     | 
    
         
            -
              metadata.gz:  
     | 
| 
       7 
     | 
    
         
            -
              data.tar.gz:  
     | 
| 
      
 6 
     | 
    
         
            +
              metadata.gz: 627981de849f31ca58ba38ff7e40915baf7cbf32f4a78d005f35a2590b853a477e28e71c209b5974cb9f4eae311d3f9e4c14c4fd690a253c16584bcac7d48fb1
         
     | 
| 
      
 7 
     | 
    
         
            +
              data.tar.gz: 362e2719a19069a27cad8937d3ed7cabd81a7fd57a2b9f6a1dfa72ff439adf3fd64e691f651ad46020cdd4cdd08d0b6161b93adfcaf75c96040cf75451397a2f
         
     | 
| 
         
            File without changes
         
     | 
    
        data/.rubocop_todo.yml
    CHANGED
    
    | 
         @@ -1,6 +1,6 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            # This configuration was generated by
         
     | 
| 
       2 
2 
     | 
    
         
             
            # `rubocop --auto-gen-config`
         
     | 
| 
       3 
     | 
    
         
            -
            # on 2025-10- 
     | 
| 
      
 3 
     | 
    
         
            +
            # on 2025-10-27 21:36:52 UTC using RuboCop version 1.75.7.
         
     | 
| 
       4 
4 
     | 
    
         
             
            # The point is for the user to remove these configuration records
         
     | 
| 
       5 
5 
     | 
    
         
             
            # one by one as the offenses are removed from the code base.
         
     | 
| 
       6 
6 
     | 
    
         
             
            # Note that changes in the inspected code, or installation of new
         
     | 
| 
         @@ -13,12 +13,7 @@ Lint/DuplicateBranch: 
     | 
|
| 
       13 
13 
     | 
    
         
             
                - 'lib/na/action.rb'
         
     | 
| 
       14 
14 
     | 
    
         
             
                - 'lib/na/colors.rb'
         
     | 
| 
       15 
15 
     | 
    
         | 
| 
       16 
     | 
    
         
            -
            # Offense count:  
     | 
| 
       17 
     | 
    
         
            -
            Lint/SelfAssignment:
         
     | 
| 
       18 
     | 
    
         
            -
              Exclude:
         
     | 
| 
       19 
     | 
    
         
            -
                - 'lib/na/string.rb'
         
     | 
| 
       20 
     | 
    
         
            -
             
     | 
| 
       21 
     | 
    
         
            -
            # Offense count: 1
         
     | 
| 
      
 16 
     | 
    
         
            +
            # Offense count: 2
         
     | 
| 
       22 
17 
     | 
    
         
             
            # This cop supports safe autocorrection (--autocorrect).
         
     | 
| 
       23 
18 
     | 
    
         
             
            # Configuration parameters: AutoCorrect, AllowUnusedKeywordArguments, IgnoreEmptyMethods, IgnoreNotImplementedMethods, NotImplementedExceptions.
         
     | 
| 
       24 
19 
     | 
    
         
             
            # NotImplementedExceptions: NotImplementedError
         
     | 
| 
         @@ -26,46 +21,53 @@ Lint/UnusedMethodArgument: 
     | 
|
| 
       26 
21 
     | 
    
         
             
              Exclude:
         
     | 
| 
       27 
22 
     | 
    
         
             
                - 'lib/na/string.rb'
         
     | 
| 
       28 
23 
     | 
    
         | 
| 
       29 
     | 
    
         
            -
            # Offense count:  
     | 
| 
      
 24 
     | 
    
         
            +
            # Offense count: 36
         
     | 
| 
       30 
25 
     | 
    
         
             
            # Configuration parameters: AllowedMethods, AllowedPatterns, CountRepeatedAttributes.
         
     | 
| 
       31 
26 
     | 
    
         
             
            Metrics/AbcSize:
         
     | 
| 
       32 
     | 
    
         
            -
              Max:  
     | 
| 
      
 27 
     | 
    
         
            +
              Max: 243
         
     | 
| 
       33 
28 
     | 
    
         | 
| 
       34 
     | 
    
         
            -
            # Offense count:  
     | 
| 
      
 29 
     | 
    
         
            +
            # Offense count: 9
         
     | 
| 
       35 
30 
     | 
    
         
             
            # Configuration parameters: CountComments, CountAsOne, AllowedMethods, AllowedPatterns.
         
     | 
| 
       36 
31 
     | 
    
         
             
            # AllowedMethods: refine
         
     | 
| 
       37 
32 
     | 
    
         
             
            Metrics/BlockLength:
         
     | 
| 
       38 
33 
     | 
    
         
             
              Max: 144
         
     | 
| 
       39 
34 
     | 
    
         | 
| 
       40 
     | 
    
         
            -
            # Offense count:  
     | 
| 
      
 35 
     | 
    
         
            +
            # Offense count: 5
         
     | 
| 
       41 
36 
     | 
    
         
             
            # Configuration parameters: CountComments, CountAsOne.
         
     | 
| 
       42 
37 
     | 
    
         
             
            Metrics/ClassLength:
         
     | 
| 
       43 
     | 
    
         
            -
              Max:  
     | 
| 
      
 38 
     | 
    
         
            +
              Max: 777
         
     | 
| 
       44 
39 
     | 
    
         | 
| 
       45 
     | 
    
         
            -
            # Offense count:  
     | 
| 
      
 40 
     | 
    
         
            +
            # Offense count: 25
         
     | 
| 
       46 
41 
     | 
    
         
             
            # Configuration parameters: AllowedMethods, AllowedPatterns.
         
     | 
| 
       47 
42 
     | 
    
         
             
            Metrics/CyclomaticComplexity:
         
     | 
| 
       48 
     | 
    
         
            -
              Max:  
     | 
| 
      
 43 
     | 
    
         
            +
              Max: 70
         
     | 
| 
       49 
44 
     | 
    
         | 
| 
       50 
     | 
    
         
            -
            # Offense count:  
     | 
| 
      
 45 
     | 
    
         
            +
            # Offense count: 40
         
     | 
| 
       51 
46 
     | 
    
         
             
            # Configuration parameters: CountComments, CountAsOne, AllowedMethods, AllowedPatterns.
         
     | 
| 
       52 
47 
     | 
    
         
             
            Metrics/MethodLength:
         
     | 
| 
       53 
48 
     | 
    
         
             
              Max: 146
         
     | 
| 
       54 
49 
     | 
    
         | 
| 
       55 
     | 
    
         
            -
            # Offense count:  
     | 
| 
      
 50 
     | 
    
         
            +
            # Offense count: 3
         
     | 
| 
       56 
51 
     | 
    
         
             
            # Configuration parameters: CountComments, CountAsOne.
         
     | 
| 
       57 
52 
     | 
    
         
             
            Metrics/ModuleLength:
         
     | 
| 
       58 
     | 
    
         
            -
              Max:  
     | 
| 
      
 53 
     | 
    
         
            +
              Max: 779
         
     | 
| 
       59 
54 
     | 
    
         | 
| 
       60 
55 
     | 
    
         
             
            # Offense count: 4
         
     | 
| 
       61 
56 
     | 
    
         
             
            # Configuration parameters: CountKeywordArgs, MaxOptionalParameters.
         
     | 
| 
       62 
57 
     | 
    
         
             
            Metrics/ParameterLists:
         
     | 
| 
       63 
58 
     | 
    
         
             
              Max: 19
         
     | 
| 
       64 
59 
     | 
    
         | 
| 
       65 
     | 
    
         
            -
            # Offense count:  
     | 
| 
      
 60 
     | 
    
         
            +
            # Offense count: 24
         
     | 
| 
       66 
61 
     | 
    
         
             
            # Configuration parameters: AllowedMethods, AllowedPatterns.
         
     | 
| 
       67 
62 
     | 
    
         
             
            Metrics/PerceivedComplexity:
         
     | 
| 
       68 
     | 
    
         
            -
              Max:  
     | 
| 
      
 63 
     | 
    
         
            +
              Max: 81
         
     | 
| 
      
 64 
     | 
    
         
            +
             
     | 
| 
      
 65 
     | 
    
         
            +
            # Offense count: 1
         
     | 
| 
      
 66 
     | 
    
         
            +
            # Configuration parameters: ForbiddenDelimiters.
         
     | 
| 
      
 67 
     | 
    
         
            +
            # ForbiddenDelimiters: (?i-mx:(^|\s)(EO[A-Z]{1}|END)(\s|$))
         
     | 
| 
      
 68 
     | 
    
         
            +
            Naming/HeredocDelimiterNaming:
         
     | 
| 
      
 69 
     | 
    
         
            +
              Exclude:
         
     | 
| 
      
 70 
     | 
    
         
            +
                - 'lib/na/editor.rb'
         
     | 
| 
       69 
71 
     | 
    
         | 
| 
       70 
72 
     | 
    
         
             
            # Offense count: 3
         
     | 
| 
       71 
73 
     | 
    
         
             
            # This cop supports unsafe autocorrection (--autocorrect-all).
         
     | 
| 
         @@ -74,21 +76,6 @@ Security/YAMLLoad: 
     | 
|
| 
       74 
76 
     | 
    
         
             
                - 'lib/na/next_action.rb'
         
     | 
| 
       75 
77 
     | 
    
         
             
                - 'lib/na/theme.rb'
         
     | 
| 
       76 
78 
     | 
    
         | 
| 
       77 
     | 
    
         
            -
            # Offense count: 8
         
     | 
| 
       78 
     | 
    
         
            -
            # Configuration parameters: AllowedConstants.
         
     | 
| 
       79 
     | 
    
         
            -
            Style/Documentation:
         
     | 
| 
       80 
     | 
    
         
            -
              Exclude:
         
     | 
| 
       81 
     | 
    
         
            -
                - 'spec/**/*'
         
     | 
| 
       82 
     | 
    
         
            -
                - 'test/**/*'
         
     | 
| 
       83 
     | 
    
         
            -
                - 'lib/na/action.rb'
         
     | 
| 
       84 
     | 
    
         
            -
                - 'lib/na/array.rb'
         
     | 
| 
       85 
     | 
    
         
            -
                - 'lib/na/benchmark.rb'
         
     | 
| 
       86 
     | 
    
         
            -
                - 'lib/na/editor.rb'
         
     | 
| 
       87 
     | 
    
         
            -
                - 'lib/na/hash.rb'
         
     | 
| 
       88 
     | 
    
         
            -
                - 'lib/na/project.rb'
         
     | 
| 
       89 
     | 
    
         
            -
                - 'lib/na/theme.rb'
         
     | 
| 
       90 
     | 
    
         
            -
                - 'lib/na/todo.rb'
         
     | 
| 
       91 
     | 
    
         
            -
             
     | 
| 
       92 
79 
     | 
    
         
             
            # Offense count: 2
         
     | 
| 
       93 
80 
     | 
    
         
             
            # This cop supports safe autocorrection (--autocorrect).
         
     | 
| 
       94 
81 
     | 
    
         
             
            Style/IfUnlessModifier:
         
     | 
| 
         @@ -104,13 +91,21 @@ Style/ModuleFunction: 
     | 
|
| 
       104 
91 
     | 
    
         
             
              Exclude:
         
     | 
| 
       105 
92 
     | 
    
         
             
                - 'lib/na/colors.rb'
         
     | 
| 
       106 
93 
     | 
    
         | 
| 
      
 94 
     | 
    
         
            +
            # Offense count: 1
         
     | 
| 
      
 95 
     | 
    
         
            +
            # This cop supports safe autocorrection (--autocorrect).
         
     | 
| 
      
 96 
     | 
    
         
            +
            # Configuration parameters: EnforcedStyle.
         
     | 
| 
      
 97 
     | 
    
         
            +
            # SupportedStyles: single_quotes, double_quotes
         
     | 
| 
      
 98 
     | 
    
         
            +
            Style/StringLiteralsInInterpolation:
         
     | 
| 
      
 99 
     | 
    
         
            +
              Exclude:
         
     | 
| 
      
 100 
     | 
    
         
            +
                - 'lib/na/action.rb'
         
     | 
| 
      
 101 
     | 
    
         
            +
             
     | 
| 
       107 
102 
     | 
    
         
             
            # Offense count: 1
         
     | 
| 
       108 
103 
     | 
    
         
             
            # This cop supports safe autocorrection (--autocorrect).
         
     | 
| 
       109 
104 
     | 
    
         
             
            Style/YAMLFileRead:
         
     | 
| 
       110 
105 
     | 
    
         
             
              Exclude:
         
     | 
| 
       111 
106 
     | 
    
         
             
                - 'lib/na/theme.rb'
         
     | 
| 
       112 
107 
     | 
    
         | 
| 
       113 
     | 
    
         
            -
            # Offense count:  
     | 
| 
      
 108 
     | 
    
         
            +
            # Offense count: 22
         
     | 
| 
       114 
109 
     | 
    
         
             
            # This cop supports safe autocorrection (--autocorrect).
         
     | 
| 
       115 
110 
     | 
    
         
             
            # Configuration parameters: AllowHeredoc, AllowURI, URISchemes, IgnoreCopDirectives, AllowedPatterns, SplitStrings.
         
     | 
| 
       116 
111 
     | 
    
         
             
            # URISchemes: http, https
         
     | 
    
        data/CHANGELOG.md
    CHANGED
    
    | 
         @@ -1,3 +1,21 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            ### 1.2.86
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            2025-10-27 17:16
         
     | 
| 
      
 4 
     | 
    
         
            +
             
     | 
| 
      
 5 
     | 
    
         
            +
            ### 1.2.85
         
     | 
| 
      
 6 
     | 
    
         
            +
             
     | 
| 
      
 7 
     | 
    
         
            +
            2025-10-26 08:43
         
     | 
| 
      
 8 
     | 
    
         
            +
             
     | 
| 
      
 9 
     | 
    
         
            +
            #### IMPROVED
         
     | 
| 
      
 10 
     | 
    
         
            +
             
     | 
| 
      
 11 
     | 
    
         
            +
            - YARD documentation
         
     | 
| 
      
 12 
     | 
    
         
            +
            - Currently at 100% YARD documentation
         
     | 
| 
      
 13 
     | 
    
         
            +
             
     | 
| 
      
 14 
     | 
    
         
            +
            #### FIXED
         
     | 
| 
      
 15 
     | 
    
         
            +
             
     | 
| 
      
 16 
     | 
    
         
            +
            - Nil handler for trunc_middle
         
     | 
| 
      
 17 
     | 
    
         
            +
            - Nil handler for hihglight_file
         
     | 
| 
      
 18 
     | 
    
         
            +
             
     | 
| 
       1 
19 
     | 
    
         
             
            ### 1.2.84
         
     | 
| 
       2 
20 
     | 
    
         | 
| 
       3 
21 
     | 
    
         
             
            2025-10-25 15:50
         
     | 
    
        data/Gemfile
    CHANGED
    
    
    
        data/Gemfile.lock
    CHANGED
    
    | 
         @@ -1,7 +1,7 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            PATH
         
     | 
| 
       2 
2 
     | 
    
         
             
              remote: .
         
     | 
| 
       3 
3 
     | 
    
         
             
              specs:
         
     | 
| 
       4 
     | 
    
         
            -
                na (1.2. 
     | 
| 
      
 4 
     | 
    
         
            +
                na (1.2.86)
         
     | 
| 
       5 
5 
     | 
    
         
             
                  chronic (~> 0.10, >= 0.10.2)
         
     | 
| 
       6 
6 
     | 
    
         
             
                  git (~> 3.0.0)
         
     | 
| 
       7 
7 
     | 
    
         
             
                  gli (~> 2.21.0)
         
     | 
| 
         @@ -36,6 +36,7 @@ GEM 
     | 
|
| 
       36 
36 
     | 
    
         
             
                concurrent-ruby (1.3.5)
         
     | 
| 
       37 
37 
     | 
    
         
             
                connection_pool (2.5.4)
         
     | 
| 
       38 
38 
     | 
    
         
             
                diff-lcs (1.6.2)
         
     | 
| 
      
 39 
     | 
    
         
            +
                docile (1.4.1)
         
     | 
| 
       39 
40 
     | 
    
         
             
                drb (2.2.3)
         
     | 
| 
       40 
41 
     | 
    
         
             
                git (3.0.2)
         
     | 
| 
       41 
42 
     | 
    
         
             
                  activesupport (>= 5.0)
         
     | 
| 
         @@ -69,6 +70,12 @@ GEM 
     | 
|
| 
       69 
70 
     | 
    
         
             
                  rspec-support (~> 3.13.0)
         
     | 
| 
       70 
71 
     | 
    
         
             
                rspec-support (3.13.6)
         
     | 
| 
       71 
72 
     | 
    
         
             
                securerandom (0.4.1)
         
     | 
| 
      
 73 
     | 
    
         
            +
                simplecov (0.22.0)
         
     | 
| 
      
 74 
     | 
    
         
            +
                  docile (~> 1.1)
         
     | 
| 
      
 75 
     | 
    
         
            +
                  simplecov-html (~> 0.11)
         
     | 
| 
      
 76 
     | 
    
         
            +
                  simplecov_json_formatter (~> 0.1)
         
     | 
| 
      
 77 
     | 
    
         
            +
                simplecov-html (0.13.2)
         
     | 
| 
      
 78 
     | 
    
         
            +
                simplecov_json_formatter (0.1.4)
         
     | 
| 
       72 
79 
     | 
    
         
             
                tty-cursor (0.7.1)
         
     | 
| 
       73 
80 
     | 
    
         
             
                tty-reader (0.9.0)
         
     | 
| 
       74 
81 
     | 
    
         
             
                  tty-cursor (~> 0.7)
         
     | 
| 
         @@ -103,6 +110,7 @@ DEPENDENCIES 
     | 
|
| 
       103 
110 
     | 
    
         
             
              rake
         
     | 
| 
       104 
111 
     | 
    
         
             
              rdoc (~> 4.3)
         
     | 
| 
       105 
112 
     | 
    
         
             
              rspec (~> 3.0)
         
     | 
| 
      
 113 
     | 
    
         
            +
              simplecov (~> 0.22.0)
         
     | 
| 
       106 
114 
     | 
    
         
             
              tty-spinner (~> 0.9, >= 0.9.0)
         
     | 
| 
       107 
115 
     | 
    
         | 
| 
       108 
116 
     | 
    
         
             
            BUNDLED WITH
         
     | 
    
        data/README.md
    CHANGED
    
    | 
         @@ -9,7 +9,7 @@ 
     | 
|
| 
       9 
9 
     | 
    
         
             
            _If you're one of the rare people like me who find this useful, feel free to
         
     | 
| 
       10 
10 
     | 
    
         
             
            [buy me some coffee][donate]._
         
     | 
| 
       11 
11 
     | 
    
         | 
| 
       12 
     | 
    
         
            -
            The current version of `na` is 1.2. 
     | 
| 
      
 12 
     | 
    
         
            +
            The current version of `na` is 1.2.86.
         
     | 
| 
       13 
13 
     | 
    
         | 
| 
       14 
14 
     | 
    
         
             
            `na` ("next action") is a command line tool designed to make it easy to see what your next actions are for any project, right from the command line. It works with TaskPaper-formatted files (but any plain text format will do), looking for `@na` tags (or whatever you specify) in todo files in your current folder.
         
     | 
| 
       15 
15 
     | 
    
         | 
| 
         @@ -76,7 +76,7 @@ SYNOPSIS 
     | 
|
| 
       76 
76 
     | 
    
         
             
                na [global options] command [command options] [arguments...]
         
     | 
| 
       77 
77 
     | 
    
         | 
| 
       78 
78 
     | 
    
         
             
            VERSION
         
     | 
| 
       79 
     | 
    
         
            -
                1.2. 
     | 
| 
      
 79 
     | 
    
         
            +
                1.2.86
         
     | 
| 
       80 
80 
     | 
    
         | 
| 
       81 
81 
     | 
    
         
             
            GLOBAL OPTIONS
         
     | 
| 
       82 
82 
     | 
    
         
             
                -a, --add               - Add a next action (deprecated, for backwards compatibility)
         
     | 
    
        data/bin/commands/update.rb
    CHANGED
    
    | 
         @@ -164,13 +164,33 @@ class App 
     | 
|
| 
       164 
164 
     | 
    
         | 
| 
       165 
165 
     | 
    
         
             
                  options[:exact] = true unless options[:replace].nil?
         
     | 
| 
       166 
166 
     | 
    
         | 
| 
      
 167 
     | 
    
         
            +
                  # Check for PATH:LINE format in arguments
         
     | 
| 
      
 168 
     | 
    
         
            +
                  target_file = nil
         
     | 
| 
      
 169 
     | 
    
         
            +
                  target_line = nil
         
     | 
| 
      
 170 
     | 
    
         
            +
                  if args.count.positive?
         
     | 
| 
      
 171 
     | 
    
         
            +
                    pathline_match = args.join(' ').strip.match(/^(.+):(\d+)$/)
         
     | 
| 
      
 172 
     | 
    
         
            +
                    if pathline_match
         
     | 
| 
      
 173 
     | 
    
         
            +
                      target_file = pathline_match[1]
         
     | 
| 
      
 174 
     | 
    
         
            +
                      target_line = pathline_match[2].to_i
         
     | 
| 
      
 175 
     | 
    
         
            +
             
     | 
| 
      
 176 
     | 
    
         
            +
                      # Verify file exists
         
     | 
| 
      
 177 
     | 
    
         
            +
                      if File.exist?(target_file)
         
     | 
| 
      
 178 
     | 
    
         
            +
                        options[:file] = target_file
         
     | 
| 
      
 179 
     | 
    
         
            +
                        options[:target_line] = target_line
         
     | 
| 
      
 180 
     | 
    
         
            +
                        action = nil  # Skip search processing
         
     | 
| 
      
 181 
     | 
    
         
            +
                      else
         
     | 
| 
      
 182 
     | 
    
         
            +
                        NA.notify("#{NA.theme[:error]}File not found: #{target_file}", exit_code: 1)
         
     | 
| 
      
 183 
     | 
    
         
            +
                      end
         
     | 
| 
      
 184 
     | 
    
         
            +
                    end
         
     | 
| 
      
 185 
     | 
    
         
            +
                  end
         
     | 
| 
      
 186 
     | 
    
         
            +
             
     | 
| 
       167 
187 
     | 
    
         
             
                  if args.count.positive?
         
     | 
| 
       168 
188 
     | 
    
         
             
                    action = args.join(' ').strip
         
     | 
| 
       169 
189 
     | 
    
         
             
                  else
         
     | 
| 
       170 
190 
     | 
    
         
             
                    action = nil
         
     | 
| 
       171 
191 
     | 
    
         
             
                  end
         
     | 
| 
       172 
192 
     | 
    
         
             
                  tokens = nil
         
     | 
| 
       173 
     | 
    
         
            -
                  if action && !action.empty?
         
     | 
| 
      
 193 
     | 
    
         
            +
                  if action && !action.empty? && !target_line
         
     | 
| 
       174 
194 
     | 
    
         
             
                    if options[:exact]
         
     | 
| 
       175 
195 
     | 
    
         
             
                      tokens = action
         
     | 
| 
       176 
196 
     | 
    
         
             
                    elsif options[:regex]
         
     | 
| 
         @@ -218,10 +238,11 @@ class App 
     | 
|
| 
       218 
238 
     | 
    
         
             
                      done: options[:done]
         
     | 
| 
       219 
239 
     | 
    
         
             
                    })
         
     | 
| 
       220 
240 
     | 
    
         
             
                    todo.actions.each do |action_obj|
         
     | 
| 
       221 
     | 
    
         
            -
                      # Format: filename: 
     | 
| 
       222 
     | 
    
         
            -
                      display  
     | 
| 
      
 241 
     | 
    
         
            +
                      # Format: filename:LINENUM:parent > action
         
     | 
| 
      
 242 
     | 
    
         
            +
                      # Include line number in display for unique matching
         
     | 
| 
      
 243 
     | 
    
         
            +
                      display = "#{File.basename(action_obj.file_path)}:#{action_obj.file_line}:#{action_obj.parent.join('>')} | #{action_obj.action}"
         
     | 
| 
       223 
244 
     | 
    
         
             
                      candidate_actions << display
         
     | 
| 
       224 
     | 
    
         
            -
                      targets_for_selection << { file: action_obj. 
     | 
| 
      
 245 
     | 
    
         
            +
                      targets_for_selection << { file: action_obj.file_path, line: action_obj.file_line, action: action_obj }
         
     | 
| 
       225 
246 
     | 
    
         
             
                    end
         
     | 
| 
       226 
247 
     | 
    
         
             
                  end
         
     | 
| 
       227 
248 
     | 
    
         | 
| 
         @@ -237,9 +258,24 @@ class App 
     | 
|
| 
       237 
258 
     | 
    
         
             
                    if selector
         
     | 
| 
       238 
259 
     | 
    
         
             
                      require 'open3'
         
     | 
| 
       239 
260 
     | 
    
         
             
                      input = candidate_actions.join("\n")
         
     | 
| 
       240 
     | 
    
         
            -
             
     | 
| 
       241 
     | 
    
         
            -
                       
     | 
| 
       242 
     | 
    
         
            -
                       
     | 
| 
      
 261 
     | 
    
         
            +
             
     | 
| 
      
 262 
     | 
    
         
            +
                      # Use popen3 to properly handle stdin for fzf
         
     | 
| 
      
 263 
     | 
    
         
            +
                      Open3.popen3(selector) do |stdin, stdout, stderr, wait_thr|
         
     | 
| 
      
 264 
     | 
    
         
            +
                        stdin.write(input)
         
     | 
| 
      
 265 
     | 
    
         
            +
                        stdin.close
         
     | 
| 
      
 266 
     | 
    
         
            +
             
     | 
| 
      
 267 
     | 
    
         
            +
                        output = stdout.read
         
     | 
| 
      
 268 
     | 
    
         
            +
             
     | 
| 
      
 269 
     | 
    
         
            +
                        selected = output.split("\n").map(&:strip).reject(&:empty?)
         
     | 
| 
      
 270 
     | 
    
         
            +
             
     | 
| 
      
 271 
     | 
    
         
            +
                        # Track which candidates have been matched to avoid duplicates
         
     | 
| 
      
 272 
     | 
    
         
            +
                        selected_indices = []
         
     | 
| 
      
 273 
     | 
    
         
            +
                        candidate_actions.each_index do |i|
         
     | 
| 
      
 274 
     | 
    
         
            +
                          if selected.include?(candidate_actions[i])
         
     | 
| 
      
 275 
     | 
    
         
            +
                            selected_indices << i unless selected_indices.include?(i)
         
     | 
| 
      
 276 
     | 
    
         
            +
                          end
         
     | 
| 
      
 277 
     | 
    
         
            +
                        end
         
     | 
| 
      
 278 
     | 
    
         
            +
                      end
         
     | 
| 
       243 
279 
     | 
    
         
             
                    else
         
     | 
| 
       244 
280 
     | 
    
         
             
                      # Fallback: select all or prompt for search string
         
     | 
| 
       245 
281 
     | 
    
         
             
                      selected_indices = (0...candidate_actions.size).to_a
         
     | 
| 
         @@ -326,12 +362,7 @@ class App 
     | 
|
| 
       326 
362 
     | 
    
         
             
                    when :finish
         
     | 
| 
       327 
363 
     | 
    
         
             
                      options[:finish] = true
         
     | 
| 
       328 
364 
     | 
    
         
             
                    when :edit
         
     | 
| 
       329 
     | 
    
         
            -
                      #  
     | 
| 
       330 
     | 
    
         
            -
                      edit_action = targets_for_selection[selected_indices.first][:action]
         
     | 
| 
       331 
     | 
    
         
            -
                      editor_content = "#{edit_action.action}\n#{edit_action.note.join("\n")}"
         
     | 
| 
       332 
     | 
    
         
            -
                      new_action, new_note = NA::Editor.format_input(NA::Editor.fork_editor(editor_content))
         
     | 
| 
       333 
     | 
    
         
            -
                      edit_action.action = new_action
         
     | 
| 
       334 
     | 
    
         
            -
                      edit_action.note = new_note
         
     | 
| 
      
 365 
     | 
    
         
            +
                      # Just set the flag - multi-action editor will handle it below
         
     | 
| 
       335 
366 
     | 
    
         
             
                      options[:edit] = true
         
     | 
| 
       336 
367 
     | 
    
         
             
                    when :priority
         
     | 
| 
       337 
368 
     | 
    
         
             
                      options[:priority] = param_value
         
     | 
| 
         @@ -384,7 +415,17 @@ class App 
     | 
|
| 
       384 
415 
     | 
    
         
             
                    end
         
     | 
| 
       385 
416 
     | 
    
         
             
                  end
         
     | 
| 
       386 
417 
     | 
    
         
             
                  did_direct_update = false
         
     | 
| 
      
 418 
     | 
    
         
            +
             
     | 
| 
      
 419 
     | 
    
         
            +
                  # Group selected actions by file for batch processing
         
     | 
| 
      
 420 
     | 
    
         
            +
                  actions_by_file = {}
         
     | 
| 
       387 
421 
     | 
    
         
             
                  selected_indices.each do |idx|
         
     | 
| 
      
 422 
     | 
    
         
            +
                    file = targets_for_selection[idx][:file]
         
     | 
| 
      
 423 
     | 
    
         
            +
                    actions_by_file[file] ||= []
         
     | 
| 
      
 424 
     | 
    
         
            +
                    actions_by_file[file] << targets_for_selection[idx][:action]
         
     | 
| 
      
 425 
     | 
    
         
            +
                  end
         
     | 
| 
      
 426 
     | 
    
         
            +
             
     | 
| 
      
 427 
     | 
    
         
            +
                  # Process each file's actions
         
     | 
| 
      
 428 
     | 
    
         
            +
                  actions_by_file.each do |file, action_list|
         
     | 
| 
       388 
429 
     | 
    
         
             
                    # Rebuild all derived variables from options after menu-driven assignment
         
     | 
| 
       389 
430 
     | 
    
         
             
                    add_tags = options[:tag] ? options[:tag].join(',').split(/ *, */).map { |t| t.sub(/^@/, '') } : []
         
     | 
| 
       390 
431 
     | 
    
         
             
                    remove_tags = options[:remove] ? options[:remove].join(',').split(/ *, */).map { |t| t.sub(/^@/, '') } : []
         
     | 
| 
         @@ -399,29 +440,64 @@ class App 
     | 
|
| 
       399 
440 
     | 
    
         
             
                    if options[:note] && defined?(param_value) && param_value
         
     | 
| 
       400 
441 
     | 
    
         
             
                      note_val = [param_value]
         
     | 
| 
       401 
442 
     | 
    
         
             
                    end
         
     | 
| 
       402 
     | 
    
         
            -
             
     | 
| 
       403 
     | 
    
         
            -
                    #  
     | 
| 
       404 
     | 
    
         
            -
                     
     | 
| 
       405 
     | 
    
         
            -
             
     | 
| 
       406 
     | 
    
         
            -
             
     | 
| 
       407 
     | 
    
         
            -
             
     | 
| 
       408 
     | 
    
         
            -
                       
     | 
| 
       409 
     | 
    
         
            -
             
     | 
| 
       410 
     | 
    
         
            -
                       
     | 
| 
       411 
     | 
    
         
            -
                       
     | 
| 
       412 
     | 
    
         
            -
             
     | 
| 
       413 
     | 
    
         
            -
             
     | 
| 
       414 
     | 
    
         
            -
             
     | 
| 
       415 
     | 
    
         
            -
             
     | 
| 
       416 
     | 
    
         
            -
             
     | 
| 
       417 
     | 
    
         
            -
             
     | 
| 
       418 
     | 
    
         
            -
             
     | 
| 
       419 
     | 
    
         
            -
             
     | 
| 
       420 
     | 
    
         
            -
             
     | 
| 
       421 
     | 
    
         
            -
             
     | 
| 
       422 
     | 
    
         
            -
             
     | 
| 
       423 
     | 
    
         
            -
             
     | 
| 
       424 
     | 
    
         
            -
             
     | 
| 
      
 443 
     | 
    
         
            +
             
     | 
| 
      
 444 
     | 
    
         
            +
                    # Handle edit with multiple actions
         
     | 
| 
      
 445 
     | 
    
         
            +
                    if options[:edit]
         
     | 
| 
      
 446 
     | 
    
         
            +
                      # Open editor once with all actions for this file
         
     | 
| 
      
 447 
     | 
    
         
            +
                      editor_content = NA::Editor.format_multi_action_input(action_list)
         
     | 
| 
      
 448 
     | 
    
         
            +
                      edited_content = NA::Editor.fork_editor(editor_content)
         
     | 
| 
      
 449 
     | 
    
         
            +
                      edited_actions = NA::Editor.parse_multi_action_output(edited_content)
         
     | 
| 
      
 450 
     | 
    
         
            +
             
     | 
| 
      
 451 
     | 
    
         
            +
                      # If markers were removed but we have the same number of actions, match by position
         
     | 
| 
      
 452 
     | 
    
         
            +
                      if edited_actions.empty? && action_list.size > 0
         
     | 
| 
      
 453 
     | 
    
         
            +
                        # Parse content line by line, skipping comments and blanks
         
     | 
| 
      
 454 
     | 
    
         
            +
                        non_comment_lines = edited_content.lines.map(&:strip).reject { |l| l.empty? || l.start_with?('#') }
         
     | 
| 
      
 455 
     | 
    
         
            +
             
     | 
| 
      
 456 
     | 
    
         
            +
                        # Match each non-comment line to an action by position
         
     | 
| 
      
 457 
     | 
    
         
            +
                        action_list.each_with_index do |action_obj, idx|
         
     | 
| 
      
 458 
     | 
    
         
            +
                          if non_comment_lines[idx]
         
     | 
| 
      
 459 
     | 
    
         
            +
                            # Split into action and notes
         
     | 
| 
      
 460 
     | 
    
         
            +
                            lines = non_comment_lines[idx..-1]
         
     | 
| 
      
 461 
     | 
    
         
            +
                            action_text = lines[0]
         
     | 
| 
      
 462 
     | 
    
         
            +
                            note_lines = lines[1..-1] || []
         
     | 
| 
      
 463 
     | 
    
         
            +
             
     | 
| 
      
 464 
     | 
    
         
            +
                            # Store by file:line key
         
     | 
| 
      
 465 
     | 
    
         
            +
                            key = "#{action_obj.file_path}:#{action_obj.file_line}"
         
     | 
| 
      
 466 
     | 
    
         
            +
                            edited_actions[key] = [action_text, note_lines]
         
     | 
| 
      
 467 
     | 
    
         
            +
                          end
         
     | 
| 
      
 468 
     | 
    
         
            +
                        end
         
     | 
| 
      
 469 
     | 
    
         
            +
                      end
         
     | 
| 
      
 470 
     | 
    
         
            +
             
     | 
| 
      
 471 
     | 
    
         
            +
                      # Update each action with edited content
         
     | 
| 
      
 472 
     | 
    
         
            +
                      action_list.each do |action_obj|
         
     | 
| 
      
 473 
     | 
    
         
            +
                        key = "#{action_obj.file_path}:#{action_obj.file_line}"
         
     | 
| 
      
 474 
     | 
    
         
            +
                        if edited_actions[key]
         
     | 
| 
      
 475 
     | 
    
         
            +
                          action_obj.action, action_obj.note = edited_actions[key]
         
     | 
| 
      
 476 
     | 
    
         
            +
                        end
         
     | 
| 
      
 477 
     | 
    
         
            +
                      end
         
     | 
| 
      
 478 
     | 
    
         
            +
                    end
         
     | 
| 
      
 479 
     | 
    
         
            +
             
     | 
| 
      
 480 
     | 
    
         
            +
                    # Update each action
         
     | 
| 
      
 481 
     | 
    
         
            +
                    action_list.each do |action_obj|
         
     | 
| 
      
 482 
     | 
    
         
            +
                      NA.update_action(file, nil,
         
     | 
| 
      
 483 
     | 
    
         
            +
                        add: action_obj,
         
     | 
| 
      
 484 
     | 
    
         
            +
                        add_tag: add_tags,
         
     | 
| 
      
 485 
     | 
    
         
            +
                        all: true,
         
     | 
| 
      
 486 
     | 
    
         
            +
                        append: append,
         
     | 
| 
      
 487 
     | 
    
         
            +
                        delete: options[:delete],
         
     | 
| 
      
 488 
     | 
    
         
            +
                        done: options[:done],
         
     | 
| 
      
 489 
     | 
    
         
            +
                        edit: false,  # Already handled above
         
     | 
| 
      
 490 
     | 
    
         
            +
                        finish: options[:finish],
         
     | 
| 
      
 491 
     | 
    
         
            +
                        move: target_proj,
         
     | 
| 
      
 492 
     | 
    
         
            +
                        note: note_val,
         
     | 
| 
      
 493 
     | 
    
         
            +
                        overwrite: options[:overwrite],
         
     | 
| 
      
 494 
     | 
    
         
            +
                        priority: priority,
         
     | 
| 
      
 495 
     | 
    
         
            +
                        project: options[:project],
         
     | 
| 
      
 496 
     | 
    
         
            +
                        remove_tag: remove_tags,
         
     | 
| 
      
 497 
     | 
    
         
            +
                        replace: options[:replace],
         
     | 
| 
      
 498 
     | 
    
         
            +
                        search_note: options[:search_notes],
         
     | 
| 
      
 499 
     | 
    
         
            +
                        tagged: nil)
         
     | 
| 
      
 500 
     | 
    
         
            +
                    end
         
     | 
| 
       425 
501 
     | 
    
         
             
                    did_direct_update = true
         
     | 
| 
       426 
502 
     | 
    
         
             
                  end
         
     | 
| 
       427 
503 
     | 
    
         
             
                  if did_direct_update
         
     | 
| 
         @@ -539,8 +615,15 @@ class App 
     | 
|
| 
       539 
615 
     | 
    
         | 
| 
       540 
616 
     | 
    
         
             
                  NA.notify("#{NA.theme[:error]}No search terms provided", exit_code: 1) if tokens.nil? && options[:tagged].empty?
         
     | 
| 
       541 
617 
     | 
    
         | 
| 
      
 618 
     | 
    
         
            +
                  # Handle target_line if provided (from PATH:LINE format)
         
     | 
| 
      
 619 
     | 
    
         
            +
                  search_tokens = if options[:target_line]
         
     | 
| 
      
 620 
     | 
    
         
            +
                                   { target_line: options[:target_line] }
         
     | 
| 
      
 621 
     | 
    
         
            +
                                 else
         
     | 
| 
      
 622 
     | 
    
         
            +
                                   tokens
         
     | 
| 
      
 623 
     | 
    
         
            +
                                 end
         
     | 
| 
      
 624 
     | 
    
         
            +
             
     | 
| 
       542 
625 
     | 
    
         
             
                  targets.each do |target|
         
     | 
| 
       543 
     | 
    
         
            -
                    NA.update_action(target,  
     | 
| 
      
 626 
     | 
    
         
            +
                    NA.update_action(target, search_tokens,
         
     | 
| 
       544 
627 
     | 
    
         
             
                                     add_tag: add_tags,
         
     | 
| 
       545 
628 
     | 
    
         
             
                                     all: options[:all],
         
     | 
| 
       546 
629 
     | 
    
         
             
                                     append: append,
         
     | 
    
        data/lib/na/action.rb
    CHANGED
    
    | 
         @@ -1,14 +1,25 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            # frozen_string_literal: true
         
     | 
| 
       2 
2 
     | 
    
         | 
| 
       3 
3 
     | 
    
         
             
            module NA
         
     | 
| 
      
 4 
     | 
    
         
            +
              # Represents a single actionable item in a todo file, with tags, notes, and project context.
         
     | 
| 
      
 5 
     | 
    
         
            +
              #
         
     | 
| 
      
 6 
     | 
    
         
            +
              # @example Create a new action
         
     | 
| 
      
 7 
     | 
    
         
            +
              #   action = NA::Action.new('todo.txt', 'Inbox', [], '- Buy milk', 1)
         
     | 
| 
       4 
8 
     | 
    
         
             
              class Action < Hash
         
     | 
| 
       5 
9 
     | 
    
         
             
                attr_reader :file, :project, :tags, :line
         
     | 
| 
       6 
10 
     | 
    
         
             
                attr_accessor :parent, :action, :note
         
     | 
| 
       7 
11 
     | 
    
         | 
| 
      
 12 
     | 
    
         
            +
                # @example
         
     | 
| 
      
 13 
     | 
    
         
            +
                #   action = NA::Action.new('todo.txt', 'Inbox', [], '- Buy milk', 1)
         
     | 
| 
       8 
14 
     | 
    
         
             
                def initialize(file, project, parent, action, idx, note = [])
         
     | 
| 
       9 
15 
     | 
    
         
             
                  super()
         
     | 
| 
       10 
16 
     | 
    
         | 
| 
       11 
     | 
    
         
            -
                   
     | 
| 
      
 17 
     | 
    
         
            +
                  # Store file in PATH:LINE format if line is available
         
     | 
| 
      
 18 
     | 
    
         
            +
                  @file = if idx.is_a?(Integer)
         
     | 
| 
      
 19 
     | 
    
         
            +
                            "#{file}:#{idx}"
         
     | 
| 
      
 20 
     | 
    
         
            +
                          else
         
     | 
| 
      
 21 
     | 
    
         
            +
                            file
         
     | 
| 
      
 22 
     | 
    
         
            +
                          end
         
     | 
| 
       12 
23 
     | 
    
         
             
                  @project = project
         
     | 
| 
       13 
24 
     | 
    
         
             
                  @parent = parent
         
     | 
| 
       14 
25 
     | 
    
         
             
                  @action = action.gsub('{', '\\{')
         
     | 
| 
         @@ -17,6 +28,32 @@ module NA 
     | 
|
| 
       17 
28 
     | 
    
         
             
                  @note = note
         
     | 
| 
       18 
29 
     | 
    
         
             
                end
         
     | 
| 
       19 
30 
     | 
    
         | 
| 
      
 31 
     | 
    
         
            +
                # Extract file path and line number from PATH:LINE format
         
     | 
| 
      
 32 
     | 
    
         
            +
                #
         
     | 
| 
      
 33 
     | 
    
         
            +
                # @return [Array] [file_path, line_number]
         
     | 
| 
      
 34 
     | 
    
         
            +
                def file_line_parts
         
     | 
| 
      
 35 
     | 
    
         
            +
                  if @file.to_s.include?(':')
         
     | 
| 
      
 36 
     | 
    
         
            +
                    path, line = @file.split(':', 2)
         
     | 
| 
      
 37 
     | 
    
         
            +
                    [path, line.to_i]
         
     | 
| 
      
 38 
     | 
    
         
            +
                  else
         
     | 
| 
      
 39 
     | 
    
         
            +
                    [@file, @line]
         
     | 
| 
      
 40 
     | 
    
         
            +
                  end
         
     | 
| 
      
 41 
     | 
    
         
            +
                end
         
     | 
| 
      
 42 
     | 
    
         
            +
             
     | 
| 
      
 43 
     | 
    
         
            +
                # Get just the file path without line number
         
     | 
| 
      
 44 
     | 
    
         
            +
                #
         
     | 
| 
      
 45 
     | 
    
         
            +
                # @return [String] File path
         
     | 
| 
      
 46 
     | 
    
         
            +
                def file_path
         
     | 
| 
      
 47 
     | 
    
         
            +
                  file_line_parts.first
         
     | 
| 
      
 48 
     | 
    
         
            +
                end
         
     | 
| 
      
 49 
     | 
    
         
            +
             
     | 
| 
      
 50 
     | 
    
         
            +
                # Get the line number
         
     | 
| 
      
 51 
     | 
    
         
            +
                #
         
     | 
| 
      
 52 
     | 
    
         
            +
                # @return [Integer] Line number
         
     | 
| 
      
 53 
     | 
    
         
            +
                def file_line
         
     | 
| 
      
 54 
     | 
    
         
            +
                  file_line_parts.last
         
     | 
| 
      
 55 
     | 
    
         
            +
                end
         
     | 
| 
      
 56 
     | 
    
         
            +
             
     | 
| 
       20 
57 
     | 
    
         
             
                # Update the action string and note with priority, tags, and completion status
         
     | 
| 
       21 
58 
     | 
    
         
             
                #
         
     | 
| 
       22 
59 
     | 
    
         
             
                # @param priority [Integer] Priority value to set
         
     | 
| 
         @@ -25,6 +62,8 @@ module NA 
     | 
|
| 
       25 
62 
     | 
    
         
             
                # @param remove_tag [Array<String>] Tags to remove
         
     | 
| 
       26 
63 
     | 
    
         
             
                # @param note [Array<String>] Notes to set
         
     | 
| 
       27 
64 
     | 
    
         
             
                # @return [void]
         
     | 
| 
      
 65 
     | 
    
         
            +
                # @example
         
     | 
| 
      
 66 
     | 
    
         
            +
                #   action.process(priority: 5, finish: true, add_tag: ['urgent'], remove_tag: ['waiting'], note: ['Call Bob'])
         
     | 
| 
       28 
67 
     | 
    
         
             
                def process(priority: 0, finish: false, add_tag: [], remove_tag: [], note: [])
         
     | 
| 
       29 
68 
     | 
    
         
             
                  string = @action.dup
         
     | 
| 
       30 
69 
     | 
    
         | 
| 
         @@ -54,13 +93,15 @@ module NA 
     | 
|
| 
       54 
93 
     | 
    
         
             
                # String representation of the action
         
     | 
| 
       55 
94 
     | 
    
         
             
                #
         
     | 
| 
       56 
95 
     | 
    
         
             
                # @return [String]
         
     | 
| 
      
 96 
     | 
    
         
            +
                # @example
         
     | 
| 
      
 97 
     | 
    
         
            +
                #   action.to_s #=> "{ project: 'Inbox', ... }"
         
     | 
| 
       57 
98 
     | 
    
         
             
                def to_s
         
     | 
| 
       58 
99 
     | 
    
         
             
                  note = if @note.count.positive?
         
     | 
| 
       59 
100 
     | 
    
         
             
                           "\n#{@note.join("\n")}"
         
     | 
| 
       60 
101 
     | 
    
         
             
                         else
         
     | 
| 
       61 
102 
     | 
    
         
             
                           ''
         
     | 
| 
       62 
103 
     | 
    
         
             
                         end
         
     | 
| 
       63 
     | 
    
         
            -
                  "(#{ 
     | 
| 
      
 104 
     | 
    
         
            +
                  "(#{file_path}:#{file_line}) #{@project}:#{@parent.join(">")} | #{@action}#{note}"
         
     | 
| 
       64 
105 
     | 
    
         
             
                end
         
     | 
| 
       65 
106 
     | 
    
         | 
| 
       66 
107 
     | 
    
         
             
                # Pretty string representation of the action with color formatting
         
     | 
| 
         @@ -72,7 +113,7 @@ module NA 
     | 
|
| 
       72 
113 
     | 
    
         
             
                         else
         
     | 
| 
       73 
114 
     | 
    
         
             
                           ''
         
     | 
| 
       74 
115 
     | 
    
         
             
                         end
         
     | 
| 
       75 
     | 
    
         
            -
                  "{x}#{NA.theme[:filename]}#{File.basename( 
     | 
| 
      
 116 
     | 
    
         
            +
                  "{x}#{NA.theme[:filename]}#{File.basename(file_path)}:#{file_line}{x}#{NA.theme[:bracket]}[{x}#{NA.theme[:project]}#{@project}:#{@parent.join('>')}{x}#{NA.theme[:bracket]}]{x} | #{NA.theme[:action]}#{@action}{x}#{NA.theme[:note]}#{note}"
         
     | 
| 
       76 
117 
     | 
    
         
             
                end
         
     | 
| 
       77 
118 
     | 
    
         | 
| 
       78 
119 
     | 
    
         
             
                # Inspect the action object
         
     | 
| 
         @@ -102,49 +143,67 @@ module NA 
     | 
|
| 
       102 
143 
     | 
    
         
             
                  NA::Benchmark.measure('Action.pretty') do
         
     | 
| 
       103 
144 
     | 
    
         
             
                    # Use cached theme instead of loading every time
         
     | 
| 
       104 
145 
     | 
    
         
             
                    theme = NA.theme
         
     | 
| 
       105 
     | 
    
         
            -
                     
     | 
| 
      
 146 
     | 
    
         
            +
                    # Merge templates if provided
         
     | 
| 
      
 147 
     | 
    
         
            +
                    if template[:templates]
         
     | 
| 
      
 148 
     | 
    
         
            +
                      theme = theme.dup
         
     | 
| 
      
 149 
     | 
    
         
            +
                      theme[:templates] = theme[:templates].merge(template[:templates])
         
     | 
| 
      
 150 
     | 
    
         
            +
                      template = theme.merge(template.reject { |k| k == :templates })
         
     | 
| 
      
 151 
     | 
    
         
            +
                    else
         
     | 
| 
      
 152 
     | 
    
         
            +
                      template = theme.merge(template)
         
     | 
| 
      
 153 
     | 
    
         
            +
                    end
         
     | 
| 
       106 
154 
     | 
    
         | 
| 
       107 
155 
     | 
    
         
             
                    # Pre-compute common template parts to avoid repeated processing
         
     | 
| 
       108 
156 
     | 
    
         
             
                    output_template = template[:templates][:output]
         
     | 
| 
       109 
157 
     | 
    
         
             
                    needs_filename = output_template.include?('%filename')
         
     | 
| 
      
 158 
     | 
    
         
            +
                    needs_line = output_template.include?('%line')
         
     | 
| 
       110 
159 
     | 
    
         
             
                    needs_parents = output_template.include?('%parents') || output_template.include?('%parent')
         
     | 
| 
       111 
160 
     | 
    
         
             
                    needs_project = output_template.include?('%project')
         
     | 
| 
       112 
161 
     | 
    
         | 
| 
       113 
162 
     | 
    
         
             
                    # Create the hierarchical parent string (optimized)
         
     | 
| 
       114 
163 
     | 
    
         
             
                    parents = if needs_parents && @parent.any?
         
     | 
| 
       115 
164 
     | 
    
         
             
                                parent_parts = @parent.map { |par| "#{template[:parent]}#{par}" }.join(template[:parent_divider])
         
     | 
| 
       116 
     | 
    
         
            -
                                 
     | 
| 
      
 165 
     | 
    
         
            +
                                # parent_parts already has color codes embedded, create final string directly
         
     | 
| 
      
 166 
     | 
    
         
            +
                                "#{NA::Color.template("{x}#{template[:bracket]}[")}#{parent_parts}#{NA::Color.template("{x}#{template[:bracket]}]{x}")}"
         
     | 
| 
       117 
167 
     | 
    
         
             
                              else
         
     | 
| 
       118 
168 
     | 
    
         
             
                                ''
         
     | 
| 
       119 
169 
     | 
    
         
             
                              end
         
     | 
| 
       120 
170 
     | 
    
         | 
| 
       121 
     | 
    
         
            -
                    # Create the project string (optimized)
         
     | 
| 
      
 171 
     | 
    
         
            +
                    # Create the project string (optimized) - Ensure color reset before project
         
     | 
| 
       122 
172 
     | 
    
         
             
                    project = if needs_project && !@project.empty?
         
     | 
| 
       123 
     | 
    
         
            -
                                NA::Color.template("{x}#{template[:project]}#{@project}{x} 
     | 
| 
      
 173 
     | 
    
         
            +
                                NA::Color.template("{x}#{template[:project]}#{@project}{x}")
         
     | 
| 
       124 
174 
     | 
    
         
             
                              else
         
     | 
| 
       125 
175 
     | 
    
         
             
                                ''
         
     | 
| 
       126 
176 
     | 
    
         
             
                              end
         
     | 
| 
       127 
177 
     | 
    
         | 
| 
       128 
178 
     | 
    
         
             
                    # Create the source filename string (optimized)
         
     | 
| 
       129 
179 
     | 
    
         
             
                    filename = if needs_filename
         
     | 
| 
       130 
     | 
    
         
            -
                                  
     | 
| 
      
 180 
     | 
    
         
            +
                                 path_only = file_path # Extract just the path from PATH:LINE
         
     | 
| 
      
 181 
     | 
    
         
            +
                                 path = path_only.sub(%r{^\./}, '').sub(/#{Dir.home}/, '~')
         
     | 
| 
       131 
182 
     | 
    
         
             
                                 if File.dirname(path) == '.'
         
     | 
| 
       132 
183 
     | 
    
         
             
                                   fname = NA.include_ext ? File.basename(path) : File.basename(path, ".#{extension}")
         
     | 
| 
       133 
184 
     | 
    
         
             
                                   fname = "./#{fname}" if NA.show_cwd_indicator
         
     | 
| 
       134 
     | 
    
         
            -
                                   NA::Color.template("#{template[:filename]}#{fname} 
     | 
| 
      
 185 
     | 
    
         
            +
                                   NA::Color.template("#{template[:filename]}#{fname}{x}")
         
     | 
| 
       135 
186 
     | 
    
         
             
                                 else
         
     | 
| 
       136 
187 
     | 
    
         
             
                                   colored = (NA.include_ext ? path : path.sub(/\.#{extension}$/, '')).highlight_filename
         
     | 
| 
       137 
     | 
    
         
            -
                                   NA::Color.template("#{template[:filename]}#{colored} 
     | 
| 
      
 188 
     | 
    
         
            +
                                   NA::Color.template("#{template[:filename]}#{colored}{x}")
         
     | 
| 
       138 
189 
     | 
    
         
             
                                 end
         
     | 
| 
       139 
190 
     | 
    
         
             
                               else
         
     | 
| 
       140 
191 
     | 
    
         
             
                                 ''
         
     | 
| 
       141 
192 
     | 
    
         
             
                               end
         
     | 
| 
       142 
193 
     | 
    
         | 
| 
      
 194 
     | 
    
         
            +
                    # Create the line number string (optimized)
         
     | 
| 
      
 195 
     | 
    
         
            +
                    line_num = if needs_line && @line
         
     | 
| 
      
 196 
     | 
    
         
            +
                                 NA::Color.template("#{template[:line]}:#{@line} {x}")
         
     | 
| 
      
 197 
     | 
    
         
            +
                               else
         
     | 
| 
      
 198 
     | 
    
         
            +
                                 ''
         
     | 
| 
      
 199 
     | 
    
         
            +
                               end
         
     | 
| 
      
 200 
     | 
    
         
            +
             
     | 
| 
       143 
201 
     | 
    
         
             
                    # colorize the action and highlight tags (optimized)
         
     | 
| 
       144 
202 
     | 
    
         
             
                    action_text = @action.dup
         
     | 
| 
       145 
203 
     | 
    
         
             
                    action_text.gsub!(/\{(.*?)\}/, '\\{\1\\}')
         
     | 
| 
       146 
204 
     | 
    
         
             
                    action_text = action_text.sub(/ @#{NA.na_tag}\b/, '')
         
     | 
| 
       147 
     | 
    
         
            -
                    action  
     | 
| 
      
 205 
     | 
    
         
            +
                    # Reset colors before action to prevent bleeding from parents/project
         
     | 
| 
      
 206 
     | 
    
         
            +
                    action = NA::Color.template("{x}#{template[:action]}#{action_text}{x}")
         
     | 
| 
       148 
207 
     | 
    
         
             
                    action = action.highlight_tags(color: template[:tags],
         
     | 
| 
       149 
208 
     | 
    
         
             
                                                   parens: template[:value_parens],
         
     | 
| 
       150 
209 
     | 
    
         
             
                                                   value: template[:values],
         
     | 
| 
         @@ -159,8 +218,8 @@ module NA 
     | 
|
| 
       159 
218 
     | 
    
         
             
                          width = @cached_width ||= TTY::Screen.columns
         
     | 
| 
       160 
219 
     | 
    
         
             
                          # Calculate indent more efficiently - avoid repeated template processing
         
     | 
| 
       161 
220 
     | 
    
         
             
                          base_template = output_template.gsub('%action', '').gsub('%note', '')
         
     | 
| 
       162 
     | 
    
         
            -
                          base_output = base_template.gsub('%filename', filename).gsub('%project', project).gsub(/%parents?/,
         
     | 
| 
       163 
     | 
    
         
            -
             
     | 
| 
      
 221 
     | 
    
         
            +
                          base_output = base_template.gsub('%filename', filename).gsub('%line', line_num).gsub('%project', project).gsub(/%parents?/,
         
     | 
| 
      
 222 
     | 
    
         
            +
                                                                                                                                         parents)
         
     | 
| 
       164 
223 
     | 
    
         
             
                          indent = NA::Color.uncolor(NA::Color.template(base_output)).length
         
     | 
| 
       165 
224 
     | 
    
         
             
                          note = NA::Color.template(@note.wrap(width, indent, template[:note]))
         
     | 
| 
       166 
225 
     | 
    
         
             
                        else
         
     | 
| 
         @@ -175,7 +234,7 @@ module NA 
     | 
|
| 
       175 
234 
     | 
    
         
             
                    if detect_width && !action.empty?
         
     | 
| 
       176 
235 
     | 
    
         
             
                      width = @cached_width ||= TTY::Screen.columns
         
     | 
| 
       177 
236 
     | 
    
         
             
                      base_template = output_template.gsub('%action', '').gsub('%note', '')
         
     | 
| 
       178 
     | 
    
         
            -
                      base_output = base_template.gsub('%filename', filename).gsub('%project', project).gsub(/%parents?/, parents)
         
     | 
| 
      
 237 
     | 
    
         
            +
                      base_output = base_template.gsub('%filename', filename).gsub('%line', line_num).gsub('%project', project).gsub(/%parents?/, parents)
         
     | 
| 
       179 
238 
     | 
    
         
             
                      indent = NA::Color.uncolor(NA::Color.template(base_output)).length
         
     | 
| 
       180 
239 
     | 
    
         
             
                      action = action.wrap(width, indent)
         
     | 
| 
       181 
240 
     | 
    
         
             
                    end
         
     | 
| 
         @@ -183,6 +242,7 @@ module NA 
     | 
|
| 
       183 
242 
     | 
    
         
             
                    # Replace variables in template string and output colorized (optimized)
         
     | 
| 
       184 
243 
     | 
    
         
             
                    final_output = output_template.dup
         
     | 
| 
       185 
244 
     | 
    
         
             
                    final_output.gsub!('%filename', filename)
         
     | 
| 
      
 245 
     | 
    
         
            +
                    final_output.gsub!('%line', line_num)
         
     | 
| 
       186 
246 
     | 
    
         
             
                    final_output.gsub!('%project', project)
         
     | 
| 
       187 
247 
     | 
    
         
             
                    final_output.gsub!(/%parents?/, parents)
         
     | 
| 
       188 
248 
     | 
    
         
             
                    final_output.gsub!('%action', action.highlight_search(regexes))
         
     | 
    
        data/lib/na/array.rb
    CHANGED
    
    | 
         @@ -1,10 +1,17 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            # frozen_string_literal: true
         
     | 
| 
       2 
2 
     | 
    
         | 
| 
      
 3 
     | 
    
         
            +
            ##
         
     | 
| 
      
 4 
     | 
    
         
            +
            # Extensions to Ruby's Array class for todo management and formatting.
         
     | 
| 
      
 5 
     | 
    
         
            +
            #
         
     | 
| 
      
 6 
     | 
    
         
            +
            # @example Remove bad elements from an array
         
     | 
| 
      
 7 
     | 
    
         
            +
            #   ['foo', '', nil, 0, false, 'bar'].remove_bad #=> ['foo', 'bar']
         
     | 
| 
       3 
8 
     | 
    
         
             
            class ::Array
         
     | 
| 
       4 
9 
     | 
    
         
             
              # Like Array#compact -- removes nil items, but also
         
     | 
| 
       5 
10 
     | 
    
         
             
              # removes empty strings, zero or negative numbers and FalseClass items
         
     | 
| 
       6 
11 
     | 
    
         
             
              #
         
     | 
| 
       7 
12 
     | 
    
         
             
              # @return [Array] Array without "bad" elements
         
     | 
| 
      
 13 
     | 
    
         
            +
              # @example
         
     | 
| 
      
 14 
     | 
    
         
            +
              #   ['foo', '', nil, 0, false, 'bar'].remove_bad #=> ['foo', 'bar']
         
     | 
| 
       8 
15 
     | 
    
         
             
              def remove_bad
         
     | 
| 
       9 
16 
     | 
    
         
             
                compact.map { |x| x.is_a?(String) ? x.strip : x }.select(&:good?)
         
     | 
| 
       10 
17 
     | 
    
         
             
              end
         
     | 
| 
         @@ -15,6 +22,8 @@ class ::Array 
     | 
|
| 
       15 
22 
     | 
    
         
             
              # @param indent [Integer] Indentation spaces
         
     | 
| 
       16 
23 
     | 
    
         
             
              # @param color [String] Color code to apply
         
     | 
| 
       17 
24 
     | 
    
         
             
              # @return [Array, String] Wrapped and colorized lines
         
     | 
| 
      
 25 
     | 
    
         
            +
              # @example
         
     | 
| 
      
 26 
     | 
    
         
            +
              #   ['foo', 'bar'].wrap(80, 2, '{g}') #=> "\n{g}  • foo{x}\n{g}  • bar{x}"
         
     | 
| 
       18 
27 
     | 
    
         
             
              def wrap(width, indent, color)
         
     | 
| 
       19 
28 
     | 
    
         
             
                return map { |l| "#{color}  #{l.wrap(width, 2)}" } if width < 60
         
     | 
| 
       20 
29 
     | 
    
         |