Skip to main content
aboutsummaryrefslogtreecommitdiffstats
blob: db1ec7b27da0fd684c53d9dc3ef16c71c3aeb539 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
/*******************************************************************************
 * Copyright (c) 2008 Wind River Systems, Inc. and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     Anton Leherbauer (Wind River Systems) - initial API and implementation
 *******************************************************************************/
package org.eclipse.cdt.internal.ui.text.contentassist;

import java.io.File;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import org.eclipse.core.resources.IContainer;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IResourceProxy;
import org.eclipse.core.resources.IResourceProxyVisitor;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.Path;
import org.eclipse.jface.resource.ImageDescriptor;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.ITypedRegion;
import org.eclipse.jface.text.TextUtilities;
import org.eclipse.jface.text.contentassist.ICompletionProposal;
import org.eclipse.jface.text.contentassist.IContextInformation;
import org.eclipse.swt.graphics.Image;

import org.eclipse.cdt.core.model.CoreModel;
import org.eclipse.cdt.core.model.IInclude;
import org.eclipse.cdt.core.model.ITranslationUnit;
import org.eclipse.cdt.core.parser.IExtendedScannerInfo;
import org.eclipse.cdt.core.parser.IScannerInfo;
import org.eclipse.cdt.ui.CUIPlugin;
import org.eclipse.cdt.ui.text.ICPartitions;
import org.eclipse.cdt.ui.text.contentassist.ContentAssistInvocationContext;
import org.eclipse.cdt.ui.text.contentassist.ICompletionProposalComputer;

import org.eclipse.cdt.internal.ui.viewsupport.CElementImageProvider;

/**
 * A proposal computer for include directives.
 *
 * @since 5.0
 */
public class InclusionProposalComputer implements ICompletionProposalComputer {

	private String fErrorMessage;

	public List<ICompletionProposal> computeCompletionProposals(ContentAssistInvocationContext context, IProgressMonitor monitor) {
		List<ICompletionProposal> proposals= Collections.emptyList();
		fErrorMessage= null;
		
		if (context instanceof CContentAssistInvocationContext) {
			CContentAssistInvocationContext cContext= (CContentAssistInvocationContext) context;
			if (inIncludeDirective(cContext)) {
				// add include file proposals
				proposals= new ArrayList<ICompletionProposal>();
				try {
					addInclusionProposals(cContext, proposals);
				} catch (Exception exc) {
					fErrorMessage= exc.getMessage();
					CUIPlugin.log(exc);
				}
			}
		}
		return proposals;
	}

	public List<IContextInformation> computeContextInformation(ContentAssistInvocationContext context, IProgressMonitor monitor) {
		return null;
	}

	public String getErrorMessage() {
		return fErrorMessage;
	}

	public void sessionEnded() {
	}

	public void sessionStarted() {
	}

	/**
	 * Test whether the invocation offset is inside the file name part if an include directive.
	 * 
	 * @param context  the invocation context
	 * @return <code>true</code> if the invocation offset is inside or before the directive keyword
	 */
	private boolean inIncludeDirective(CContentAssistInvocationContext context) {
		IDocument doc = context.getDocument();
		int offset = context.getInvocationOffset();
		
		try {
			final ITypedRegion partition= TextUtilities.getPartition(doc, ICPartitions.C_PARTITIONING, offset, true);
			if (ICPartitions.C_PREPROCESSOR.equals(partition.getType())) {
				String ppPrefix= doc.get(partition.getOffset(), offset - partition.getOffset());
				if (ppPrefix.matches("\\s*#\\s*include\\s*[\"<][^\">]*")) { //$NON-NLS-1$
					// we are inside the file name part of the include directive
					return true;
				}
			}
			
		} catch (BadLocationException exc) {
		}
		return false;
	}

