1 /******************************************************************************* 2 * Copyright (c) 2010 - 2013 by Timotei Dolean <timotei21@gmail.com> 3 * 4 * This program and the accompanying materials are made available 5 * under the terms of the Eclipse Public License v1.0 6 * which accompanies this distribution, and is available at 7 * http://www.eclipse.org/legal/epl-v10.html 8 *******************************************************************************/ 9 package org.wesnoth.ui.contentassist; 10 11 import com.google.common.base.Function; 12 import com.google.common.base.Predicates; 13 import com.google.common.collect.Collections2; 14 15 import java.util.ArrayList; 16 import java.util.Collection; 17 import java.util.List; 18 import java.util.Map.Entry; 19 20 import org.eclipse.core.resources.IFile; 21 import org.eclipse.emf.ecore.EObject; 22 import org.eclipse.jface.text.contentassist.ICompletionProposal; 23 import org.eclipse.jface.viewers.ILabelProvider; 24 import org.eclipse.jface.viewers.StyledString; 25 import org.eclipse.swt.graphics.Image; 26 import org.eclipse.xtext.Assignment; 27 import org.eclipse.xtext.RuleCall; 28 import org.eclipse.xtext.nodemodel.ILeafNode; 29 import org.eclipse.xtext.nodemodel.INode; 30 import org.eclipse.xtext.nodemodel.util.NodeModelUtils; 31 import org.eclipse.xtext.ui.editor.contentassist.ContentAssistContext; 32 import org.eclipse.xtext.ui.editor.contentassist.ICompletionProposalAcceptor; 33 34 import org.wesnoth.installs.WesnothInstallsUtils; 35 import org.wesnoth.preprocessor.Define; 36 import org.wesnoth.projects.ProjectCache; 37 import org.wesnoth.projects.ProjectUtils; 38 import org.wesnoth.schema.SchemaParser; 39 import org.wesnoth.templates.TemplateProvider; 40 import org.wesnoth.ui.editor.WMLEditor; 41 import org.wesnoth.utils.ResourceUtils; 42 import org.wesnoth.utils.StringUtils; 43 import org.wesnoth.utils.WMLUtils; 44 import org.wesnoth.wml.WMLConfig; 45 import org.wesnoth.wml.WMLKey; 46 import org.wesnoth.wml.WMLKeyValue; 47 import org.wesnoth.wml.WMLTag; 48 import org.wesnoth.wml.WMLVariable; 49 import org.wesnoth.wml.WMLVariable.Scope; 50 51 /** 52 * Proposal provider for WML content assist 53 */ 54 public class WMLProposalProvider extends AbstractWMLProposalProvider 55 { 56 protected SchemaParser schemaParser_; 57 protected ProjectCache projectCache_; 58 protected int dependencyIndex_; 59 60 protected static final int KEY_VALUE_PRIORITY = 1700; 61 protected static final int KEY_NAME_PRIORITY = 1500; 62 protected static final int TAG_PRIORITY = 1000; 63 protected static final int MACRO_CALL_PRIORITY = 100; 64 65 private static boolean IMAGES_INITED = false; 66 private static Image MACRO_CALL_IMAGE = null; 67 private static Image SCENARIO_VALUE_IMAGE = null; 68 private static Image WML_KEY_IMAGE = null; 69 private static Image WML_TAG_IMAGE = null; 70 71 /** 72 * For priorities, see: {@link #KEY_NAME_PRIORITY} 73 * {@link #KEY_VALUE_PRIORITY} {@link #TAG_PRIORITY} 74 * {@link #MACRO_CALL_PRIORITY} 75 */ WMLProposalProvider( )76 public WMLProposalProvider( ) 77 { 78 super( ); 79 } 80 initImages( )81 private void initImages( ) 82 { 83 if( IMAGES_INITED ) { 84 return; 85 } 86 87 ILabelProvider labelProvider = getLabelProvider( ); 88 89 MACRO_CALL_IMAGE = labelProvider.getImage( "macrocall.png" ); 90 SCENARIO_VALUE_IMAGE = labelProvider.getImage( "scenario.png" ); 91 WML_KEY_IMAGE = labelProvider.getImage( "wmlkey.png" ); 92 WML_TAG_IMAGE = labelProvider.getImage( "wmltag.png" ); 93 } 94 95 /** 96 * Initializes the proposal provider with all needed values 97 */ initProposalProvider( )98 private void initProposalProvider( ) 99 { 100 if( projectCache_ != null ) { 101 return; 102 } 103 104 IFile file = WMLEditor.getActiveEditorFile( ); 105 projectCache_ = ProjectUtils.getCacheForProject( file.getProject( ) ); 106 107 // load the schema so we know what to suggest for autocomplete 108 schemaParser_ = SchemaParser.getInstance( WesnothInstallsUtils 109 .getInstallNameForResource( file ) ); 110 111 dependencyIndex_ = ResourceUtils.getDependencyIndex( file ); 112 113 initImages( ); 114 } 115 116 @Override createProposals( ContentAssistContext context, ICompletionProposalAcceptor acceptor )117 public void createProposals( ContentAssistContext context, 118 ICompletionProposalAcceptor acceptor ) 119 { 120 initProposalProvider( ); 121 122 super.createProposals( context, acceptor ); 123 } 124 125 @Override completeWMLKey_Name( EObject model, Assignment assignment, ContentAssistContext context, ICompletionProposalAcceptor acceptor )126 public void completeWMLKey_Name( EObject model, Assignment assignment, 127 ContentAssistContext context, ICompletionProposalAcceptor acceptor ) 128 { 129 super.completeWMLKey_Name( model, assignment, context, acceptor ); 130 131 if( model != null ) { 132 addKeyNameProposals( model, context, acceptor ); 133 } 134 } 135 136 @Override complete_WMLKeyValue( EObject model, RuleCall ruleCall, ContentAssistContext context, ICompletionProposalAcceptor acceptor )137 public void complete_WMLKeyValue( EObject model, RuleCall ruleCall, 138 ContentAssistContext context, ICompletionProposalAcceptor acceptor ) 139 { 140 super.complete_WMLKeyValue( model, ruleCall, context, acceptor ); 141 142 if( model != null ) { 143 addKeyValueProposals( model, context, acceptor ); 144 } 145 } 146 147 @Override complete_WMLTag( EObject model, RuleCall ruleCall, ContentAssistContext context, ICompletionProposalAcceptor acceptor )148 public void complete_WMLTag( EObject model, RuleCall ruleCall, 149 ContentAssistContext context, ICompletionProposalAcceptor acceptor ) 150 { 151 super.complete_WMLTag( model, ruleCall, context, acceptor ); 152 153 if( model != null ) { 154 addTagProposals( model, true, context, acceptor ); 155 } 156 } 157 158 @Override completeWMLTag_Name( EObject model, Assignment assignment, ContentAssistContext context, ICompletionProposalAcceptor acceptor )159 public void completeWMLTag_Name( EObject model, Assignment assignment, 160 ContentAssistContext context, ICompletionProposalAcceptor acceptor ) 161 { 162 super.completeWMLTag_Name( model, assignment, context, acceptor ); 163 164 if( model != null ) { 165 addTagProposals( model, false, context, acceptor ); 166 } 167 } 168 169 @Override completeWMLMacroCall_Name( EObject model, Assignment assignment, ContentAssistContext context, ICompletionProposalAcceptor acceptor )170 public void completeWMLMacroCall_Name( EObject model, 171 Assignment assignment, ContentAssistContext context, 172 ICompletionProposalAcceptor acceptor ) 173 { 174 super.completeWMLMacroCall_Name( model, assignment, context, acceptor ); 175 176 if( model != null ) { 177 addMacroCallProposals( model, false, context, acceptor ); 178 } 179 } 180 181 @Override complete_WMLMacroCall( EObject model, RuleCall ruleCall, ContentAssistContext context, ICompletionProposalAcceptor acceptor )182 public void complete_WMLMacroCall( EObject model, RuleCall ruleCall, 183 ContentAssistContext context, ICompletionProposalAcceptor acceptor ) 184 { 185 super.complete_WMLMacroCall( model, ruleCall, context, acceptor ); 186 187 if( model != null ) { 188 addMacroCallProposals( model, true, context, acceptor ); 189 } 190 } 191 addMacroCallProposals( EObject model, boolean ruleProposal, ContentAssistContext context, ICompletionProposalAcceptor acceptor )192 private void addMacroCallProposals( EObject model, boolean ruleProposal, 193 ContentAssistContext context, ICompletionProposalAcceptor acceptor ) 194 { 195 INode currentNode = context.getCurrentNode( ); 196 197 boolean appendEndBrace = currentNode != null && ! currentNode.getText( ).equals( "}" ); 198 199 200 for( Entry< String, Define > define: projectCache_.getDefines( ) 201 .entrySet( ) ) { 202 StringBuilder proposal = new StringBuilder( 10 ); 203 if( ruleProposal == true ) { 204 proposal.append( "{" ); //$NON-NLS-1$ 205 } 206 proposal.append( define.getKey( ) ); 207 208 for( String arg: define.getValue( ).getArguments( ) ) { 209 proposal.append( " " + arg ); //$NON-NLS-1$ 210 } 211 212 if( appendEndBrace ) { 213 proposal.append( "}" ); //$NON-NLS-1$ 214 } 215 216 acceptor.accept( createCompletionProposal( proposal.toString( ), 217 define.getKey( ), MACRO_CALL_IMAGE, context, 218 MACRO_CALL_PRIORITY ) ); 219 } 220 } 221 addKeyValueProposals( EObject model, ContentAssistContext context, ICompletionProposalAcceptor acceptor )222 private void addKeyValueProposals( EObject model, 223 ContentAssistContext context, ICompletionProposalAcceptor acceptor ) 224 { 225 if( model == null || ! ( model instanceof WMLKey ) ) { 226 return; 227 } 228 WMLKey key = ( WMLKey ) model; 229 String keyName = key.getName( ); 230 231 // handle the next_scenario and first_scenario 232 if( keyName.equals( "next_scenario" ) || //$NON-NLS-1$ 233 keyName.equals( "first_scenario" ) ) //$NON-NLS-1$ 234 { 235 for( WMLConfig config: projectCache_.getWMLConfigs( ).values( ) ) { 236 if( StringUtils.isNullOrEmpty( config.ScenarioId ) ) { 237 continue; 238 } 239 acceptor.accept( createCompletionProposal( config.ScenarioId, 240 config.ScenarioId, SCENARIO_VALUE_IMAGE, context, 241 KEY_VALUE_PRIORITY ) ); 242 } 243 } 244 else if( model.eContainer( ) != null 245 && model.eContainer( ) instanceof WMLTag ) { 246 WMLTag parent = ( WMLTag ) model.eContainer( ); 247 String tagName = parent.getName( ); 248 WMLTag tag = schemaParser_.getTags( ).get( tagName ); 249 if( tag != null ) { 250 WMLKey tagKey = WMLUtils.getKeyByName( tag, keyName ); 251 if( tagKey != null && tagKey.is_Enum( ) ) { 252 for( WMLKeyValue val: tagKey.getValues( ) ) { 253 acceptor.accept( createCompletionProposal( 254 val.toString( ), context, KEY_VALUE_PRIORITY ) ); 255 } 256 } 257 } 258 259 if( ( tagName.equals( "event" ) || tagName.equals( "fire_event" ) ) 260 && keyName.equals( "name" ) ) { 261 // add events 262 List< String > events = new ArrayList< String >( ); 263 events.addAll( TemplateProvider.getInstance( ) 264 .getCAC( "events" ) ); 265 events.addAll( projectCache_.getEvents( ) ); 266 267 for( String event: events ) { 268 acceptor 269 .accept( createCompletionProposal( event, context ) ); 270 } 271 } 272 else { 273 final int nodeOffset = NodeModelUtils.getNode( model ) 274 .getTotalOffset( ); 275 List< String > variables = new ArrayList< String >( ); 276 277 // add CAC variables 278 variables.addAll( TemplateProvider.getInstance( ).getCAC( 279 "variables" ) ); 280 281 // filter variables by index 282 Collection< String > projectVariables = Collections2.transform( 283 projectCache_.getVariables( ).values( ), 284 new Function< WMLVariable, String >( ) { 285 286 @Override 287 public String apply( WMLVariable from ) 288 { 289 for( Scope scope: from.getScopes( ) ) { 290 if( scope.contains( dependencyIndex_, 291 nodeOffset ) ) { 292 return from.getName( ); 293 } 294 } 295 296 return null; 297 } 298 } ); 299 300 variables.addAll( Collections2.filter( projectVariables, 301 Predicates.notNull( ) ) ); 302 303 for( String variable: variables ) { 304 acceptor.accept( createCompletionProposal( "$" + variable, 305 context ) ); 306 } 307 } 308 } 309 } 310 addKeyNameProposals( EObject model, ContentAssistContext context, ICompletionProposalAcceptor acceptor )311 private void addKeyNameProposals( EObject model, 312 ContentAssistContext context, ICompletionProposalAcceptor acceptor ) 313 { 314 WMLTag tag = null; 315 if( model instanceof WMLTag ) { 316 tag = ( WMLTag ) model; 317 } 318 else if( model.eContainer( ) instanceof WMLTag ) { 319 tag = ( WMLTag ) model.eContainer( ); 320 } 321 322 if( tag != null ) { 323 WMLTag schemaTag = schemaParser_.getTags( ).get( tag.getName( ) ); 324 // try getting the custom ones 325 if( schemaTag == null ) { 326 schemaTag = projectCache_.getWMLTags( ).get( tag.getName( ) ); 327 } 328 329 if( schemaTag != null ) { 330 for( WMLKey key: schemaTag.getWMLKeys( ) ) { 331 // skip forbidden keys 332 if( key.is_Forbidden( ) ) { 333 continue; 334 } 335 336 boolean toAdd = true; 337 // check only non-repeatable keys 338 if( ! key.is_Repeatable( ) ) { 339 // don't suggest already completed keys 340 toAdd = ( WMLUtils.getKeyByName( tag, key.getName( ) ) != null ); 341 } 342 343 if( toAdd ) { 344 acceptor.accept( createCompletionProposal( 345 key.getName( ) + "=", //$NON-NLS-1$ 346 key.getName( ), WML_KEY_IMAGE, context, 347 KEY_NAME_PRIORITY ) ); 348 } 349 } 350 } 351 } 352 } 353 addTagProposals( EObject model, boolean ruleProposal, ContentAssistContext context, ICompletionProposalAcceptor acceptor )354 private void addTagProposals( EObject model, boolean ruleProposal, 355 ContentAssistContext context, ICompletionProposalAcceptor acceptor ) 356 { 357 WMLTag parentTag = null; 358 if( model instanceof WMLTag ) { 359 parentTag = ( WMLTag ) model; 360 } 361 else if( model.eContainer( ) instanceof WMLTag ) { 362 parentTag = ( WMLTag ) model.eContainer( ); 363 } 364 INode currentNode = context.getCurrentNode( ); 365 366 if( currentNode == null ) { 367 return; 368 } 369 370 boolean appendEndBracket = ! currentNode.getText( ).equals( "]" ); 371 372 WMLTag sourceTag = null; 373 String indent = ""; 374 // find the just previous leaf node (which contains the indent) 375 ILeafNode leafNode = NodeModelUtils.findLeafNodeAtOffset( 376 NodeModelUtils.getNode( model ), 377 currentNode.getTotalOffset( ) - 1 - context.getPrefix( ).length( ) ); 378 379 if( leafNode != null ) { 380 indent = leafNode.getText( ); 381 } 382 383 if( parentTag != null ) { 384 sourceTag = schemaParser_.getTags( ).get( parentTag.getName( ) ); 385 } 386 else // we are at the root 387 { 388 sourceTag = schemaParser_.getTags( ).get( "root" ); //$NON-NLS-1$ 389 } 390 391 // remove new lines from the indent 392 indent = indent.replaceAll( "\\r|\\n", "" ); //$NON-NLS-1$ //$NON-NLS-2$ 393 394 if( sourceTag != null ) { 395 for( WMLTag tag: sourceTag.getWMLTags( ) ) { 396 // skip forbidden tags 397 if( tag.is_Forbidden( ) ) { 398 continue; 399 } 400 401 // check that non-repeatable tags aren't suggested again 402 if( ! tag.is_Repeatable( ) && parentTag != null ) { 403 if( WMLUtils.getTagByName( parentTag, tag.getName( ) ) != null ) { 404 return; 405 } 406 } 407 408 acceptor.accept( createTagProposal( tag, indent, ruleProposal, 409 context, appendEndBracket ) ); 410 } 411 } 412 413 // parsed custom tags 414 for( WMLTag tag: projectCache_.getWMLTags( ).values( ) ) { 415 acceptor.accept( createTagProposal( tag, "", ruleProposal, context, //$NON-NLS-1$ 416 appendEndBracket ) ); 417 } 418 } 419 420 /** 421 * Returns the proposal for the specified tag, usign the specified indent 422 * 423 * @param tag 424 * The tag from which to construct the proposal 425 * @param indent 426 * The indent used to indent the tag and subsequent keys 427 * @param ruleProposal 428 * Whether this is a proposal for an entire rule or not 429 * @param context 430 * @param appendEndBracked 431 * @return 432 */ createTagProposal( WMLTag tag, String indent, boolean ruleProposal, ContentAssistContext context, boolean appendEndBracked )433 private ICompletionProposal createTagProposal( WMLTag tag, String indent, 434 boolean ruleProposal, ContentAssistContext context, 435 boolean appendEndBracked ) 436 { 437 StringBuilder proposal = new StringBuilder( ); 438 if( ruleProposal ) { 439 proposal.append( "[" ); //$NON-NLS-1$ 440 } 441 proposal.append( tag.getName( ) ); 442 proposal.append( "]\n" ); //$NON-NLS-1$ 443 for( WMLKey key: tag.getWMLKeys( ) ) { 444 if( key.is_Required( ) ) { 445 proposal.append( String.format( "\t%s%s=\n", //$NON-NLS-1$ 446 indent, key.getName( ) ) ); 447 } 448 } 449 proposal.append( String.format( "%s[/%s%s",//$NON-NLS-1$ 450 indent, 451 tag.getName( ), 452 appendEndBracked ? "]": "" ) ); 453 454 return createCompletionProposal( proposal.toString( ), tag.getName( ), 455 WML_TAG_IMAGE, context, TAG_PRIORITY ); 456 } 457 createCompletionProposal( String proposal, ContentAssistContext context, int priority )458 private ICompletionProposal createCompletionProposal( String proposal, 459 ContentAssistContext context, int priority ) 460 { 461 return createCompletionProposal( proposal, null, null, priority, 462 context.getPrefix( ), context ); 463 } 464 createCompletionProposal( String proposal, String displayString, Image image, ContentAssistContext contentAssistContext, int priority )465 private ICompletionProposal createCompletionProposal( String proposal, 466 String displayString, Image image, 467 ContentAssistContext contentAssistContext, int priority ) 468 { 469 return createCompletionProposal( proposal, new StyledString( 470 displayString ), image, priority, 471 contentAssistContext.getPrefix( ), contentAssistContext ); 472 } 473 } 474