1 // Copyright (C) 2011-2018 ycmd contributors
2 //
3 // This file is part of ycmd.
4 //
5 // ycmd is free software: you can redistribute it and/or modify
6 // it under the terms of the GNU General Public License as published by
7 // the Free Software Foundation, either version 3 of the License, or
8 // (at your option) any later version.
9 //
10 // ycmd is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 // GNU General Public License for more details.
14 //
15 // You should have received a copy of the GNU General Public License
16 // along with ycmd.  If not, see <http://www.gnu.org/licenses/>.
17 
18 #include "PythonSupport.h"
19 #include "Candidate.h"
20 #include "Repository.h"
21 #include "Result.h"
22 #include "Utils.h"
23 
24 #include <utility>
25 #include <vector>
26 
27 namespace YouCompleteMe {
28 
29 namespace {
30 
CandidatesFromObjectList(const pybind11::list & candidates,pybind11::str candidate_property,size_t num_candidates)31 std::vector< const Candidate * > CandidatesFromObjectList(
32   const pybind11::list& candidates,
33   pybind11::str candidate_property,
34   size_t num_candidates ) {
35   std::vector< std::string > candidate_strings( num_candidates );
36   auto it = candidate_strings.begin();
37 
38   if ( !PyUnicode_GET_LENGTH( candidate_property.ptr() ) ) {
39     for ( size_t i = 0; i < num_candidates; ++i ) {
40       *it++ = GetUtf8String( PyList_GET_ITEM( candidates.ptr(), i ) );
41     }
42   } else {
43     for ( size_t i = 0; i < num_candidates; ++i ) {
44         auto element = PyDict_GetItem( PyList_GET_ITEM( candidates.ptr(), i ),
45                                        candidate_property.ptr() );
46         *it++ = GetUtf8String( element );
47     }
48   }
49 
50   return Repository< Candidate >::Instance().GetElements(
51            std::move( candidate_strings ) );
52 }
53 
54 } // unnamed namespace
55 
56 
FilterAndSortCandidates(const pybind11::list & candidates,pybind11::str candidate_property,std::string & query,const size_t max_candidates)57 pybind11::list FilterAndSortCandidates(
58   const pybind11::list& candidates,
59   pybind11::str candidate_property,
60   std::string& query,
61   const size_t max_candidates ) {
62 
63   auto num_candidates = size_t( PyList_GET_SIZE( candidates.ptr() ) );
64   std::vector< const Candidate * > repository_candidates =
65     CandidatesFromObjectList( candidates,
66                               std::move( candidate_property ),
67                               num_candidates );
68 
69   std::vector< ResultAnd< size_t > > result_and_objects;
70   {
71     pybind11::gil_scoped_release unlock;
72     Word query_object( std::move( query ) );
73 
74     for ( size_t i = 0; i < num_candidates; ++i ) {
75       const Candidate *candidate = repository_candidates[ i ];
76 
77       if ( candidate->IsEmpty() || !candidate->ContainsBytes( query_object ) ) {
78         continue;
79       }
80 
81       Result result = candidate->QueryMatchResult( query_object );
82 
83       if ( result.IsSubsequence() ) {
84         result_and_objects.emplace_back( result, i );
85       }
86     }
87 
88     PartialSort( result_and_objects, max_candidates );
89   }
90 
91   pybind11::list filtered_candidates( result_and_objects.size() );
92   for ( size_t i = 0; i < result_and_objects.size(); ++i ) {
93     auto new_candidate = PyList_GET_ITEM(
94         candidates.ptr(),
95         result_and_objects[ i ].extra_object_ );
96     Py_INCREF( new_candidate );
97     PyList_SET_ITEM( filtered_candidates.ptr(), i, new_candidate );
98   }
99 
100   return filtered_candidates;
101 }
102 
103 
GetUtf8String(pybind11::handle value)104 std::string GetUtf8String( pybind11::handle value ) {
105   // If already a unicode or string (or something derived from it)
106   // pybind will already convert to utf8 when converting to std::string.
107   // For `bytes` the contents are left untouched:
108   if ( PyUnicode_CheckExact( value.ptr() ) ) {
109     ssize_t size = 0;
110     const char* buffer = nullptr;
111     buffer = PyUnicode_AsUTF8AndSize( value.ptr(), &size );
112     return { buffer, static_cast< size_t >( size ) };
113   }
114   if ( PyBytes_CheckExact( value.ptr() ) ) {
115     ssize_t size = 0;
116     char* buffer = nullptr;
117     PyBytes_AsStringAndSize( value.ptr(), &buffer, &size );
118     return { buffer, static_cast< size_t >( size ) };
119   }
120 
121   // Otherwise go through Python's built-in `str`.
122   pybind11::str keep_alive( value );
123   ssize_t size = 0;
124   const char* buffer =
125       PyUnicode_AsUTF8AndSize( keep_alive.ptr(), &size );
126   return { buffer, static_cast< size_t >( size ) };
127 }
128 
129 } // namespace YouCompleteMe
130