	private void addInclusionProposals(CContentAssistInvocationContext context, List<ICompletionProposal> proposals) throws Exception {
		if (context.isContextInformationStyle()) {
			return;
		}
		String prefix;
		boolean angleBrackets= false;
		prefix = computeIncludePrefix(context);
		if (prefix.length() > 0) {
			angleBrackets= prefix.charAt(0) == '<';
			prefix= prefix.substring(1);
		}
		IPath prefixPath= new Path(prefix);
		final ITranslationUnit tu= context.getTranslationUnit();
		String[] potentialIncludes= collectIncludeFiles(tu, prefixPath, angleBrackets);
		if (potentialIncludes.length > 0) {
			IInclude[] includes= context.getTranslationUnit().getIncludes();
			Set<String> alreadyIncluded= new HashSet<String>();
			for (IInclude includeDirective : includes) {
				alreadyIncluded.add(includeDirective.getElementName());
			}
			Image image = getImage(CElementImageProvider.getIncludeImageDescriptor());
			for (String include : potentialIncludes) {
				if (alreadyIncluded.add(include)) {
					final char openingBracket= angleBrackets ? '<' : '"';
					final char closingBracket= angleBrackets ? '>' : '"';
					String repString= openingBracket + include;
					final String dispString= repString + closingBracket;
					int repLength = prefix.length() + 1;
					int repOffset= context.getInvocationOffset() - repLength;
					final boolean needClosingBracket= context.getDocument().getChar(repOffset + repLength) != closingBracket;
					if (needClosingBracket) {
						repString += closingBracket;
					}
					final boolean isDir= include.endsWith("/"); //$NON-NLS-1$
					final int relevance= computeRelevance(prefix, include) + (isDir ? 0 : 1);
					final CCompletionProposal proposal= createProposal(repOffset, repLength, repString, dispString, image, relevance, context);
					if (!isDir && !needClosingBracket) {
						// put cursor behind closing bracket
						proposal.setCursorPosition(repString.length() + 1);
					}
					proposals.add(proposal);
				}
			}
		}
	}
	
	/**
	 * Collect potential include files for the given translation unit.
	 * 
	 * @param tu  the translation unit to include the file
	 * @param prefixPath  the path part to match the sub-directory and file name
	 * @param angleBrackets  whether angle brackets enclose the include file name
	 * @return an array of incude file names
	 * @throws CoreException
	 */
	private String[] collectIncludeFiles(final ITranslationUnit tu, IPath prefixPath, boolean angleBrackets) throws CoreException {
		final List<String> includeFiles= new ArrayList<String>();
		if (!angleBrackets) {
			// search in current directory
			IResource resource= tu.getResource();
			if (resource != null) {
				IContainer parent= resource.getParent();
				collectIncludeFilesFromContainer(tu, parent, prefixPath, includeFiles);
			} else {
				IPath location= tu.getLocation();
				if (location != null) {
					collectIncludeFilesFromDirectory(tu, location.removeLastSegments(1), prefixPath, includeFiles);
				}
			}
		}
		IScannerInfo info= tu.getScannerInfo(true);
		if (info != null) {
			collectIncludeFilesFromScannerInfo(tu, info, prefixPath, angleBrackets, includeFiles);
		}
		return includeFiles.toArray(new String[includeFiles.size()]);
	}

	/**
	 * @param tu  the translation unit to include the file
	 * @param info  the scanner info for this translation unit
	 * @param prefixPath  the path part to match the sub-directory and file name
	 * @param angleBrackets  whether angle brackets enclose the include file name
	 * @param includeFiles  the result list
	 */
	private void collectIncludeFilesFromScannerInfo(ITranslationUnit tu, IScannerInfo info, IPath prefixPath, boolean angleBrackets, List<String> includeFiles) {
		if (!angleBrackets && info instanceof IExtendedScannerInfo) {
			IExtendedScannerInfo extendedInfo= (IExtendedScannerInfo) info;
			String[] quoteIncludes= extendedInfo.getLocalIncludePath();
			
			if (quoteIncludes != null) {
				for (String quoteInclude : quoteIncludes) {
					IPath includeDir= new Path(quoteInclude);
					collectIncludeFilesFromDirectory(tu, includeDir, prefixPath, includeFiles);
				}
			}
		}
		
		String[] allIncludes= info.getIncludePaths();
		for (String allInclude : allIncludes) {
			IPath includeDir= new Path(allInclude);
			collectIncludeFilesFromDirectory(tu, includeDir, prefixPath, includeFiles);
		}
	}

	/**
	 * Collect include files from the given file system directory.
	 * 
	 * @param tu  the translation unit to include the file
	 * @param directory  the file system path of the directory
	 * @param prefixPath  the path part to match the sub-directory and file name
	 * @param includeFiles  the result list
	 */
	private void collectIncludeFilesFromDirectory(ITranslationUnit tu, IPath directory, IPath prefixPath, List<String> includeFiles) {
		final String namePrefix;
		if (prefixPath.segmentCount() == 0) {
			namePrefix= ""; //$NON-NLS-1$
		} else if (prefixPath.hasTrailingSeparator()) {
			namePrefix= ""; //$NON-NLS-1$
			prefixPath= prefixPath.removeTrailingSeparator();
			directory= directory.append(prefixPath);
		} else {
			namePrefix= prefixPath.lastSegment();
			prefixPath= prefixPath.removeLastSegments(1);
			if (prefixPath.segmentCount() > 0) {
				directory= directory.append(prefixPath);
			}
		}
		final File fileDir = directory.toFile();
		if (!fileDir.exists()) {
			return;
		}
		final int prefixLength = namePrefix.length();
		final IProject project= tu.getCProject().getProject();
		File[] files= fileDir.listFiles();
		for (File file : files) {
			final String name= file.getName();
			if (name.length() >= prefixLength && namePrefix.equalsIgnoreCase(name.substring(0, prefixLength))) {
				if (file.isFile()) {
					if (CoreModel.isValidCXXHeaderUnitName(project, name) || CoreModel.isValidCHeaderUnitName(project, name)) {
						includeFiles.add(prefixPath.append(name).toString());
					}
				} else if (file.isDirectory()) {
					includeFiles.add(prefixPath.append(name).addTrailingSeparator().toString());
				}
			}
		}
	}

	/**
	 * Collect include files from the given resource container.
	 * 
	 * @param tu  the translation unit to include the file
	 * @param parent  the resource container
	 * @param prefixPath  the path part to match the sub-directory and file name
	 * @param includeFiles  the result list
	 * @throws CoreException
	 */
	private void collectIncludeFilesFromContainer(final ITranslationUnit tu, IContainer parent, IPath prefixPath, final List<String> includeFiles) throws CoreException {
		final String namePrefix;
		if (prefixPath.segmentCount() == 0) {
			namePrefix= ""; //$NON-NLS-1$
		} else if (prefixPath.hasTrailingSeparator()) {
			namePrefix= ""; //$NON-NLS-1$
			prefixPath= prefixPath.removeTrailingSeparator();
			parent= parent.getFolder(prefixPath);
		} else {
			namePrefix= prefixPath.lastSegment();
			prefixPath= prefixPath.removeLastSegments(1);
			if (prefixPath.segmentCount() > 0) {
				parent= parent.getFolder(prefixPath);
			}
		}
		if (!parent.exists()) {
			return;
		}
		final IPath cPrefixPath= prefixPath;
		final int prefixLength = namePrefix.length();
		final IProject project= tu.getCProject().getProject();
		parent.accept(new IResourceProxyVisitor() {
			boolean fFirstVisit= true;
			public boolean visit(IResourceProxy proxy) throws CoreException {
				final int type= proxy.getType();
				final String name= proxy.getName();
				if (fFirstVisit) {
					fFirstVisit= false;
					return true;
				}
				if (name.length() >= prefixLength && namePrefix.equalsIgnoreCase(name.substring(0, prefixLength))) {
					if (type == IResource.FILE) {
						if (CoreModel.isValidCXXHeaderUnitName(project, name) || CoreModel.isValidCHeaderUnitName(project, name)) {
							includeFiles.add(cPrefixPath.append(name).toString());
						}
					} else if (type == IResource.FOLDER) {
						includeFiles.add(cPrefixPath.append(name).addTrailingSeparator().toString());
					}
				}
				return false;
			}}, IResource.DEPTH_ONE);
	}

	/**
	 * Compute the file name portion in an incomplete include directive.
	 * 
	 * @param context
	 * @return the file name portion including the opening bracket or quote
	 * @throws BadLocationException
	 */
	private String computeIncludePrefix(CContentAssistInvocationContext context) throws BadLocationException {
		IDocument document= context.getDocument();
		if (document == null)
			return null;
		int end= context.getInvocationOffset();
		int start= end;
		while (--start >= 0) {
			final char ch= document.getChar(start);
			if (ch == '"' || ch == '<')
				break;
		}
		return document.get(start, end - start);
	}


	/**
	 * Compute base relevance depending on quality of name / prefix match.
	 * 
	 * @param prefix  the completion pefix
	 * @param match  the matching identifier
	 * @return a relevance value inidicating the quality of the name match
	 */
	protected int computeRelevance(String prefix, String match) {
		int baseRelevance= 0;
		boolean caseMatch= prefix.length() > 0 && match.startsWith(prefix);
		if (caseMatch) {
			baseRelevance += RelevanceConstants.CASE_MATCH_RELEVANCE;
		}
		return baseRelevance;
	}

	private CCompletionProposal createProposal(int repOffset, int repLength, String repString, String dispString, Image image, int relevance, CContentAssistInvocationContext context) {
		return new CCompletionProposal(repString, repOffset, repLength, image, dispString, dispString, relevance, context.getViewer());
	}

	private Image getImage(ImageDescriptor desc) {
		return desc != null ? CUIPlugin.getImageDescriptorRegistry().get(desc) : null;
	}
	
}

Back to the top